2 # Generate a release announcement message.
4 my $VERSION = '2008-02-08 10:34'; # 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-2008 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
);
40 # Nobody ever checks the status of print()s. That's okay, because
41 # if any do fail, we're guaranteed to get an indicator when we close()
44 # Close stdout now, and if there were no errors, return happy status.
45 # If stdout has already been closed by the script, though, do nothing.
51 # Errors closing stdout. Indicate that, and hope stderr is OK.
52 warn "$ME: closing standard output: $!\n";
54 # Don't be so arrogant as to assume that we're the first END handler
55 # defined, and thus the last one invoked. There may be others yet
56 # to come. $? will be passed on to them, and to the final _exit().
58 # If it isn't already an error, make it one (and if it _is_ an error,
59 # preserve the value: it might be important).
66 my $STREAM = ($exit_code == 0 ?
*STDOUT
: *STDERR
);
69 print $STREAM "Try `$ME --help' for more information.\n";
73 my @types = sort keys %valid_release_types;
79 Generate an announcement message.
81 These options must be specified:
83 --release-type=TYPE TYPE must be one of @types
84 --package-name=PACKAGE_NAME
85 --previous-version=VER
87 --gpg-key-id=ID The GnuPG ID of the key used to sign the tarballs
88 --url-directory=URL_DIR
90 The following are optional:
93 --bootstrap-tools=TOOL_LIST a comma-separated list of tools, e.g.,
94 autoconf,automake,bison,gnulib
95 --gnulib-snapshot-date=DATE if gnulib is in the bootstrap tool list,
96 then report this as the snapshot date.
97 If not specified, use the current date/time.
98 If you specify a date here, be sure it is UTC.
100 --help display this help and exit
101 --version output version information and exit
109 =item C<%size> = C<sizes (@file)>
111 Compute the sizes of the C<@file> and return them as a hash. Return
112 C<undef> if one of the computation failed.
122 foreach my $f (@file)
124 my $cmd = "du --human $f";
126 # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
128 and (warn "$ME: command failed: `$cmd'\n"), $fail = 1;
130 $t =~ s/^([\d.]+[MkK]).*/${1}B/;
133 return $fail ?
undef : %res;
136 =item C<print_locations ($title, \@url, \%size, @file)
138 Print a section C<$title> dedicated to the list of <@file>, which
139 sizes are stored in C<%size>, and which are available from the C<@url>.
143 sub print_locations
($\@\
%@
)
145 my ($title, $url, $size, @file) = @_;
146 print "Here are the $title:\n";
147 foreach my $url (@
{$url})
152 print " (", $$size{$file}, ")"
153 if exists $$size{$file};
160 =item C<print_checksums (@file)
162 Print the MD5 and SHA1 signature section for each C<@file>.
166 sub print_checksums
(@
)
170 print "Here are the MD5 and SHA1 checksums:\n";
173 foreach my $meth (qw
(md5 sha1
))
175 foreach my $f (@file)
178 or die "$ME: $f: cannot open for reading: $!\n";
182 ? Digest
::MD5
->new->addfile(*IN
)->hexdigest
183 : Digest
::SHA1
->new->addfile(*IN
)->hexdigest);
192 =item C<print_news_deltas ($news_file, $prev_version, $curr_version)
194 Print the section of the NEWS file C<$news_file> addressing changes
195 between versions C<$prev_version> and C<$curr_version>.
199 sub print_news_deltas
($$$)
201 my ($news_file, $prev_version, $curr_version) = @_;
203 print "\n$news_file\n\n";
205 # Print all lines from $news_file, starting with the first one
206 # that mentions $curr_version up to but not including
207 # the first occurrence of $prev_version.
210 my $re_prefix = qr/\* (?:Noteworthy|Major) change/;
212 open NEWS
, '<', $news_file
213 or die "$ME: $news_file: cannot open for reading: $!\n";
214 while (defined (my $line = <NEWS
>))
218 # Match lines like these:
219 # * Major changes in release 5.0.1:
220 # * Noteworthy changes in release 6.6 (2006-11-22) [stable]
221 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o
228 # This regexp must not match version numbers in NEWS items.
229 # For example, they might well say `introduced in 4.5.5',
230 # and we don't want that to match.
231 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o
239 or die "$ME: $news_file: no matching lines for `$curr_version'\n";
242 sub print_changelog_deltas
($$)
244 my ($package_name, $prev_version) = @_;
246 # Print new ChangeLog entries.
248 # First find all CVS-controlled ChangeLog files.
251 find
({wanted
=> sub {$_ eq 'ChangeLog' && -d
'CVS'
252 and push @changelog, $File::Find
::name
}},
255 # If there are no ChangeLog files, we're done.
258 my %changelog = map {$_ => 1} @changelog;
260 # Reorder the list of files so that if there are ChangeLog
261 # files in the specified directories, they're listed first,
263 my @dir = qw
( . src lib m4 config doc
);
265 # A typical @changelog array might look like this:
275 my $dot_slash = $d eq '.' ?
$d : "./$d";
276 my $target = "$dot_slash/ChangeLog";
277 delete $changelog{$target}
278 and push @reordered, $target;
281 # Append any remaining ChangeLog files.
282 push @reordered, sort keys %changelog;
284 # Remove leading `./'.
285 @reordered = map { s!^\./!!; $_ } @reordered;
287 print "\nChangeLog entries:\n\n";
288 # print join ("\n", @reordered), "\n";
290 $prev_version =~ s/\./_/g;
291 my $prev_cvs_tag = "\U$package_name\E-$prev_version";
293 my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
294 open DIFF
, '-|', $cmd
295 or die "$ME: cannot run `$cmd': $!\n";
296 # Print two types of lines, making minor changes:
297 # Lines starting with `+++ ', e.g.,
298 # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247
299 # and those starting with `+'.
300 # Don't print the others.
301 my $prev_printed_line_empty = 1;
302 while (defined (my $line = <DIFF
>))
304 if ($line =~ /^\+\+\+ /)
306 my $separator = "*"x70
."\n";
309 $prev_printed_line_empty
311 print $separator, $line, $separator;
313 elsif ($line =~ /^\+/)
317 $prev_printed_line_empty = ($line =~ /^$/);
322 # The exit code should be 1.
323 # Allow in case there are no modified ChangeLog entries.
324 $?
== 256 || $?
== 128
325 or warn "$ME: warning: `cmd' had unexpected exit code or signal ($?)\n";
328 sub get_tool_versions
($$)
330 my ($tool_list, $gnulib_version) = @_;
335 my @tool_version_pair;
336 foreach my $t (@
$tool_list)
340 push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version;
343 # Assume that the last "word" on the first line of
344 # `tool --version` output is the version string.
345 my ($first_line, undef) = split ("\n", `$t --version`);
346 if ($first_line =~ /.* (\d[\w.-]+)$/)
349 push @tool_version_pair, "$t $1";
354 and $first_line = '';
355 warn "$ME: $t: unexpected --version output\n:$first_line";
363 return @tool_version_pair;
367 # Neutralize the locale, so that, for instance, "du" does not
368 # issue "1,2" instead of "1.2", what confuses our regexps.
383 'release-type=s' => \
$release_type,
384 'package-name=s' => \
$package_name,
385 'previous-version=s' => \
$prev_version,
386 'current-version=s' => \
$curr_version,
387 'gpg-key-id=s' => \
$gpg_key_id,
388 'url-directory=s' => \
@url_dir_list,
389 'news=s' => \
@news_file,
390 'bootstrap-tools=s' => \
$bootstrap_tools,
391 'gnulib-version=s' => \
$gnulib_version,
393 help
=> sub { usage
0 },
394 version
=> sub { print "$ME version $VERSION\n"; exit },
398 # Ensure that sure each required option is specified.
400 or (warn "$ME: release type not specified\n"), $fail = 1;
402 or (warn "$ME: package name not specified\n"), $fail = 1;
404 or (warn "$ME: previous version string not specified\n"), $fail = 1;
406 or (warn "$ME: current version string not specified\n"), $fail = 1;
408 or (warn "$ME: GnuPG key ID not specified\n"), $fail = 1;
410 or (warn "$ME: URL directory name(s) not specified\n"), $fail = 1;
412 my @tool_list = split ',', $bootstrap_tools;
414 grep (/^gnulib$/, @tool_list) ^ defined $gnulib_version
415 and (warn "$ME: when specifying gnulib as a tool, you must also specify\n"
416 . "--gnulib-version=V, where V is the result of running git describe\n"
417 . "in the gnulib source directory.\n"), $fail = 1;
419 exists $valid_release_types{$release_type}
420 or (warn "$ME: `$release_type': invalid release type\n"), $fail = 1;
423 and (warn "$ME: too many arguments:\n", join ("\n", @ARGV), "\n"),
428 my $my_distdir = "$package_name-$curr_version";
429 my $tgz = "$my_distdir.tar.gz";
430 my $tbz = "$my_distdir.tar.bz2";
431 my $lzma = "$my_distdir.tar.lzma";
432 my $xd = "$package_name-$prev_version-$curr_version.xdelta";
434 my @tarballs = grep {-f
$_} ($tgz, $tbz, $lzma);
435 my @sizable = @tarballs;
437 and push @sizable, $xd;
438 my %size = sizes
(@sizable);
442 # The markup is escaped as <\# so that when this script is sent by
443 # mail (or part of a diff), Gnus is not triggered.
446 Subject: $my_distdir released
448 <\#secure method=pgpmime mode=sign>
450 FIXME: put comments here
454 print_locations
("compressed sources", @url_dir_list, %size, @tarballs);
456 and print_locations
("xdelta diffs (useful? if so, "
457 . "please tell bug-gnulib\@gnu.org)",
458 @url_dir_list, %size, $xd);
459 my @sig_files = map { "$_.sig" } @tarballs;
460 print_locations
("GPG detached signatures[*]", @url_dir_list, %size,
463 print_checksums
(@sizable);
467 [*] You can use either of the above signature files to verify that
468 the corresponding file (without the .sig suffix) is intact. First,
469 be sure to download both the .sig file and the corresponding tarball.
470 Then, run a command like this:
472 gpg --verify $tgz.sig
474 If that command fails because you don't have the required public key,
475 then run this command to import it:
477 gpg --keyserver wwwkeys.pgp.net --recv-keys $gpg_key_id
479 and rerun the \`gpg --verify' command.
482 my @tool_versions = get_tool_versions
(\
@tool_list, $gnulib_version);
484 and print "\nThis release was bootstrapped with the following tools:",
485 join ('', map {"\n $_"} @tool_versions), "\n";
487 print_news_deltas
($_, $prev_version, $curr_version)
490 $release_type eq 'major'
491 or print_changelog_deltas
($package_name, $prev_version);
496 ### Setup "GNU" style for perl-mode and cperl-mode.
498 ## perl-indent-level: 2
499 ## perl-continued-statement-offset: 2
500 ## perl-continued-brace-offset: 0
501 ## perl-brace-offset: 0
502 ## perl-brace-imaginary-offset: 0
503 ## perl-label-offset: -2
504 ## cperl-indent-level: 2
505 ## cperl-brace-offset: 0
506 ## cperl-continued-brace-offset: 0
507 ## cperl-label-offset: -2
508 ## cperl-extra-newline-before-brace: t
509 ## cperl-merge-trailing-else: nil
510 ## cperl-continued-statement-offset: 2
511 ## eval: (add-hook 'write-file-hooks 'time-stamp)
512 ## time-stamp-start: "my $VERSION = '"
513 ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
514 ## time-stamp-time-zone: "UTC"
515 ## time-stamp-end: "'; # UTC"