1 #! /usr/bin/env python3
9 from datetime import datetime
10 from pathlib import Path
13 opts, args = getopt.gnu_getopt(sys.argv[1:], "ho:",
15 "binary", "no-binary",
18 "builder=", "build-number="])
19 except getopt.GetoptError as err:
20 # print help information and exit:
21 print(err) # will print something like "option -a not recognized"
30 if o in ("-h", "--help"):
35 elif o == "--no-binary":
39 elif o == "--no-batch":
41 elif o in ("-o", "--output"):
43 elif o == "--builder":
45 elif o == "--build-number":
48 assert False, "unhandled option"
50 builder = socket.gethostname()
52 if len(args) not in (1, 2):
53 sys.stderr.write("%s: exactly one or two nonoption arguments are required\n"
58 tarball = os.path.abspath(args[0])
60 pass # Tarball will be generated later.
62 # Select build number.
63 if build_number is None:
64 build_number = datetime.now().strftime("%Y%m%d%H%M%S")
66 topdir = os.path.dirname(sys.argv[0])
67 topdir = os.getcwd() + "/topdir" if not topdir.startswith("/")
69 # Create build directory.
71 builddir = "builds/%s" % $build_number
72 if not Path('builds').is_dir():
75 builddir = os.path.abspath(builddir)
76 if not Path(builddir).is_dir():
80 resultsdir = "%s/results" % builddir
82 os.mkdir("%s/vars" % resultsdir)
84 varsfile = "%s/VARS" % resultsdir
85 VARS = open(varsfile, "wt", buffering=1)
87 logfile = "%s/LOG" % resultsdir
88 LOG = open(logfile, "wt", buffering=1)
90 set_var("builder", builder)
91 set_var("build_number", build_number)
93 GIT = "git --git-dir=%s/.git" % topdir
96 LOG.write("
\f\n$msg\n")
101 def set_var(var, value):
102 VARS.write("$var=$value\n")
104 print("\t$var=$value")
106 VAR = open("%s/vars/%s/" % (resultsdir, var), "wt")
107 VAR.write("%s\n" % value)
111 def saved_result(name, product):
112 start_step("Saving %s: %s" % (name, product))
114 def save_result(name, src, rm_src = False):
115 basename = os.path.basename(src)
116 dst = "%s/%s" % (resultsdir, basename)
118 saved_result(name, basename)
119 run("cp -R %s %s" % (src, dst))
126 def save_result_if_exists(name, src, rm_src = False):
127 if Path(src).exists():
128 save_result(name, src, rm_src)
130 start_step("%s does not exist, cannot save" % src)
132 def ref_to_commit(ref, repo = '.'):
133 commit = subprocess.check_output("cd %s && %s rev-parse %s"
135 return commit.rstrip()
137 #ssw = "https://alpha.gnu.org/gnu/ssw/spread-sheet-widget-0.4.tar.gz"
138 ssw = "https://git.savannah.gnu.org/git/ssw.git master"
142 if ssw.endswith('.tar.gz'):
143 ssw_file = os.path.basename(ssw)
144 start_step("Retrieve spread-sheet-widget tarball %s" % ssw_file)
148 start_step("Extract %s into %s" % (ssw_file, ssw_dir))
149 run("tar xzf %s" % ssw_file)
151 ssw_url, ssw_ref = ssw.split()
154 start_step("Clone spread-sheet-widget into %s" % ssw_dir)
155 run("git clone %s %s" % (ssw_url, ssw_dir))
157 start_step("Check out %s in ssw" % ssw_ref)
158 run("cd %s && git checkout %s" % (ssw_dir, ssw_ref))
159 set_var("ssw_commit", ref_to_commit("HEAD", "ssw"))
161 start_step("Bootstrap ssw")
162 run("cd %s && ./bootstrap" % ssw_dir)
166 start_step("Configure spread-sheet-widget")
167 run("cd %s && ./configure --prefix=''" % ssw_dir)
169 start_step("Build spread-sheet-widget")
170 run("cd %s && make -j10" % ssw_dir)
172 start_step("Install spread-sheet-widget")
173 run("cd %s && make -j10 install DESTDIR=$PWD/inst" % ssw_dir)
175 start_step("Fetch branch from Git")
176 set_var("git_repo", repo)
177 set_var("git_branch", branch)
178 run("%s fetch %s +%s:refs/builds/%s/pspp"
179 % (GIT, repo, branch, build_number))
181 # Get revision number.
182 set_var("pspp_ref", "refs/builds/%s/pspp" % build_number)
183 my revision = ref_to_commit("refs/builds/%s/pspp" % build_number)
184 set_var("pspp_commit", revision)
185 my abbrev_commit = revision[:6]
188 start_step("Extract branch into source directory")
189 run("%s archive --format=tar --prefix=pspp/ refs/builds/%s/pspp | tar xf -"
190 % (GIT, build_number))
192 # Extract version number.
193 start_step("Extract repository version number")
194 trace = subprocess.checkoutput("cd pspp && autoconf -t AC_INIT").rstrip()
195 fields = trace.split(':')
196 file, line, macro, package, repo_version = fields[:5]
198 set_var("repo_version", repo_version)
200 # Is this a "gnits" mode tree?
201 start_step("Checking Automake mode")
204 for s in open("pspp/Makefile.am"):
208 LOG.write("Automake mode is %s\n" % am_mode)
210 # Generate version number for build.
211 # We want to append -g012345, but if we're in Gnits mode and the
212 # version number already has a hyphen, we have to omit it.
213 start_step("Generate build version number")
214 version = repo_version
215 version += '-' if '-' not in version
216 version += 'g' + abbrev_commit
217 set_var("version", version)
219 # Append -g012345 to configure.ac version number.
220 start_step("Updating version number in %s" % file)
221 fullname = "pspp/%s" % file
222 NEWFILE = open("%s.new" % fullname, "w")
224 for s in open(fullname):
229 NEWFILE.write("AC_INIT([%s], [%s]" % (package, version))
231 NEWFILE.write(", [%s]" % field)
234 os.rename("%s.new" % fullname, fullname)
236 # Get Gnulib commit number.
237 start_step("Reading README.Git to find Gnulib commit number")
238 fullname = "pspp/README.Git"
240 for s in open(fullname):
241 m = re.match('\s+commit ([0-9a-fA-F]{8,})', s)
243 gnulib_commit = m.group(1)
245 if gnulib_commit is None:
246 sys.stderr.write("%s does not specify a Git commit number\n"
249 set_var("gnulib_commit", gnulib_commit)
251 # Add note to beginning of NEWS (otherwise "make dist" fails).
252 start_step("Updating NEWS")
253 fullname = "pspp/NEWS"
254 NEWFILE = open("%s.new" % fullname, "w")
255 found_changes = False
256 for s in open(fullname):
257 if not found_changes and s.startswith('Changes'):
260 Changes from %(repo_version)s to %(version)s:
262 * Built from PSPP commit %(revision)s
263 in branch %(branch)s on builder %(builder)s.
265 * Built from Gnulib commit %(gnulib_commit)s.
267 """ % {'repo_version': repo_version,
269 'revision': revision,
272 'gnulib_commit': gnulib_commit})
276 os.rename("%s.new" % fullname, $fullname)
278 # If we don't already have that Gnulib commit, update Gnulib.
279 rc = os.system("%s rev-parse --verify --quiet $gnulib_commit^0 > /dev/null"
282 start_step("Updating Gnulib to obtain commit")
283 run("%s fetch gnulib" % GIT)
284 run("%s update-ref refs/builds/%s/gnulib %s"
285 % (GIT, build_number, gnulib_commit))
286 set_var("gnulib_ref", "refs/builds/%s/gnulib" % build_number)
288 # Extract gnulib source.
289 start_step("Extract Gnulib source")
290 run("%s archive --format=tar --prefix=gnulib/ %s | tar xf -"
291 % (GIT, gnulib_commit))
294 start_step("Bootstrap (make -f Smake)")
295 run("cd pspp && make -f Smake -j10", "bootstrap")
298 start_step("Configure source")
302 "../configure PKG_CONFIG_PATH=$PWD/../../../source/%s/inst/lib/pkgconfig" % ssw_dir, "configure")
305 start_step("Make source tarball")
306 run("cd pspp/_build && make dist", "dist")
307 tarname = "pspp-%s.tar.gz" % version
308 tarball = save_result("source distribution", "pspp/_build/%s" % tarname, 1)
310 # Save translation templates.
311 potfile = "pspp/_build/po/pspp.pot"
312 if not Path(potfile).exists():
313 potfile = "pspp/po/pspp.pot"
314 save_result("translation templates", potfile)
317 start_step("Build user manual")
318 run("cd pspp && GENDOCS_TEMPLATE_DIR=%s %s/gendocs.sh -s doc/pspp.texi -I doc -o %s/user-manual --email bug-gnu-pspp\@gnu.org pspp \"GNU PSPP User Manual\""
319 % (topdir, topdir, resultsdir),
321 saved_result("User Manual", "user-manual")
323 # Build developer's guide
324 start_step("Build developers guide")
325 run("cd pspp && GENDOCS_TEMPLATE_DIR=%s %s/gendocs.sh -s doc/pspp-dev.texi -I doc -o %s/dev-guide --email bug-gnu-pspp\@gnu.org pspp-dev \"GNU PSPP Developers Guide\""
326 % (topdir, topdir, resultsdir), "dev-guide")
327 saved_result("Developers Guide", "dev-guide")
329 start_step("Starting from $tarball")
332 start_step("Save tarball to Git")
333 run("GIT_DIR=%s/.git %s/git-import-tar %s refs/builds/%s/dist"
334 % (topdir, topdir, tarball, build_number), "git-dist")
335 set_var("dist_ref", "refs/builds/%s/dist" % build_number)
336 set_var("dist_commit", ref_to_commit("refs/builds/%s/dist" % build_number))
338 start_step("Determining %s target directory" % tarball)
339 sample_filename = subprocess.check_output("zcat %s | tar tf - | head -1"
341 tarball_dir = re.match('(?:[./])*([^/]+)/', sample_filename).group(1)
342 set_var("dist_dir", tarball_dir)
344 start_step("Extracting source tarball")
345 run("zcat %s | (cd %s && tar xf -)" % (tarball, builddir))
347 start_step("Extracting tar version")
348 version_line = subprocess.check_output("cd %s/%s && "
349 "./configure --version | head -1"
350 % (builddir, tarball_dir)).rstrip()
351 version = re.search('configure (\S+)$', version_line).group(1)
352 set_var("dist_version", version)
353 binary_version = "%s-%s-build%s" % (version, builder, build_number)
354 set_var("binary_version", binary_version)
356 start_step("Configuring")
357 run("chmod -R a-w %s/%s" % (builddir, tarball_dir))
358 run("chmod u+w %s/%s" % (builddir, tarball_dir))
359 run("chmod -R u+w %s/%s/perl-module" % (builddir, tarball_dir))
360 run("mkdir %s/%s/_build" % (builddir, tarball_dir))
361 run("chmod a-w %s/%s" % (builddir, tarball_dir))
363 "cd %(builddir)s/%(tarball_dir)s/_build && ../configure "
364 "--with-perl-module --enable-relocatable --prefix='' "
365 "PKG_CONFIG_PATH=$PWD/../../../source/%(ssw_dir)s/inst/lib/pkgconfig "
366 "CPPFLAGS=\"-I$PWD/../../../source/%(ssw_dir)s/inst/include\" "
367 "LDFLAGS=\"-L$PWD/../../../source/%(ssw_dir)s/inst/lib\""
368 % {"builddir": builddir,
369 "tarball_dir": tarball_dir,
372 for basename in ("config.h", "config.log"):
373 save_result_if_exists("build configuration",
374 "%s/%s/_build/%s" % (builddir, tarball_dir,
380 run("cd %s/%s/_build && make -j10" % (builddir, tarball_dir), "build")
381 run("cd %s/%s/_build/perl-module && perl Makefile.PL && make -j10"
382 % (builddir, tarball_dir), "build Perl module")
384 start_step("Install")
385 run("cd %s/%s/_build && make install DESTDIR=$PWD/pspp-%s"
386 % (builddir, tarball_dir, binary_version), "install")
387 run("cd ../source/%s && make -j10 install DESTDIR=%s/%s/_build/pspp-%s"
388 % (ssw_dir, binary_version))
389 run("cd %s/%s/_build/perl-module && "
390 "make install DESTDIR=%s/%s/_build/pspp-%s"
391 % (builddir, tarball_dir, builddir, tarball_dir, binary_version),
392 "install Perl module")
393 run("cd %s/%s/_build/perl-module && make install DESTDIR=$PWD/inst"
394 % (builddir, tarball_dir))
396 start_step("Make binary distribution")
397 run("cd %s/%s/_build && tar cfz pspp-%s.tar.gz pspp-%s"
398 % (builddir, tarball_dir, binary_version, binary_version))
399 save_result("binary distribution", "%s/%s/_build/pspp-%s.tar.gz"
400 % (builddir, tarball_dir, binary_version), 1)
403 ok = try_run("cd %s/%s/_build && make check" % (builddir, tarball_dir),
405 for basename in ("tests/testsuite.log", "tests/testsuite.dir"):
406 save_result_if_exists("test logs", "%s/%s/_build/%s"
407 % (builddir, tarball_dir, basename))
411 start_step("Uninstall")
412 run("cd ../source/%s && make -j10 uninstall DESTDIR=%s/%s/_build/pspp-%s"
413 % (ssw_dir, builddir, tarball_dir, binary_version))
414 run("cd %s/%s/_build && make uninstall DESTDIR=$PWD/pspp-%s"
415 % (builddir, tarball_dir, binary_version), "uninstall")
417 start_step("Check uninstall")
418 run("(cd %s/%s/_build/perl-module/inst && find -type f -print) | "
419 "(cd %s/%s/_build/pspp-%s && xargs rm)"
420 % (builddir, tarball_dir,
421 builddir, tarball_dir, binary_version)), "uninstall Perl module")
422 run("cd %s/%s/_build && "
423 "make distuninstallcheck distuninstallcheck_dir=$PWD/pspp-%s"
424 % (builddir, tarball_dir, binary_version),
425 "distuninstallcheck")
430 start_step("Success")
434 %s, for building and testing PSPP
435 usage: %s [OPTIONS] [TARBALL | REPO REFSPEC]
436 where TARBALL is the name of a tarball produced by "make dist"
437 or REPO and REFSPEC are a Git repo and refspec (e.g. branch) to clone.
440 --help Print this usage message and exit
441 --no-binary Build source tarballs but no binaries.
442 --batch Do not print progress to stdout."""
443 % (sys.argv[0], sys.argv[0]))
446 def run(command, id):
447 if not try_run(command, id):
450 def try_run(command, id):
451 LOG.write("%s\n", command)
453 p = subprocess.Popen(command, shell=True,
454 stdout=subprocess.PIPE,
455 stderr=subprocess.STDOUT)
457 start = datetime.now()
458 est_time = 0 if id is None else read_timing(id)
464 elapsed = (datetime.now() - start).seconds
465 progress = "%d lines logged, %d s elapsed" % (lines, elapsed)
468 left = est_time - elapsed
470 progress += ", ETA %d s" % left
472 sys.stdout.write("\r%s%s\r"
473 % (progress, " " * (79 - len(progress))))
475 sys.stdout.write("\r%s\r" % (' ' * 79))
478 write_timing(id, (datetime.now() - start).seconds)
485 sys.stderr.write("%s: child died with signal %d\n" % (command, -rc))
487 sys.stderr.write("%s: child exited with value %d\n" % (command, rc))
492 for s in open("%s/timings" % topdir):
493 m = re.match('([^=]+)=(.*)', s.rstrip())
495 key, value = m.groups()
499 except FileNotFoundError:
503 def write_timing(id, time):
504 tmpname = "%s/timings.tmp%d" % (topdir, os.getpid())
505 NEWTIMINGS = open(tmpname, 'w')
507 OLDTIMINGS = open("%s/timings", "r")
509 m = re.match('([^=]+)=(.*)', s.rstrip())
511 key, value = m.groups()
515 except FileNotFoundError:
517 NEWTIMINGS.write("%s=%s\n" % (id, time))
519 os.rename(tmpname, "%s/timings" % topdir)
522 sys.stderr.write("Build failed, refer to:\n\t%s\nfor details.\n" % logfile)