<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/perl
# A simple system for making software releases
# (c) 2003--2011 Martin Mares &lt;mj@ucw.cz&gt;

package UCW::Release;
use strict;
use warnings;
use Getopt::Long;

our $verbose = 0;

sub new($$) {
	my ($class,$basename) = @_;
	my $s = {
		"PACKAGE" =&gt; $basename,
		"rules" =&gt; [
			# p=preprocess, s=subst, -=discard
			'(^|/)(CVS|\.arch-ids|{arch}|\.git|tmp)/' =&gt; '-',
			'\.sw[a-z]$' =&gt; '-',
			'\.(lsm|spec)$' =&gt; 'ps',
			'(^|/)README$' =&gt; 's'
			],
		"directories" =&gt; [
			],
		"conditions" =&gt; {
			},
		"DATE" =&gt; `date '+%Y-%m-%d' | tr -d '\n'`,
		"LSMDATE" =&gt; `date '+%y%m%d' | tr -d '\n'`,
		"distfiles" =&gt; [
			],
		"archivedir" =&gt; $ENV{HOME} . "/archives/sw/$basename",
		"uploads" =&gt; [
			],
		# Options
		"do_test" =&gt; 1,
		"do_patch" =&gt; 1,
		"diff_against" =&gt; "",
		"do_upload" =&gt; 1,
		"do_sign" =&gt; 1,
	};
	bless $s;
	return $s;
}

sub GetVersionFromFile($) {
	my ($s,$file,$rx) = @_;
	open F, $file or die "Unable to open $file for version autodetection";
	while (&lt;F&gt;) {
		chomp;
		if (/$rx/) {
			$s-&gt;{"VERSION"} = $1;
			print "Detected version $1 from $file\n" if $verbose;
			last;
		}
	}
	close F;
	if (!defined $s-&gt;{"VERSION"}) { die "Failed to auto-detect version"; }
	return $s-&gt;{"VERSION"};
}

sub GetVersionsFromChangelog($) {
	my ($s,$file,$rx) = @_;
	open F, $file or die "Unable to open $file for version autodetection";
	while (&lt;F&gt;) {
		chomp;
		if (/$rx/) {
			if (!defined $s-&gt;{"VERSION"}) {
				$s-&gt;{"VERSION"} = $1;
				print "Detected version $1 from $file\n" if $verbose;
			} elsif ($s-&gt;{"VERSION"} eq $1) {
				# do nothing
			} else {
				$s-&gt;{"OLDVERSION"} = $1;
				print "Detected previous version $1 from $file\n" if $verbose;
				last;
			}
		}
	}
	close F;
	if (!defined $s-&gt;{"VERSION"}) { die "Failed to auto-detect version"; }
	return $s-&gt;{"VERSION"};
}

sub InitDist($) {
	my ($s,$dd) = @_;
	$s-&gt;{"DISTDIR"} = $dd;
	print "Initializing dist directory $dd\n" if $verbose;
	`rm -rf $dd`; die if $?;
	`mkdir -p $dd`; die if $?;
}

sub ExpandVar($$) {
	my ($s,$v) = @_;
	if (defined $s-&gt;{$v}) {
		return $s-&gt;{$v};
	} else {
		die "Reference to unknown variable $v";
	}
}

