Improve build
[pspp] / build-pspp
1 #! /usr/bin/env perl
2
3 use Getopt::Long qw(:config bundling no_ignore_case);
4 use POSIX;
5 use Cwd;
6
7 use strict;
8 use warnings;
9
10 my $help = 0;
11 my $build_binary = 1;
12 GetOptions ("h|help" => \$help,
13             "binary!" => \$build_binary);
14
15 usage () if $help;
16
17 die "$0: exactly one or two nonoption arguments are required\n"
18   if @ARGV != 1 && @ARGV != 2;
19
20 my $builder = `hostname`;
21 chomp $builder;
22
23 # Select build number.
24 my $build_number = POSIX::strftime("%Y%m%d%H%M%S", localtime);
25
26 my $topdir = getcwd ();
27
28 # Create build directory.
29 my $builddir = "builds/$build_number";
30 mkdir "builds" or die "builds: mkdir: $!\n" if ! -d "builds";
31 mkdir $builddir or die "$builddir: mkdir: $!\n";
32
33 our $resultsdir = "$builddir/results";
34 mkdir $resultsdir or die "$resultsdir: mkdir: $!\n";
35 mkdir "$resultsdir/vars" or die "$resultsdir/vars: mkdir: $!\n";
36
37 my $logfile = "$resultsdir/LOG";
38 open (LOG, '>', $logfile) or die "creating $logfile failed: $!\n";
39
40 set_var ("builder", $builder);
41 set_var ("build_number", $build_number);
42
43 sub start_step {
44     my ($msg) = @_;
45     print LOG "\f\n$msg\n";
46     print "$msg\n";
47 }
48
49 sub set_var {
50     my ($var, $value) = @_;
51     open (VAR, '>', "$resultsdir/vars/$var")
52       or die "creating $resultsdir/$var failed: $!\n";
53     print VAR "$value\n";
54     close VAR;
55     print LOG "$var=$value\n";
56     print "\t$var=$value\n";
57 }
58
59 sub save_result {
60     my ($src, $rm_src) = @_;
61     my ($dst) = $src;
62     $dst =~ s(^.*/)();
63     $dst = "$resultsdir/$dst";
64
65     start_step ("Saving $src");
66     run ("cp -R $src $dst");
67
68     if (defined ($rm_src) && $rm_src) {
69         run ("rm $src");
70     }
71
72     return $dst;
73 }
74
75 sub save_result_if_exists {
76     my ($src, $rm_src) = @_;
77     if (-e $src) {
78         save_result (@_);
79     } else {
80         start_step ("$src does not exist, cannot save");
81     }
82 }
83
84 my $tarball;
85 if (@ARGV == 2) {
86     my ($repo, $branch) = @ARGV;
87
88     # Fetch branch
89     start_step ("Fetch $repo, branch $branch");
90     run ("git fetch $repo +$branch:buildtmp/$$/pspp");
91
92     # Get revision number.
93     my $revision = `git rev-parse buildtmp/$$/pspp`;
94     chomp $revision;
95     set_var ("pspp_commit", $revision);
96     my $abbrev_commit = substr ($revision, 0, 6);
97
98     # Extract source.
99     start_step ("Extract branch into $builddir/pspp$build_number");
100     run ("git archive --format=tar --prefix=pspp$build_number/ buildtmp/$$/pspp | (cd $builddir && tar xf -)");
101
102     # Extract version number.
103     start_step ("Extract repository version number");
104     my $trace = `cd $builddir/pspp$build_number && autoconf -t AC_INIT`;
105     chomp $trace;
106     my ($file, $line, $macro, $package, $repo_version, @rest)
107       = split (':', $trace);
108     set_var ("repo_version", $repo_version);
109
110     # Is this a "gnits" mode tree?
111     start_step ("Checking Automake mode");
112     open (MAKEFILE_AM, '<', "$builddir/pspp$build_number/Makefile.am");
113     my $am_mode = "gnu";
114     while (<MAKEFILE>) {
115         if (/gnits/) {
116             $am_mode = "gnits";
117             last;
118         }
119     }
120     set_var ("am_mode", $am_mode);
121
122     # Generate version number for build.
123     # We want to append -g012345, but if we're in Gnits mode and the
124     # version number already has a hyphen, we have to omit it.
125     start_step ("Generate build version number");
126     my $version = $repo_version;
127     $version .= '-' unless $version =~ /-/;
128     $version .= "g$abbrev_commit";
129     set_var ("version", $version);
130
131     # Append -g012345 to configure.ac version number.
132     start_step ("Updating version number in $file");
133     my $fullname = "$builddir/pspp$build_number/$file";
134     open (OLDFILE, '<', $fullname)
135       or die "opening $fullname failed: $!\n";
136     open (NEWFILE, '>', "$fullname.new")
137       or die "creating $fullname.new failed: $!\n";
138     while (<OLDFILE>) {
139         if ($. != $line) {
140             print NEWFILE $_;
141         } else {
142             print NEWFILE "AC_INIT([$package], [$version]";
143             print NEWFILE ", [$_]" foreach @rest;
144             print NEWFILE ")\n";
145         }
146     }
147     close (NEWFILE);
148     close (OLDFILE);
149     rename ("$fullname.new", $fullname)
150       or die "rename $fullname.new to $fullname failed: $!\n";
151
152     # Add note to beginning of NEWS (otherwise "make dist" fails).
153     start_step ("Updating NEWS");
154     $fullname = "$builddir/pspp$build_number/NEWS";
155     open (OLDFILE, '<', $fullname)
156       or die "opening $fullname failed: $!\n";
157     open (NEWFILE, '>', "$fullname.new")
158       or die "creating $fullname.new failed: $!\n";
159     my $found_changes = 0;
160     while (<OLDFILE>) {
161         if (!$found_changes && /^Changes/) {
162             $found_changes = 1;
163             print NEWFILE <<EOF;
164 Changes from $repo_version to $version:
165
166  * Built automatically from commit $revision
167    in branch $branch by builder $builder
168
169 EOF
170         }
171         print NEWFILE $_;
172     }
173     close (NEWFILE);
174     close (OLDFILE);
175     rename ("$fullname.new", $fullname)
176       or die "rename $fullname.new to $fullname failed: $!\n";
177
178     # Get Gnulib commit number.
179     start_step ("Reading README.Git to find Gnulib commit number");
180     my $gnulib_commit;
181     $fullname = "$builddir/pspp$build_number/README.Git";
182     open (README_GIT, '<', $fullname)
183       or die "opening $fullname failed: $!\n";
184     while (<README_GIT>) {
185         ($gnulib_commit) = /^\s+commit ([0-9a-fA-F]{8,})/ and last;
186     }
187     die "$fullname does not specify a Git commit number\n"
188       if !defined ($gnulib_commit);
189     set_var ("gnulib_commit", $gnulib_commit);
190
191     # If we don't already have that Gnulib commit, update Gnulib.
192     `git rev-parse $gnulib_commit`;
193     if ($? != 0) {
194         start_step ("Updating Gnulib to obtain commit");
195         run ("git fetch gnulib");
196     }
197
198     # Extract gnulib source.
199     start_step ("Extract Gnulib source");
200     run ("git archive --format=tar --prefix=gnulib/ $gnulib_commit | (cd $builddir && tar xf -)");
201
202     # Bootstrap.
203     start_step ("Bootstrap (make -f Smake)");
204     run ("cd $builddir/pspp$build_number && make -f Smake");
205
206     # Configure.
207     start_step ("Configure source");
208     run ("cd $builddir/pspp$build_number && mkdir _build && cd _build && ../configure");
209
210     # Distribute.
211     start_step ("Make source tarball");
212     run ("cd $builddir/pspp$build_number/_build && make dist");
213     my $tarname = "pspp-$version.tar.gz";
214     $tarball = save_result ("$builddir/pspp$build_number/_build/$tarname", 1);
215
216     # Build user manual
217     start_step ("Build user manual");
218     run ("cd $builddir/pspp$build_number && cp _build/doc/*.texi doc/");
219     run ("cd $builddir/pspp$build_number && GENDOCS_TEMPLATE_DIR=$topdir $topdir/gendocs.sh -s doc/pspp.texinfo -o $topdir/$builddir/results/user-manual --email bug-gnu-pspp\@gnu.org pspp \"GNU PSPP User Manual\"");
220
221     # Build developer's guide
222     start_step ("Build developers guide");
223     run ("cd $builddir/pspp$build_number && GENDOCS_TEMPLATE_DIR=$topdir $topdir/gendocs.sh -s doc/pspp-dev.texinfo -o $topdir/$builddir/results/dev-guide --email bug-gnu-pspp\@gnu.org pspp \"GNU PSPP Developers Guide\"");
224 } else {
225     $tarball = $ARGV[0];
226 }
227
228 if ($build_binary) {
229     start_step ("Determining $tarball target directory");
230     my $sample_filename = `zcat $tarball | tar tf - | head -1`;
231     my ($tarball_dir) = $sample_filename =~ m%^(?:[./])*([^/]+)/%;
232     set_var ("dist_dir", $tarball_dir);
233
234     start_step ("Extracting $tarball into $builddir/$tarball_dir");
235     run ("zcat $tarball | (cd $builddir && tar xf -)");
236
237     start_step ("Extracting tar version");
238     my ($version) = `cd $builddir/$tarball_dir && ./configure --version | head -1`
239       =~ /configure (\S+)$/;
240     set_var ("dist_version", $version);
241     my ($binary_version) = "$version-$builder-build$build_number";
242     set_var ("binary_version", $binary_version);
243
244     start_step ("Configuring");
245     run ("chmod -R a-w $builddir/$tarball_dir");
246     run ("chmod u+w $builddir/$tarball_dir");
247     run ("mkdir $builddir/$tarball_dir/_build");
248     run ("chmod a-w $builddir/$tarball_dir");
249     my $ok = try_run ("cd $builddir/$tarball_dir/_build && ../configure --enable-relocatable --prefix=''");
250     for my $basename ("config.h", "config.log") {
251         save_result_if_exists ("$builddir/$tarball_dir/_build/$basename");
252     }
253     exit 1 if !$ok;
254
255     start_step ("Build");
256     run ("cd $builddir/$tarball_dir/_build && make");
257
258     start_step ("Install");
259     run ("cd $builddir/$tarball_dir/_build && make install DESTDIR=\$PWD/pspp-$binary_version");
260
261     start_step ("Make binary distribution");
262     run ("cd $builddir/$tarball_dir/_build && tar cfz pspp-$binary_version.tar.gz pspp-$binary_version");
263     save_result ("$builddir/$tarball_dir/_build/pspp-$binary_version.tar.gz", 1);
264
265     start_step ("Check");
266     $ok = try_run ("cd $builddir/$tarball_dir/_build && make check");
267     for my $basename ("tests/testsuite.log", "tests/testsuite.dir") {
268         save_result_if_exists ("$builddir/$tarball_dir/_build/$basename");
269     }
270     exit 1 if !$ok;
271
272     start_step ("Uninstall");
273     run ("cd $builddir/$tarball_dir/_build && make uninstall DESTDIR=\$PWD/pspp-$binary_version");
274
275     start_step ("Check uninstall");
276     run ("cd $builddir/$tarball_dir/_build && make distuninstallcheck distuninstallcheck_dir=\$PWD/pspp-$binary_version");
277
278     # distcleancheck
279 }
280
281 start_step ("Success");
282
283 sub usage {
284     print <<EOF;
285 $0, for building and testing PSPP
286 usage: $0 [OPTIONS] [TARBALL | REPO REFSPEC]
287 where TARBALL is the name of a tarball produced by "make dist"
288    or REPO and REFSPEC are a Git repo and refspec (e.g. branch) to clone.
289
290 Options:
291   --help            Print this usage message and exit
292 EOF
293     exit(0);
294 }
295
296 sub run {
297     exit 1 if !try_run(@_);
298 }
299
300 sub try_run {
301     my ($command) = @_;
302
303     print LOG "$command\n";
304
305     my $pid = open (COMMAND, '-|');
306     if (!defined ($pid)) {
307         die "fork failed: $!\n";
308     } elsif (!$pid) {
309         dup2 (1, 2);
310         exec ($command);
311         die "$command: exec failed: $!\n";
312     }
313
314     local ($|) = 1;
315     my $i = 0;
316     while (<COMMAND>) {
317         print LOG $_;
318         print "\r", $i++;
319     }
320     close (COMMAND);
321     print "\r      \r";
322
323     return 1 if !$?;
324
325     if ($? & 127) {
326         printf "%s: child died with signal %d, %s coredump\n",
327           $command, ($? & 127),  ($? & 128) ? 'with' : 'without';
328     } else {
329         printf "%s: child exited with value %d\n", $command, $? >> 8;
330     }
331     return 0;
332 }