[ci] Move 32-bit MSVC 2019 and 2022 builds to GHA
[xapian.git] / xapian-maintainer-tools / make-xapian-git-snapshot-tarballs
blob7cdb6366389aa18c4ca85a18ac592d7cb2492bf5
1 #!/usr/bin/perl -w
3 # Copyright (C) 2011,2012,2013,2015,2018,2019 Olly Betts
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to
7 # deal in the Software without restriction, including without limitation the
8 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 # sell copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 # IN THE SOFTWARE.
23 use strict;
24 use Fcntl ':flock';
25 use File::Find;
26 use File::Path;
27 use Sys::Hostname;
28 use POSIX;
30 my $MAXLOADPERCPU = 1.5;
32 my $verbose = exists $ENV{VERBOSE};
34 sub run_command {
35 my $cmd = shift;
36 my $out = `$cmd`;
37 chomp $out;
38 return $out;
41 my $force = 0;
42 if (scalar @ARGV && $ARGV[0] eq '--force') {
43 shift @ARGV;
44 $force = 1;
47 # Configuration:
48 my $work = "/home/olly/tmp/xapian-git-snapshot";
49 # Add ccache's directory first so we'll use it if it is installed.
50 $ENV{PATH} = "/usr/lib/ccache:/home/olly/install/bin:/usr/bin:/bin";
51 # Currently $repourl needs to be a filing system path.
52 my $repourl = '/home/xapian-git-write/xapian';
53 my $webbase = '/srv/www';
55 # Create the work directory first, since we need it to exist so we can create
56 # the lockfile.
57 mkpath($work, 0, 0755);
58 chdir $work or die $!;
60 # Prevent multiple instances of this script from running at once.
61 # Use the same lockfile that fetch does.
62 open LOCK, '>', 'flockme' or die "Couldn't open 'flockme' for writing: $!\n";
63 unless (flock LOCK, LOCK_EX|LOCK_NB) {
64 # If we're building a tagged release, we want to wait rather than exit.
65 unless (scalar @ARGV && $ARGV[0] =~ m!^tags/! && flock LOCK, LOCK_EX) {
66 # Work directory already in use. Don't print anything unless STDERR
67 # is a tty - the cron job will send it as mail, which we don't really
68 # want.
69 print STDERR "'flockme' is already locked, can't build '$ARGV[0]' right now\n"
70 if -t STDERR;
71 exit 1;
75 # Check the load average AFTER getting the lock, since we generate output if
76 # the load is too high, and it will probably be too high if we're already
77 # running.
78 my $HOSTNAME = Sys::Hostname::hostname();
79 # Check the load average isn't too high.
80 if (!$force) {
81 if (-e "/var/run/dobackup") {
82 print STDERR "$HOSTNAME: Backup running (/var/run/dobackup exists)\n"
83 if -t STDERR;
84 exit(1);
86 if (((`uptime 2>/dev/null`)[0] =~ /.*: (\d+(?:\.\d+)?),/) &&
87 ($1 > $MAXLOADPERCPU)) {
88 my $loadavg = $1;
89 # `getconf _NPROCESSORS_ONLN` on linux gives e.g. 2
90 # `sysctl hw.ncpu` on openbsd (and prob. freebsd & darwin) gives e.g. hw.ncpu=2
91 # `psrinfo|grep -c on-line` on Solaris or OSF/1 gives e.g. 2
92 my $ncpu;
93 # Works on Linux, at least back to kernel 2.2.26.
94 $ncpu ||= run_command("getconf _NPROCESSORS_ONLN 2>/dev/null|grep -v '[^0-9]'");
95 # Works on OpenBSD (and apparently FreeBSD and Darwin).
96 $ncpu ||= run_command("sysctl hw.ncpu 2>/dev/null|sed 's/.*=//'");
97 # Works on Solaris and OSF/1.
98 $ncpu ||= run_command("PATH=/usr/sbin:\$PATH psrinfo 2>/dev/null|grep -c on-line");
99 # Works on Linux, just in case the getconf version doesn't. Different
100 # architectures have different formats for /proc/cpuinfo so this won't
101 # work as widely as getconf _NPROCESSORS_ONLN will.
102 $ncpu ||= run_command("grep -c processor /proc/cpuinfo 2>/dev/null");
103 $ncpu ||= 1;
104 if ($loadavg > $ncpu * $MAXLOADPERCPU) {
105 $ncpu ||= "unknown";
106 print STDERR "$HOSTNAME: High load average: $loadavg ($ncpu CPUs)\n";
107 exit(1);
112 # If tags or branches are specified, default to branches for which there are
113 # existing directories.
114 if (scalar @ARGV == 0) {
115 sub find_git_refs {
116 my $ref = $File::Find::name;
117 open CMD, "git -C \Q$repourl\E show --format=oneline --no-patch \Q$ref\E -- 2>/dev/null|" or die $!;
118 my $first_line = <CMD>;
119 close CMD or return;
120 # Valid ref, so don't recurse into it.
121 $File::Find::prune = 1;
122 # Filter out tags - those generally don't change and we only build them
123 # when explicitly listed on the command line.
124 if (substr($first_line, 0, 4) ne 'tag ') {
125 push @ARGV, $ref;
128 find(\&find_git_refs, glob('[A-Za-z0-9]*'));
131 # Or if there are no directories, default to git master.
132 if (scalar @ARGV == 0) {
133 mkdir 'master';
134 @ARGV = 'master';
137 my $status = 0;
138 foreach my $ref (@ARGV) {
139 chdir $work or die $!;
140 # Restrict to sane characters.
141 next if $ref !~ /^[-A-Za-z0-9_.\/]+$/;
142 if (! -d $ref) {
143 print "*** No directory for '$ref'\n";
144 $status = 1;
145 next;
148 my $is_tag;
150 open CMD, "git -C \Q$repourl\E show --format=oneline --no-patch \Q$ref\E -- 2>/dev/null|" or die $!;
151 my $first_line = <CMD>;
152 if (!close CMD) {
153 print "*** Not a valid git ref: $ref\n";
154 $status = 1;
155 next;
158 $is_tag = (substr($first_line, 0, 4) eq 'tag ');
161 my $logfile = "$ref/snapshot.log";
162 my $log = '';
163 # Check out into a 'xapian' subdirectory of the rev's directory.
164 my $co_dir = "$ref/xapian";
165 if (! -d "$co_dir/.git") {
166 system "chmod", "-R", "+w", $co_dir if -d $co_dir;
167 system "rm", "-rf", $co_dir;
168 open CMD, "git clone --branch \Q$ref\E \Q$repourl\E \Q$co_dir\E 2>&1|" or die $!;
169 $log = join '', <CMD>;
170 close CMD or do { print $log; die $!; };
171 chdir $co_dir or die $!;
172 } else {
173 # Revert any local changes.
174 chdir $co_dir or die $!;
175 $log = "git reset --hard:\n".`git reset --hard 2>&1`."git pull --ff-only:\n";
176 open CMD, "git pull --ff-only 2>&1|" or die $!;
177 my $changed = 1;
178 while (<CMD>) {
179 $log .= $_;
180 if ($changed && !$force) {
181 if (/^Already up-to-date/) {
182 $changed = 0;
186 close CMD or die $!;
187 if (!$changed) {
188 $verbose and print "No changes\n";
189 next;
192 my ($revision) = `git describe --always`;
193 chomp($revision);
194 my ($revcount) = ($revision =~ /-([0-9]+)-/);
195 if (!defined $revcount) {
196 # Workaround for branches from before the first git tag - count commits since 1.3.1.
197 $revcount = scalar(@{[`git log --format='%H' 1daf0071342d883ca308762f269a63f6ec5df981..`]});
198 $revision = "v1.3.1-$revcount-g$revision";
201 chdir $work or die $!;
203 # Don't repeat a build for the same revision.
204 next if -f "$logfile.$revision";
206 open LOG, ">", "$logfile.$revision" or die $!;
207 # Flush output after every print.
208 my $old_fh = select(LOG);
209 $| = 1;
210 select($old_fh);
212 print LOG $log;
213 $log = undef;
215 if (!$is_tag) {
216 # Modify configure.ac files to insert $revision into version string.
217 foreach my $configure_ac
218 (glob("\Q$co_dir\E/xapian*/configure.ac"),
219 glob("\Q$co_dir\E/xapian*/*/configure.ac")) {
220 open OUT, ">", "tmp.out" or die $!;
221 open IN, "<", $configure_ac or die $!;
222 while (<IN>) {
223 s/(^(?:AC_INIT\([^,]*|m4_define\(\[project_version\]),.*?[0-9])(\s*[),\]])/$1_git$revcount$2/g;
224 print OUT;
226 close IN or die $!;
227 close OUT or die $!;
228 rename "tmp.out", $configure_ac or die $!;
230 if (-f "$co_dir/search-xapian/Makefile.PL") {
231 my $snap_version;
232 my $fnm = "$co_dir/search-xapian/Xapian.pm";
233 open OUT, ">", "tmp.out" or die $!;
234 open IN, "<", $fnm or die $!;
235 while (<IN>) {
236 if (s/^(our \$VERSION = ')(\d+\.\d+\.\d+\.)\d+(.*)/$1$2$revcount$3 # git snapshot/) {
237 $snap_version = $2 . $revcount;
239 print OUT;
241 close IN or die $!;
242 close OUT or die $!;
243 rename "tmp.out", $fnm or die $!;
245 $fnm = "$co_dir/search-xapian/README";
246 open OUT, ">", "tmp.out" or die $!;
247 open IN, "<", $fnm or die $!;
248 $_ = <IN>;
249 s/(\d+\.\d+\.\d+\.)\d+/$1$revcount (git snapshot)/;
250 print OUT;
251 while (<IN>) {
252 print OUT;
254 close IN or die $!;
255 close OUT or die $!;
256 rename "tmp.out", $fnm or die $!;
258 $fnm = "$co_dir/search-xapian/Changes";
259 open OUT, ">", "tmp.out" or die $!;
260 open IN, "<", $fnm or die $!;
261 while (<IN>) {
262 print OUT;
263 last if /^\s*$/;
265 print OUT $snap_version.strftime(" %a %b %e %H:%M:%S %Z %Y\n",gmtime());
266 print OUT "\t- git snapshot of revision $revision.\n\n";
267 while (<IN>) {
268 print OUT;
270 close IN or die $!;
271 close OUT or die $!;
272 rename "tmp.out", $fnm or die $!;
276 system "chmod", "-R", "+w", "$ref/build" if -d "$ref/build";
277 system "rm", "-rf", "$ref/build";
278 mkpath("$ref/build", 0, 0755) or die $!;
279 chdir "$ref/build" or die $!;
281 # Note the current time so we can find sources which weren't used during
282 # the build. Sleep for a couple of seconds first to avoid needing to
283 # worry about timestamps equal to $timestamp.
284 sleep 2;
286 # Skip xapian-letor for now, as atreus lacks libsvm-dev, or the ability
287 # to install a new enough one from a package.
288 open TOUCH, '>>', '../xapian/xapian-letor/.nobootstrap' and close TOUCH;
290 open F, '<', '../xapian/bootstrap' or die $!;
291 <F>;
292 my $timestamp = (stat F)[8];
293 close F;
295 $log = `../xapian/bootstrap 2>&1`;
296 print LOG $log;
297 if ($?) {
298 print "*** bootstrap failed for '$ref':";
299 print $log;
300 $status = 1;
301 next;
303 $log = undef;
305 # On the old server, javac kept going into memory eating loops, so we
306 # skipped the java bindings. Reenable for now...
307 #$log = `../xapian/configure --enable-quiet --enable-maintainer-mode --without-java 2>&1`;
308 $log = `../xapian/configure --enable-quiet --enable-maintainer-mode 2>&1`;
309 print LOG $log;
310 if ($?) {
311 print "*** configure failed for '$ref':";
312 print $log;
313 $status = 1;
314 next;
316 $log = undef;
318 $log = `make -s 2>&1`;
319 print LOG $log;
320 if ($?) {
321 print "*** make failed for '$ref':";
322 print $log;
323 $status = 1;
324 next;
326 $log = undef;
328 my %unused_files = ();
329 sub check_unused_files_from_build {
330 return if $File::Find::name eq '../xapian';
331 my $f = substr($File::Find::name, length('../xapian/'));
332 if ($_ eq 'autom4te.cache' ||
333 $_ eq 'debian' ||
334 $f eq 'search-xapian/blib' ||
335 $f eq 'swig' ||
336 $f eq 'xapian-applications/omega/common' ||
337 $f eq 'xapian-data' || # FIXME: make check should use this
338 $f eq 'xapian-maintainer-tools' ||
339 $f eq 'BUILD' ||
340 $f eq 'INST' ||
341 $f eq '.git' ||
342 /^Search-Xapian-\d+\.\d+\.\d+\.\d+$/) {
343 if (-d $File::Find::name) {
344 # Don't descend into these subdirectories.
345 $File::Find::prune = 1;
346 return;
349 return unless -f $File::Find::name and (stat _)[8] < $timestamp;
350 return if $_ eq '.gitignore';
351 return if $_ eq 'config.h.in~';
352 return if $_ eq 'NEWS.SKELETON';
353 return if $f eq 'README';
354 return if $f eq 'Vagrantfile';
355 return if $f eq '.appveyor.yml';
356 return if $f eq '.github/workflows/ci.yml';
357 return if /^Search-Xapian-\d+\.\d+\.\d+\.\d+\.tar\..z$/;
358 ++$unused_files{$f};
359 print "Unused during make: $f\n";
361 find(\&check_unused_files_from_build, '../xapian');
363 my $lib = (<xapian-core/.libs/libxapian*.so>)[0];
364 my $unstripped_so = -s $lib;
365 $log = `strip \Q$lib\E`;
366 print LOG $log;
367 $log = undef;
368 my $stripped_so = -s $lib;
370 open SIZELOG, ">>/home/olly/xapian-autobuild-stats.log";
371 print SIZELOG "$ref\trev=$revision\tunstripped_so=$unstripped_so\tstripped_so=$stripped_so\n";
372 close SIZELOG;
374 # Be more verbose about test failures.
375 $ENV{VERBOSE} = 1;
377 # Skip timed tests which can be flaky under variable load.
378 $ENV{AUTOMATED_TESTING} = 1;
380 # Need to set LD_LIBRARY_PATH so that e.g. omega run from omegatest finds
381 # a suitable "libxapian.so".
382 $log = `env LD_LIBRARY_PATH="\`pwd\`"/xapian-core/.libs make -s distcheck VALGRIND= 2>&1`;
383 print LOG $log;
384 if ($?) {
385 print "*** make distcheck failed for '$ref':";
386 print $log;
387 $status = 1;
388 next;
390 $log = undef;
392 # This finds files we don't ship or use to get to what we ship:
393 find(\&check_unused_files_from_build, '../xapian');
395 my $d = "$webbase/oligarchy.co.uk/xapian/$ref";
396 if ($is_tag && $ref =~ m!^v?([\d.]+)$!) {
397 $d = "$webbase/oligarchy.co.uk/xapian/$1";
399 if (! -d $d) {
400 mkpath($d, 0, 0755) or die $!;
401 open HTACCESS, ">", "$d/.htaccess" or die $!;
402 print HTACCESS "IndexOptions NameWidth=*\n";
403 close HTACCESS or die $!;
404 } else {
405 if (-d "$d/old") {
406 # Delete snapshots more than a week old, but leave at least one.
407 my $num_of_days_to_keep = 7;
408 my @o = glob "$d/old/*.tar.?z";
409 my $n = scalar @o;
410 @o = grep {-M $_ > $num_of_days_to_keep} @o;
411 $n -= scalar @o;
412 unlink @o if $n > 0;
413 } else {
414 mkdir "$d/old", 0755 or die $!;
415 open HTACCESS, ">", "$d/old/.htaccess" or die $!;
416 print HTACCESS "IndexOptions NameWidth=*\n";
417 close HTACCESS or die $!;
419 for (glob "$d/*.tar.?z") {
420 my ($leaf) = m!([^/]*)$!;
421 rename $_, "$d/old/$leaf";
424 for (glob("*/*.tar.?z"), glob("xapian-applications/*/*.tar.?z"), glob("*.tar.?z")) {
425 print LOG "Moving '$_' to '$d'\n";
426 system("mv", $_, $d);
427 if ($?) {
428 print LOG "Failed with exit code $?\n";
429 } else {
430 print LOG "OK\n";
433 for (glob("search-xapian/*.tar.?z")) {
434 print LOG2 "Moving '$_' to '$d'\n";
435 system("mv", $_, $d);
436 if ($?) {
437 print LOG2 "Failed with exit code $?\n";
438 } else {
439 print LOG2 "OK\n";
442 chdir("..");
443 system("rm -rf xapian/search-xapian/Search-Xapian-*");
444 close LOG;
445 # Expire logs more than 10 days old
446 unlink grep {-M $_ > 10} glob 'snapshot.log.*';
449 system("/home/olly/bin/plot-sizes");
450 exit($status);