2
# Generate a release announcement message.
4
my $VERSION = '2008-06-02 08:18'; # 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 keys.gnupg.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"