4 # Generate a release announcement message.
6 # Copyright (C) 2002-2020 Free Software Foundation, Inc.
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 # Written by Jim Meyering
23 # This is a prologue that allows to run a perl script as an executable
24 # on systems that are compliant to a POSIX version before POSIX:2017.
25 # On such systems, the usual invocation of an executable through execlp()
26 # or execvp() fails with ENOEXEC if it is a script that does not start
27 # with a #! line. The script interpreter mentioned in the #! line has
28 # to be /bin/sh, because on GuixSD systems that is the only program that
29 # has a fixed file name. The second line is essential for perl and is
30 # also useful for editing this file in Emacs. The next two lines below
31 # are valid code in both sh and perl. When executed by sh, they re-execute
32 # the script through the perl program found in $PATH. The '-x' option
33 # is essential as well; without it, perl would re-execute the script
34 # through /bin/sh. When executed by perl, the next two lines are a no-op.
35 eval 'exec perl -wSx "$0" "$@"'
38 my
$VERSION = '2020-05-10 16:13'; # UTC
39 # The definition above must lie within the first 8 lines in order
40 # for the Emacs time-stamp write hook (at end) to update it.
41 # If you change this file with Emacs, please let the write hook
42 # do its job. Otherwise, update this string manually.
46 use POSIX qw
(strftime
);
48 (my
$ME = $0) =~ s|.
*/||
;
50 my
%valid_release_types
= map
{$_ => 1} qw
(alpha beta stable
);
51 my @archive_suffixes
= qw
(tar.gz
tar.bz2
tar.lz
tar.lzma
tar.xz
);
54 'md5' => (eval { require Digest
::MD5
; } and
'Digest::MD5'),
55 'sha1' => ((eval { require Digest
::SHA
; } and
'Digest::SHA')
56 or
(eval { require Digest
::SHA1
; } and
'Digest::SHA1'))
63 my
$STREAM = ($exit_code == 0 ?
*STDOUT
: *STDERR
);
66 print
$STREAM "Try '$ME --help' for more information.\n";
70 my @types
= sort keys
%valid_release_types
;
73 Generate an announcement message. Run this from builddir.
77 These options must be specified:
79 --release-type=TYPE TYPE must be one of @types
80 --package-name=PACKAGE_NAME
81 --previous-version=VER
83 --gpg-key-id=ID The GnuPG ID of the key used to sign the tarballs
84 --url-directory=URL_DIR
86 The following are optional:
88 --news=NEWS_FILE include the NEWS section about this release
89 from this NEWS_FILE; accumulates.
90 --srcdir=DIR where to find the NEWS_FILEs (default: $srcdir)
91 --bootstrap-tools=TOOL_LIST a comma-separated list of tools, e.g.,
92 autoconf,automake,bison,gnulib
93 --gnulib-version=VERSION report VERSION as the gnulib version, where
94 VERSION is the result of running git describe
95 in the gnulib source directory.
96 required if gnulib is in TOOL_LIST.
97 --no-print-checksums do not emit MD5 or SHA1 checksums
98 --archive-suffix=SUF add SUF to the list of archive suffixes
99 --mail-headers=HEADERS a space-separated list of mail headers, e.g.,
100 To: x\@example.com Cc: y-announce\@example.com,...
102 --help display this help and exit
103 --version output version information and exit
111 =item C
<%size
> = C
<sizes
(@
file)>
113 Compute the sizes of the C
<@
file> and
return them as a
hash. Return
114 C
<undef
> if one of the computation failed.
124 foreach my
$f (@
file)
126 my
$cmd = "du -h $f";
128 # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
130 and
(warn
"command failed: '$cmd'\n"), $fail = 1;
132 $t =~ s
/^\s
*([\d.
]+[MkK
]).
*/${1}B
/;
135 return $fail ? undef
: %res
;
138 =item C
<print_locations
($title, \@url
, \
%size
, @
file)
140 Print a section C
<$title> dedicated to the list of
<@
file>, which
141 sizes are stored
in C
<%size
>, and
which are available from the C
<@url
>.
145 sub print_locations
($\@\
%@
)
147 my
($title, $url, $size, @
file) = @_
;
148 print
"Here are the $title:\n";
149 foreach my
$url (@
{$url})
154 print
" (", $
$size{$file}, ")"
155 if exists $
$size{$file};
162 =item C
<print_checksums
(@
file)
164 Print the MD5 and SHA1 signature section
for each C
<@
file>.
168 sub print_checksums
(@
)
172 print
"Here are the MD5 and SHA1 checksums:\n";
175 foreach my
$meth (qw
(md5 sha1
))
177 my
$class = $digest_classes{$meth} or next
;
178 foreach my
$f (@
file)
181 or die
"$ME: $f: cannot open for reading: $!\n";
183 my
$dig = $class->new-
>addfile
(*IN
)->hexdigest
;
191 =item C
<print_news_deltas
($news_file, $prev_version, $curr_version)
193 Print the section of the NEWS
file C
<$news_file> addressing changes
194 between versions C
<$prev_version> and C
<$curr_version>.
198 sub print_news_deltas
($$$
)
200 my
($news_file, $prev_version, $curr_version) = @_
;
202 my
$news_name = $news_file;
203 $news_name =~ s|^\Q
$srcdir\E
/||
;
205 print
"\n$news_name\n\n";
207 # Print all lines from $news_file, starting with the first one
208 # that mentions $curr_version up to but not including
209 # the first occurrence of $prev_version.
212 my
$re_prefix = qr
/(?
:\
* )?
(?
:Noteworthy c|Major c|C
)(?i
:hanges
)/;
215 open NEWS
, '<', $news_file
216 or die
"$ME: $news_file: cannot open for reading: $!\n";
217 while (defined
(my
$line = <NEWS
>))
221 # Match lines like these:
222 # * Major changes in release 5.0.1:
223 # * Noteworthy changes in release 6.6 (2006-11-22) [stable]
224 $line =~
/^
$re_prefix.
*(?
:[^\d.
]|$
)\Q
$curr_version\E
(?
:[^\d.
]|$
)/o
231 # This regexp must not match version numbers in NEWS items.
232 # For example, they might well say "introduced in 4.5.5",
233 # and we don't want that to match.
234 $line =~
/^
$re_prefix.
*(?
:[^\d.
]|$
)\Q
$prev_version\E
(?
:[^\d.
]|$
)/o
244 or die
"$ME: $news_file: no matching lines for '$curr_version'\n";
246 or die
"$ME: $news_file: no news item found for '$curr_version'\n";
249 sub print_changelog_deltas
($$
)
251 my
($package_name, $prev_version) = @_
;
253 # Print new ChangeLog entries.
255 # First find all CVS-controlled ChangeLog files.
258 find ({wanted
=> sub
{$_ eq
'ChangeLog' && -d 'CVS'
259 and push @changelog
, $File::Find
::name
}},
262 # If there are no ChangeLog files, we're done.
265 my
%changelog
= map
{$_ => 1} @changelog
;
267 # Reorder the list of files so that if there are ChangeLog
268 # files in the specified directories, they're listed first,
270 my @dir
= qw
( . src lib
m4 config doc
);
272 # A typical @changelog array might look like this:
282 my
$dot_slash = $d eq
'.' ?
$d : "./$d";
283 my
$target = "$dot_slash/ChangeLog";
284 delete
$changelog{$target}
285 and push @reordered
, $target;
288 # Append any remaining ChangeLog files.
289 push @reordered
, sort keys
%changelog
;
291 # Remove leading './'.
292 @reordered
= map
{ s
!^\.
/!!; $_ } @reordered
;
294 print
"\nChangeLog entries:\n\n";
295 # print join ("\n", @reordered), "\n";
297 $prev_version =~ s
/\.
/_
/g
;
298 my
$prev_cvs_tag = "\U$package_name\E-$prev_version";
300 my
$cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
301 open DIFF
, '-|', $cmd
302 or die
"$ME: cannot run '$cmd': $!\n";
303 # Print two types of lines, making minor changes:
304 # Lines starting with '+++ ', e.g.,
305 # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247
306 # and those starting with '+'.
307 # Don't print the others.
308 my
$prev_printed_line_empty = 1;
309 while (defined
(my
$line = <DIFF
>))
311 if ($line =~
/^\
+\
+\
+ /)
313 my
$separator = "*"x70 .
"\n";
316 $prev_printed_line_empty
318 print
$separator, $line, $separator;
320 elsif
($line =~
/^\
+/)
324 $prev_printed_line_empty = ($line =~
/^$
/);
329 # The exit code should be 1.
330 # Allow in case there are no modified ChangeLog entries.
331 $?
== 256 || $?
== 128
332 or warn
"warning: '$cmd' had unexpected exit code or signal ($?)\n";
335 sub get_tool_versions
($$
)
337 my
($tool_list, $gnulib_version) = @_
;
342 my @tool_version_pair
;
343 foreach my
$t (@
$tool_list)
347 push @tool_version_pair
, ucfirst
$t .
' ' .
$gnulib_version;
350 # Assume that the last "word" on the first line of
351 # 'tool --version' output is the version string.
352 my
($first_line, undef
) = split ("\n", `$t --version`);
353 if ($first_line =~
/.
* (\d
[\w.
-]+)$
/)
356 push @tool_version_pair
, "$t $1";
361 and
$first_line = '';
362 warn
"$t: unexpected --version output\n:$first_line";
370 return @tool_version_pair
;
374 # Use the C locale so that, for instance, "du" does not
375 # print "1,2" instead of "1.2", which would confuse our regexps.
388 my
$print_checksums_p = 1;
390 # Reformat the warnings before displaying them.
391 local $SIG{__WARN__
} = sub
394 # Warnings from GetOptions.
395 $msg =~ s
/Option
(\w
)/option
--$1/;
401 'mail-headers=s' => \
$mail_headers,
402 'release-type=s' => \
$release_type,
403 'package-name=s' => \
$package_name,
404 'previous-version=s' => \
$prev_version,
405 'current-version=s' => \
$curr_version,
406 'gpg-key-id=s' => \
$gpg_key_id,
407 'url-directory=s' => \@url_dir_list
,
408 'news=s' => \@news_file
,
409 'srcdir=s' => \
$srcdir,
410 'bootstrap-tools=s' => \
$bootstrap_tools,
411 'gnulib-version=s' => \
$gnulib_version,
412 'print-checksums!' => \
$print_checksums_p,
413 'archive-suffix=s' => \@archive_suffixes
,
415 help => sub
{ usage
0 },
416 version
=> sub
{ print
"$ME version $VERSION\n"; exit },
420 # Ensure that each required option is specified.
422 or
(warn
"release type not specified\n"), $fail = 1;
424 or
(warn
"package name not specified\n"), $fail = 1;
426 or
(warn
"previous version string not specified\n"), $fail = 1;
428 or
(warn
"current version string not specified\n"), $fail = 1;
430 or
(warn
"GnuPG key ID not specified\n"), $fail = 1;
432 or
(warn
"URL directory name(s) not specified\n"), $fail = 1;
434 my @tool_list
= split ',', $bootstrap_tools
437 grep (/^gnulib$
/, @tool_list
) ^ defined
$gnulib_version
438 and
(warn
"when specifying gnulib as a tool, you must also specify\n"
439 .
"--gnulib-version=V, where V is the result of running git describe\n"
440 .
"in the gnulib source directory.\n"), $fail = 1;
442 !$release_type || exists
$valid_release_types{$release_type}
443 or
(warn
"'$release_type': invalid release type\n"), $fail = 1;
446 and
(warn
"too many arguments:\n", join ("\n", @ARGV
), "\n"),
451 my
$my_distdir = "$package_name-$curr_version";
453 my
$xd = "$package_name-$prev_version-$curr_version.xdelta";
455 my @candidates
= map
{ "$my_distdir.$_" } @archive_suffixes
;
456 my @tarballs
= grep {-f $_} @candidates
;
459 or die
"$ME: none of " .
join(', ', @candidates
) .
" were found\n";
460 my @sizable
= @tarballs
;
462 and push @sizable
, $xd;
463 my
%size
= sizes
(@sizable
);
468 if (defined
$mail_headers)
470 ($headers = $mail_headers) =~ s
/\s
+(\S
+:)/\n$1/g
;
474 # The markup is escaped as <\# so that when this script is sent by
475 # mail (or part of a diff), Gnus is not triggered.
478 ${headers}Subject: $my_distdir released [$release_type]
480 <\#secure method=pgpmime mode=sign>
482 FIXME: put comments here
486 if (@url_dir_list
== 1 && @tarballs
== 1)
488 # When there's only one tarball and one URL, use a more concise form.
489 my
$m = "$url_dir_list[0]/$tarballs[0]";
490 print
"Here are the compressed sources and a GPG detached signature[*]:\n"
496 print_locations
("compressed sources", @url_dir_list
, %size
, @tarballs
);
498 and print_locations
("xdelta diffs (useful? if so, "
499 .
"please tell bug-gnulib\@gnu.org)",
500 @url_dir_list
, %size
, $xd);
501 my @sig_files
= map
{ "$_.sig" } @tarballs
;
502 print_locations
("GPG detached signatures[*]", @url_dir_list
, %size
,
506 if ($url_dir_list[0] =~
"gnu\.org")
508 print
"Use a mirror for higher download bandwidth:\n";
509 if (@tarballs
== 1 && $url_dir_list[0] =~ m
!https
://ftp\.gnu\.org
/gnu
/!)
511 (my
$m = "$url_dir_list[0]/$tarballs[0]")
512 =~ s
!https
://ftp\.gnu\.org
/gnu
/!https
://ftpmirror\.gnu\.org
/!;
519 print
" https://www.gnu.org/order/ftp.html\n\n";
524 and print_checksums
(@sizable
);
527 [*] Use a .sig file to verify that the corresponding file (without the
528 .sig suffix) is intact. First, be sure to download both the .sig file
529 and the corresponding tarball. Then, run a command like this:
531 gpg --verify $tarballs[0].sig
533 If that command fails because you don't have the required public key,
534 then run this command to import it:
536 gpg --keyserver keys.gnupg.net --recv-keys $gpg_key_id
538 and rerun the 'gpg --verify' command.
541 my @tool_versions
= get_tool_versions
(\@tool_list
, $gnulib_version);
543 and print
"\nThis release was bootstrapped with the following tools:",
544 join ('', map
{"\n $_"} @tool_versions
), "\n";
546 print_news_deltas
($_, $prev_version, $curr_version)
549 $release_type eq
'stable'
550 or print_changelog_deltas
($package_name, $prev_version);
555 ### Setup "GNU" style for perl-mode and cperl-mode.
558 ## perl-indent-level: 2
559 ## perl-continued-statement-offset: 2
560 ## perl-continued-brace-offset: 0
561 ## perl-brace-offset: 0
562 ## perl-brace-imaginary-offset: 0
563 ## perl-label-offset: -2
564 ## perl-extra-newline-before-brace: t
565 ## perl-merge-trailing-else: nil
566 ## eval: (add-hook 'before-save-hook 'time-stamp)
567 ## time-stamp-line-limit: 50
568 ## time-stamp-start: "my $VERSION = '"
569 ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
570 ## time-stamp-time-zone: "UTC0"
571 ## time-stamp-end: "'; # UTC"