sub CopyFile($$$$) {
	my ($s,$f,$dir,$action) = @_;

	(my $d = $f) =~ s@(^|/)[^/]*$@@;
	$d = "$dir/$d";
	-d $d || `mkdir -p $d`; die if $?;

	my $preprocess = ($action =~ /p/);
	my $subst = ($action =~ /s/);
	if ($preprocess || $subst) {
		open I, "$f" or die "open($f): $?";
		open O, "&gt;$dir/$f" or die "open($dir/$f): $!";
		my @ifs = ();	# stack of conditions, 1=satisfied
		my $empty = 0;	# last line was empty
		my $is_makefile = ($f =~ /(Makefile|.mk)$/);
		while (&lt;I&gt;) {
			if ($subst) {
				s/@([0-9A-Za-z_]+)@/$s-&gt;ExpandVar($1)/ge;
			}
			if ($preprocess) {
				if (/^#/ || $is_makefile) {
					if (/^#?ifdef\s+(\w+)/) {
						if (defined ${$s-&gt;{"conditions"}}{$1}) {
							push @ifs, ${$s-&gt;{"conditions"}}{$1};
							next;
						}
						push @ifs, 0;
					} elsif (/^#ifndef\s+(\w+)/) {
						if (defined ${$s-&gt;{"conditions"}}{$1}) {
							push @ifs, -${$s-&gt;{"conditions"}}{$1};
							next;
						}
						push @ifs, 0;
					} elsif (/^#if\s+/) {
						push @ifs, 0;
					} elsif (/^#?endif/) {
						my $x = pop @ifs;
						defined $x or die "Improper nesting of conditionals";
						$x &amp;&amp; next;
					} elsif (/^#?else/) {
						my $x = pop @ifs;
						defined $x or die "Improper nesting of conditionals";
						push @ifs, -$x;
						$x &amp;&amp; next;
					}
				}
				@ifs &amp;&amp; $ifs[$#ifs] &lt; 0 &amp;&amp; next;
				if (/^$/) {
					$empty &amp;&amp; next;
					$empty = 1;
				} else { $empty = 0; }
			}
			print O;
		}
		close O;
		close I;
		! -x $f or chmod(0755, "$dir/$f") or die "chmod($dir/$f): $!";
	} else {
		`cp -a "$f" "$dir/$f"`; die if $?;
	}
}

sub GenPackage($) {
	my ($s) = @_;
	$s-&gt;{"PKG"} = $s-&gt;{"PACKAGE"} . "-" . $s-&gt;{"VERSION"};
	my $dd = $s-&gt;{"DISTDIR"};
	my $pkg = $s-&gt;{"PKG"};
	my $dir = "$dd/$pkg";
	print "Generating $dir\n";

	FILES: foreach my $f (`find . -type f`) {
		chomp $f;
		$f =~ s/^\.\///;
		my $action = "";
		my @rules = @{$s-&gt;{"rules"}};
		while (@rules) {
			my $rule = shift @rules;
			my $act = shift @rules;
			if ($f =~ $rule) {
				$action = $act;
				last;
			}
		}
		($action =~ /-/) &amp;&amp; next FILES;
		print "$f ($action)\n" if $verbose;
		$s-&gt;CopyFile($f, $dir, $action);
	}

	foreach my $d (@{$s-&gt;{"directories"}}) {
		`mkdir -p $dir/$d`; die if $?;
	}

	if (-f "$dir/Makefile") {
		print "Cleaning up\n";
		`cd $dir &amp;&amp; make distclean &gt;&amp;2`; die if $?;
	}

	print "Creating $dd/$pkg.tar.gz\n";
	my $tarvv = $verbose ? "vv" : "";
	`cd $dd &amp;&amp; tar cz${tarvv}f $pkg.tar.gz $pkg &gt;&amp;2`; die if $?;
	push @{$s-&gt;{"distfiles"}}, "$dd/$pkg.tar.gz";

	if ($s-&gt;{'do_sign'}) {
		print "Signing package\n";
		system "gpg", "--armor", "--detach-sig", "$dd/$pkg.tar.gz";
		die if $?;
		rename "$dd/$pkg.tar.gz.asc", "$dd/$pkg.tar.gz.sign" or die "No signature produced!?\n";
		push @{$s-&gt;{"distfiles"}}, "$dd/$pkg.tar.gz.sign";
	}

	my $adir = $s-&gt;{"archivedir"};
	my $afile = "$adir/$pkg.tar.gz";
	print "Archiving to $afile\n";
	-d $adir or `mkdir -p $adir`;
	`cp $dd/$pkg.tar.gz $afile`; die if $?;

	return $dir;
}

sub GenFile($$) {
	my ($s,$f) = @_;
	my $sf = $s-&gt;{"DISTDIR"} . "/" . $s-&gt;{"PKG"} . "/$f";
	my $df = $s-&gt;{"DISTDIR"} . "/$f";
	print "Generating $df\n";
	`cp $sf $df`; die if $?;
	push @{$s-&gt;{"distfiles"}}, $df;
}

sub ParseOptions($) {
	my ($s) = @_;
	GetOptions(
		"verbose!" =&gt; \$verbose,
		"test!" =&gt; \$s-&gt;{"do_test"},
		"patch!" =&gt; \$s-&gt;{"do_patch"},
		"diff-against=s" =&gt; \$s-&gt;{"diff_against"},
		"upload!" =&gt; \$s-&gt;{"do_upload"},
		"sign!" =&gt; \$s-&gt;{"do_sign"},
	) || die "Syntax: release [--verbose] [--test] [--nopatch] [--diff-against=&lt;version&gt;] [--noupload] [--nosign]";
}

sub Test($) {
	my ($s) = @_;
	my $dd = $s-&gt;{"DISTDIR"};
	my $pkg = $s-&gt;{"PKG"};
	my $log = "$dd/$pkg.log";
	print "Doing a test compilation\n";
	`( cd $dd/$pkg &amp;&amp; make ) &gt;$log 2&gt;&amp;1`;
	die "There were errors. Please inspect $log" if $?;
	`grep -q [Ww]arning $log`;
	$? or print "There were warnings! Please inspect $log.\n";
	print "Cleaning up\n";
	`cd $dd/$pkg &amp;&amp; make distclean`; die if $?;
}

sub MakePatch($) {
	my ($s) = @_;
	my $dd = $s-&gt;{"DISTDIR"};
	my $pkg1 = $s-&gt;{"PKG"};
	my $oldver;
	if ($s-&gt;{"diff_against"} ne "") {
		$oldver = $s-&gt;{"diff_against"};
	} elsif (defined $s-&gt;{"OLDVERSION"}) {
		$oldver = $s-&gt;{"OLDVERSION"};
	} else {
		print "WARNING: No previous version known. No patch generated.\n";
		return;
	}
	my $pkg0 = $s-&gt;{"PACKAGE"} . "-" . $oldver;

	my $oldarch = $s-&gt;{"archivedir"} . "/" . $pkg0 . ".tar.gz";
	-f $oldarch or die "MakePatch: $oldarch not found";
	print "Unpacking $pkg0 from $oldarch\n";
	`cd $dd &amp;&amp; tar xzf $oldarch`; die if $?;

	my $diff = $s-&gt;{"PACKAGE"} . "-" . $oldver . "-" . $s-&gt;{"VERSION"} . ".diff.gz";
	print "Creating a patch from $pkg0 to $pkg1: $diff\n";
	`cd $dd &amp;&amp; diff -ruN $pkg0 $pkg1 | gzip &gt;$diff`; die if $?;
	push @{$s-&gt;{"distfiles"}}, "$dd/$diff";
}

sub Upload($) {
	my ($s) = @_;
	foreach my $u (@{$s-&gt;{"uploads"}}) {
		my $url = $u-&gt;{"url"};
		print "Upload to $url :\n";
		my @files = ();
		my $filter = $u-&gt;{"filter"} || ".*";
		foreach my $f (@{$s-&gt;{"distfiles"}}) {
			if ($f =~ $filter) {
				print "\t$f\n";
				push @files, $f;
			}
		}
		print "&lt;confirm&gt; "; &lt;STDIN&gt;;
		if ($url =~ m@^scp://([^/]+)(.*)@) {
			$, = " ";
			my $host = $1;
			my $dir = $2;
			$dir =~ s@^/~@~@;
			$dir =~ s@^/\./@@;
			my $cmd = "scp @files $host:$dir\n";
			`$cmd`; die if $?;
		} elsif ($url =~ m@ftp://([^/]+)(.*)@) {
			my $host = $1;
			my $dir = $2;
			open FTP, "|ftp -v $host" or die;
			print FTP "cd $dir\n";
			foreach my $f (@files) {
				(my $ff = $f) =~ s@.*\/([^/].*)@$1@;
				print FTP "put $f $ff\n";
			}
			print FTP "bye\n";
			close FTP;
			die if $?;
		} else {
			die "Don't know how to handle this URL scheme";
		}
	}
}

sub Dispatch($) {
	my ($s) = @_;
	$s-&gt;Test if $s-&gt;{"do_test"};
	$s-&gt;MakePatch if $s-&gt;{"do_patch"};
	$s-&gt;Upload if $s-&gt;{"do_upload"};
}

1;
</pre></body></html>