announce-gen 16 KB

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