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 = '2011-04-29 21:01'; # 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-2011 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
78 --mail-headers=HEADERS a space-separated list of mail headers, e.g.,
79 To: x\@example.com Cc: y-announce\@example.com,...
81 --help display this help and exit
82 --version output version information and exit
90 =item C<%size> = C<sizes (@file)>
92 Compute the sizes of the C<@file> and return them as a hash. Return
93 C<undef> if one of the computation failed.
103 foreach my $f (@file)
105 my $cmd = "du --human $f";
107 # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
109 and (warn "$ME: command failed: `$cmd'\n"), $fail = 1;
111 $t =~ s/^([\d.]+[MkK]).*/${1}B/;
114 return $fail ? undef : %res;
117 =item C<print_locations ($title, \@url, \%size, @file)
119 Print a section C<$title> dedicated to the list of <@file>, which
120 sizes are stored in C<%size>, and which are available from the C<@url>.
124 sub print_locations ($\@\%@)
126 my ($title, $url, $size, @file) = @_;
127 print "Here are the $title:\n";
128 foreach my $url (@{$url})
133 print " (", $$size{$file}, ")"
134 if exists $$size{$file};
141 =item C<print_checksums (@file)
143 Print the MD5 and SHA1 signature section for each C<@file>.
147 sub print_checksums (@)
151 print "Here are the MD5 and SHA1 checksums:\n";
154 foreach my $meth (qw (md5 sha1))
156 foreach my $f (@file)
159 or die "$ME: $f: cannot open for reading: $!\n";
163 ? Digest::MD5->new->addfile(*IN)->hexdigest
164 : Digest::SHA1->new->addfile(*IN)->hexdigest);
172 =item C<print_news_deltas ($news_file, $prev_version, $curr_version)
174 Print the section of the NEWS file C<$news_file> addressing changes
175 between versions C<$prev_version> and C<$curr_version>.
179 sub print_news_deltas ($$$)
181 my ($news_file, $prev_version, $curr_version) = @_;
183 my $news_name = $news_file;
184 $news_name =~ s|^\./||;
186 print "\n$news_name\n\n";
188 # Print all lines from $news_file, starting with the first one
189 # that mentions $curr_version up to but not including
190 # the first occurrence of $prev_version.
193 my $re_prefix = qr/(?:\* )?(?:Noteworthy c|Major c|C)(?i:hanges)/;
195 open NEWS, '<', $news_file
196 or die "$ME: $news_file: cannot open for reading: $!\n";
197 while (defined (my $line = <NEWS>))
201 # Match lines like these:
202 # * Major changes in release 5.0.1:
203 # * Noteworthy changes in release 6.6 (2006-11-22) [stable]
204 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o
211 # This regexp must not match version numbers in NEWS items.
212 # For example, they might well say `introduced in 4.5.5',
213 # and we don't want that to match.
214 $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o
222 or die "$ME: $news_file: no matching lines for `$curr_version'\n";
225 sub print_changelog_deltas ($$)
227 my ($package_name, $prev_version) = @_;
229 # Print new ChangeLog entries.
231 # First find all CVS-controlled ChangeLog files.
234 find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS'
235 and push @changelog, $File::Find::name}},
238 # If there are no ChangeLog files, we're done.
241 my %changelog = map {$_ => 1} @changelog;
243 # Reorder the list of files so that if there are ChangeLog
244 # files in the specified directories, they're listed first,
246 my @dir = qw ( . src lib m4 config doc );
248 # A typical @changelog array might look like this:
258 my $dot_slash = $d eq '.' ? $d : "./$d";
259 my $target = "$dot_slash/ChangeLog";
260 delete $changelog{$target}
261 and push @reordered, $target;
264 # Append any remaining ChangeLog files.
265 push @reordered, sort keys %changelog;
267 # Remove leading `./'.
268 @reordered = map { s!^\./!!; $_ } @reordered;
270 print "\nChangeLog entries:\n\n";
271 # print join ("\n", @reordered), "\n";
273 $prev_version =~ s/\./_/g;
274 my $prev_cvs_tag = "\U$package_name\E-$prev_version";
276 my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
277 open DIFF, '-|', $cmd
278 or die "$ME: cannot run `$cmd': $!\n";
279 # Print two types of lines, making minor changes:
280 # Lines starting with `+++ ', e.g.,
281 # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247
282 # and those starting with `+'.
283 # Don't print the others.
284 my $prev_printed_line_empty = 1;
285 while (defined (my $line = <DIFF>))
287 if ($line =~ /^\+\+\+ /)
289 my $separator = "*"x70 ."\n";
292 $prev_printed_line_empty
294 print $separator, $line, $separator;
296 elsif ($line =~ /^\+/)
300 $prev_printed_line_empty = ($line =~ /^$/);
305 # The exit code should be 1.
306 # Allow in case there are no modified ChangeLog entries.
307 $? == 256 || $? == 128
308 or warn "$ME: warning: `cmd' had unexpected exit code or signal ($?)\n";
311 sub get_tool_versions ($$)
313 my ($tool_list, $gnulib_version) = @_;
318 my @tool_version_pair;
319 foreach my $t (@$tool_list)
323 push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version;
326 # Assume that the last "word" on the first line of
327 # `tool --version` output is the version string.
328 my ($first_line, undef) = split ("\n", `$t --version`);
329 if ($first_line =~ /.* (\d[\w.-]+)$/)
332 push @tool_version_pair, "$t $1";
337 and $first_line = '';
338 warn "$ME: $t: unexpected --version output\n:$first_line";
346 return @tool_version_pair;
350 # Neutralize the locale, so that, for instance, "du" does not
351 # issue "1,2" instead of "1.2", what confuses our regexps.
364 my $print_checksums_p = 1;
368 'mail-headers=s' => \$mail_headers,
369 'release-type=s' => \$release_type,
370 'package-name=s' => \$package_name,
371 'previous-version=s' => \$prev_version,
372 'current-version=s' => \$curr_version,
373 'gpg-key-id=s' => \$gpg_key_id,
374 'url-directory=s' => \@url_dir_list,
375 'news=s' => \@news_file,
376 'bootstrap-tools=s' => \$bootstrap_tools,
377 'gnulib-version=s' => \$gnulib_version,
378 'print-checksums!' => \$print_checksums_p,
379 'archive-suffix=s' => \@archive_suffixes,
381 help => sub { usage 0 },
382 version => sub { print "$ME version $VERSION\n"; exit },
386 # Ensure that sure each required option is specified.
388 or (warn "$ME: release type not specified\n"), $fail = 1;
390 or (warn "$ME: package name not specified\n"), $fail = 1;
392 or (warn "$ME: previous version string not specified\n"), $fail = 1;
394 or (warn "$ME: current version string not specified\n"), $fail = 1;
396 or (warn "$ME: GnuPG key ID not specified\n"), $fail = 1;
398 or (warn "$ME: URL directory name(s) not specified\n"), $fail = 1;
400 my @tool_list = split ',', $bootstrap_tools;
402 grep (/^gnulib$/, @tool_list) ^ defined $gnulib_version
403 and (warn "$ME: when specifying gnulib as a tool, you must also specify\n"
404 . "--gnulib-version=V, where V is the result of running git describe\n"
405 . "in the gnulib source directory.\n"), $fail = 1;
407 exists $valid_release_types{$release_type}
408 or (warn "$ME: `$release_type': invalid release type\n"), $fail = 1;
411 and (warn "$ME: too many arguments:\n", join ("\n", @ARGV), "\n"),
416 my $my_distdir = "$package_name-$curr_version";
418 my $xd = "$package_name-$prev_version-$curr_version.xdelta";
420 my @candidates = map { "$my_distdir.$_" } @archive_suffixes;
421 my @tarballs = grep {-f $_} @candidates;
424 or die "$ME: none of " . join(', ', @candidates) . " were found\n";
425 my @sizable = @tarballs;
427 and push @sizable, $xd;
428 my %size = sizes (@sizable);
433 if (defined $mail_headers)
435 ($headers = $mail_headers) =~ s/\s+(\S+:)/\n$1/g;
439 # The markup is escaped as <\# so that when this script is sent by
440 # mail (or part of a diff), Gnus is not triggered.
443 ${headers}Subject: $my_distdir released [$release_type]
445 <\#secure method=pgpmime mode=sign>
447 FIXME: put comments here
451 print_locations ("compressed sources", @url_dir_list, %size, @tarballs);
453 and print_locations ("xdelta diffs (useful? if so, "
454 . "please tell bug-gnulib\@gnu.org)",
455 @url_dir_list, %size, $xd);
456 my @sig_files = map { "$_.sig" } @tarballs;
457 print_locations ("GPG detached signatures[*]", @url_dir_list, %size,
459 if ($url_dir_list[0] =~ "gnu\.org")
461 print "To reduce load on the main server, use a mirror listed at:\n";
462 print " http://www.gnu.org/order/ftp.html\n\n";
466 and print_checksums (@sizable);
469 [*] You can use either of the above signature files to verify that
470 the corresponding file (without the .sig suffix) is intact. First,
471 be sure to download both the .sig file and the corresponding tarball.
472 Then, run a command like this:
474 gpg --verify $tarballs[0].sig
476 If that command fails because you don't have the required public key,
477 then run this command to import it:
479 gpg --keyserver keys.gnupg.net --recv-keys $gpg_key_id
481 and rerun the \`gpg --verify' command.
484 my @tool_versions = get_tool_versions (\@tool_list, $gnulib_version);
486 and print "\nThis release was bootstrapped with the following tools:",
487 join ('', map {"\n $_"} @tool_versions), "\n";
489 print_news_deltas ($_, $prev_version, $curr_version)
492 $release_type eq 'stable'
493 or print_changelog_deltas ($package_name, $prev_version);
498 ### Setup "GNU" style for perl-mode and cperl-mode.
501 ## perl-indent-level: 2
502 ## perl-continued-statement-offset: 2
503 ## perl-continued-brace-offset: 0
504 ## perl-brace-offset: 0
505 ## perl-brace-imaginary-offset: 0
506 ## perl-label-offset: -2
507 ## perl-extra-newline-before-brace: t
508 ## perl-merge-trailing-else: nil
509 ## eval: (add-hook 'write-file-hooks 'time-stamp)
510 ## time-stamp-start: "my $VERSION = '"
511 ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
512 ## time-stamp-time-zone: "UTC"
513 ## time-stamp-end: "'; # UTC"