1 eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}'
2 & eval 'exec perl -wS "$0" $argv:q'
4 # Generate a release announcement message.
6 my $VERSION = '2012-04-19 14:36'; # UTC
7 # The definition above must lie within the first 8 lines in order
8 # for the Emacs time-stamp write hook (at end) to update it.
9 # If you change this file with Emacs, please let the write hook
10 # do its job. Otherwise, update this string manually.
12 # Copyright (C) 2002-2012 Free Software Foundation, Inc.
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 # Written by Jim Meyering
33 eval { require Digest::SHA; }
34 or eval 'use Digest::SHA1';
35 use POSIX qw(strftime);
37 (my $ME = $0) =~ s|.*/||;
39 my %valid_release_types = map {$_ => 1} qw (alpha beta stable);
40 my @archive_suffixes = ('tar.gz', 'tar.bz2', 'tar.lzma', 'tar.xz');
45 my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
48 print $STREAM "Try '$ME --help' for more information.\n";
52 my @types = sort keys %valid_release_types;
55 Generate an announcement message.
59 These options must be specified:
61 --release-type=TYPE TYPE must be one of @types
62 --package-name=PACKAGE_NAME
63 --previous-version=VER
65 --gpg-key-id=ID The GnuPG ID of the key used to sign the tarballs
66 --url-directory=URL_DIR
68 The following are optional:
71 --bootstrap-tools=TOOL_LIST a comma-separated list of tools, e.g.,
72 autoconf,automake,bison,gnulib
73 --gnulib-version=VERSION report VERSION as the gnulib version, where
74 VERSION is the result of running git describe
75 in the gnulib source directory.
76 required if gnulib is in TOOL_LIST.
77 --no-print-checksums do not emit MD5 or SHA1 checksums
78 --archive-suffix=SUF add SUF to the list of archive suffixes
79 --mail-headers=HEADERS a space-separated list of mail headers, e.g.,
80 To: x\@example.com Cc: y-announce\@example.com,...
82 --help display this help and exit
83 --version output version information and exit
91 =item C<%size> = C<sizes (@file)>
93 Compute the sizes of the C<@file> and return them as a hash. Return
94 C<undef> if one of the computation failed.
104 foreach my $f (@file)
106 my $cmd = "du --human $f";
108 # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
110 and (warn "$ME: command failed: '$cmd'\n"), $fail = 1;
112 $t =~ s/^([\d.]+[MkK]).*/${1}B/;
115 return $fail ? undef : %res;
118 =item C<print_locations ($title, \@url, \%size, @file)
120 Print a section C<$title> dedicated to the list of <@file>, which
121 sizes are stored in C<%size>, and which are available from the C<@url>.
125 sub print_locations ($\@\%@)
127 my ($title, $url, $size, @file) = @_;
128 print "Here are the $title:\n";
129 foreach my $url (@{$url})
134 print " (", $$size{$file}, ")"
135 if exists $$size{$file};
142 =item C<print_checksums (@file)
144 Print the MD5 and SHA1 signature section for each C<@file>.
148 sub print_checksums (@)
152 print "Here are the MD5 and SHA1 checksums:\n";
155 foreach my $meth (qw (md5 sha1))
157 foreach my $f (@file)
160 or die "$ME: $f: cannot open for reading: $!\n";
164 ? Digest::MD5->new->addfile(*IN)->hexdigest
165 : Digest::SHA1->new->addfile(*IN)->hexdigest);
173 =item C<print_news_deltas ($news_file, $prev_version, $curr_version)
175 Print the section of the NEWS file C<$news_file> addressing changes
176 between versions C<$prev_version> and C<$curr_version>.
180 sub print_news_deltas ($$$)
182 my ($news_file, $prev_version, $curr_version) = @_;
184 my $news_name = $news_file;
185 $news_name =~ s|^\./||;
187 print "\n$news_name\n\n";
189 # Print all lines from $news_file, starting with the first one
190 # that mentions $curr_version up to but not including
191 # the first occurrence of $prev_version.
194 my $re_prefix = qr/(?:\* )?(?:Noteworthy c|Major c|C)(?i:hanges)/;
197 open NEWS, '<', $news_file
198 or die "$ME: $news_file: cannot open for reading: $!\n";
199 while (defined (my $line = <NEWS>))
203 # Match lines like these:
204 # * Major changes in release 5.0.1:
205 # * Noteworthy changes in release 6.6 (2006-11-22) [stable]
206 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o
213 # This regexp must not match version numbers in NEWS items.
214 # For example, they might well say "introduced in 4.5.5",
215 # and we don't want that to match.
216 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o
226 or die "$ME: $news_file: no matching lines for '$curr_version'\n";
228 or die "$ME: $news_file: no news item found for '$curr_version'\n";
231 sub print_changelog_deltas ($$)
233 my ($package_name, $prev_version) = @_;
235 # Print new ChangeLog entries.
237 # First find all CVS-controlled ChangeLog files.
240 find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS'
241 and push @changelog, $File::Find::name}},
244 # If there are no ChangeLog files, we're done.
247 my %changelog = map {$_ => 1} @changelog;
249 # Reorder the list of files so that if there are ChangeLog
250 # files in the specified directories, they're listed first,
252 my @dir = qw ( . src lib m4 config doc );
254 # A typical @changelog array might look like this:
264 my $dot_slash = $d eq '.' ? $d : "./$d";
265 my $target = "$dot_slash/ChangeLog";
266 delete $changelog{$target}
267 and push @reordered, $target;
270 # Append any remaining ChangeLog files.
271 push @reordered, sort keys %changelog;
273 # Remove leading './'.
274 @reordered = map { s!^\./!!; $_ } @reordered;
276 print "\nChangeLog entries:\n\n";
277 # print join ("\n", @reordered), "\n";
279 $prev_version =~ s/\./_/g;
280 my $prev_cvs_tag = "\U$package_name\E-$prev_version";
282 my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
283 open DIFF, '-|', $cmd
284 or die "$ME: cannot run '$cmd': $!\n";
285 # Print two types of lines, making minor changes:
286 # Lines starting with '+++ ', e.g.,
287 # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247
288 # and those starting with '+'.
289 # Don't print the others.
290 my $prev_printed_line_empty = 1;
291 while (defined (my $line = <DIFF>))
293 if ($line =~ /^\+\+\+ /)
295 my $separator = "*"x70 ."\n";
298 $prev_printed_line_empty
300 print $separator, $line, $separator;
302 elsif ($line =~ /^\+/)
306 $prev_printed_line_empty = ($line =~ /^$/);
311 # The exit code should be 1.
312 # Allow in case there are no modified ChangeLog entries.
313 $? == 256 || $? == 128
314 or warn "$ME: warning: '$cmd' had unexpected exit code or signal ($?)\n";
317 sub get_tool_versions ($$)
319 my ($tool_list, $gnulib_version) = @_;
324 my @tool_version_pair;
325 foreach my $t (@$tool_list)
329 push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version;
332 # Assume that the last "word" on the first line of
333 # 'tool --version' output is the version string.
334 my ($first_line, undef) = split ("\n", `$t --version`);
335 if ($first_line =~ /.* (\d[\w.-]+)$/)
338 push @tool_version_pair, "$t $1";
343 and $first_line = '';
344 warn "$ME: $t: unexpected --version output\n:$first_line";
352 return @tool_version_pair;
356 # Neutralize the locale, so that, for instance, "du" does not
357 # issue "1,2" instead of "1.2", what confuses our regexps.
370 my $print_checksums_p = 1;
374 'mail-headers=s' => \$mail_headers,
375 'release-type=s' => \$release_type,
376 'package-name=s' => \$package_name,
377 'previous-version=s' => \$prev_version,
378 'current-version=s' => \$curr_version,
379 'gpg-key-id=s' => \$gpg_key_id,
380 'url-directory=s' => \@url_dir_list,
381 'news=s' => \@news_file,
382 'bootstrap-tools=s' => \$bootstrap_tools,
383 'gnulib-version=s' => \$gnulib_version,
384 'print-checksums!' => \$print_checksums_p,
385 'archive-suffix=s' => \@archive_suffixes,
387 help => sub { usage 0 },
388 version => sub { print "$ME version $VERSION\n"; exit },
392 # Ensure that sure each required option is specified.
394 or (warn "$ME: release type not specified\n"), $fail = 1;
396 or (warn "$ME: package name not specified\n"), $fail = 1;
398 or (warn "$ME: previous version string not specified\n"), $fail = 1;
400 or (warn "$ME: current version string not specified\n"), $fail = 1;
402 or (warn "$ME: GnuPG key ID not specified\n"), $fail = 1;
404 or (warn "$ME: URL directory name(s) not specified\n"), $fail = 1;
406 my @tool_list = split ',', $bootstrap_tools;
408 grep (/^gnulib$/, @tool_list) ^ defined $gnulib_version
409 and (warn "$ME: when specifying gnulib as a tool, you must also specify\n"
410 . "--gnulib-version=V, where V is the result of running git describe\n"
411 . "in the gnulib source directory.\n"), $fail = 1;
413 exists $valid_release_types{$release_type}
414 or (warn "$ME: '$release_type': invalid release type\n"), $fail = 1;
417 and (warn "$ME: too many arguments:\n", join ("\n", @ARGV), "\n"),
422 my $my_distdir = "$package_name-$curr_version";
424 my $xd = "$package_name-$prev_version-$curr_version.xdelta";
426 my @candidates = map { "$my_distdir.$_" } @archive_suffixes;
427 my @tarballs = grep {-f $_} @candidates;
430 or die "$ME: none of " . join(', ', @candidates) . " were found\n";
431 my @sizable = @tarballs;
433 and push @sizable, $xd;
434 my %size = sizes (@sizable);
439 if (defined $mail_headers)
441 ($headers = $mail_headers) =~ s/\s+(\S+:)/\n$1/g;
445 # The markup is escaped as <\# so that when this script is sent by
446 # mail (or part of a diff), Gnus is not triggered.
449 ${headers}Subject: $my_distdir released [$release_type]
451 <\#secure method=pgpmime mode=sign>
453 FIXME: put comments here
457 if (@url_dir_list == 1 && @tarballs == 1)
459 # When there's only one tarball and one URL, use a more concise form.
460 my $m = "$url_dir_list[0]/$tarballs[0]";
461 print "Here are the compressed sources and a GPG detached signature[*]:\n"
467 print_locations ("compressed sources", @url_dir_list, %size, @tarballs);
469 and print_locations ("xdelta diffs (useful? if so, "
470 . "please tell bug-gnulib\@gnu.org)",
471 @url_dir_list, %size, $xd);
472 my @sig_files = map { "$_.sig" } @tarballs;
473 print_locations ("GPG detached signatures[*]", @url_dir_list, %size,
477 if ($url_dir_list[0] =~ "gnu\.org")
479 print "Use a mirror for higher download bandwidth:\n";
480 if (@tarballs == 1 && $url_dir_list[0] =~ m!http://ftp\.gnu\.org/gnu/!)
482 (my $m = "$url_dir_list[0]/$tarballs[0]")
483 =~ s!http://ftp\.gnu\.org/gnu/!http://ftpmirror\.gnu\.org/!;
490 print " http://www.gnu.org/order/ftp.html\n\n";
495 and print_checksums (@sizable);
498 [*] Use a .sig file to verify that the corresponding file (without the
499 .sig suffix) is intact. First, be sure to download both the .sig file
500 and the corresponding tarball. Then, run a command like this:
502 gpg --verify $tarballs[0].sig
504 If that command fails because you don't have the required public key,
505 then run this command to import it:
507 gpg --keyserver keys.gnupg.net --recv-keys $gpg_key_id
509 and rerun the 'gpg --verify' command.
512 my @tool_versions = get_tool_versions (\@tool_list, $gnulib_version);
514 and print "\nThis release was bootstrapped with the following tools:",
515 join ('', map {"\n $_"} @tool_versions), "\n";
517 print_news_deltas ($_, $prev_version, $curr_version)
520 $release_type eq 'stable'
521 or print_changelog_deltas ($package_name, $prev_version);
526 ### Setup "GNU" style for perl-mode and cperl-mode.
529 ## perl-indent-level: 2
530 ## perl-continued-statement-offset: 2
531 ## perl-continued-brace-offset: 0
532 ## perl-brace-offset: 0
533 ## perl-brace-imaginary-offset: 0
534 ## perl-label-offset: -2
535 ## perl-extra-newline-before-brace: t
536 ## perl-merge-trailing-else: nil
537 ## eval: (add-hook 'write-file-hooks 'time-stamp)
538 ## time-stamp-start: "my $VERSION = '"
539 ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
540 ## time-stamp-time-zone: "UTC"
541 ## time-stamp-end: "'; # UTC"