6177b0ec7457da8ea1e4a3f6c1e31e24c738c919
[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 version number");
104     my $trace = `cd $builddir/pspp$build_number && autoconf -t AC_INIT`;
105     chomp $trace;
106     my ($file, $line, $macro, $package, $version, @rest) = split (':', $trace);
107     set_var ("pspp_version", $version);
108
109     # Append -g012345 to AC_INIT version number.
110     start_step ("Adding -g$abbrev_commit to version number");
111     my $fullname = "$builddir/pspp$build_number/$file";
112     open (OLDFILE, '<', $fullname)
113       or die "opening $fullname failed: $!\n";
114     open (NEWFILE, '>', "$fullname.new")
115       or die "creating $fullname.new failed: $!\n";
116     while (<OLDFILE>) {
117         if ($. != $line) {
118             print NEWFILE $_;
119         } else {
120             print NEWFILE "AC_INIT([$package], [$version-g$abbrev_commit]";
121             print NEWFILE ", [$_]" foreach @rest;
122             print NEWFILE ")\n";
123         }
124     }
125     close (NEWFILE);
126     close (OLDFILE);
127     rename ("$fullname.new", $fullname)
128       or die "rename $fullname.new to $fullname failed: $!\n";
129
130     # Add note to beginning of NEWS (otherwise "make dist" fails).
131     start_step ("Updating NEWS");
132     $fullname = "$builddir/pspp$build_number/NEWS";
133     open (OLDFILE, '<', $fullname)
134       or die "opening $fullname failed: $!\n";
135     open (NEWFILE, '>', "$fullname.new")
136       or die "creating $fullname.new failed: $!\n";
137     my $found_changes = 0;
138     while (<OLDFILE>) {
139         if (!$found_changes && /^Changes/) {
140             $found_changes = 1;
141             print NEWFILE <<EOF;
142 Changes from $version to $version-g$abbrev_commit:
143
144  * Built automatically from commit $revision
145    in branch $branch by builder $builder
146
147 EOF
148         }
149         print NEWFILE $_;
150     }
151     close (NEWFILE);
152     close (OLDFILE);
153     rename ("$fullname.new", $fullname)
154       or die "rename $fullname.new to $fullname failed: $!\n";
155
156     # Get Gnulib commit number.
157     start_step ("Reading README.Git to find Gnulib commit number");
158     my $gnulib_commit;
159     $fullname = "$builddir/pspp$build_number/README.Git";
160     open (README_GIT, '<', $fullname)
161       or die "opening $fullname failed: $!\n";
162     while (<README_GIT>) {
163         ($gnulib_commit) = /^\s+commit ([0-9a-fA-F]{8,})/ and last;
164     }
165     die "$fullname does not specify a Git commit number\n"
166       if !defined ($gnulib_commit);
167     set_var ("gnulib_commit", $gnulib_commit);
168
169     # If we don't already have that Gnulib commit, update Gnulib.
170     `git rev-parse $gnulib_commit`;
171     if ($? != 0) {
172         start_step ("Updating Gnulib to obtain commit");
173         run ("git fetch gnulib");
174     }
175
176     # Extract gnulib source.
177     start_step ("Extract Gnulib source");
178     run ("git archive --format=tar --prefix=gnulib/ $gnulib_commit | (cd $builddir && tar xf -)");
179
180     # Bootstrap.
181     start_step ("Bootstrap (make -f Smake)");
182     run ("cd $builddir/pspp$build_number && make -f Smake");
183
184     # Configure.
185     start_step ("Configure source");
186     run ("cd $builddir/pspp$build_number && mkdir _build && cd _build && ../configure");
187
188     # Distribute.
189     start_step ("Make source tarball");
190     run ("cd $builddir/pspp$build_number/_build && make dist");
191     my $tarname = "pspp-$version-g$abbrev_commit.tar.gz";
192     $tarball = save_result ("$builddir/pspp$build_number/_build/$tarname", 1);
193
194     # Build the manual in various formats.
195     start_step ("Build manual");
196     run ("cd $builddir/pspp$build_number/doc && GENDOCS_TEMPLATE_DIR=$topdir/gnulib/doc $topdir/gnulib/build-aux/gendocs.sh --email bug-gnu-pspp\@gnu.org pspp \\\"GNU PSPP Manual\\\"");
197     save_result ("$builddir/pspp$build_number/doc/manual");
198 } else {
199     $tarball = $ARGV[0];
200 }
201
202 if ($build_binary) {
203     start_step ("Determining $tarball target directory");
204     my $sample_filename = `zcat $tarball | tar tf - | head -1`;
205     my ($tarball_dir) = $sample_filename =~ m%^(?:[./])*([^/]+)/%;
206     set_var ("dist_dir", $tarball_dir);
207
208     start_step ("Extracting $tarball into $builddir/$tarball_dir");
209     run ("zcat $tarball | (cd $builddir && tar xf -)");
210
211     start_step ("Extracting tar version");
212     my ($version) = `cd $builddir/$tarball_dir && ./configure --version | head -1`
213       =~ /configure (\S+)$/;
214     set_var ("dist_version", $version);
215     my ($binary_version) = "$version-$builder-build$build_number";
216     set_var ("binary_version", $binary_version);
217
218     start_step ("Configuring");
219     run ("chmod -R a-w $builddir/$tarball_dir");
220     run ("chmod u+w $builddir/$tarball_dir");
221     run ("mkdir $builddir/$tarball_dir/_build");
222     run ("chmod a-w $builddir/$tarball_dir");
223     my $ok = try_run ("cd $builddir/$tarball_dir/_build && ../configure --enable-relocatable --prefix=''");
224     for my $basename ("config.h", "config.log") {
225         save_result_if_exists ("$builddir/$tarball_dir/_build/$basename");
226     }
227     exit 1 if !$ok;
228
229     start_step ("Build");
230     run ("cd $builddir/$tarball_dir/_build && make");
231
232     start_step ("Install");
233     run ("cd $builddir/$tarball_dir/_build && make install DESTDIR=\$PWD/pspp-$binary_version");
234
235     start_step ("Make binary distribution");
236     run ("cd $builddir/$tarball_dir/_build && tar cfz pspp-$binary_version.tar.gz pspp-$binary_version");
237     save_result ("$builddir/$tarball_dir/_build/pspp-$binary_version.tar.gz", 1);
238
239     start_step ("Check");
240     $ok = try_run ("cd $builddir/$tarball_dir/_build && make check");
241     for my $basename ("tests/testsuite.log", "tests/testsuite.dir") {
242         save_result_if_exists ("$builddir/$tarball_dir/_build/$basename");
243     }
244     exit 1 if !$ok;
245
246     start_step ("Uninstall");
247     run ("cd $builddir/$tarball_dir/_build && make uninstall DESTDIR=\$PWD/pspp-$binary_version");
248
249     start_step ("Check uninstall");
250     run ("cd $builddir/$tarball_dir/_build && make distuninstallcheck distuninstallcheck_dir=\$PWD/pspp-$binary_version");
251
252     # distcleancheck
253 }
254
255 start_step ("Success");
256
257 sub usage {
258     print <<EOF;
259 $0, for building and testing PSPP
260 usage: $0 [OPTIONS] [TARBALL | REPO REFSPEC]
261 where TARBALL is the name of a tarball produced by "make dist"
262    or REPO and REFSPEC are a Git repo and refspec (e.g. branch) to clone.
263
264 Options:
265   --help            Print this usage message and exit
266 EOF
267     exit(0);
268 }
269
270 sub run {
271     exit 1 if !try_run(@_);
272 }
273
274 sub try_run {
275     my ($command) = @_;
276
277     print LOG "$command\n";
278
279     my $pid = open (COMMAND, '-|');
280     if (!defined ($pid)) {
281         die "fork failed: $!\n";
282     } elsif (!$pid) {
283         dup2 (1, 2);
284         exec ($command);
285         die "$command: exec failed: $!\n";
286     }
287
288     local ($|) = 1;
289     my $i = 0;
290     while (<COMMAND>) {
291         print LOG $_;
292         print "\r", $i++;
293     }
294     close (COMMAND);
295     print "\r      \r";
296
297     return 1 if !$?;
298
299     if ($? & 127) {
300         printf "%s: child died with signal %d, %s coredump\n",
301           $command, ($? & 127),  ($? & 128) ? 'with' : 'without';
302     } else {
303         printf "%s: child exited with value %d\n", $command, $? >> 8;
304     }
305     return 0;
306 }