work on autobuild system
[pspp] / build-pspp
1 #! /usr/bin/env perl
2
3 use Getopt::Long qw(:config bundling no_ignore_case);
4 use POSIX;
5
6 use strict;
7 use warnings;
8
9 my $help = 0;
10 GetOptions ("h|help" => \$help);
11
12 usage () if $help;
13
14 die "$0: exactly one or two nonoption arguments are required\n"
15   if @ARGV != 1 && @ARGV != 2;
16
17 my $builder = `hostname`;
18 chomp $builder;
19
20 # Select build number.
21 my $build_number = POSIX::strftime("%Y%m%d%H%M%S", localtime);
22
23 # Create build directory.
24 my $builddir = "builds/$build_number";
25 mkdir "builds" or die "builds: mkdir: $!\n" if ! -d "builds";
26 mkdir $builddir or die "$builddir: mkdir: $!\n";
27
28 our $resultsdir = "$builddir/results";
29 mkdir $resultsdir or die "$resultsdir: mkdir: $!\n";
30 mkdir "$resultsdir/vars" or die "$resultsdir/vars: mkdir: $!\n";
31
32 my $logfile = "$resultsdir/LOG";
33 open (LOG, '>', $logfile) or die "creating $logfile failed: $!\n";
34
35 set_var ("builder", $builder);
36 set_var ("build_number", $build_number);
37
38 sub start_step {
39     my ($msg) = @_;
40     print LOG "\f\n$msg\n";
41     print "$msg\n";
42 }
43
44 sub set_var {
45     my ($var, $value) = @_;
46     open (VAR, '>', "$resultsdir/vars/$var")
47       or die "creating $resultsdir/$var failed: $!\n";
48     print VAR "$value\n";
49     close VAR;
50     print LOG "$var=$value\n";
51     print "\t$var=$value\n";
52 }
53
54 my $tarball;
55 if (@ARGV == 2) {
56     my ($repo, $branch) = @ARGV;
57
58     # Fetch branch
59     start_step ("Fetch $repo, branch $branch");
60     run ("git fetch $repo +$branch:buildtmp/$$/pspp");
61
62     # Get revision number.
63     my $revision = `git rev-parse buildtmp/$$/pspp`;
64     chomp $revision;
65     set_var ("pspp_commit", $revision);
66     my $abbrev_commit = substr ($revision, 0, 6);
67
68     # Extract source.
69     start_step ("Extract branch into $builddir/pspp$build_number");
70     run ("git archive --format=tar --prefix=pspp$build_number/ buildtmp/$$/pspp | (cd $builddir && tar xf -)");
71
72     # Extract version number.
73     start_step ("Extract version number");
74     my $trace = `cd $builddir/pspp$build_number && autoconf -t AC_INIT`;
75     chomp $trace;
76     my ($file, $line, $macro, $package, $version, @rest) = split (':', $trace);
77     set_var ("pspp_version", $version);
78
79     # Append -g012345 to AC_INIT version number.
80     start_step ("Adding -g$abbrev_commit to version number");
81     my $fullname = "$builddir/pspp$build_number/$file";
82     open (OLDFILE, '<', $fullname)
83       or die "opening $fullname failed: $!\n";
84     open (NEWFILE, '>', "$fullname.new")
85       or die "creating $fullname.new failed: $!\n";
86     while (<OLDFILE>) {
87         if ($. != $line) {
88             print NEWFILE $_;
89         } else {
90             print NEWFILE "AC_INIT([$package], [$version-g$abbrev_commit]";
91             print NEWFILE ", [$_]" foreach @rest;
92             print NEWFILE ")\n";
93         }
94     }
95     close (NEWFILE);
96     close (OLDFILE);
97     rename ("$fullname.new", $fullname)
98       or die "rename $fullname.new to $fullname failed: $!\n";
99
100     # Add note to beginning of NEWS (otherwise "make dist" fails).
101     start_step ("Updating NEWS");
102     $fullname = "$builddir/pspp$build_number/NEWS";
103     open (OLDFILE, '<', $fullname)
104       or die "opening $fullname failed: $!\n";
105     open (NEWFILE, '>', "$fullname.new")
106       or die "creating $fullname.new failed: $!\n";
107     my $found_changes = 0;
108     while (<OLDFILE>) {
109         if (!$found_changes && /^Changes/) {
110             $found_changes = 1;
111             print NEWFILE <<EOF;
112 Changes from $version to $version-g$abbrev_commit:
113
114  * Built automatically from commit $revision
115    in branch $branch by builder $builder
116
117 EOF
118         }
119         print NEWFILE $_;
120     }
121     close (NEWFILE);
122     close (OLDFILE);
123     rename ("$fullname.new", $fullname)
124       or die "rename $fullname.new to $fullname failed: $!\n";
125
126     # Get Gnulib commit number.
127     start_step ("Reading README.Git to find Gnulib commit number");
128     my $gnulib_commit;
129     $fullname = "$builddir/pspp$build_number/README.Git";
130     open (README_GIT, '<', $fullname)
131       or die "opening $fullname failed: $!\n";
132     while (<README_GIT>) {
133         ($gnulib_commit) = /^\s+commit ([0-9a-fA-F]{8,})/ and last;
134     }
135     die "$fullname does not specify a Git commit number\n"
136       if !defined ($gnulib_commit);
137     set_var ("gnulib_commit", $gnulib_commit);
138
139     # If we don't already have that Gnulib commit, update Gnulib.
140     `git rev-parse $gnulib_commit`;
141     if ($? != 0) {
142         start_step ("Updating Gnulib to obtain commit");
143         run ("git fetch gnulib");
144     }
145
146     # Extract gnulib source.
147     start_step ("Extract Gnulib source");
148     run ("git archive --format=tar --prefix=gnulib/ $gnulib_commit | (cd $builddir && tar xf -)");
149
150     # Bootstrap.
151     start_step ("Bootstrap (make -f Smake)");
152     run ("cd $builddir/pspp$build_number && make -f Smake");
153
154     # Configure.
155     start_step ("Configure source");
156     run ("cd $builddir/pspp$build_number && mkdir _build && cd _build && ../configure");
157
158     # Distribute.
159     start_step ("Make dist tarball");
160     run ("cd $builddir/pspp$build_number/_build && make dist");
161
162     $tarball = "$builddir/pspp$build_number/_build/pspp-$version-g$abbrev_commit.tar.gz";
163 } else {
164     $tarball = $ARGV[0];
165 }
166
167 start_step ("Determining $tarball target directory");
168 my $sample_filename = `zcat $tarball | tar tf - | head -1`;
169 my ($tarball_dir) = $sample_filename =~ m%^(?:[./])*([^/]+)/%;
170 set_var ("dist_dir", $tarball_dir);
171
172 start_step ("Extracting $tarball into $builddir/$tarball_dir");
173 run ("zcat $tarball | (cd $builddir && tar xf -)");
174
175 start_step ("Extracting tar version");
176 my ($version) = `cd $builddir/$tarball_dir && ./configure --version | head -1`
177   =~ /configure (\S+)$/;
178 set_var ("dist_version", $version);
179 my ($binary_version) = "$version-$builder-build$build_number";
180 set_var ("binary_version", $binary_version);
181
182 start_step ("Configuring");
183 run ("chmod -R a-w $builddir/$tarball_dir");
184 run ("chmod u+w $builddir/$tarball_dir");
185 run ("mkdir $builddir/$tarball_dir/_build");
186 run ("chmod a-w $builddir/$tarball_dir");
187 run ("cd $builddir/$tarball_dir/_build && ../configure --enable-relocatable --prefix=''");
188
189 start_step ("Build");
190 run ("cd $builddir/$tarball_dir/_build && make");
191
192 start_step ("Install");
193 run ("cd $builddir/$tarball_dir/_build && make install DESTDIR=\$PWD/pspp-$binary_version");
194
195 start_step ("Make binary distribution");
196 run ("cd $builddir/$tarball_dir/_build && tar cfz pspp-$binary_version.tar.gz pspp-$binary_version");
197
198 start_step ("Check");
199 run ("cd $builddir/$tarball_dir/_build && make check");
200
201 start_step ("Uninstall");
202 run ("cd $builddir/$tarball_dir/_build && make uninstall DESTDIR=\$PWD/pspp-$binary_version");
203
204 start_step ("Check uninstall");
205 run ("cd $builddir/$tarball_dir/_build && make distuninstallcheck distuninstallcheck_dir=\$PWD/pspp-$binary_version");
206
207 # distcleancheck
208
209 start_step ("Success");
210
211 sub usage {
212     print <<EOF;
213 $0, for building and testing PSPP
214 usage: $0 [OPTIONS] [TARBALL | REPO REFSPEC]
215 where TARBALL is the name of a tarball produced by "make dist"
216    or REPO and REFSPEC are a Git repo and refspec (e.g. branch) to clone.
217
218 Options:
219   --help            Print this usage message and exit
220 EOF
221     exit(0);
222 }
223
224 sub run {
225     my ($command) = @_;
226
227     print LOG "$command\n";
228
229     my $pid = open (COMMAND, '-|');
230     if (!defined ($pid)) {
231         die "fork failed: $!\n";
232     } elsif (!$pid) {
233         dup2 (1, 2);
234         exec ($command);
235         die "$command: exec failed: $!\n";
236     }
237
238     local ($|) = 1;
239     my $i = 0;
240     while (<COMMAND>) {
241         print LOG $_;
242         print "\r", $i++;
243     }
244     close (COMMAND);
245     print "\r      \r";
246
247     if ($? == 0) {
248         return;
249     } elsif ($? & 127) {
250         printf "%s: child died with signal %d, %s coredump\n",
251           $command, ($? & 127),  ($? & 128) ? 'with' : 'without';
252     } else {
253         printf "%s: child exited with value %d\n", $command, $? >> 8;
254     }
255     exit 1;
256 }
257
258 # Clone source
259 # Add build number
260 # Tag build
261 # Clone gnulib at correct commit number
262 # Run gnulib-tool.
263 # Run configure
264 # Make dist
265
266 # Unpack dist
267 # Run configure
268 # Check
269 # Install
270 # Make binary dist
271 # Build mingw32 installer
272 # Other distcheck stuff?
273
274 # Distribute manual in various formats