2 # Generate a release announcement message.
4 my $VERSION = '2009-09-11 09:50'; # 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 stable);
37 my @archive_suffixes = ('tar.gz', 'tar.bz2', 'tar.lzma', 'tar.xz');
42 my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
45 print $STREAM "Try `$ME --help' for more information.\n";
49 my @types = sort keys %valid_release_types;
52 Generate an announcement message.
56 These options must be specified:
58 --release-type=TYPE TYPE must be one of @types
59 --package-name=PACKAGE_NAME
60 --previous-version=VER
62 --gpg-key-id=ID The GnuPG ID of the key used to sign the tarballs
63 --url-directory=URL_DIR
65 The following are optional:
68 --bootstrap-tools=TOOL_LIST a comma-separated list of tools, e.g.,
69 autoconf,automake,bison,gnulib
70 --gnulib-version=VERSION report VERSION as the gnulib version, where
71 VERSION is the result of running git describe
72 in the gnulib source directory.
73 required if gnulib is in TOOL_LIST.
74 --no-print-checksums do not emit MD5 or SHA1 checksums
75 --archive-suffix=SUF add SUF to the list of archive suffixes
77 --help display this help and exit
78 --version output version information and exit
86 =item C<%size> = C<sizes (@file)>
88 Compute the sizes of the C<@file> and return them as a hash. Return
89 C<undef> if one of the computation failed.
101 my $cmd = "du --human $f";
103 # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
105 and (warn "$ME: command failed: `$cmd'\n"), $fail = 1;
107 $t =~ s/^([\d.]+[MkK]).*/${1}B/;
110 return $fail ? undef : %res;
113 =item C<print_locations ($title, \@url, \%size, @file)
115 Print a section C<$title> dedicated to the list of <@file>, which
116 sizes are stored in C<%size>, and which are available from the C<@url>.
120 sub print_locations ($\@\%@)
122 my ($title, $url, $size, @file) = @_;
123 print "Here are the $title:\n";
124 foreach my $url (@{$url})
129 print " (", $$size{$file}, ")"
130 if exists $$size{$file};
137 =item C<print_checksums (@file)
139 Print the MD5 and SHA1 signature section for each C<@file>.
143 sub print_checksums (@)
147 print "Here are the MD5 and SHA1 checksums:\n";
150 foreach my $meth (qw (md5 sha1))
152 foreach my $f (@file)
155 or die "$ME: $f: cannot open for reading: $!\n";
159 ? Digest::MD5->new->addfile(*IN)->hexdigest
160 : Digest::SHA1->new->addfile(*IN)->hexdigest);
168 =item C<print_news_deltas ($news_file, $prev_version, $curr_version)
170 Print the section of the NEWS file C<$news_file> addressing changes
171 between versions C<$prev_version> and C<$curr_version>.
175 sub print_news_deltas ($$$)
177 my ($news_file, $prev_version, $curr_version) = @_;
179 print "\n$news_file\n\n";
181 # Print all lines from $news_file, starting with the first one
182 # that mentions $curr_version up to but not including
183 # the first occurrence of $prev_version.
186 my $re_prefix = qr/(?:\* )?(?:Noteworthy c|Major c|C)(?i:hanges)/;
188 open NEWS, '<', $news_file
189 or die "$ME: $news_file: cannot open for reading: $!\n";
190 while (defined (my $line = <NEWS>))
194 # Match lines like these:
195 # * Major changes in release 5.0.1:
196 # * Noteworthy changes in release 6.6 (2006-11-22) [stable]
197 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o
204 # This regexp must not match version numbers in NEWS items.
205 # For example, they might well say `introduced in 4.5.5',
206 # and we don't want that to match.
207 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o
215 or die "$ME: $news_file: no matching lines for `$curr_version'\n";
218 sub print_changelog_deltas ($$)
220 my ($package_name, $prev_version) = @_;
222 # Print new ChangeLog entries.
224 # First find all CVS-controlled ChangeLog files.
227 find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS'
228 and push @changelog, $File::Find::name}},
231 # If there are no ChangeLog files, we're done.
234 my %changelog = map {$_ => 1} @changelog;
236 # Reorder the list of files so that if there are ChangeLog
237 # files in the specified directories, they're listed first,
239 my @dir = qw ( . src lib m4 config doc );
241 # A typical @changelog array might look like this:
251 my $dot_slash = $d eq '.' ? $d : "./$d";
252 my $target = "$dot_slash/ChangeLog";
253 delete $changelog{$target}
254 and push @reordered, $target;
257 # Append any remaining ChangeLog files.
258 push @reordered, sort keys %changelog;
260 # Remove leading `./'.
261 @reordered = map { s!^\./!!; $_ } @reordered;
263 print "\nChangeLog entries:\n\n";
264 # print join ("\n", @reordered), "\n";
266 $prev_version =~ s/\./_/g;
267 my $prev_cvs_tag = "\U$package_name\E-$prev_version";
269 my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
270 open DIFF, '-|', $cmd
271 or die "$ME: cannot run `$cmd': $!\n";
272 # Print two types of lines, making minor changes:
273 # Lines starting with `+++ ', e.g.,
274 # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247
275 # and those starting with `+'.
276 # Don't print the others.
277 my $prev_printed_line_empty = 1;
278 while (defined (my $line = <DIFF>))
280 if ($line =~ /^\+\+\+ /)
282 my $separator = "*"x70 ."\n";
285 $prev_printed_line_empty
287 print $separator, $line, $separator;
289 elsif ($line =~ /^\+/)
293 $prev_printed_line_empty = ($line =~ /^$/);
298 # The exit code should be 1.
299 # Allow in case there are no modified ChangeLog entries.
300 $? == 256 || $? == 128
301 or warn "$ME: warning: `cmd' had unexpected exit code or signal ($?)\n";
304 sub get_tool_versions ($$)
306 my ($tool_list, $gnulib_version) = @_;
311 my @tool_version_pair;
312 foreach my $t (@$tool_list)
316 push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version;
319 # Assume that the last "word" on the first line of
320 # `tool --version` output is the version string.
321 my ($first_line, undef) = split ("\n", `$t --version`);
322 if ($first_line =~ /.* (\d[\w.-]+)$/)
325 push @tool_version_pair, "$t $1";
330 and $first_line = '';
331 warn "$ME: $t: unexpected --version output\n:$first_line";
339 return @tool_version_pair;
343 # Neutralize the locale, so that, for instance, "du" does not
344 # issue "1,2" instead of "1.2", what confuses our regexps.
356 my $print_checksums_p = 1;
360 'release-type=s' => \$release_type,
361 'package-name=s' => \$package_name,
362 'previous-version=s' => \$prev_version,
363 'current-version=s' => \$curr_version,
364 'gpg-key-id=s' => \$gpg_key_id,
365 'url-directory=s' => \@url_dir_list,
366 'news=s' => \@news_file,
367 'bootstrap-tools=s' => \$bootstrap_tools,
368 'gnulib-version=s' => \$gnulib_version,
369 'print-checksums!' => \$print_checksums_p,
370 'archive-suffix=s' => \@archive_suffixes,
372 help => sub { usage 0 },
373 version => sub { print "$ME version $VERSION\n"; exit },
377 # Ensure that sure each required option is specified.
379 or (warn "$ME: release type not specified\n"), $fail = 1;
381 or (warn "$ME: package name not specified\n"), $fail = 1;
383 or (warn "$ME: previous version string not specified\n"), $fail = 1;
385 or (warn "$ME: current version string not specified\n"), $fail = 1;
387 or (warn "$ME: GnuPG key ID not specified\n"), $fail = 1;
389 or (warn "$ME: URL directory name(s) not specified\n"), $fail = 1;
391 my @tool_list = split ',', $bootstrap_tools;
393 grep (/^gnulib$/, @tool_list) ^ defined $gnulib_version
394 and (warn "$ME: when specifying gnulib as a tool, you must also specify\n"
395 . "--gnulib-version=V, where V is the result of running git describe\n"
396 . "in the gnulib source directory.\n"), $fail = 1;
398 exists $valid_release_types{$release_type}
399 or (warn "$ME: `$release_type': invalid release type\n"), $fail = 1;
402 and (warn "$ME: too many arguments:\n", join ("\n", @ARGV), "\n"),
407 my $my_distdir = "$package_name-$curr_version";
409 my $xd = "$package_name-$prev_version-$curr_version.xdelta";
411 my @candidates = map { "$my_distdir.$_" } @archive_suffixes;
412 my @tarballs = grep {-f $_} @candidates;
415 or die "$ME: none of " . join(', ', @candidates) . " were found\n";
416 my @sizable = @tarballs;
418 and push @sizable, $xd;
419 my %size = sizes (@sizable);
423 # The markup is escaped as <\# so that when this script is sent by
424 # mail (or part of a diff), Gnus is not triggered.
427 Subject: $my_distdir released [$release_type]
429 <\#secure method=pgpmime mode=sign>
431 FIXME: put comments here
435 print_locations ("compressed sources", @url_dir_list, %size, @tarballs);
437 and print_locations ("xdelta diffs (useful? if so, "
438 . "please tell bug-gnulib\@gnu.org)",
439 @url_dir_list, %size, $xd);
440 my @sig_files = map { "$_.sig" } @tarballs;
441 print_locations ("GPG detached signatures[*]", @url_dir_list, %size,
445 and print_checksums (@sizable);
448 [*] You can use either of the above signature files to verify that
449 the corresponding file (without the .sig suffix) is intact. First,
450 be sure to download both the .sig file and the corresponding tarball.
451 Then, run a command like this:
453 gpg --verify $tarballs[0].sig
455 If that command fails because you don't have the required public key,
456 then run this command to import it:
458 gpg --keyserver keys.gnupg.net --recv-keys $gpg_key_id
460 and rerun the \`gpg --verify' command.
463 my @tool_versions = get_tool_versions (\@tool_list, $gnulib_version);
465 and print "\nThis release was bootstrapped with the following tools:",
466 join ('', map {"\n $_"} @tool_versions), "\n";
468 print_news_deltas ($_, $prev_version, $curr_version)
471 $release_type eq 'stable'
472 or print_changelog_deltas ($package_name, $prev_version);
477 ### Setup "GNU" style for perl-mode and cperl-mode.
479 ## perl-indent-level: 2
480 ## perl-continued-statement-offset: 2
481 ## perl-continued-brace-offset: 0
482 ## perl-brace-offset: 0
483 ## perl-brace-imaginary-offset: 0
484 ## perl-label-offset: -2
485 ## cperl-indent-level: 2
486 ## cperl-brace-offset: 0
487 ## cperl-continued-brace-offset: 0
488 ## cperl-label-offset: -2
489 ## cperl-extra-newline-before-brace: t
490 ## cperl-merge-trailing-else: nil
491 ## cperl-continued-statement-offset: 2
492 ## eval: (add-hook 'write-file-hooks 'time-stamp)
493 ## time-stamp-start: "my $VERSION = '"
494 ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
495 ## time-stamp-time-zone: "UTC"
496 ## time-stamp-end: "'; # UTC"