]> mj.ucw.cz Git - checkmail.git/blob - maint/release.pm
release.pm: implement Git repository tagging
[checkmail.git] / maint / release.pm
1 #!/usr/bin/perl
2 # A simple system for making software releases
3 # (c) 2003--2010 Martin Mares <mj@ucw.cz>
4
5 package UCW::Release;
6 use strict;
7 use warnings;
8 use Getopt::Long;
9
10 our $verbose = 0;
11
12 sub new($$) {
13         my ($class,$basename) = @_;
14         my $s = {
15                 "PACKAGE" => $basename,
16                 "rules" => [
17                         # p=preprocess, s=subst, -=discard
18                         '(^|/)(CVS|\.arch-ids|{arch}|\.git|tmp)/' => '-',
19                         '\.(lsm|spec)$' => 'ps',
20                         '(^|/)README$' => 's'
21                         ],
22                 "directories" => [
23                         ],
24                 "conditions" => {
25                         },
26                 "DATE" => `date '+%Y-%m-%d' | tr -d '\n'`,
27                 "LSMDATE" => `date '+%y%m%d' | tr -d '\n'`,
28                 "distfiles" => [
29                         ],
30                 "archivedir" => "/home/mj/tmp/archives/$basename",
31                 "uploads" => [
32                         ],
33                 # Options
34                 "do_test" => 1,
35                 "do_patch" => 1,
36                 "diff_against" => "",
37                 "do_upload" => 1,
38                 "do_git_tag" => 0,
39                 "force_git_tag" => 0,
40         };
41         bless $s;
42         return $s;
43 }
44
45 sub GetVersionFromFile($) {
46         my ($s,$file,$rx) = @_;
47         open F, $file or die "Unable to open $file for version autodetection";
48         while (<F>) {
49                 chomp;
50                 if (/$rx/) {
51                         $s->{"VERSION"} = $1;
52                         print "Detected version $1 from $file\n" if $verbose;
53                         last;
54                 }
55         }
56         close F;
57         if (!defined $s->{"VERSION"}) { die "Failed to auto-detect version"; }
58         return $s->{"VERSION"};
59 }
60
61 sub GetVersionsFromChangelog($) {
62         my ($s,$file,$rx) = @_;
63         open F, $file or die "Unable to open $file for version autodetection";
64         while (<F>) {
65                 chomp;
66                 if (/$rx/) {
67                         if (!defined $s->{"VERSION"}) {
68                                 $s->{"VERSION"} = $1;
69                                 print "Detected version $1 from $file\n" if $verbose;
70                         } elsif ($s->{"VERSION"} eq $1) {
71                                 # do nothing
72                         } else {
73                                 $s->{"OLDVERSION"} = $1;
74                                 print "Detected previous version $1 from $file\n" if $verbose;
75                                 last;
76                         }
77                 }
78         }
79         close F;
80         if (!defined $s->{"VERSION"}) { die "Failed to auto-detect version"; }
81         return $s->{"VERSION"};
82 }
83
84 sub InitDist($) {
85         my ($s,$dd) = @_;
86         $s->{"DISTDIR"} = $dd;
87         print "Initializing dist directory $dd\n" if $verbose;
88         `rm -rf $dd`; die if $?;
89         `mkdir -p $dd`; die if $?;
90 }
91
92 sub ExpandVar($$) {
93         my ($s,$v) = @_;
94         if (defined $s->{$v}) {
95                 return $s->{$v};
96         } else {
97                 die "Reference to unknown variable $v";
98         }
99 }
100
101 sub CopyFile($$$$) {
102         my ($s,$f,$dir,$action) = @_;
103
104         (my $d = $f) =~ s@(^|/)[^/]*$@@;
105         $d = "$dir/$d";
106         -d $d || `mkdir -p $d`; die if $?;
107
108         my $preprocess = ($action =~ /p/);
109         my $subst = ($action =~ /s/);
110         if ($preprocess || $subst) {
111                 open I, "$f" or die "open($f): $?";
112                 open O, ">$dir/$f" or die "open($dir/$f): $!";
113                 my @ifs = ();   # stack of conditions, 1=satisfied
114                 my $empty = 0;  # last line was empty
115                 my $is_makefile = ($f =~ /(Makefile|.mk)$/);
116                 while (<I>) {
117                         if ($subst) {
118                                 s/@([0-9A-Za-z_]+)@/$s->ExpandVar($1)/ge;
119                         }
120                         if ($preprocess) {
121                                 if (/^#/ || $is_makefile) {
122                                         if (/^#?ifdef\s+(\w+)/) {
123                                                 if (defined ${$s->{"conditions"}}{$1}) {
124                                                         push @ifs, ${$s->{"conditions"}}{$1};
125                                                         next;
126                                                 }
127                                                 push @ifs, 0;
128                                         } elsif (/^#ifndef\s+(\w+)/) {
129                                                 if (defined ${$s->{"conditions"}}{$1}) {
130                                                         push @ifs, -${$s->{"conditions"}}{$1};
131                                                         next;
132                                                 }
133                                                 push @ifs, 0;
134                                         } elsif (/^#if\s+/) {
135                                                 push @ifs, 0;
136                                         } elsif (/^#?endif/) {
137                                                 my $x = pop @ifs;
138                                                 defined $x or die "Improper nesting of conditionals";
139                                                 $x && next;
140                                         } elsif (/^#?else/) {
141                                                 my $x = pop @ifs;
142                                                 defined $x or die "Improper nesting of conditionals";
143                                                 push @ifs, -$x;
144                                                 $x && next;
145                                         }
146                                 }
147                                 @ifs && $ifs[$#ifs] < 0 && next;
148                                 if (/^$/) {
149                                         $empty && next;
150                                         $empty = 1;
151                                 } else { $empty = 0; }
152                         }
153                         print O;
154                 }
155                 close O;
156                 close I;
157                 ! -x $f or chmod(0755, "$dir/$f") or die "chmod($dir/$f): $!";
158         } else {
159                 `cp -a "$f" "$dir/$f"`; die if $?;
160         }
161 }
162
163 sub GenPackage($) {
164         my ($s) = @_;
165         $s->{"PKG"} = $s->{"PACKAGE"} . "-" . $s->{"VERSION"};
166         my $dd = $s->{"DISTDIR"};
167         my $pkg = $s->{"PKG"};
168         my $dir = "$dd/$pkg";
169         print "Generating $dir\n";
170
171         FILES: foreach my $f (`find . -type f`) {
172                 chomp $f;
173                 $f =~ s/^\.\///;
174                 my $action = "";
175                 my @rules = @{$s->{"rules"}};
176                 while (@rules) {
177                         my $rule = shift @rules;
178                         my $act = shift @rules;
179                         if ($f =~ $rule) {
180                                 $action = $act;
181                                 last;
182                         }
183                 }
184                 ($action =~ /-/) && next FILES;
185                 print "$f ($action)\n" if $verbose;
186                 $s->CopyFile($f, $dir, $action);
187         }
188
189         foreach my $d (@{$s->{"directories"}}) {
190                 `mkdir -p $dir/$d`; die if $?;
191         }
192
193         if (-f "$dir/Makefile") {
194                 print "Cleaning up\n";
195                 `cd $dir && make distclean >&2`; die if $?;
196         }
197
198         print "Creating $dd/$pkg.tar.gz\n";
199         my $tarvv = $verbose ? "vv" : "";
200         `cd $dd && tar cz${tarvv}f $pkg.tar.gz $pkg >&2`; die if $?;
201         push @{$s->{"distfiles"}}, "$dd/$pkg.tar.gz";
202
203         my $adir = $s->{"archivedir"};
204         my $afile = "$adir/$pkg.tar.gz";
205         print "Archiving to $afile\n";
206         -d $adir or `mkdir -p $adir`;
207         `cp $dd/$pkg.tar.gz $afile`; die if $?;
208
209         return $dir;
210 }
211
212 sub GenFile($$) {
213         my ($s,$f) = @_;
214         my $sf = $s->{"DISTDIR"} . "/" . $s->{"PKG"} . "/$f";
215         my $df = $s->{"DISTDIR"} . "/$f";
216         print "Generating $df\n";
217         `cp $sf $df`; die if $?;
218         push @{$s->{"distfiles"}}, $df;
219 }
220
221 sub Usage($) {
222         my ($s) = @_;
223         my $usage = <<FOE ;
224 Usage: $0 <options>
225
226 Options:
227 --[no]verbose           Be chatty about the inner workings of the release system {verbose}
228 --[no]test              Test the package before uploading {do_test}
229 --[no]patch             Make a patch against the previous version {do_patch}
230 --diff-against=<ver>    Set which version we create the patch against
231 --[no]upload            Upload released files {do_upload}
232 --[no]git-tag           Tag the Git repository with "v<version>" {do_git_tag}
233 --force-git-tag         Rewrite the Git tag if it already exists {force_git_tag}
234 FOE
235         sub state($) {
236                 return "(default: " . ($_ ? "on" : "off") . ")";
237         }
238         $usage =~ s[{(\w+)}][state($s->{$1})]ge;
239         die $usage;
240 }
241
242 sub ParseOptions($) {
243         my ($s) = @_;
244         $s->{"do_git_tag"} = 1 if (-d ".git");
245         GetOptions(
246                 "verbose!" => \$verbose,
247                 "test!" => \$s->{"do_test"},
248                 "patch!" => \$s->{"do_patch"},
249                 "diff-against=s" => \$s->{"diff_against"},
250                 "upload!" => \$s->{"do_upload"},
251                 'git-tag!' => \$s->{"do_git_tag"},
252                 'force-git-tag!' => \$s->{"force_git_tag"},
253         ) || $s->Usage;
254 }
255
256 sub Test($) {
257         my ($s) = @_;
258         my $dd = $s->{"DISTDIR"};
259         my $pkg = $s->{"PKG"};
260         my $log = "$dd/$pkg.log";
261         print "Doing a test compilation\n";
262         `( cd $dd/$pkg && make ) >$log 2>&1`;
263         die "There were errors. Please inspect $log" if $?;
264         `grep -q [Ww]arning $log`;
265         $? or print "There were warnings! Please inspect $log.\n";
266         print "Cleaning up\n";
267         `cd $dd/$pkg && make distclean`; die if $?;
268 }
269
270 sub MakePatch($) {
271         my ($s) = @_;
272         my $dd = $s->{"DISTDIR"};
273         my $pkg1 = $s->{"PKG"};
274         my $oldver;
275         if ($s->{"diff_against"} ne "") {
276                 $oldver = $s->{"diff_against"};
277         } elsif (defined $s->{"OLDVERSION"}) {
278                 $oldver = $s->{"OLDVERSION"};
279         } else {
280                 print "WARNING: No previous version known. No patch generated.\n";
281                 return;
282         }
283         my $pkg0 = $s->{"PACKAGE"} . "-" . $oldver;
284
285         my $oldarch = $s->{"archivedir"} . "/" . $pkg0 . ".tar.gz";
286         -f $oldarch or die "MakePatch: $oldarch not found";
287         print "Unpacking $pkg0 from $oldarch\n";
288         `cd $dd && tar xzf $oldarch`; die if $?;
289
290         my $diff = $s->{"PACKAGE"} . "-" . $oldver . "-" . $s->{"VERSION"} . ".diff.gz";
291         print "Creating a patch from $pkg0 to $pkg1: $diff\n";
292         `cd $dd && diff -ruN $pkg0 $pkg1 | gzip >$diff`; die if $?;
293         push @{$s->{"distfiles"}}, "$dd/$diff";
294 }
295
296 sub Upload($) {
297         my ($s) = @_;
298         foreach my $u (@{$s->{"uploads"}}) {
299                 my $url = $u->{"url"};
300                 print "Upload to $url :\n";
301                 my @files = ();
302                 my $filter = $u->{"filter"} || ".*";
303                 foreach my $f (@{$s->{"distfiles"}}) {
304                         if ($f =~ $filter) {
305                                 print "\t$f\n";
306                                 push @files, $f;
307                         }
308                 }
309                 print "<confirm> "; <STDIN>;
310                 if ($url =~ m@^scp://([^/]+)(.*)@) {
311                         $, = " ";
312                         my $host = $1;
313                         my $dir = $2;
314                         $dir =~ s@^/~@~@;
315                         $dir =~ s@^/\./@@;
316                         my $cmd = "scp @files $host:$dir\n";
317                         `$cmd`; die if $?;
318                 } elsif ($url =~ m@ftp://([^/]+)(.*)@) {
319                         my $host = $1;
320                         my $dir = $2;
321                         open FTP, "|ftp -v $host" or die;
322                         print FTP "cd $dir\n";
323                         foreach my $f (@files) {
324                                 (my $ff = $f) =~ s@.*\/([^/].*)@$1@;
325                                 print FTP "put $f $ff\n";
326                         }
327                         print FTP "bye\n";
328                         close FTP;
329                         die if $?;
330                 } else {
331                         die "Don't know how to handle this URL scheme";
332                 }
333         }
334 }
335
336 sub GitTag($) {
337         my ($s) = @_;
338         my $tag = 'v' . $s->{'VERSION'};
339         my $force = ($s->{'force_git_tag'} ? '--force' : '');
340         print "Tagging Git repository with $tag\n";
341         `git tag $tag $force`; die if $?;
342         print "Pushing the tags upstream\n";
343         `git push --tags`; die if $?;
344 }
345
346 sub Dispatch($) {
347         my ($s) = @_;
348         $s->Test if $s->{"do_test"};
349         $s->MakePatch if $s->{"do_patch"};
350         $s->GitTag if $s->{"do_git_tag"};
351         $s->Upload if $s->{"do_upload"};
352 }
353
354 1;