2 # Generate a release announcement message.
4 my $VERSION = '2009-03-05 09:52'; # UTC
5 # The definition above must lie within the first 8 lines in order
6 # for the Emacs time-stamp write hook (at end) to update it.
7 # If you change this file with Emacs, please let the write hook
8 # do its job. Otherwise, update this string manually.
10 # Copyright (C) 2002-2009 Free Software Foundation, Inc.
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # Written by Jim Meyering
32 use POSIX qw(strftime);
34 (my $ME = $0) =~ s|.*/||;
36 my %valid_release_types = map {$_ => 1} qw (alpha beta major);
37 my @archive_suffixes = ('tar.gz', 'tar.bz2', 'tar.lzma', 'tar.xz');
41 # Nobody ever checks the status of print()s. That's okay, because
42 # if any do fail, we're guaranteed to get an indicator when we close()
45 # Close stdout now, and if there were no errors, return happy status.
46 # If stdout has already been closed by the script, though, do nothing.
52 # Errors closing stdout. Indicate that, and hope stderr is OK.
53 warn "$ME: closing standard output: $!\n";
55 # Don't be so arrogant as to assume that we're the first END handler
56 # defined, and thus the last one invoked. There may be others yet
57 # to come. $? will be passed on to them, and to the final _exit().
59 # If it isn't already an error, make it one (and if it _is_ an error,
60 # preserve the value: it might be important).
67 my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
70 print $STREAM "Try `$ME --help' for more information.\n";
74 my @types = sort keys %valid_release_types;
80 Generate an announcement message.
82 These options must be specified:
84 --release-type=TYPE TYPE must be one of @types
85 --package-name=PACKAGE_NAME
86 --previous-version=VER
88 --gpg-key-id=ID The GnuPG ID of the key used to sign the tarballs
89 --url-directory=URL_DIR
91 The following are optional:
94 --bootstrap-tools=TOOL_LIST a comma-separated list of tools, e.g.,
95 autoconf,automake,bison,gnulib
96 --gnulib-version=VERSION report VERSION as the gnulib version, where
97 VERSION is the result of running git describe
98 in the gnulib source directory.
99 required if gnulib is in TOOL_LIST.
100 --no-print-checksums do not emit MD5 or SHA1 checksums
101 --archive-suffix=SUF add SUF to the list of archive suffixes
103 --help display this help and exit
104 --version output version information and exit
112 =item C<%size> = C<sizes (@file)>
114 Compute the sizes of the C<@file> and return them as a hash. Return
115 C<undef> if one of the computation failed.
125 foreach my $f (@file)
127 my $cmd = "du --human $f";
129 # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
131 and (warn "$ME: command failed: `$cmd'\n"), $fail = 1;
133 $t =~ s/^([\d.]+[MkK]).*/${1}B/;
136 return $fail ? undef : %res;
139 =item C<print_locations ($title, \@url, \%size, @file)
141 Print a section C<$title> dedicated to the list of <@file>, which
142 sizes are stored in C<%size>, and which are available from the C<@url>.
146 sub print_locations ($\@\%@)
148 my ($title, $url, $size, @file) = @_;
149 print "Here are the $title:\n";
150 foreach my $url (@{$url})
155 print " (", $$size{$file}, ")"
156 if exists $$size{$file};
163 =item C<print_checksums (@file)
165 Print the MD5 and SHA1 signature section for each C<@file>.
169 sub print_checksums (@)
173 print "Here are the MD5 and SHA1 checksums:\n";
176 foreach my $meth (qw (md5 sha1))
178 foreach my $f (@file)
181 or die "$ME: $f: cannot open for reading: $!\n";
185 ? Digest::MD5->new->addfile(*IN)->hexdigest
186 : Digest::SHA1->new->addfile(*IN)->hexdigest);
194 =item C<print_news_deltas ($news_file, $prev_version, $curr_version)
196 Print the section of the NEWS file C<$news_file> addressing changes
197 between versions C<$prev_version> and C<$curr_version>.
201 sub print_news_deltas ($$$)
203 my ($news_file, $prev_version, $curr_version) = @_;
205 print "\n$news_file\n\n";
207 # Print all lines from $news_file, starting with the first one
208 # that mentions $curr_version up to but not including
209 # the first occurrence of $prev_version.
212 my $re_prefix = qr/(?:\* )?(?:Noteworthy c|Major c|C)(?i:hanges)/;
214 open NEWS, '<', $news_file
215 or die "$ME: $news_file: cannot open for reading: $!\n";
216 while (defined (my $line = <NEWS>))
220 # Match lines like these:
221 # * Major changes in release 5.0.1:
222 # * Noteworthy changes in release 6.6 (2006-11-22) [stable]
223 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o
230 # This regexp must not match version numbers in NEWS items.
231 # For example, they might well say `introduced in 4.5.5',
232 # and we don't want that to match.
233 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o
241 or die "$ME: $news_file: no matching lines for `$curr_version'\n";
244 sub print_changelog_deltas ($$)
246 my ($package_name, $prev_version) = @_;
248 # Print new ChangeLog entries.
250 # First find all CVS-controlled ChangeLog files.
253 find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS'
254 and push @changelog, $File::Find::name}},
257 # If there are no ChangeLog files, we're done.
260 my %changelog = map {$_ => 1} @changelog;
262 # Reorder the list of files so that if there are ChangeLog
263 # files in the specified directories, they're listed first,
265 my @dir = qw ( . src lib m4 config doc );
267 # A typical @changelog array might look like this:
277 my $dot_slash = $d eq '.' ? $d : "./$d";
278 my $target = "$dot_slash/ChangeLog";
279 delete $changelog{$target}
280 and push @reordered, $target;
283 # Append any remaining ChangeLog files.
284 push @reordered, sort keys %changelog;
286 # Remove leading `./'.
287 @reordered = map { s!^\./!!; $_ } @reordered;
289 print "\nChangeLog entries:\n\n";
290 # print join ("\n", @reordered), "\n";
292 $prev_version =~ s/\./_/g;
293 my $prev_cvs_tag = "\U$package_name\E-$prev_version";
295 my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
296 open DIFF, '-|', $cmd
297 or die "$ME: cannot run `$cmd': $!\n";
298 # Print two types of lines, making minor changes:
299 # Lines starting with `+++ ', e.g.,
300 # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247
301 # and those starting with `+'.
302 # Don't print the others.
303 my $prev_printed_line_empty = 1;
304 while (defined (my $line = <DIFF>))
306 if ($line =~ /^\+\+\+ /)
308 my $separator = "*"x70 ."\n";
311 $prev_printed_line_empty
313 print $separator, $line, $separator;
315 elsif ($line =~ /^\+/)
319 $prev_printed_line_empty = ($line =~ /^$/);
324 # The exit code should be 1.
325 # Allow in case there are no modified ChangeLog entries.
326 $? == 256 || $? == 128
327 or warn "$ME: warning: `cmd' had unexpected exit code or signal ($?)\n";
330 sub get_tool_versions ($$)
332 my ($tool_list, $gnulib_version) = @_;
337 my @tool_version_pair;
338 foreach my $t (@$tool_list)
342 push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version;
345 # Assume that the last "word" on the first line of
346 # `tool --version` output is the version string.
347 my ($first_line, undef) = split ("\n", `$t --version`);
348 if ($first_line =~ /.* (\d[\w.-]+)$/)
351 push @tool_version_pair, "$t $1";
356 and $first_line = '';
357 warn "$ME: $t: unexpected --version output\n:$first_line";
365 return @tool_version_pair;
369 # Neutralize the locale, so that, for instance, "du" does not
370 # issue "1,2" instead of "1.2", what confuses our regexps.
382 my $print_checksums_p = 1;
386 'release-type=s' => \$release_type,
387 'package-name=s' => \$package_name,
388 'previous-version=s' => \$prev_version,
389 'current-version=s' => \$curr_version,
390 'gpg-key-id=s' => \$gpg_key_id,
391 'url-directory=s' => \@url_dir_list,
392 'news=s' => \@news_file,
393 'bootstrap-tools=s' => \$bootstrap_tools,
394 'gnulib-version=s' => \$gnulib_version,
395 'print-checksums!' => \$print_checksums_p,
396 'archive-suffix=s' => \@archive_suffixes,
398 help => sub { usage 0 },
399 version => sub { print "$ME version $VERSION\n"; exit },
403 # Ensure that sure each required option is specified.
405 or (warn "$ME: release type not specified\n"), $fail = 1;
407 or (warn "$ME: package name not specified\n"), $fail = 1;
409 or (warn "$ME: previous version string not specified\n"), $fail = 1;
411 or (warn "$ME: current version string not specified\n"), $fail = 1;
413 or (warn "$ME: GnuPG key ID not specified\n"), $fail = 1;
415 or (warn "$ME: URL directory name(s) not specified\n"), $fail = 1;
417 my @tool_list = split ',', $bootstrap_tools;
419 grep (/^gnulib$/, @tool_list) ^ defined $gnulib_version
420 and (warn "$ME: when specifying gnulib as a tool, you must also specify\n"
421 . "--gnulib-version=V, where V is the result of running git describe\n"
422 . "in the gnulib source directory.\n"), $fail = 1;
424 exists $valid_release_types{$release_type}
425 or (warn "$ME: `$release_type': invalid release type\n"), $fail = 1;
428 and (warn "$ME: too many arguments:\n", join ("\n", @ARGV), "\n"),
433 my $my_distdir = "$package_name-$curr_version";
435 my $xd = "$package_name-$prev_version-$curr_version.xdelta";
437 my @candidates = map { "$my_distdir.$_" } @archive_suffixes;
438 my @tarballs = grep {-f $_} @candidates;
441 or die "$ME: none of " . join(', ', @candidates) . " were found\n";
442 my @sizable = @tarballs;
444 and push @sizable, $xd;
445 my %size = sizes (@sizable);
449 # The markup is escaped as <\# so that when this script is sent by
450 # mail (or part of a diff), Gnus is not triggered.
453 Subject: $my_distdir released
455 <\#secure method=pgpmime mode=sign>
457 FIXME: put comments here
461 print_locations ("compressed sources", @url_dir_list, %size, @tarballs);
463 and print_locations ("xdelta diffs (useful? if so, "
464 . "please tell bug-gnulib\@gnu.org)",
465 @url_dir_list, %size, $xd);
466 my @sig_files = map { "$_.sig" } @tarballs;
467 print_locations ("GPG detached signatures[*]", @url_dir_list, %size,
471 and print_checksums (@sizable);
474 [*] You can use either of the above signature files to verify that
475 the corresponding file (without the .sig suffix) is intact. First,
476 be sure to download both the .sig file and the corresponding tarball.
477 Then, run a command like this:
479 gpg --verify $tarballs[0].sig
481 If that command fails because you don't have the required public key,
482 then run this command to import it:
484 gpg --keyserver keys.gnupg.net --recv-keys $gpg_key_id
486 and rerun the \`gpg --verify' command.
489 my @tool_versions = get_tool_versions (\@tool_list, $gnulib_version);
491 and print "\nThis release was bootstrapped with the following tools:",
492 join ('', map {"\n $_"} @tool_versions), "\n";
494 print_news_deltas ($_, $prev_version, $curr_version)
497 $release_type eq 'major'
498 or print_changelog_deltas ($package_name, $prev_version);
503 ### Setup "GNU" style for perl-mode and cperl-mode.
505 ## perl-indent-level: 2
506 ## perl-continued-statement-offset: 2
507 ## perl-continued-brace-offset: 0
508 ## perl-brace-offset: 0
509 ## perl-brace-imaginary-offset: 0
510 ## perl-label-offset: -2
511 ## cperl-indent-level: 2
512 ## cperl-brace-offset: 0
513 ## cperl-continued-brace-offset: 0
514 ## cperl-label-offset: -2
515 ## cperl-extra-newline-before-brace: t
516 ## cperl-merge-trailing-else: nil
517 ## cperl-continued-statement-offset: 2
518 ## eval: (add-hook 'write-file-hooks 'time-stamp)
519 ## time-stamp-start: "my $VERSION = '"
520 ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
521 ## time-stamp-time-zone: "UTC"
522 ## time-stamp-end: "'; # UTC"