1#!/usr/bin/perl 2# A simple system for making software releases 3# (c) 2003--2011 Martin Mares <[email protected]> 4 5package UCW::Release; 6use strict; 7use warnings; 8use Getopt::Long; 9 10our $verbose = 0; 11 12sub 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 '\.sw[a-z]$' => '-', 20 '\.(lsm|spec)$' => 'ps', 21 '(^|/)README$' => 's' 22 ], 23 "directories" => [ 24 ], 25 "conditions" => { 26 }, 27 "DATE" => `date '+%Y-%m-%d' | tr -d '\n'`, 28 "LSMDATE" => `date '+%y%m%d' | tr -d '\n'`, 29 "distfiles" => [ 30 ], 31 "archivedir" => $ENV{HOME} . "/archives/sw/$basename", 32 "uploads" => [ 33 ], 34 # Options 35 "do_test" => 1, 36 "do_patch" => 1, 37 "diff_against" => "", 38 "do_upload" => 1, 39 "do_sign" => 1, 40 }; 41 bless $s; 42 return $s; 43} 44 45sub 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 61sub 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 84sub 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 92sub 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 101sub 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 163sub 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 if ($s->{'do_sign'}) { 204 print "Signing package\n"; 205 system "gpg", "--armor", "--detach-sig", "$dd/$pkg.tar.gz"; 206 die if $?; 207 rename "$dd/$pkg.tar.gz.asc", "$dd/$pkg.tar.gz.sign" or die "No signature produced!?\n"; 208 push @{$s->{"distfiles"}}, "$dd/$pkg.tar.gz.sign"; 209 } 210 211 my $adir = $s->{"archivedir"}; 212 my $afile = "$adir/$pkg.tar.gz"; 213 print "Archiving to $afile\n"; 214 -d $adir or `mkdir -p $adir`; 215 `cp $dd/$pkg.tar.gz $afile`; die if $?; 216 217 return $dir; 218} 219 220sub GenFile($$) { 221 my ($s,$f) = @_; 222 my $sf = $s->{"DISTDIR"} . "/" . $s->{"PKG"} . "/$f"; 223 my $df = $s->{"DISTDIR"} . "/$f"; 224 print "Generating $df\n"; 225 `cp $sf $df`; die if $?; 226 push @{$s->{"distfiles"}}, $df; 227} 228 229sub ParseOptions($) { 230 my ($s) = @_; 231 GetOptions( 232 "verbose!" => \$verbose, 233 "test!" => \$s->{"do_test"}, 234 "patch!" => \$s->{"do_patch"}, 235 "diff-against=s" => \$s->{"diff_against"}, 236 "upload!" => \$s->{"do_upload"}, 237 "sign!" => \$s->{"do_sign"}, 238 ) || die "Syntax: release [--verbose] [--test] [--nopatch] [--diff-against=<version>] [--noupload] [--nosign]"; 239} 240 241sub Test($) { 242 my ($s) = @_; 243 my $dd = $s->{"DISTDIR"}; 244 my $pkg = $s->{"PKG"}; 245 my $log = "$dd/$pkg.log"; 246 print "Doing a test compilation\n"; 247 `( cd $dd/$pkg && make ) >$log 2>&1`; 248 die "There were errors. Please inspect $log" if $?; 249 `grep -q [Ww]arning $log`; 250 $? or print "There were warnings! Please inspect $log.\n"; 251 print "Cleaning up\n"; 252 `cd $dd/$pkg && make distclean`; die if $?; 253} 254 255sub MakePatch($) { 256 my ($s) = @_; 257 my $dd = $s->{"DISTDIR"}; 258 my $pkg1 = $s->{"PKG"}; 259 my $oldver; 260 if ($s->{"diff_against"} ne "") { 261 $oldver = $s->{"diff_against"}; 262 } elsif (defined $s->{"OLDVERSION"}) { 263 $oldver = $s->{"OLDVERSION"}; 264 } else { 265 print "WARNING: No previous version known. No patch generated.\n"; 266 return; 267 } 268 my $pkg0 = $s->{"PACKAGE"} . "-" . $oldver; 269 270 my $oldarch = $s->{"archivedir"} . "/" . $pkg0 . ".tar.gz"; 271 -f $oldarch or die "MakePatch: $oldarch not found"; 272 print "Unpacking $pkg0 from $oldarch\n"; 273 `cd $dd && tar xzf $oldarch`; die if $?; 274 275 my $diff = $s->{"PACKAGE"} . "-" . $oldver . "-" . $s->{"VERSION"} . ".diff.gz"; 276 print "Creating a patch from $pkg0 to $pkg1: $diff\n"; 277 `cd $dd && diff -ruN $pkg0 $pkg1 | gzip >$diff`; die if $?; 278 push @{$s->{"distfiles"}}, "$dd/$diff"; 279} 280 281sub Upload($) { 282 my ($s) = @_; 283 foreach my $u (@{$s->{"uploads"}}) { 284 my $url = $u->{"url"}; 285 print "Upload to $url :\n"; 286 my @files = (); 287 my $filter = $u->{"filter"} || ".*"; 288 foreach my $f (@{$s->{"distfiles"}}) { 289 if ($f =~ $filter) { 290 print "\t$f\n"; 291 push @files, $f; 292 } 293 } 294 print "<confirm> "; <STDIN>; 295 if ($url =~ m@^scp://([^/]+)(.*)@) { 296 $, = " "; 297 my $host = $1; 298 my $dir = $2; 299 $dir =~ s@^/~@~@; 300 $dir =~ s@^/\./@@; 301 my $cmd = "scp @files $host:$dir\n"; 302 `$cmd`; die if $?; 303 } elsif ($url =~ m@ftp://([^/]+)(.*)@) { 304 my $host = $1; 305 my $dir = $2; 306 open FTP, "|ftp -v $host" or die; 307 print FTP "cd $dir\n"; 308 foreach my $f (@files) { 309 (my $ff = $f) =~ s@.*\/([^/].*)@$1@; 310 print FTP "put $f $ff\n"; 311 } 312 print FTP "bye\n"; 313 close FTP; 314 die if $?; 315 } else { 316 die "Don't know how to handle this URL scheme"; 317 } 318 } 319} 320 321sub Dispatch($) { 322 my ($s) = @_; 323 $s->Test if $s->{"do_test"}; 324 $s->MakePatch if $s->{"do_patch"}; 325 $s->Upload if $s->{"do_upload"}; 326} 327 3281; 329