2 # A simple system for making software releases
3 # (c) 2003--2010 Martin Mares <mj@ucw.cz>
13 my ($class,$basename) = @_;
15 "PACKAGE" => $basename,
17 # p=preprocess, s=subst, -=discard
18 '(^|/)(CVS|\.arch-ids|{arch}|\.git|tmp)/' => '-',
19 '\.(lsm|spec)$' => 'ps',
20 '(^|/)README$' => 's',
27 "DATE" => `date '+%Y-%m-%d' | tr -d '\n'`,
28 "LSMDATE" => `date '+%y%m%d' | tr -d '\n'`,
31 "archivedir" => "/home/mj/tmp/archives/$basename",
50 sub GetVersionFromFile($) {
51 my ($s,$file,$rx) = @_;
52 open F, $file or die "Unable to open $file for version autodetection";
57 print "Detected version $1 from $file\n" if $verbose;
62 if (!defined $s->{"VERSION"}) { die "Failed to auto-detect version"; }
63 return $s->{"VERSION"};
66 sub GetVersionsFromChangelog($) {
67 my ($s,$file,$rx) = @_;
68 open F, $file or die "Unable to open $file for version autodetection";
72 if (!defined $s->{"VERSION"}) {
74 print "Detected version $1 from $file\n" if $verbose;
75 } elsif ($s->{"VERSION"} eq $1) {
78 $s->{"OLDVERSION"} = $1;
79 print "Detected previous version $1 from $file\n" if $verbose;
85 if (!defined $s->{"VERSION"}) { die "Failed to auto-detect version"; }
86 return $s->{"VERSION"};
91 $s->{"DISTDIR"} = $dd;
92 print "Initializing dist directory $dd\n" if $verbose;
93 `rm -rf $dd`; die if $?;
94 `mkdir -p $dd`; die if $?;
99 if (defined $s->{$v}) {
102 die "Reference to unknown variable $v";
107 my ($s,$f,$dir,$action) = @_;
109 (my $d = $f) =~ s@(^|/)[^/]*$@@;
111 -d $d || `mkdir -p $d`; die if $?;
113 my $preprocess = ($action =~ /p/);
114 my $subst = ($action =~ /s/);
115 if ($preprocess || $subst) {
116 open I, "$f" or die "open($f): $?";
117 open O, ">$dir/$f" or die "open($dir/$f): $!";
118 my @ifs = (); # stack of conditions, 1=satisfied
119 my $empty = 0; # last line was empty
120 my $is_makefile = ($f =~ /(Makefile|.mk)$/);
123 s/@([0-9A-Za-z_]+)@/$s->ExpandVar($1)/ge;
126 if (/^#/ || $is_makefile) {
127 if (/^#?ifdef\s+(\w+)/) {
128 if (defined ${$s->{"conditions"}}{$1}) {
129 push @ifs, ${$s->{"conditions"}}{$1};
133 } elsif (/^#ifndef\s+(\w+)/) {
134 if (defined ${$s->{"conditions"}}{$1}) {
135 push @ifs, -${$s->{"conditions"}}{$1};
139 } elsif (/^#if\s+/) {
141 } elsif (/^#?endif/) {
143 defined $x or die "Improper nesting of conditionals";
145 } elsif (/^#?else/) {
147 defined $x or die "Improper nesting of conditionals";
152 @ifs && $ifs[$#ifs] < 0 && next;
156 } else { $empty = 0; }
162 ! -x $f or chmod(0755, "$dir/$f") or die "chmod($dir/$f): $!";
164 `cp -a "$f" "$dir/$f"`; die if $?;
170 $s->{"PKG"} = $s->{"PACKAGE"} . "-" . $s->{"VERSION"};
171 my $dd = $s->{"DISTDIR"};
172 my $pkg = $s->{"PKG"};
173 my $dir = "$dd/$pkg";
174 print "Generating $dir\n";
176 FILES: foreach my $f (`find . -type f`) {
180 my @rules = @{$s->{"rules"}};
182 my $rule = shift @rules;
183 my $act = shift @rules;
189 ($action =~ /-/) && next FILES;
190 print "$f ($action)\n" if $verbose;
191 $s->CopyFile($f, $dir, $action);
194 foreach my $d (@{$s->{"directories"}}) {
195 `mkdir -p $dir/$d`; die if $?;
198 if (-f "$dir/Makefile") {
199 print "Cleaning up\n";
200 `cd $dir && make distclean >&2`; die if $?;
203 print "Creating $dd/$pkg.tar.gz\n";
204 my $tarvv = $verbose ? "vv" : "";
205 `cd $dd && tar cz${tarvv}f $pkg.tar.gz $pkg >&2`; die if $?;
206 push @{$s->{"distfiles"}}, "$dd/$pkg.tar.gz";
208 my $adir = $s->{"archivedir"};
209 my $afile = "$adir/$pkg.tar.gz";
210 print "Archiving to $afile\n";
211 -d $adir or `mkdir -p $adir`;
212 `cp $dd/$pkg.tar.gz $afile`; die if $?;
219 my $sf = $s->{"DISTDIR"} . "/" . $s->{"PKG"} . "/$f";
220 my $df = $s->{"DISTDIR"} . "/$f";
221 print "Generating $df\n";
222 `cp $sf $df`; die if $?;
223 push @{$s->{"distfiles"}}, $df;
232 --[no]verbose Be chatty about the inner workings of the release system {verbose}
233 --[no]test Test the package before uploading {do_test}
234 --[no]patch Make a patch against the previous version {do_patch}
235 --diff-against=<ver> Set which version we create the patch against
236 --[no]upload Upload released files {do_upload}
237 --[no]git-tag Tag the Git repository with "v<version>" {do_git_tag}
238 --force-git-tag Rewrite the Git tag if it already exists {force_git_tag}
239 --[no]notify Call scripts to notify the world about the release {do_notify}
242 return "(default: " . ($_[0] ? "on" : "off") . ")";
244 $usage =~ s[{(\w+)}][state($s->{$1})]ge;
248 sub ParseOptions($) {
250 $s->{"do_git_tag"} = 1 if (-d ".git");
251 $s->{"do_notify"} = 1 if @{$s->{"notifiers"}};
253 "verbose!" => \$verbose,
254 "test!" => \$s->{"do_test"},
255 "patch!" => \$s->{"do_patch"},
256 "diff-against=s" => \$s->{"diff_against"},
257 "upload!" => \$s->{"do_upload"},
258 'git-tag!' => \$s->{"do_git_tag"},
259 'force-git-tag!' => \$s->{"force_git_tag"},
260 'notify!' => \$s->{"do_notify"},
266 my $dd = $s->{"DISTDIR"};
267 my $pkg = $s->{"PKG"};
268 my $log = "$dd/$pkg.log";
269 my $test_cmd = $s->{"test_cmd"};
270 print "Doing a test compilation\n";
271 `( cd $dd/$pkg && $test_cmd ) >$log 2>&1`;
272 die "There were errors. Please inspect $log" if $?;
273 `grep -q [Ww]arning $log`;
274 $? or print "There were warnings! Please inspect $log.\n";
275 print "Cleaning up\n";
276 `cd $dd/$pkg && make distclean`; die if $?;
281 my $dd = $s->{"DISTDIR"};
282 my $pkg1 = $s->{"PKG"};
284 if ($s->{"diff_against"} ne "") {
285 $oldver = $s->{"diff_against"};
286 } elsif (defined $s->{"OLDVERSION"}) {
287 $oldver = $s->{"OLDVERSION"};
289 print "WARNING: No previous version known. No patch generated.\n";
292 my $pkg0 = $s->{"PACKAGE"} . "-" . $oldver;
294 my $oldarch = $s->{"archivedir"} . "/" . $pkg0 . ".tar.gz";
295 -f $oldarch or die "MakePatch: $oldarch not found";
296 print "Unpacking $pkg0 from $oldarch\n";
297 `cd $dd && tar xzf $oldarch`; die if $?;
299 my $diff = $s->{"PACKAGE"} . "-" . $oldver . "-" . $s->{"VERSION"} . ".diff.gz";
300 print "Creating a patch from $pkg0 to $pkg1: $diff\n";
301 `cd $dd && diff -ruN $pkg0 $pkg1 | gzip >$diff`; die if $?;
302 push @{$s->{"distfiles"}}, "$dd/$diff";
307 foreach my $u (@{$s->{"uploads"}}) {
308 my $url = $u->{"url"};
309 print "Upload to $url :\n";
311 my $filter = $u->{"filter"} || ".*";
312 foreach my $f (@{$s->{"distfiles"}}) {
318 print "<confirm> "; <STDIN>;
319 if ($url =~ m@^scp://([^/]+)(.*)@) {
325 my $cmd = "scp @files $host:$dir\n";
327 } elsif ($url =~ m@ftp://([^/]+)(.*)@) {
330 open FTP, "|ftp -v $host" or die;
331 print FTP "cd $dir\n";
332 foreach my $f (@files) {
333 (my $ff = $f) =~ s@.*\/([^/].*)@$1@;
334 print FTP "put $f $ff\n";
340 die "Don't know how to handle this URL scheme";
347 my $tag = 'v' . $s->{'VERSION'};
348 my $force = ($s->{'force_git_tag'} ? '--force' : '');
349 print "Tagging Git repository with $tag\n";
350 `git tag $tag $force`; die if $?;
351 print "Pushing the tags upstream\n";
352 `git push --tags`; die if $?;
355 sub AddUcwNotifier($) {
357 push @{$r->{"notifiers"}}, sub {
359 print "Updating web pages\n";
360 my $pkg = $s->{'PACKAGE'};
361 my $ver = $s->{'VERSION'};
362 `ssh jw 'cd web && bin/release-prog $pkg $ver'`; die if $?;
368 $s->Test if $s->{"do_test"};
369 $s->MakePatch if $s->{"do_patch"};
370 $s->GitTag if $s->{"do_git_tag"};
371 $s->Upload if $s->{"do_upload"};
372 if ($s->{"do_notify"}) {
373 for my $f (@{$s->{"notifiers"}}) {