3 # Copyright (C) 2011,2012,2013,2015,2018 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
30 my $MAXLOADPERCPU = 1.5;
40 if (scalar @ARGV && $ARGV[0] eq '--force') {
46 my $work = "/home/olly/tmp/xapian-git-snapshot";
47 # Add ccache's directory first so we'll use it if it is installed.
48 $ENV{PATH
} = "/usr/lib/ccache:/home/olly/install/bin:/usr/bin:/bin";
49 # Currently $repourl needs to be a filing system path.
50 my $repourl = '/home/xapian-git-write/xapian';
51 my $webbase = '/srv/www';
53 # Create the work directory first, since we need it to exist so we can create
55 mkpath
($work, 0, 0755);
56 chdir $work or die $!;
58 # Prevent multiple instances of this script from running at once.
59 # Use the same lockfile that fetch does.
60 open LOCK
, '>', 'flockme' or die "Couldn't open 'flockme' for writing: $!\n";
61 unless (flock LOCK
, LOCK_EX
|LOCK_NB
) {
62 # If we're building a tagged release, we want to wait rather than exit.
63 unless (scalar @ARGV && $ARGV[0] =~ m!^tags/! && flock LOCK
, LOCK_EX
) {
64 # Work directory already in use. Don't print anything unless STDOUT
65 # is a tty - the cron job will send it as mail, which we don't really
67 print STDERR
"'flockme' is already locked, can't build '$ARGV[0]' right now\n"
73 # Check the load average AFTER getting the lock, since we generate output if
74 # the load is too high, and it will probably be too high if we're already
76 my $HOSTNAME = Sys
::Hostname
::hostname
();
77 # Check the load average isn't too high.
79 if (-e
"/var/run/dobackup") {
80 print STDERR
"$HOSTNAME: Backup running (/var/run/dobackup exists)\n"
84 if (((`uptime 2>/dev/null`)[0] =~ /.*: (\d+(?:\.\d+)?),/) &&
85 ($1 > $MAXLOADPERCPU)) {
87 # `getconf _NPROCESSORS_ONLN` on linux gives e.g. 2
88 # `sysctl hw.ncpu` on openbsd (and prob. freebsd & darwin) gives e.g. hw.ncpu=2
89 # `psrinfo|grep -c on-line` on Solaris or OSF/1 gives e.g. 2
91 # Works on Linux, at least back to kernel 2.2.26.
92 $ncpu ||= run_command
("getconf _NPROCESSORS_ONLN 2>/dev/null|grep -v '[^0-9]'");
93 # Works on OpenBSD (and apparently FreeBSD and Darwin).
94 $ncpu ||= run_command
("sysctl hw.ncpu 2>/dev/null|sed 's/.*=//'");
95 # Works on Solaris and OSF/1.
96 $ncpu ||= run_command
("PATH=/usr/sbin:\$PATH psrinfo 2>/dev/null|grep -c on-line");
97 # Works on Linux, just in case the getconf version doesn't. Different
98 # architectures have different formats for /proc/cpuinfo so this won't
99 # work as widely as getconf _NPROCESSORS_ONLN will.
100 $ncpu ||= run_command
("grep -c processor /proc/cpuinfo 2>/dev/null");
102 if ($loadavg > $ncpu * $MAXLOADPERCPU) {
104 print STDERR
"$HOSTNAME: High load average: $loadavg ($ncpu CPUs)\n";
110 # If tags or branches are specified, default to branches for which there are
111 # existing directories.
112 if (scalar @ARGV == 0) {
114 my $ref = $File::Find
::name
;
115 open CMD
, "git -C \Q$repourl\E show --format=oneline --no-patch \Q$ref\E -- 2>/dev/null|" or die $!;
116 my $first_line = <CMD
>;
118 # Valid ref, so don't recurse into it.
119 $File::Find
::prune
= 1;
120 # Filter out tags - those generally don't change and we only build them
121 # when explicitly listed on the command line.
122 if (substr($first_line, 0, 4) ne 'tag ') {
126 find
(\
&find_git_refs
, glob('[A-Za-z0-9]*'));
129 # Or if there are no directories, default to git master.
130 if (scalar @ARGV == 0) {
136 foreach my $ref (@ARGV) {
137 chdir $work or die $!;
138 # Restrict to sane characters.
139 next if $ref !~ /^[-A-Za-z0-9_.\/]+$/;
141 print "*** No directory for '$ref'\n";
148 open CMD
, "git -C \Q$repourl\E show --format=oneline --no-patch \Q$ref\E -- 2>/dev/null|" or die $!;
149 my $first_line = <CMD
>;
151 print "*** Not a valid git ref: $ref\n";
156 $is_tag = (substr($first_line, 0, 4) eq 'tag ');
159 my $logfile = "$ref/snapshot.log";
161 # Check out into a 'xapian' subdirectory of the rev's directory.
162 my $co_dir = "$ref/xapian";
163 if (! -d
"$co_dir/.git") {
164 system "chmod", "-R", "+w", $co_dir if -d
$co_dir;
165 system "rm", "-rf", $co_dir;
166 open CMD
, "git clone --branch \Q$ref\E \Q$repourl\E \Q$co_dir\E 2>&1|" or die $!;
167 $log = join '', <CMD
>;
168 close CMD
or do { print $log; die $!; };
169 chdir $co_dir or die $!;
171 # Revert any local changes.
172 chdir $co_dir or die $!;
173 $log = "git reset --hard:\n".`git reset --hard 2>&1`."git pull --ff-only:\n";
174 open CMD
, "git pull --ff-only 2>&1|" or die $!;
178 if ($changed && !$force) {
179 if (/^Already up-to-date/) {
186 $ENV{'VERBOSE'} and print "No changes\n";
190 my ($revision) = `git describe --always`;
192 my ($revcount) = ($revision =~ /-([0-9]+)-/);
193 if (!defined $revcount) {
194 # Workaround for branches from before the first git tag - count commits since 1.3.1.
195 $revcount = scalar(@
{[`git log --format='%H' 1daf0071342d883ca308762f269a63f6ec5df981..`]});
196 $revision = "v1.3.1-$revcount-g$revision";
199 chdir $work or die $!;
201 # Don't repeat a build for the same revision.
202 next if -f
"$logfile.$revision";
204 open LOG
, ">", "$logfile.$revision" or die $!;
205 # Flush output after every print.
206 my $old_fh = select(LOG
);
214 # Modify configure.ac files to insert $revision into version string.
215 foreach my $configure_ac
216 (glob("\Q$co_dir\E/xapian*/configure.ac"),
217 glob("\Q$co_dir\E/xapian*/*/configure.ac")) {
218 open OUT
, ">", "tmp.out" or die $!;
219 open IN
, "<", $configure_ac or die $!;
221 s/(^(?:AC_INIT\([^,]*|m4_define\(\[project_version\]),.*?[0-9])(\s*[),\]])/$1_git$revcount$2/g;
226 rename "tmp.out", $configure_ac or die $!;
228 if (-f
"$co_dir/search-xapian/Makefile.PL") {
230 my $fnm = "$co_dir/search-xapian/Xapian.pm";
231 open OUT
, ">", "tmp.out" or die $!;
232 open IN
, "<", $fnm or die $!;
234 if (s/^(our \$VERSION = ')(\d+\.\d+\.\d+\.)\d+(.*)/$1$2$revcount$3 # git snapshot/) {
235 $snap_version = $2 . $revcount;
241 rename "tmp.out", $fnm or die $!;
243 $fnm = "$co_dir/search-xapian/README";
244 open OUT
, ">", "tmp.out" or die $!;
245 open IN
, "<", $fnm or die $!;
247 s/(\d+\.\d+\.\d+\.)\d+/$1$revcount (git snapshot)/;
254 rename "tmp.out", $fnm or die $!;
256 $fnm = "$co_dir/search-xapian/Changes";
257 open OUT
, ">", "tmp.out" or die $!;
258 open IN
, "<", $fnm or die $!;
263 print OUT
$snap_version.strftime
(" %a %b %e %H:%M:%S %Z %Y\n",gmtime());
264 print OUT
"\t- git snapshot of revision $revision.\n\n";
270 rename "tmp.out", $fnm or die $!;
274 system "chmod", "-R", "+w", "$ref/build" if -d
"$ref/build";
275 system "rm", "-rf", "$ref/build";
276 mkpath
("$ref/build", 0, 0755) or die $!;
277 chdir "$ref/build" or die $!;
279 # Note the current time so we can find sources which weren't used during
280 # the build. Sleep for a couple of seconds first to avoid needing to
281 # worry about timestamps equal to $timestamp.
284 open F
, '<', '../xapian/bootstrap' or die $!;
286 my $timestamp = (stat F
)[8];
289 $log = `../xapian/bootstrap 2>&1`;
292 print "*** bootstrap failed for '$ref':";
299 # On the old server, javac kept going into memory eating loops, so we
300 # skipped the java bindings. Reenable for now...
301 #$log = `../xapian/configure --enable-quiet --enable-maintainer-mode --without-java 2>&1`;
302 $log = `../xapian/configure --enable-quiet --enable-maintainer-mode 2>&1`;
305 print "*** configure failed for '$ref':";
312 $log = `make -s 2>&1`;
315 print "*** make failed for '$ref':";
322 my %unused_files = ();
323 sub check_unused_files_from_build
{
324 return if $File::Find
::name
eq '../xapian';
325 my $f = substr($File::Find
::name
, length('../xapian/'));
326 if ($_ eq 'autom4te.cache' ||
328 $f eq 'search-xapian/blib' ||
330 $f eq 'xapian-applications/omega/common' ||
331 $f eq 'xapian-data' || # FIXME: make check should use this
332 $f eq 'xapian-maintainer-tools' ||
336 /^Search-Xapian-\d+\.\d+\.\d+\.\d+$/) {
337 if (-d
$File::Find
::name
) {
338 # Don't descend into these subdirectories.
339 $File::Find
::prune
= 1;
343 return unless -f
$File::Find
::name
and (stat _
)[8] < $timestamp;
344 return if $_ eq '.gitignore';
345 return if $_ eq 'config.h.in~';
346 return if $_ eq 'NEWS.SKELETON';
347 return if $f eq 'README';
348 return if $f eq 'Vagrantfile';
349 return if $f eq '.appveyor.yml';
350 return if $f eq '.travis.yml';
351 return if $f eq '.clang-format';
352 return if /^Search-Xapian-\d+\.\d+\.\d+\.\d+\.tar\..z$/;
354 print "Unused during make: $f\n";
356 find
(\
&check_unused_files_from_build
, '../xapian');
358 my $lib = (<xapian
-core
/.libs/libxapian
*.so
>)[0];
359 my $unstripped_so = -s
$lib;
360 $log = `strip \Q$lib\E`;
363 my $stripped_so = -s
$lib;
365 open SIZELOG
, ">>/home/olly/xapian-autobuild-stats.log";
366 print SIZELOG
"$ref\trev=$revision\tunstripped_so=$unstripped_so\tstripped_so=$stripped_so\n";
369 # Be more verbose about test failures.
372 # Need to set LD_LIBRARY_PATH so that e.g. omega run from omegatest finds
373 # a suitable "libxapian.so".
374 $log = `env LD_LIBRARY_PATH="\`pwd\
`"/xapian-core/.libs make -s distcheck VALGRIND= 2>&1`;
377 print "*** make distcheck failed for '$ref':";
384 # This finds files we don't ship or use to get to what we ship:
385 find
(\
&check_unused_files_from_build
, '../xapian');
387 my $d = "$webbase/oligarchy.co.uk/xapian/$ref";
388 if ($is_tag && $ref =~ m!^v?([\d.]+)$!) {
389 $d = "$webbase/oligarchy.co.uk/xapian/$1";
392 mkpath
($d, 0, 0755) or die $!;
393 open HTACCESS
, ">", "$d/.htaccess" or die $!;
394 print HTACCESS
"IndexOptions NameWidth=*\n";
395 close HTACCESS
or die $!;
398 # Delete snapshots more than a week old, but leave at least one.
399 my $num_of_days_to_keep = 7;
400 my @o = glob "$d/old/*.tar.?z";
402 @o = grep {-M
$_ > $num_of_days_to_keep} @o;
406 mkdir "$d/old", 0755 or die $!;
407 open HTACCESS
, ">", "$d/old/.htaccess" or die $!;
408 print HTACCESS
"IndexOptions NameWidth=*\n";
409 close HTACCESS
or die $!;
411 for (glob "$d/*.tar.?z") {
412 my ($leaf) = m!([^/]*)$!;
413 rename $_, "$d/old/$leaf";
416 for (glob("*/*.tar.?z"), glob("xapian-applications/*/*.tar.?z"), glob("*.tar.?z")) {
417 print LOG
"Moving '$_' to '$d'\n";
418 system("mv", $_, $d);
420 print LOG
"Failed with exit code $?\n";
425 for (glob("search-xapian/*.tar.?z")) {
426 print LOG2
"Moving '$_' to '$d'\n";
427 system("mv", $_, $d);
429 print LOG2
"Failed with exit code $?\n";
435 system("rm -rf xapian/search-xapian/Search-Xapian-*");
437 # Expire logs more than 10 days old
438 unlink grep {-M
$_ > 10} glob 'snapshot.log.*';
441 system("/home/olly/bin/plot-sizes");