| #!/bin/sh | |
| #! -*-perl-*- | |
| # Generate a release announcement message. | |
| # Copyright (C) 2002-2026 Free Software Foundation, Inc. | |
| # | |
| # This program is free software: you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation, either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # This program is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
| # | |
| # Written by Jim Meyering | |
| # This prologue allows running a perl script as an executable | |
| # on systems that are compliant to a POSIX version before POSIX:2017. | |
| # On such systems, the usual invocation of an executable through execlp() | |
| # or execvp() fails with ENOEXEC if it is a script that does not start | |
| # with a #! line. The script interpreter mentioned in the #! line has | |
| # to be /bin/sh, because on GuixSD systems that is the only program that | |
| # has a fixed file name. The second line is essential for perl and is | |
| # also useful for editing this file in Emacs. The next two lines below | |
| # are valid code in both sh and perl. When executed by sh, they re-execute | |
| # the script through the perl program found in $PATH. The '-x' option | |
| # is essential as well; without it, perl would re-execute the script | |
| # through /bin/sh. When executed by perl, the next two lines are a no-op. | |
| eval 'exec perl -wSx "$0" "$@"' | |
| if 0; | |
| my $VERSION = '2026-05-12 19:49'; # UTC | |
| # The definition above must lie within the first 8 lines in order | |
| # for the Emacs time-stamp write hook (at end) to update it. | |
| # If you change this file with Emacs, please let the write hook | |
| # do its job. Otherwise, update this string manually. | |
| (my $copyright_year = $VERSION) =~ s/^(\d*)-.*$/$1/; | |
| use strict; | |
| use Getopt::Long; | |
| use POSIX qw(strftime); | |
| (my $ME = $0) =~ s|.*/||; | |
| my %valid_release_types = map {$_ => 1} qw (alpha beta stable); | |
| my @archive_suffixes = qw (tar.gz tar.bz2 tar.bz3 tar.lz tar.lzma tar.xz | |
| tar.zst zip); | |
| my $srcdir = '.'; | |
| sub usage ($) | |
| { | |
| my ($exit_code) = @_; | |
| my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); | |
| if ($exit_code != 0) | |
| { | |
| print $STREAM "Try '$ME --help' for more information.\n"; | |
| } | |
| else | |
| { | |
| my @types = sort keys %valid_release_types; | |
| print $STREAM <<EOF; | |
| Usage: $ME [OPTIONS] | |
| Generate an announcement message. Run this from builddir. | |
| OPTIONS: | |
| These options must be specified: | |
| --release-type=TYPE TYPE must be one of @types | |
| --package-name=PACKAGE_NAME | |
| --previous-version=VER | |
| --current-version=VER | |
| --gpg-key-id=ID The GnuPG ID of the key used to sign the tarballs | |
| --url-directory=URL_DIR | |
| The following are optional: | |
| --news=NEWS_FILE include the NEWS section about this release | |
| from this NEWS_FILE; accumulates. | |
| --srcdir=DIR where to find the NEWS_FILEs (default: $srcdir) | |
| --bootstrap-tools=TOOL_LIST a comma-separated list of tools, e.g., | |
| autoconf,automake,bison,gnulib | |
| --gnulib-version=VERSION report VERSION as the gnulib version, where | |
| VERSION is the result of running git describe | |
| in the gnulib source directory. | |
| required if gnulib is in TOOL_LIST. | |
| --gpg-key-email=EMAIL The email address of the key used to | |
| sign the tarballs | |
| --gpg-keyring-url=URL URL pointing to keyring containing the key used | |
| to sign the tarballs | |
| --no-print-checksums do not emit SHA256 or SHA3-256 checksums | |
| --cksum-checksums emit SHA256 and SHA3-256 checksums in a form | |
| that requires cksum from coreutils or OpenBSD | |
| --archive-suffix=SUF add SUF to the list of archive suffixes | |
| --mail-headers=HEADERS a space-separated list of mail headers, e.g., | |
| To: x\@example.com Cc: y-announce\@example.com,... | |
| --help display this help and exit | |
| --version output version information and exit | |
| Send patches and bug reports to <bug-gnulib\@gnu.org>. | |
| EOF | |
| } | |
| exit $exit_code; | |
| } | |
| =over | |
| =item C<%size = sizes (@file)> | |
| Compute the sizes of the C<@file> and return them as a hash. Return | |
| an empty list if any computation failed. | |
| =cut | |
| sub sizes (@) | |
| { | |
| my (@file) = @_; | |
| my $fail = 0; | |
| my %res; | |
| foreach my $f (@file) | |
| { | |
| my $cmd = "du -h -L $f"; | |
| my $t = `$cmd`; | |
| # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS | |
| $? | |
| and (warn "command failed: '$cmd'\n"), $fail = 1; | |
| chomp $t; | |
| $t =~ s/^\s*([\d.]+[MkK]).*/${1}B/; | |
| $res{$f} = $t; | |
| } | |
| return $fail ? () : %res; | |
| } | |
| =item C<print_locations ($title, \@url, \%size, @file)> | |
| Print a section C<$title> dedicated to the list of C<@file>, which | |
| sizes are stored in C<%size>, and which are available from the C<@url>. | |
| =cut | |
| sub print_locations ($\@\%@) | |
| { | |
| my ($title, $url, $size, @file) = @_; | |
| print "Here are the $title:\n"; | |
| foreach my $url (@{$url}) | |
| { | |
| for my $file (@file) | |
| { | |
| print " $url/$file"; | |
| print " (", $$size{$file}, ")" | |
| if exists $$size{$file}; | |
| print "\n"; | |
| } | |
| } | |
| print "\n"; | |
| } | |
| =item C<print_checksums (@file)> | |
| Print the SHA256 and SHA3-256 signature section for each C<@file>. | |
| =cut | |
| # This digest function omits the "=" padding that is required by cksum, | |
| # so add the 0..2 bytes of padding required for each of Digest's algorithms. | |
| # To verify such a digest, users need | |
| # - a particular command ('cksum -a sha256 --check') | |
| # - and particular tools (coreutils >= 9.2 or OpenBSD's cksum since 2007). | |
| sub digest_file_base64_wrap ($$) | |
| { | |
| my ($file, $alg) = @_; | |
| my $h = digest_file_base64($file, $alg); | |
| $alg =~ tr{-}{}d; | |
| my %pad = (MD5 => 2, SHA1 => 1, SHA256 => 1, SHA384 => 0, SHA512 => 2, | |
| SHA3256 => 1, SHA3384 => 0, SHA3512 => 2); | |
| (my $alg_tag = $alg) =~ s/^SHA3(\d{3})/SHA3-$1/; | |
| return $alg_tag . " (" . $file . ") = " . $h . '=' x $pad{$alg}; | |
| } | |
| sub print_checksums ($@) | |
| { | |
| my ($prefer_cksum, @file) = @_; | |
| print "Here are the SHA256 and SHA3-256 checksums:\n"; | |
| print "\n"; | |
| use Digest::file qw(digest_file_hex digest_file_base64); | |
| if ($prefer_cksum) | |
| { | |
| foreach my $f (@file) | |
| { | |
| print ' ', digest_file_base64_wrap ($f, "SHA-256"), "\n"; | |
| print ' ', digest_file_base64_wrap ($f, "SHA3-256"), "\n"; | |
| } | |
| print "\nVerify the base64 SHA256 checksum with 'cksum -a sha256 --check'\n"; | |
| print "from coreutils-9.2 or OpenBSD's cksum since 2007.\n"; | |
| print "\nVerify the base64 SHA3-256 checksum with 'cksum -a sha3 --check'\n"; | |
| print "from coreutils-9.8.\n\n"; | |
| } | |
| else | |
| { | |
| foreach my $f (@file) | |
| { | |
| print " File: $f\n"; | |
| print ' SHA256 sum: ', digest_file_hex ($f, "SHA-256"), "\n"; | |
| print ' SHA3-256 sum: ', digest_file_hex ($f, "SHA3-256"), "\n"; | |
| print "\n"; | |
| } | |
| print "Verify the SHA256 checksum with either sha256sum, sha256, or\n"; | |
| print "'shasum -a 256'.\n"; | |
| print "\nVerify the SHA3-256 checksum with 'cksum -a sha3 -l 256'\n"; | |
| print "from coreutils-9.8.\n\n"; | |
| } | |
| } | |
| =item C<print_news_deltas ($news_file, $prev_version, $curr_version)> | |
| Print the section of the NEWS file C<$news_file> addressing changes | |
| between versions C<$prev_version> and C<$curr_version>. | |
| =cut | |
| sub print_news_deltas ($$$) | |
| { | |
| my ($news_file, $prev_version, $curr_version) = @_; | |
| my $news_name = $news_file; | |
| $news_name =~ s|^\Q$srcdir\E/||; | |
| print "\n$news_name\n\n"; | |
| # Print all lines from $news_file, starting with the first one | |
| # that mentions $curr_version up to but not including | |
| # the first occurrence of $prev_version. | |
| my $in_items; | |
| my $re_prefix = qr/(?:[#*]+ )?(?:Noteworthy c|Major c|C)(?i:hanges)/; | |
| my $found_news; | |
| open NEWS, '<', $news_file | |
| or die "$ME: $news_file: cannot open for reading: $!\n"; | |
| while (defined (my $line = <NEWS>)) | |
| { | |
| if ( ! $in_items) | |
| { | |
| # Match lines like these: | |
| # * Major changes in release 5.0.1: | |
| # * Noteworthy changes in release 6.6 (2006-11-22) [stable] | |
| $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o | |
| or next; | |
| $in_items = 1; | |
| print $line; | |
| } | |
| else | |
| { | |
| # This regexp must not match version numbers in NEWS items. | |
| # For example, they might well say "introduced in 4.5.5", | |
| # and we don't want that to match. | |
| $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o | |
| and last; | |
| print $line; | |
| $line =~ /\S/ | |
| and $found_news = 1; | |
| } | |
| } | |
| close NEWS; | |
| $in_items | |
| or die "$ME: $news_file: no matching lines for '$curr_version'\n"; | |
| $found_news | |
| or die "$ME: $news_file: no news item found for '$curr_version'\n"; | |
| } | |
| sub print_changelog_deltas ($$) | |
| { | |
| my ($package_name, $prev_version) = @_; | |
| # Print new ChangeLog entries. | |
| # First find all CVS-controlled ChangeLog files. | |
| use File::Find; | |
| my @changelog; | |
| find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS' | |
| and push @changelog, $File::Find::name}}, | |
| '.'); | |
| # If there are no ChangeLog files, we're done. | |
| @changelog | |
| or return; | |
| my %changelog = map {$_ => 1} @changelog; | |
| # Reorder the list of files so that if there are ChangeLog | |
| # files in the specified directories, they're listed first, | |
| # in this order: | |
| my @dir = qw ( . src lib m4 config doc ); | |
| # A typical @changelog array might look like this: | |
| # ./ChangeLog | |
| # ./po/ChangeLog | |
| # ./m4/ChangeLog | |
| # ./lib/ChangeLog | |
| # ./doc/ChangeLog | |
| # ./config/ChangeLog | |
| my @reordered; | |
| foreach my $d (@dir) | |
| { | |
| my $dot_slash = $d eq '.' ? $d : "./$d"; | |
| my $target = "$dot_slash/ChangeLog"; | |
| delete $changelog{$target} | |
| and push @reordered, $target; | |
| } | |
| # Append any remaining ChangeLog files. | |
| push @reordered, sort keys %changelog; | |
| # Remove leading './'. | |
| @reordered = map { s!^\./!!; $_ } @reordered; | |
| print "\nChangeLog entries:\n\n"; | |
| # print join ("\n", @reordered), "\n"; | |
| $prev_version =~ s/\./_/g; | |
| my $prev_cvs_tag = "\U$package_name\E-$prev_version"; | |
| my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered"; | |
| open DIFF, '-|', $cmd | |
| or die "$ME: cannot run '$cmd': $!\n"; | |
| # Print two types of lines, making minor changes: | |
| # Lines starting with '+++ ', e.g., | |
| # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247 | |
| # and those starting with '+'. | |
| # Don't print the others. | |
| my $prev_printed_line_empty = 1; | |
| while (defined (my $line = <DIFF>)) | |
| { | |
| if ($line =~ /^\+\+\+ /) | |
| { | |
| my $separator = "*"x70 ."\n"; | |
| $line =~ s///; | |
| $line =~ s/\s.*//; | |
| $prev_printed_line_empty | |
| or print "\n"; | |
| print $separator, $line, $separator; | |
| } | |
| elsif ($line =~ /^\+/) | |
| { | |
| $line =~ s///; | |
| print $line; | |
| $prev_printed_line_empty = ($line =~ /^$/); | |
| } | |
| } | |
| close DIFF; | |
| # The exit code should be 1. | |
| # Allow in case there are no modified ChangeLog entries. | |
| $? == 256 || $? == 128 | |
| or warn "warning: '$cmd' had unexpected exit code or signal ($?)\n"; | |
| } | |
| sub get_tool_versions ($$) | |
| { | |
| my ($tool_list, $gnulib_version) = @_; | |
| @$tool_list | |
| or return (); | |
| my $fail; | |
| my @tool_version_pair; | |
| foreach my $t (@$tool_list) | |
| { | |
| if ($t eq 'gnulib') | |
| { | |
| push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version; | |
| next; | |
| } | |
| # Assume that the last "word" on the first line of | |
| # 'tool --version' output is the version string. | |
| my ($first_line, undef) = split ("\n", `$t --version`); | |
| if ($first_line =~ /.* ([a-f\d][\w.-]+)( Debian[\w.-]+)?$/) | |
| { | |
| $t = ucfirst $t; | |
| push @tool_version_pair, "$t $1"; | |
| } | |
| else | |
| { | |
| warn "$t: unexpected --version output:\n$first_line"; | |
| defined $first_line | |
| and $first_line = ''; | |
| $fail = 1; | |
| } | |
| } | |
| $fail | |
| and exit 1; | |
| return @tool_version_pair; | |
| } | |
| # Print a more human-friendly representation of $SEC seconds. | |
| sub readable_interval0($) | |
| { | |
| my $sec = shift; | |
| $sec < 60 and return "$sec seconds"; | |
| my $min = int($sec / 60); $sec %= 60; | |
| 30 < $sec and $min++; | |
| $min < 60 and return "$min minutes"; | |
| my $hr = int($min / 60); $min %= 60; | |
| 30 < $min and $hr++; | |
| $hr < 24 and return "$hr hours"; | |
| my $day = int($hr / 24); $hr %= 24; | |
| 12 < $hr and $day++; | |
| $day < 50 and return "$day days"; | |
| my $wk = int($day / 7); $day %= 7; | |
| 4 < $day and $wk++; | |
| return "$wk weeks"; | |
| } | |
| # Convert e.g., "1 weeks", to "1 week". | |
| sub readable_interval($) | |
| { | |
| my $interval_str = shift; | |
| my $i = readable_interval0 $interval_str; | |
| $i =~ m{^1 \w+s$} and chop $i; | |
| return $i; | |
| } | |
| { | |
| # Use the C locale so that, for instance, "du" does not | |
| # print "1,2" instead of "1.2", which would confuse our regexps. | |
| $ENV{LC_ALL} = "C"; | |
| my $mail_headers; | |
| my $release_type; | |
| my $package_name; | |
| my $prev_version; | |
| my $curr_version; | |
| my $gpg_key_id; | |
| my @url_dir_list; | |
| my @news_file; | |
| my $bootstrap_tools; | |
| my $gnulib_version; | |
| my $print_checksums_p = 1; | |
| my $cksum_checksums_p; | |
| my $gpg_key_email; | |
| my $gpg_keyring_url; | |
| # Reformat the warnings before displaying them. | |
| local $SIG{__WARN__} = sub | |
| { | |
| my ($msg) = @_; | |
| # Warnings from GetOptions. | |
| $msg =~ s/Option (\w)/option --$1/; | |
| warn "$ME: $msg"; | |
| }; | |
| GetOptions | |
| ( | |
| 'mail-headers=s' => \$mail_headers, | |
| 'release-type=s' => \$release_type, | |
| 'package-name=s' => \$package_name, | |
| 'previous-version=s' => \$prev_version, | |
| 'current-version=s' => \$curr_version, | |
| 'gpg-key-id=s' => \$gpg_key_id, | |
| 'gpg-key-email=s' => \$gpg_key_email, | |
| 'gpg-keyring-url=s' => \$gpg_keyring_url, | |
| 'url-directory=s' => \@url_dir_list, | |
| 'news=s' => \@news_file, | |
| 'srcdir=s' => \$srcdir, | |
| 'bootstrap-tools=s' => \$bootstrap_tools, | |
| 'gnulib-version=s' => \$gnulib_version, | |
| 'print-checksums!' => \$print_checksums_p, | |
| 'cksum-checksums' => \$cksum_checksums_p, | |
| 'archive-suffix=s' => \@archive_suffixes, | |
| help => sub { usage 0 }, | |
| version => | |
| sub | |
| { | |
| print "$ME version $VERSION\n"; | |
| print "Copyright (C) $copyright_year Free Software Foundation, Inc.\n"; | |
| print "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.\n" | |
| . "This is free software: you are free to change and redistribute it.\n" | |
| . "There is NO WARRANTY, to the extent permitted by law.\n"; | |
| print "\n"; | |
| my $author = "Jim Meyering"; | |
| print "Written by $author.\n"; | |
| exit | |
| }, | |
| ) or usage 1; | |
| my $fail = 0; | |
| # Ensure that each required option is specified. | |
| $release_type | |
| or (warn "release type not specified\n"), $fail = 1; | |
| $package_name | |
| or (warn "package name not specified\n"), $fail = 1; | |
| $prev_version | |
| or (warn "previous version string not specified\n"), $fail = 1; | |
| $curr_version | |
| or (warn "current version string not specified\n"), $fail = 1; | |
| $gpg_key_id | |
| or (warn "GnuPG key ID not specified\n"), $fail = 1; | |
| @url_dir_list | |
| or (warn "URL directory name(s) not specified\n"), $fail = 1; | |
| my @tool_list = split ',', $bootstrap_tools | |
| if $bootstrap_tools; | |
| grep (/^gnulib$/, @tool_list) && ! defined $gnulib_version | |
| and (warn "when specifying gnulib as a tool, you must also specify\n" | |
| . "--gnulib-version=V, where V is the result of running git describe\n" | |
| . "in the gnulib source directory.\n"), $fail = 1; | |
| ! grep (/^gnulib$/, @tool_list) && defined $gnulib_version | |
| and (warn "with --gnulib-version=V you must use --bootstrap-tools=...\n" | |
| . "including gnulib in that list"), $fail = 1; | |
| !$release_type || exists $valid_release_types{$release_type} | |
| or (warn "'$release_type': invalid release type\n"), $fail = 1; | |
| @ARGV | |
| and (warn "too many arguments:\n", join ("\n", @ARGV), "\n"), | |
| $fail = 1; | |
| $fail | |
| and usage 1; | |
| my $my_distdir = "$package_name-$curr_version"; | |
| my $xd = "$package_name-$prev_version-$curr_version.xdelta"; | |
| my @candidates = map { "$my_distdir.$_" } @archive_suffixes; | |
| my @tarballs = grep {-f $_} @candidates; | |
| @tarballs | |
| or die "$ME: none of " . join(', ', @candidates) . " were found\n"; | |
| my @sizable = @tarballs; | |
| -f $xd | |
| and push @sizable, $xd; | |
| my %size = sizes (@sizable); | |
| %size | |
| or exit 1; | |
| my $headers = ''; | |
| if (defined $mail_headers) | |
| { | |
| ($headers = $mail_headers) =~ s/\s+(\S+:)/\n$1/g; | |
| $headers .= "\n"; | |
| } | |
| # The markup is escaped as <\# so that when this script is sent by | |
| # mail (or part of a diff), Gnus is not triggered. | |
| print <<EOF; | |
| ${headers}Subject: $my_distdir released [$release_type] | |
| <\#secure method=pgpmime mode=sign> | |
| This is to announce $package_name-$curr_version, a $release_type release. | |
| FIXME: put comments here | |
| EOF | |
| my $v0 = $prev_version; | |
| my $v1 = $curr_version; | |
| (my $first_name = `git -C "$srcdir" config user.name|cut -d' ' -f1`) | |
| =~ m{\S} or die "no name? set user.name in ~/.gitconfig\n"; | |
| chomp (my $n_ci = `git -C "$srcdir" rev-list "v$v0..v$v1" | wc -l`); | |
| chomp (my $n_p = `git -C "$srcdir" shortlog "v$v0..v$v1" | grep -c '^[^ ]'`); | |
| my $this_commit_hash = `git -C "$srcdir" log --pretty=%H -1 "v$v1"`; | |
| chop $this_commit_hash; | |
| my $prev_release_date = `git -C "$srcdir" log --pretty=%ct -1 "v$v0"`; | |
| my $this_release_date = `git -C "$srcdir" log --pretty=%ct -1 "v$v1"`; | |
| my $n_seconds = $this_release_date - $prev_release_date; | |
| my $time_since_prev = readable_interval $n_seconds; | |
| my $names = `git -C "$srcdir" shortlog "v$v0..v$v1"|perl -lne '/^(\\w.*):/ and print " ".\$1'`; | |
| print <<EOF; | |
| There have been $n_ci commits by $n_p people in the $time_since_prev since $v0. | |
| See the NEWS below for a brief summary. | |
| Thanks to everyone who has contributed! | |
| The following people contributed changes to this release: | |
| $names | |
| $first_name [on behalf of the $package_name maintainers] | |
| ================================================================== | |
| Here is the GNU $package_name home page: | |
| https://gnu.org/s/$package_name/ | |
| EOF | |
| if (@url_dir_list == 1 && @tarballs == 1) | |
| { | |
| # When there's only one tarball and one URL, use a more concise form. | |
| my $m = "$url_dir_list[0]/$tarballs[0]"; | |
| print "Here are the compressed sources and a GPG detached signature:\n" | |
| . " $m\n" | |
| . " $m.sig\n\n"; | |
| } | |
| else | |
| { | |
| print_locations ("compressed sources", @url_dir_list, %size, @tarballs); | |
| -f $xd | |
| and print_locations ("xdelta diffs (useful? if so, " | |
| . "please tell bug-gnulib\@gnu.org)", | |
| @url_dir_list, %size, $xd); | |
| my @sig_files = map { "$_.sig" } @tarballs; | |
| print_locations ("GPG detached signatures", @url_dir_list, %size, | |
| @sig_files); | |
| } | |
| if ($url_dir_list[0] =~ "gnu\.org") | |
| { | |
| print "Use a mirror for higher download bandwidth:\n"; | |
| if (@tarballs == 1 && $url_dir_list[0] =~ m!https://ftp\.gnu\.org/gnu/!) | |
| { | |
| (my $m = "$url_dir_list[0]/$tarballs[0]") | |
| =~ s!https://ftp\.gnu\.org/gnu/!https://ftpmirror\.gnu\.org/!; | |
| print " $m\n" | |
| . " $m.sig\n\n"; | |
| } | |
| else | |
| { | |
| print " https://www.gnu.org/order/ftp.html\n\n"; | |
| } | |
| } | |
| $print_checksums_p | |
| and print_checksums ($cksum_checksums_p, @sizable); | |
| print <<EOF; | |
| Use a .sig file to verify that the corresponding file (without the | |
| .sig suffix) is intact. First, be sure to download both the .sig file | |
| and the corresponding tarball. Then, run a command like this: | |
| gpg --verify $tarballs[0].sig $tarballs[0] | |
| EOF | |
| my $gpg_fingerprint = `LC_ALL=C gpg --fingerprint $gpg_key_id | grep -v ^sub`; | |
| if ($gpg_fingerprint =~ /^pub/) | |
| { | |
| chop $gpg_fingerprint; | |
| $gpg_fingerprint =~ s/ \[expires:.*//mg; | |
| $gpg_fingerprint =~ s/^uid \[ultimate\]/uid /mg; | |
| $gpg_fingerprint =~ s/^/ /mg; | |
| print<<EOF | |
| The signature should match the fingerprint of the following key: | |
| $gpg_fingerprint | |
| EOF | |
| } | |
| print <<EOF; | |
| If that command fails because you don't have the required public key, | |
| or that public key has expired, try the following commands to retrieve | |
| or refresh it, and then rerun the 'gpg --verify' command. | |
| EOF | |
| if ($gpg_key_email) { | |
| print <<EOF; | |
| gpg --locate-external-key $gpg_key_email | |
| EOF | |
| } | |
| print <<EOF; | |
| gpg --recv-keys $gpg_key_id | |
| EOF | |
| if ($gpg_keyring_url) { | |
| print <<EOF; | |
| wget -q -O- '$gpg_keyring_url' | gpg --import - | |
| EOF | |
| } | |
| print <<EOF; | |
| As a last resort to find the key, you can try the official GNU | |
| keyring: | |
| wget -q https://ftp.gnu.org/gnu/gnu-keyring.gpg | |
| gpg --keyring gnu-keyring.gpg --verify $tarballs[0].sig $tarballs[0] | |
| EOF | |
| print <<EOF; | |
| This release is based on the $package_name git repository, available as | |
| git clone https://https.git.savannah.gnu.org/git/$package_name.git | |
| with commit $this_commit_hash tagged as v$v1. | |
| For a summary of changes and contributors, see: | |
| https://gitweb.git.savannah.gnu.org/gitweb/?p=$package_name.git;a=shortlog;h=v$v1 | |
| or run this command from a git-cloned $package_name directory: | |
| git shortlog v$v0..v$v1 | |
| EOF | |
| my @tool_versions = get_tool_versions (\@tool_list, $gnulib_version); | |
| @tool_versions | |
| and print "\nThis release was bootstrapped with the following tools:", | |
| join ('', map {"\n $_"} @tool_versions), "\n"; | |
| print_news_deltas ($_, $prev_version, $curr_version) | |
| foreach @news_file; | |
| $release_type eq 'stable' | |
| or print_changelog_deltas ($package_name, $prev_version); | |
| exit 0; | |
| } | |
| =back | |
| =cut | |
| ### Setup "GNU" style for perl-mode and cperl-mode. | |
| ## Local Variables: | |
| ## mode: perl | |
| ## perl-indent-level: 2 | |
| ## perl-continued-statement-offset: 2 | |
| ## perl-continued-brace-offset: 0 | |
| ## perl-brace-offset: 0 | |
| ## perl-brace-imaginary-offset: 0 | |
| ## perl-label-offset: -2 | |
| ## perl-extra-newline-before-brace: t | |
| ## perl-merge-trailing-else: nil | |
| ## eval: (add-hook 'before-save-hook 'time-stamp nil t) | |
| ## time-stamp-line-limit: 50 | |
| ## time-stamp-start: "my $VERSION = '" | |
| ## time-stamp-format: "%Y-%02m-%02d %02H:%02M" | |
| ## time-stamp-time-zone: "UTC0" | |
| ## time-stamp-end: "'; # UTC" | |
| ## End: |