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 = '2009-11-20 13: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-2010 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
34 use POSIX qw(strftime);
36 (my $ME = $0) =~ s|.*/||;
38 my %valid_release_types = map {$_ => 1} qw (alpha beta stable);
39 my @archive_suffixes = ('tar.gz', 'tar.bz2', 'tar.lzma', 'tar.xz');
44 my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
47 print $STREAM "Try `$ME --help' for more information.\n";
51 my @types = sort keys %valid_release_types;
54 Generate an announcement message.
58 These options must be specified:
60 --release-type=TYPE TYPE must be one of @types
61 --package-name=PACKAGE_NAME
62 --previous-version=VER
64 --gpg-key-id=ID The GnuPG ID of the key used to sign the tarballs
65 --url-directory=URL_DIR
67 The following are optional:
70 --bootstrap-tools=TOOL_LIST a comma-separated list of tools, e.g.,
71 autoconf,automake,bison,gnulib
72 --gnulib-version=VERSION report VERSION as the gnulib version, where
73 VERSION is the result of running git describe
74 in the gnulib source directory.
75 required if gnulib is in TOOL_LIST.
76 --no-print-checksums do not emit MD5 or SHA1 checksums
77 --archive-suffix=SUF add SUF to the list of archive suffixes
79 --help display this help and exit
80 --version output version information and exit
88 =item C<%size> = C<sizes (@file)>
90 Compute the sizes of the C<@file> and return them as a hash. Return
91 C<undef> if one of the computation failed.
101 foreach my $f (@file)
103 my $cmd = "du --human $f";
105 # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
107 and (warn "$ME: command failed: `$cmd'\n"), $fail = 1;
109 $t =~ s/^([\d.]+[MkK]).*/${1}B/;
112 return $fail ? undef : %res;
115 =item C<print_locations ($title, \@url, \%size, @file)
117 Print a section C<$title> dedicated to the list of <@file>, which
118 sizes are stored in C<%size>, and which are available from the C<@url>.
122 sub print_locations ($\@\%@)
124 my ($title, $url, $size, @file) = @_;
125 print "Here are the $title:\n";
126 foreach my $url (@{$url})
131 print " (", $$size{$file}, ")"
132 if exists $$size{$file};
139 =item C<print_checksums (@file)
141 Print the MD5 and SHA1 signature section for each C<@file>.
145 sub print_checksums (@)
149 print "Here are the MD5 and SHA1 checksums:\n";
152 foreach my $meth (qw (md5 sha1))
154 foreach my $f (@file)
157 or die "$ME: $f: cannot open for reading: $!\n";
161 ? Digest::MD5->new->addfile(*IN)->hexdigest
162 : Digest::SHA1->new->addfile(*IN)->hexdigest);
170 =item C<print_news_deltas ($news_file, $prev_version, $curr_version)
172 Print the section of the NEWS file C<$news_file> addressing changes
173 between versions C<$prev_version> and C<$curr_version>.
177 sub print_news_deltas ($$$)
179 my ($news_file, $prev_version, $curr_version) = @_;
181 print "\n$news_file\n\n";
183 # Print all lines from $news_file, starting with the first one
184 # that mentions $curr_version up to but not including
185 # the first occurrence of $prev_version.
188 my $re_prefix = qr/(?:\* )?(?:Noteworthy c|Major c|C)(?i:hanges)/;
190 open NEWS, '<', $news_file
191 or die "$ME: $news_file: cannot open for reading: $!\n";
192 while (defined (my $line = <NEWS>))
196 # Match lines like these:
197 # * Major changes in release 5.0.1:
198 # * Noteworthy changes in release 6.6 (2006-11-22) [stable]
199 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o
206 # This regexp must not match version numbers in NEWS items.
207 # For example, they might well say `introduced in 4.5.5',
208 # and we don't want that to match.
209 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o
217 or die "$ME: $news_file: no matching lines for `$curr_version'\n";
220 sub print_changelog_deltas ($$)
222 my ($package_name, $prev_version) = @_;
224 # Print new ChangeLog entries.
226 # First find all CVS-controlled ChangeLog files.
229 find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS'
230 and push @changelog, $File::Find::name}},
233 # If there are no ChangeLog files, we're done.
236 my %changelog = map {$_ => 1} @changelog;
238 # Reorder the list of files so that if there are ChangeLog
239 # files in the specified directories, they're listed first,
241 my @dir = qw ( . src lib m4 config doc );
243 # A typical @changelog array might look like this:
253 my $dot_slash = $d eq '.' ? $d : "./$d";
254 my $target = "$dot_slash/ChangeLog";
255 delete $changelog{$target}
256 and push @reordered, $target;
259 # Append any remaining ChangeLog files.
260 push @reordered, sort keys %changelog;
262 # Remove leading `./'.
263 @reordered = map { s!^\./!!; $_ } @reordered;
265 print "\nChangeLog entries:\n\n";
266 # print join ("\n", @reordered), "\n";
268 $prev_version =~ s/\./_/g;
269 my $prev_cvs_tag = "\U$package_name\E-$prev_version";
271 my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
272 open DIFF, '-|', $cmd
273 or die "$ME: cannot run `$cmd': $!\n";
274 # Print two types of lines, making minor changes:
275 # Lines starting with `+++ ', e.g.,
276 # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247
277 # and those starting with `+'.
278 # Don't print the others.
279 my $prev_printed_line_empty = 1;
280 while (defined (my $line = <DIFF>))
282 if ($line =~ /^\+\+\+ /)
284 my $separator = "*"x70 ."\n";
287 $prev_printed_line_empty
289 print $separator, $line, $separator;
291 elsif ($line =~ /^\+/)
295 $prev_printed_line_empty = ($line =~ /^$/);
300 # The exit code should be 1.
301 # Allow in case there are no modified ChangeLog entries.
302 $? == 256 || $? == 128
303 or warn "$ME: warning: `cmd' had unexpected exit code or signal ($?)\n";
306 sub get_tool_versions ($$)
308 my ($tool_list, $gnulib_version) = @_;
313 my @tool_version_pair;
314 foreach my $t (@$tool_list)
318 push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version;
321 # Assume that the last "word" on the first line of
322 # `tool --version` output is the version string.
323 my ($first_line, undef) = split ("\n", `$t --version`);
324 if ($first_line =~ /.* (\d[\w.-]+)$/)
327 push @tool_version_pair, "$t $1";
332 and $first_line = '';
333 warn "$ME: $t: unexpected --version output\n:$first_line";
341 return @tool_version_pair;
345 # Neutralize the locale, so that, for instance, "du" does not
346 # issue "1,2" instead of "1.2", what confuses our regexps.
358 my $print_checksums_p = 1;
362 'release-type=s' => \$release_type,
363 'package-name=s' => \$package_name,
364 'previous-version=s' => \$prev_version,
365 'current-version=s' => \$curr_version,
366 'gpg-key-id=s' => \$gpg_key_id,
367 'url-directory=s' => \@url_dir_list,
368 'news=s' => \@news_file,
369 'bootstrap-tools=s' => \$bootstrap_tools,
370 'gnulib-version=s' => \$gnulib_version,
371 'print-checksums!' => \$print_checksums_p,
372 'archive-suffix=s' => \@archive_suffixes,
374 help => sub { usage 0 },
375 version => sub { print "$ME version $VERSION\n"; exit },
379 # Ensure that sure each required option is specified.
381 or (warn "$ME: release type not specified\n"), $fail = 1;
383 or (warn "$ME: package name not specified\n"), $fail = 1;
385 or (warn "$ME: previous version string not specified\n"), $fail = 1;
387 or (warn "$ME: current version string not specified\n"), $fail = 1;
389 or (warn "$ME: GnuPG key ID not specified\n"), $fail = 1;
391 or (warn "$ME: URL directory name(s) not specified\n"), $fail = 1;
393 my @tool_list = split ',', $bootstrap_tools;
395 grep (/^gnulib$/, @tool_list) ^ defined $gnulib_version
396 and (warn "$ME: when specifying gnulib as a tool, you must also specify\n"
397 . "--gnulib-version=V, where V is the result of running git describe\n"
398 . "in the gnulib source directory.\n"), $fail = 1;
400 exists $valid_release_types{$release_type}
401 or (warn "$ME: `$release_type': invalid release type\n"), $fail = 1;
404 and (warn "$ME: too many arguments:\n", join ("\n", @ARGV), "\n"),
409 my $my_distdir = "$package_name-$curr_version";
411 my $xd = "$package_name-$prev_version-$curr_version.xdelta";
413 my @candidates = map { "$my_distdir.$_" } @archive_suffixes;
414 my @tarballs = grep {-f $_} @candidates;
417 or die "$ME: none of " . join(', ', @candidates) . " were found\n";
418 my @sizable = @tarballs;
420 and push @sizable, $xd;
421 my %size = sizes (@sizable);
425 # The markup is escaped as <\# so that when this script is sent by
426 # mail (or part of a diff), Gnus is not triggered.
429 Subject: $my_distdir released [$release_type]
431 <\#secure method=pgpmime mode=sign>
433 FIXME: put comments here
437 print_locations ("compressed sources", @url_dir_list, %size, @tarballs);
439 and print_locations ("xdelta diffs (useful? if so, "
440 . "please tell bug-gnulib\@gnu.org)",
441 @url_dir_list, %size, $xd);
442 my @sig_files = map { "$_.sig" } @tarballs;
443 print_locations ("GPG detached signatures[*]", @url_dir_list, %size,
445 if ($url_dir_list[0] =~ "gnu\.org")
447 print "To reduce load on the main server, use a mirror listed at:\n";
448 print " http://www.gnu.org/order/ftp.html\n\n";
452 and print_checksums (@sizable);
455 [*] You can use either of the above signature files to verify that
456 the corresponding file (without the .sig suffix) is intact. First,
457 be sure to download both the .sig file and the corresponding tarball.
458 Then, run a command like this:
460 gpg --verify $tarballs[0].sig
462 If that command fails because you don't have the required public key,
463 then run this command to import it:
465 gpg --keyserver keys.gnupg.net --recv-keys $gpg_key_id
467 and rerun the \`gpg --verify' command.
470 my @tool_versions = get_tool_versions (\@tool_list, $gnulib_version);
472 and print "\nThis release was bootstrapped with the following tools:",
473 join ('', map {"\n $_"} @tool_versions), "\n";
475 print_news_deltas ($_, $prev_version, $curr_version)
478 $release_type eq 'stable'
479 or print_changelog_deltas ($package_name, $prev_version);
484 ### Setup "GNU" style for perl-mode and cperl-mode.
487 ## perl-indent-level: 2
488 ## perl-continued-statement-offset: 2
489 ## perl-continued-brace-offset: 0
490 ## perl-brace-offset: 0
491 ## perl-brace-imaginary-offset: 0
492 ## perl-label-offset: -2
493 ## perl-extra-newline-before-brace: t
494 ## perl-merge-trailing-else: nil
495 ## eval: (add-hook 'write-file-hooks 'time-stamp)
496 ## time-stamp-start: "my $VERSION = '"
497 ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
498 ## time-stamp-time-zone: "UTC"
499 ## time-stamp-end: "'; # UTC"