*** empty log message ***
[platform/upstream/coreutils.git] / announce-gen
1 #!/usr/bin/perl -w
2 # Generate an announcement message.
3 use strict;
4
5 use Getopt::Long;
6 use Digest::MD5;
7 use Digest::SHA1;
8
9 (my $VERSION = '$Revision: 1.23 $ ') =~ tr/[0-9].//cd;
10 (my $ME = $0) =~ s|.*/||;
11
12 my %valid_release_types = map {$_ => 1} qw (alpha beta major);
13
14 END
15 {
16   # Nobody ever checks the status of print()s.  That's okay, because
17   # if any do fail, we're guaranteed to get an indicator when we close()
18   # the filehandle.
19   #
20   # Close stdout now, and if there were no errors, return happy status.
21   # If stdout has already been closed by the script, though, do nothing.
22   defined fileno STDOUT
23     or return;
24   close STDOUT
25     and return;
26
27   # Errors closing stdout.  Indicate that, and hope stderr is OK.
28   warn "$ME: closing standard output: $!\n";
29
30   # Don't be so arrogant as to assume that we're the first END handler
31   # defined, and thus the last one invoked.  There may be others yet
32   # to come.  $? will be passed on to them, and to the final _exit().
33   #
34   # If it isn't already an error, make it one (and if it _is_ an error,
35   # preserve the value: it might be important).
36   $? ||= 1;
37 }
38
39 sub usage ($)
40 {
41   my ($exit_code) = @_;
42   my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
43   if ($exit_code != 0)
44     {
45       print $STREAM "Try `$ME --help' for more information.\n";
46     }
47   else
48     {
49       my @types = sort keys %valid_release_types;
50       print $STREAM <<EOF;
51 Usage: $ME [OPTIONS]
52
53 OPTIONS:
54
55   Generate an announcement message.
56
57   FIXME: describe the following
58
59    --release-type=TYPE          TYPE must be one of @types
60    --package-name=PACKAGE_NAME
61    --previous-version=VER
62    --current-version=VER
63    --release-archive-directory=DIR
64    --url-directory=URL_DIR
65    --news=NEWS_FILE             optional
66
67    --help             display this help and exit
68    --version          output version information and exit
69
70 EOF
71     }
72   exit $exit_code;
73 }
74
75
76 =item C<%size> = C<sizes (@file)>
77
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
81 =cut
82
83 sub sizes (@)
84 {
85   my (@file) = @_;
86
87   my $fail = 0;
88   my %res;
89   foreach my $f (@file)
90     {
91       my $cmd = "du --human $f";
92       my $t = `$cmd`;
93       # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS
94       $@
95         and (warn "$ME: command failed: `$cmd'\n"), $fail = 1;
96       chomp $t;
97       $t =~ s/^([\d.]+[MkK]).*/${1}B/;
98       $res{$f} = $t;
99     }
100   return $fail ? undef : %res;
101 }
102
103 =item C<print_locations ($title, \@url, \%size, @file)
104
105 Print a section C<$title> dedicated to the list of <@file>, which
106 sizes are stored in C<%size>, and which are available from the C<@url>.
107
108 =cut
109
110 sub print_locations ($\@\%@)
111 {
112   my ($title, $url, $size, @file) = @_;
113   print "Here are the $title:\n";
114   foreach my $url (@{$url})
115     {
116       for my $file (@file)
117         {
118           print "  $url/$file";
119           print "   (", $$size{$file}, ")"
120             if exists $$size{$file};
121           print "\n";
122         }
123     }
124   print "\n";
125 }
126
127 =item C<print_signatures (@file)
128
129 Print the MD5 and SHA1 signature section for each C<@file>.
130
131 =cut
132
133 sub print_signatures (@)
134 {
135   my (@file) = @_;
136
137   print "Here are the MD5 and SHA1 checksums:\n";
138   print "\n";
139
140   foreach my $meth (qw (md5 sha1))
141     {
142       foreach my $f (@file)
143         {
144           open IN, '<', $f
145             or die "$ME: $f: cannot open for reading: $!\n";
146           binmode IN;
147           my $dig =
148             ($meth eq 'md5'
149              ? Digest::MD5->new->addfile(*IN)->hexdigest
150              : Digest::SHA1->new->addfile(*IN)->hexdigest);
151           close IN;
152           print "$dig  $f\n";
153         }
154     }
155
156
157 }
158
159 =item C<print_news_deltas ($news_file, $prev_version, $curr_version)
160
161 Print the section of the NEWS file C<$news_file> addressing changes
162 between versions C<$prev_version> and C<$curr_version>.
163
164 =cut
165
166 sub print_news_deltas ($$$)
167 {
168   my ($news_file, $prev_version, $curr_version) = @_;
169
170   print "\n$news_file\n\n";
171
172   # Print all lines from $news_file, starting with the first one
173   # that mentions $curr_version up to but not including
174   # the first occurrence of $prev_version.
175   my $in_items;
176
177   open NEWS, '<', $news_file
178     or die "$ME: $news_file: cannot open for reading: $!\n";
179   while (defined (my $line = <NEWS>))
180     {
181       if ( ! $in_items)
182         {
183           # Match lines like this one:
184           # * Major changes in release 5.0.1:
185           # but not any other line that starts with a space, *, or -.
186           $line =~ /^(\* Major changes.*|[^ *-].*)\Q$curr_version\E/o
187             or next;
188           $in_items = 1;
189           print $line;
190         }
191       else
192         {
193           # Be careful that this regexp cannot match version numbers
194           # in NEWS items -- they might well say `introduced in 4.5.5',
195           # and we don't want that to match.
196           $line =~ /^(\* Major changes.*|[^ *-].*)\Q$prev_version\E/o
197             and last;
198           print $line;
199         }
200     }
201   close NEWS;
202
203   $in_items
204     or die "$ME: $news_file: no matching lines for `$curr_version'\n";
205 }
206
207 sub print_changelog_deltas ($$)
208 {
209   my ($package_name, $prev_version) = @_;
210
211   # Print new ChangeLog entries.
212
213   # First find all CVS-controlled ChangeLog files.
214   use File::Find;
215   my @changelog;
216   find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS'
217                           and push @changelog, $File::Find::name}},
218         '.');
219
220   # If there are no ChangeLog files, we're done.
221   @changelog
222     or return;
223   my %changelog = map {$_ => 1} @changelog;
224
225   # Reorder the list of files so that if there are ChangeLog
226   # files in the specified directories, they're listed first,
227   # in this order:
228   my @dir = qw ( . src lib m4 config doc );
229
230   # A typical @changelog array might look like this:
231   # ./ChangeLog
232   # ./po/ChangeLog
233   # ./m4/ChangeLog
234   # ./lib/ChangeLog
235   # ./doc/ChangeLog
236   # ./config/ChangeLog
237   my @reordered;
238   foreach my $d (@dir)
239     {
240       my $dot_slash = $d eq '.' ? $d : "./$d";
241       my $target = "$dot_slash/ChangeLog";
242       delete $changelog{$target}
243         and push @reordered, $target;
244     }
245
246   # Append any remaining ChangeLog files.
247   push @reordered, sort keys %changelog;
248
249   # Remove leading `./'.
250   @reordered = map { s!^\./!!; $_ } @reordered;
251
252   print "\nChangeLog entries:\n\n";
253   # print join ("\n", @reordered), "\n";
254
255   $prev_version =~ s/\./_/g;
256   my $prev_cvs_tag = "\U$package_name\E-$prev_version";
257
258   my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered";
259   open DIFF, '-|', $cmd
260     or die "$ME: cannot run `$cmd': $!\n";
261   # Print two types of lines, making minor changes:
262   # Lines starting with `+++ ', e.g.,
263   # +++ ChangeLog   22 Feb 2003 16:52:51 -0000      1.247
264   # and those starting with `+'.
265   # Don't print the others.
266   my $prev_printed_line_empty = 1;
267   while (defined (my $line = <DIFF>))
268     {
269       if ($line =~ /^\+\+\+ /)
270         {
271           my $separator = "*"x70 ."\n";
272           $line =~ s///;
273           $line =~ s/\s.*//;
274           $prev_printed_line_empty
275             or print "\n";
276           print $separator, $line, $separator;
277         }
278       elsif ($line =~ /^\+/)
279         {
280           $line =~ s///;
281           print $line;
282           $prev_printed_line_empty = ($line =~ /^$/);
283         }
284     }
285   close DIFF;
286
287   # The exit code should be 1.
288   # Allow in case there are no modified ChangeLog entries.
289   $? == 256 || $? == 128
290     or warn "$ME: warning: `cmd' had unexpected exit code or signal ($?)\n";
291 }
292
293 {
294   # Neutralize the locale, so that, for instance, "du" does not
295   # issue "1,2" instead of "1.2", what confuses our regexps.
296   $ENV{LC_ALL} = "C";
297
298   my $release_type;
299   my $package_name;
300   my $prev_version;
301   my $curr_version;
302   my $release_archive_dir;
303   my @url_dir_list;
304   my @news_file;
305
306   GetOptions
307     (
308      'release-type=s'     => \$release_type,
309      'package-name=s'     => \$package_name,
310      'previous-version=s' => \$prev_version,
311      'current-version=s'  => \$curr_version,
312      'release-archive-directory=s' => \$release_archive_dir,
313      'url-directory=s'    => \@url_dir_list,
314      'news=s'             => \@news_file,
315
316      help => sub { usage 0 },
317      version => sub { print "$ME version $VERSION\n"; exit },
318     ) or usage 1;
319
320   my $fail = 0;
321   # Ensure that sure each required option is specified.
322   $release_type
323     or (warn "$ME: release type not specified\n"), $fail = 1;
324   $package_name
325     or (warn "$ME: package name not specified\n"), $fail = 1;
326   $prev_version
327     or (warn "$ME: previous version string not specified\n"), $fail = 1;
328   $curr_version
329     or (warn "$ME: current version string not specified\n"), $fail = 1;
330   $release_archive_dir
331     or (warn "$ME: release directory name not specified\n"), $fail = 1;
332   @url_dir_list
333     or (warn "$ME: URL directory name(s) not specified\n"), $fail = 1;
334
335   exists $valid_release_types{$release_type}
336     or (warn "$ME: `$release_type': invalid release type\n"), $fail = 1;
337
338   @ARGV
339     and (warn "$ME: too many arguments\n"), $fail = 1;
340   $fail
341     and usage 1;
342
343   my $my_distdir = "$package_name-$curr_version";
344   my $tgz = "$my_distdir.tar.gz";
345   my $tbz = "$my_distdir.tar.bz2";
346   my $xd = "$package_name-$prev_version-$curr_version.xdelta";
347
348   my %size = sizes ($tgz, $tbz, $xd);
349   %size
350     or exit 1;
351
352   # The markup is escaped as <\# so that when this script is sent by
353   # mail (or part of a diff), Gnus is not triggered.
354   print <<EOF;
355
356 Subject: $my_distdir released
357
358 <\#secure method=pgpmime mode=sign>
359
360 FIXME: put comments here
361
362 EOF
363
364   print_locations ("compressed sources", @url_dir_list, %size,
365                    $tgz, $tbz);
366   print_locations ("xdelta-style diffs", @url_dir_list, %size,
367                    $xd);
368   print_locations ("GPG detached signatures", @url_dir_list, %size,
369                    "$tgz.sig", "$tbz.sig");
370
371   print_signatures ($tgz, $tbz, $xd);
372
373   print_news_deltas ($_, $prev_version, $curr_version)
374     foreach @news_file;
375
376   $release_type eq 'major'
377     or print_changelog_deltas ($package_name, $prev_version);
378
379   exit 0;
380 }
381
382
383
384 ### Setup "GNU" style for perl-mode and cperl-mode.
385 ## Local Variables:
386 ## perl-indent-level: 2
387 ## perl-continued-statement-offset: 2
388 ## perl-continued-brace-offset: 0
389 ## perl-brace-offset: 0
390 ## perl-brace-imaginary-offset: 0
391 ## perl-label-offset: -2
392 ## cperl-indent-level: 2
393 ## cperl-brace-offset: 0
394 ## cperl-continued-brace-offset: 0
395 ## cperl-label-offset: -2
396 ## cperl-extra-newline-before-brace: t
397 ## cperl-merge-trailing-else: nil
398 ## cperl-continued-statement-offset: 2
399 ## End: