announce-gen 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}'
  2. & eval 'exec perl -wS "$0" $argv:q'
  3. if 0;
  4. # Generate a release announcement message.
  5. my $VERSION = '2012-06-08 06:53'; # UTC
  6. # The definition above must lie within the first 8 lines in order
  7. # for the Emacs time-stamp write hook (at end) to update it.
  8. # If you change this file with Emacs, please let the write hook
  9. # do its job. Otherwise, update this string manually.
  10. # Copyright (C) 2002-2013 Free Software Foundation, Inc.
  11. # This program is free software: you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation, either version 3 of the License, or
  14. # (at your option) any later version.
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. # Written by Jim Meyering
  22. use strict;
  23. use Getopt::Long;
  24. use Digest::MD5;
  25. eval { require Digest::SHA; }
  26. or eval 'use Digest::SHA1';
  27. use POSIX qw(strftime);
  28. (my $ME = $0) =~ s|.*/||;
  29. my %valid_release_types = map {$_ => 1} qw (alpha beta stable);
  30. my @archive_suffixes = ('tar.gz', 'tar.bz2', 'tar.lzma', 'tar.xz');
  31. my $srcdir = '.';
  32. sub usage ($)
  33. {
  34. my ($exit_code) = @_;
  35. my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
  36. if ($exit_code != 0)
  37. {
  38. print $STREAM "Try '$ME --help' for more information.\n";
  39. }
  40. else
  41. {
  42. my @types = sort keys %valid_release_types;
  43. print $STREAM <<EOF;
  44. Usage: $ME [OPTIONS]
  45. Generate an announcement message. Run this from builddir.
  46. OPTIONS:
  47. These options must be specified:
  48. --release-type=TYPE TYPE must be one of @types
  49. --package-name=PACKAGE_NAME
  50. --previous-version=VER
  51. --current-version=VER
  52. --gpg-key-id=ID The GnuPG ID of the key used to sign the tarballs
  53. --url-directory=URL_DIR
  54. The following are optional:
  55. --news=NEWS_FILE include the NEWS section about this release
  56. from this NEWS_FILE; accumulates.
  57. --srcdir=DIR where to find the NEWS_FILEs (default: $srcdir)
  58. --bootstrap-tools=TOOL_LIST a comma-separated list of tools, e.g.,
  59. autoconf,automake,bison,gnulib
  60. --gnulib-version=VERSION report VERSION as the gnulib version, where
  61. VERSION is the result of running git describe
  62. in the gnulib source directory.
  63. required if gnulib is in TOOL_LIST.
  64. --no-print-checksums do not emit MD5 or SHA1 checksums
  65. --archive-suffix=SUF add SUF to the list of archive suffixes
  66. --mail-headers=HEADERS a space-separated list of mail headers, e.g.,
  67. To: x\@example.com Cc: y-announce\@example.com,...
  68. --help display this help and exit
  69. --version output version information and exit
  70. EOF
  71. }
  72. exit $exit_code;
  73. }
  74. =item C<%size> = C<sizes (@file)>
  75. Compute the sizes of the C<@file> and return them as a hash. Return
  76. C<undef> if one of the computation failed.
  77. =cut
  78. sub sizes (@)
  79. {
  80. my (@file) = @_;
  81. my $fail = 0;
  82. my %res;
  83. foreach my $f (@file)
  84. {
  85. my $cmd = "du -h $f";
  86. my $t = `$cmd`;
  87. # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
  88. $@
  89. and (warn "command failed: '$cmd'\n"), $fail = 1;
  90. chomp $t;
  91. $t =~ s/^\s*([\d.]+[MkK]).*/${1}B/;
  92. $res{$f} = $t;
  93. }
  94. return $fail ? undef : %res;
  95. }
  96. =item C<print_locations ($title, \@url, \%size, @file)
  97. Print a section C<$title> dedicated to the list of <@file>, which
  98. sizes are stored in C<%size>, and which are available from the C<@url>.
  99. =cut
  100. sub print_locations ($\@\%@)
  101. {
  102. my ($title, $url, $size, @file) = @_;
  103. print "Here are the $title:\n";
  104. foreach my $url (@{$url})
  105. {
  106. for my $file (@file)
  107. {
  108. print " $url/$file";
  109. print " (", $$size{$file}, ")"
  110. if exists $$size{$file};
  111. print "\n";
  112. }
  113. }
  114. print "\n";
  115. }
  116. =item C<print_checksums (@file)
  117. Print the MD5 and SHA1 signature section for each C<@file>.
  118. =cut
  119. sub print_checksums (@)
  120. {
  121. my (@file) = @_;
  122. print "Here are the MD5 and SHA1 checksums:\n";
  123. print "\n";
  124. foreach my $meth (qw (md5 sha1))
  125. {
  126. foreach my $f (@file)
  127. {
  128. open IN, '<', $f
  129. or die "$ME: $f: cannot open for reading: $!\n";
  130. binmode IN;
  131. my $dig =
  132. ($meth eq 'md5'
  133. ? Digest::MD5->new->addfile(*IN)->hexdigest
  134. : Digest::SHA1->new->addfile(*IN)->hexdigest);
  135. close IN;
  136. print "$dig $f\n";
  137. }
  138. }
  139. print "\n";
  140. }
  141. =item C<print_news_deltas ($news_file, $prev_version, $curr_version)
  142. Print the section of the NEWS file C<$news_file> addressing changes
  143. between versions C<$prev_version> and C<$curr_version>.
  144. =cut
  145. sub print_news_deltas ($$$)
  146. {
  147. my ($news_file, $prev_version, $curr_version) = @_;
  148. my $news_name = $news_file;
  149. $news_name =~ s|^\Q$srcdir\E/||;
  150. print "\n$news_name\n\n";
  151. # Print all lines from $news_file, starting with the first one
  152. # that mentions $curr_version up to but not including
  153. # the first occurrence of $prev_version.
  154. my $in_items;
  155. my $re_prefix = qr/(?:\* )?(?:Noteworthy c|Major c|C)(?i:hanges)/;
  156. my $found_news;
  157. open NEWS, '<', $news_file
  158. or die "$ME: $news_file: cannot open for reading: $!\n";
  159. while (defined (my $line = <NEWS>))
  160. {
  161. if ( ! $in_items)
  162. {
  163. # Match lines like these:
  164. # * Major changes in release 5.0.1:
  165. # * Noteworthy changes in release 6.6 (2006-11-22) [stable]
  166. $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o
  167. or next;
  168. $in_items = 1;
  169. print $line;
  170. }
  171. else
  172. {
  173. # This regexp must not match version numbers in NEWS items.
  174. # For example, they might well say "introduced in 4.5.5",
  175. # and we don't want that to match.
  176. $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o
  177. and last;
  178. print $line;
  179. $line =~ /\S/
  180. and $found_news = 1;
  181. }
  182. }
  183. close NEWS;
  184. $in_items
  185. or die "$ME: $news_file: no matching lines for '$curr_version'\n";
  186. $found_news
  187. or die "$ME: $news_file: no news item found for '$curr_version'\n";
  188. }
  189. sub print_changelog_deltas ($$)
  190. {
  191. my ($package_name, $prev_version) = @_;
  192. # Print new ChangeLog entries.
  193. # First find all CVS-controlled ChangeLog files.
  194. use File::Find;
  195. my @changelog;
  196. find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS'
  197. and push @changelog, $File::Find::name}},
  198. '.');
  199. # If there are no ChangeLog files, we're done.
  200. @changelog
  201. or return;
  202. my %changelog = map {$_ => 1} @changelog;
  203. # Reorder the list of files so that if there are ChangeLog
  204. # files in the specified directories, they're listed first,
  205. # in this order:
  206. my @dir = qw ( . src lib m4 config doc );
  207. # A typical @changelog array might look like this:
  208. # ./ChangeLog
  209. # ./po/ChangeLog
  210. # ./m4/ChangeLog
  211. # ./lib/ChangeLog
  212. # ./doc/ChangeLog
  213. # ./config/ChangeLog
  214. my @reordered;
  215. foreach my $d (@dir)
  216. {
  217. my $dot_slash = $d eq '.' ? $d : "./$d";
  218. my $target = "$dot_slash/ChangeLog";
  219. delete $changelog{$target}
  220. and push @reordered, $target;
  221. }
  222. # Append any remaining ChangeLog files.
  223. push @reordered, sort keys %changelog;
  224. # Remove leading './'.
  225. @reordered = map { s!^\./!!; $_ } @reordered;
  226. print "\nChangeLog entries:\n\n";
  227. # print join ("\n", @reordered), "\n";
  228. $prev_version =~ s/\./_/g;
  229. my $prev_cvs_tag = "\U$package_name\E-$prev_version";
  230. my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
  231. open DIFF, '-|', $cmd
  232. or die "$ME: cannot run '$cmd': $!\n";
  233. # Print two types of lines, making minor changes:
  234. # Lines starting with '+++ ', e.g.,
  235. # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247
  236. # and those starting with '+'.
  237. # Don't print the others.
  238. my $prev_printed_line_empty = 1;
  239. while (defined (my $line = <DIFF>))
  240. {
  241. if ($line =~ /^\+\+\+ /)
  242. {
  243. my $separator = "*"x70 ."\n";
  244. $line =~ s///;
  245. $line =~ s/\s.*//;
  246. $prev_printed_line_empty
  247. or print "\n";
  248. print $separator, $line, $separator;
  249. }
  250. elsif ($line =~ /^\+/)
  251. {
  252. $line =~ s///;
  253. print $line;
  254. $prev_printed_line_empty = ($line =~ /^$/);
  255. }
  256. }
  257. close DIFF;
  258. # The exit code should be 1.
  259. # Allow in case there are no modified ChangeLog entries.
  260. $? == 256 || $? == 128
  261. or warn "warning: '$cmd' had unexpected exit code or signal ($?)\n";
  262. }
  263. sub get_tool_versions ($$)
  264. {
  265. my ($tool_list, $gnulib_version) = @_;
  266. @$tool_list
  267. or return ();
  268. my $fail;
  269. my @tool_version_pair;
  270. foreach my $t (@$tool_list)
  271. {
  272. if ($t eq 'gnulib')
  273. {
  274. push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version;
  275. next;
  276. }
  277. # Assume that the last "word" on the first line of
  278. # 'tool --version' output is the version string.
  279. my ($first_line, undef) = split ("\n", `$t --version`);
  280. if ($first_line =~ /.* (\d[\w.-]+)$/)
  281. {
  282. $t = ucfirst $t;
  283. push @tool_version_pair, "$t $1";
  284. }
  285. else
  286. {
  287. defined $first_line
  288. and $first_line = '';
  289. warn "$t: unexpected --version output\n:$first_line";
  290. $fail = 1;
  291. }
  292. }
  293. $fail
  294. and exit 1;
  295. return @tool_version_pair;
  296. }
  297. {
  298. # Neutralize the locale, so that, for instance, "du" does not
  299. # issue "1,2" instead of "1.2", what confuses our regexps.
  300. $ENV{LC_ALL} = "C";
  301. my $mail_headers;
  302. my $release_type;
  303. my $package_name;
  304. my $prev_version;
  305. my $curr_version;
  306. my $gpg_key_id;
  307. my @url_dir_list;
  308. my @news_file;
  309. my $bootstrap_tools;
  310. my $gnulib_version;
  311. my $print_checksums_p = 1;
  312. # Reformat the warnings before displaying them.
  313. local $SIG{__WARN__} = sub
  314. {
  315. my ($msg) = @_;
  316. # Warnings from GetOptions.
  317. $msg =~ s/Option (\w)/option --$1/;
  318. warn "$ME: $msg";
  319. };
  320. GetOptions
  321. (
  322. 'mail-headers=s' => \$mail_headers,
  323. 'release-type=s' => \$release_type,
  324. 'package-name=s' => \$package_name,
  325. 'previous-version=s' => \$prev_version,
  326. 'current-version=s' => \$curr_version,
  327. 'gpg-key-id=s' => \$gpg_key_id,
  328. 'url-directory=s' => \@url_dir_list,
  329. 'news=s' => \@news_file,
  330. 'srcdir=s' => \$srcdir,
  331. 'bootstrap-tools=s' => \$bootstrap_tools,
  332. 'gnulib-version=s' => \$gnulib_version,
  333. 'print-checksums!' => \$print_checksums_p,
  334. 'archive-suffix=s' => \@archive_suffixes,
  335. help => sub { usage 0 },
  336. version => sub { print "$ME version $VERSION\n"; exit },
  337. ) or usage 1;
  338. my $fail = 0;
  339. # Ensure that each required option is specified.
  340. $release_type
  341. or (warn "release type not specified\n"), $fail = 1;
  342. $package_name
  343. or (warn "package name not specified\n"), $fail = 1;
  344. $prev_version
  345. or (warn "previous version string not specified\n"), $fail = 1;
  346. $curr_version
  347. or (warn "current version string not specified\n"), $fail = 1;
  348. $gpg_key_id
  349. or (warn "GnuPG key ID not specified\n"), $fail = 1;
  350. @url_dir_list
  351. or (warn "URL directory name(s) not specified\n"), $fail = 1;
  352. my @tool_list = split ',', $bootstrap_tools;
  353. grep (/^gnulib$/, @tool_list) ^ defined $gnulib_version
  354. and (warn "when specifying gnulib as a tool, you must also specify\n"
  355. . "--gnulib-version=V, where V is the result of running git describe\n"
  356. . "in the gnulib source directory.\n"), $fail = 1;
  357. exists $valid_release_types{$release_type}
  358. or (warn "'$release_type': invalid release type\n"), $fail = 1;
  359. @ARGV
  360. and (warn "too many arguments:\n", join ("\n", @ARGV), "\n"),
  361. $fail = 1;
  362. $fail
  363. and usage 1;
  364. my $my_distdir = "$package_name-$curr_version";
  365. my $xd = "$package_name-$prev_version-$curr_version.xdelta";
  366. my @candidates = map { "$my_distdir.$_" } @archive_suffixes;
  367. my @tarballs = grep {-f $_} @candidates;
  368. @tarballs
  369. or die "$ME: none of " . join(', ', @candidates) . " were found\n";
  370. my @sizable = @tarballs;
  371. -f $xd
  372. and push @sizable, $xd;
  373. my %size = sizes (@sizable);
  374. %size
  375. or exit 1;
  376. my $headers = '';
  377. if (defined $mail_headers)
  378. {
  379. ($headers = $mail_headers) =~ s/\s+(\S+:)/\n$1/g;
  380. $headers .= "\n";
  381. }
  382. # The markup is escaped as <\# so that when this script is sent by
  383. # mail (or part of a diff), Gnus is not triggered.
  384. print <<EOF;
  385. ${headers}Subject: $my_distdir released [$release_type]
  386. <\#secure method=pgpmime mode=sign>
  387. FIXME: put comments here
  388. EOF
  389. if (@url_dir_list == 1 && @tarballs == 1)
  390. {
  391. # When there's only one tarball and one URL, use a more concise form.
  392. my $m = "$url_dir_list[0]/$tarballs[0]";
  393. print "Here are the compressed sources and a GPG detached signature[*]:\n"
  394. . " $m\n"
  395. . " $m.sig\n\n";
  396. }
  397. else
  398. {
  399. print_locations ("compressed sources", @url_dir_list, %size, @tarballs);
  400. -f $xd
  401. and print_locations ("xdelta diffs (useful? if so, "
  402. . "please tell bug-gnulib\@gnu.org)",
  403. @url_dir_list, %size, $xd);
  404. my @sig_files = map { "$_.sig" } @tarballs;
  405. print_locations ("GPG detached signatures[*]", @url_dir_list, %size,
  406. @sig_files);
  407. }
  408. if ($url_dir_list[0] =~ "gnu\.org")
  409. {
  410. print "Use a mirror for higher download bandwidth:\n";
  411. if (@tarballs == 1 && $url_dir_list[0] =~ m!http://ftp\.gnu\.org/gnu/!)
  412. {
  413. (my $m = "$url_dir_list[0]/$tarballs[0]")
  414. =~ s!http://ftp\.gnu\.org/gnu/!http://ftpmirror\.gnu\.org/!;
  415. print " $m\n"
  416. . " $m.sig\n\n";
  417. }
  418. else
  419. {
  420. print " http://www.gnu.org/order/ftp.html\n\n";
  421. }
  422. }
  423. $print_checksums_p
  424. and print_checksums (@sizable);
  425. print <<EOF;
  426. [*] Use a .sig file to verify that the corresponding file (without the
  427. .sig suffix) is intact. First, be sure to download both the .sig file
  428. and the corresponding tarball. Then, run a command like this:
  429. gpg --verify $tarballs[0].sig
  430. If that command fails because you don't have the required public key,
  431. then run this command to import it:
  432. gpg --keyserver keys.gnupg.net --recv-keys $gpg_key_id
  433. and rerun the 'gpg --verify' command.
  434. EOF
  435. my @tool_versions = get_tool_versions (\@tool_list, $gnulib_version);
  436. @tool_versions
  437. and print "\nThis release was bootstrapped with the following tools:",
  438. join ('', map {"\n $_"} @tool_versions), "\n";
  439. print_news_deltas ($_, $prev_version, $curr_version)
  440. foreach @news_file;
  441. $release_type eq 'stable'
  442. or print_changelog_deltas ($package_name, $prev_version);
  443. exit 0;
  444. }
  445. ### Setup "GNU" style for perl-mode and cperl-mode.
  446. ## Local Variables:
  447. ## mode: perl
  448. ## perl-indent-level: 2
  449. ## perl-continued-statement-offset: 2
  450. ## perl-continued-brace-offset: 0
  451. ## perl-brace-offset: 0
  452. ## perl-brace-imaginary-offset: 0
  453. ## perl-label-offset: -2
  454. ## perl-extra-newline-before-brace: t
  455. ## perl-merge-trailing-else: nil
  456. ## eval: (add-hook 'write-file-hooks 'time-stamp)
  457. ## time-stamp-start: "my $VERSION = '"
  458. ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
  459. ## time-stamp-time-zone: "UTC"
  460. ## time-stamp-end: "'; # UTC"
  461. ## End: