Revert "wafsamba/samba_autoconf: when setting undefined result, use empty tuple"
[Samba.git] / script / autobuild.py
blob02fff41204b7a65ff2d04b21e98a4ace524be88d
1 #!/usr/bin/env python
2 # run tests on all Samba subprojects and push to a git tree on success
3 # Copyright Andrew Tridgell 2010
4 # released under GNU GPL v3 or later
6 from __future__ import print_function
7 from subprocess import call, check_call, Popen, PIPE
8 import os
9 import tarfile
10 import sys
11 import time
12 from optparse import OptionParser
13 import smtplib
14 import email
15 from email.mime.text import MIMEText
16 from email.mime.base import MIMEBase
17 from email.mime.application import MIMEApplication
18 from email.mime.multipart import MIMEMultipart
19 from distutils.sysconfig import get_python_lib
20 import platform
22 try:
23 from waflib.Build import CACHE_SUFFIX
24 except ImportError:
25 sys.path.insert(0, "./third_party/waf")
26 from waflib.Build import CACHE_SUFFIX
29 os.environ["PYTHONUNBUFFERED"] = "1"
31 # This speeds up testing remarkably.
32 os.environ['TDB_NO_FSYNC'] = '1'
34 cleanup_list = []
36 builddirs = {
37 "ctdb": "ctdb",
38 "samba": ".",
39 "samba-py3": ".",
40 "samba-nt4": ".",
41 "samba-nt4-py3": ".",
42 "samba-fileserver": ".",
43 "samba-xc": ".",
44 "samba-o3": ".",
45 "samba-ctdb": ".",
46 "samba-libs": ".",
47 "samba-libs-py3": ".",
48 "samba-static": ".",
49 "samba-none-env": ".",
50 "samba-none-env-py3": ".",
51 "samba-ad-dc": ".",
52 "samba-ad-dc-py3": ".",
53 "samba-ad-dc-2": ".",
54 "samba-ad-dc-2-py3": ".",
55 "samba-systemkrb5": ".",
56 "samba-nopython": ".",
57 "ldb": "lib/ldb",
58 "tdb": "lib/tdb",
59 "talloc": "lib/talloc",
60 "replace": "lib/replace",
61 "tevent": "lib/tevent",
62 "pidl": "pidl"
65 defaulttasks = builddirs.keys()
67 if os.environ.get("AUTOBUILD_SKIP_SAMBA_O3", "0") == "1":
68 defaulttasks.remove("samba-o3")
70 ctdb_configure_params = " --enable-developer --picky-developer ${PREFIX}"
71 samba_configure_params = " --picky-developer ${PREFIX} ${EXTRA_PYTHON} --with-profiling-data"
73 samba_libs_envvars = "PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH"
74 samba_libs_envvars += " PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig"
75 samba_libs_envvars += " ADDITIONAL_CFLAGS='-Wmissing-prototypes'"
76 samba_libs_configure_base = samba_libs_envvars + " ./configure --abi-check --enable-debug --picky-developer -C ${PREFIX}"
77 samba_libs_configure_libs = samba_libs_configure_base + " --bundled-libraries=cmocka,popt,NONE ${EXTRA_PYTHON}"
78 samba_libs_configure_bundled_libs = " --bundled-libraries=!talloc,!pytalloc-util,!tdb,!pytdb,!ldb,!pyldb,!pyldb-util,!tevent,!pytevent,!popt"
79 samba_libs_configure_samba = samba_libs_configure_base + samba_libs_configure_bundled_libs + " ${EXTRA_PYTHON}"
81 if os.environ.get("AUTOBUILD_NO_EXTRA_PYTHON", "0") == "1":
82 extra_python = ""
83 else:
84 extra_python = "--extra-python=/usr/bin/python3"
86 tasks = {
87 "ctdb": [("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
88 ("configure", "./configure " + ctdb_configure_params, "text/plain"),
89 ("make", "make all", "text/plain"),
90 ("install", "make install", "text/plain"),
91 ("test", "make autotest", "text/plain"),
92 ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
93 ("clean", "make clean", "text/plain")],
95 # We have 'test' before 'install' because, 'test' should work without 'install (runs ad_dc_ntvfs and all the other envs)'
96 "samba": [("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
97 ("make", "make -j", "text/plain"),
98 ("test", "make test FAIL_IMMEDIATELY=1 "
99 "TESTS='${PY3_ONLY}"
100 "--exclude-env=none "
101 "--exclude-env=nt4_dc "
102 "--exclude-env=nt4_member "
103 "--exclude-env=ad_dc "
104 "--exclude-env=ad_dc_no_nss "
105 "--exclude-env=fl2003dc "
106 "--exclude-env=fl2008r2dc "
107 "--exclude-env=ad_member "
108 "--exclude-env=ad_member_idmap_rid "
109 "--exclude-env=ad_member_idmap_ad "
110 "--exclude-env=chgdcpass "
111 "--exclude-env=vampire_2000_dc "
112 "--exclude-env=fl2000dc "
113 "--exclude-env=fileserver "
114 "--exclude-env=backupfromdc "
115 "--exclude-env=restoredc "
116 "--exclude-env=renamedc "
117 "--exclude-env=offlinebackupdc "
118 "--exclude-env=labdc "
119 "'",
120 "text/plain"),
121 ("install", "make install", "text/plain"),
122 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
123 ("clean", "make clean", "text/plain")],
125 # We split out this so the isolated nt4_dc tests do not wait for ad_dc or ad_dc_ntvfs tests (which are long)
126 "samba-nt4": [("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
127 ("configure", "./configure.developer --without-ads --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
128 ("make", "make -j", "text/plain"),
129 ("test", "make test FAIL_IMMEDIATELY=1 "
130 "TESTS='${PY3_ONLY}"
131 "--include-env=nt4_dc --include-env=nt4_member'", "text/plain"),
132 ("install", "make install", "text/plain"),
133 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
134 ("clean", "make clean", "text/plain")],
136 # We split out this so the isolated ad_dc tests do not wait for ad_dc_ntvfs tests (which are long)
137 "samba-fileserver": [("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
138 ("configure", "./configure.developer --without-ad-dc --without-ldap --without-ads --without-json-audit --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
139 ("make", "make -j", "text/plain"),
140 ("test", "make test FAIL_IMMEDIATELY=1 "
141 "TESTS='${PY3_ONLY}"
142 "--include-env=fileserver'", "text/plain"),
143 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain")],
145 # We split out this so the isolated ad_dc tests do not wait for ad_dc_ntvfs tests (which are long)
146 "samba-ad-dc": [("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
147 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
148 ("make", "make -j", "text/plain"),
149 ("test", "make test FAIL_IMMEDIATELY=1 "
150 "TESTS='${PY3_ONLY}"
151 "--include-env=ad_dc "
152 "--include-env=fl2003dc "
153 "--include-env=fl2008r2dc "
154 "--include-env=ad_member "
155 "--include-env=ad_member_idmap_rid "
156 "--include-env=ad_member_idmap_ad'", "text/plain"),
157 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain")],
159 # We split out this so the isolated ad_dc tests do not wait for ad_dc_ntvfs tests (which are long)
160 "samba-ad-dc-2": [("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
161 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
162 ("make", "make -j", "text/plain"),
163 ("test", "make test FAIL_IMMEDIATELY=1 "
164 "TESTS='${PY3_ONLY}"
165 "--include-env=chgdcpass "
166 "--include-env=vampire_2000_dc "
167 "--include-env=fl2000dc "
168 "--include-env=ad_dc_no_nss "
169 "--include-env=backupfromdc "
170 "--include-env=restoredc "
171 "--include-env=renamedc "
172 "--include-env=offlinebackupdc "
173 "--include-env=labdc "
174 "'",
175 "text/plain"),
176 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain")],
178 "samba-test-only": [("configure", "./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params, "text/plain"),
179 ("make", "make -j", "text/plain"),
180 ("test", 'make test FAIL_IMMEDIATELY=1 TESTS="${TESTS}"', "text/plain")],
182 # Test cross-compile infrastructure
183 "samba-xc": [("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
184 ("configure-native", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
185 ("configure-cross-execute", "./configure.developer --out ./bin-xe --cross-compile --cross-execute=script/identity_cc.sh" \
186 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xe/ab" + samba_configure_params, "text/plain"),
187 ("configure-cross-answers", "./configure.developer --out ./bin-xa --cross-compile" \
188 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa/ab" + samba_configure_params, "text/plain"),
189 ("compare-results", "script/compare_cc_results.py "
190 "./bin/c4che/default{} "
191 "./bin-xe/c4che/default{} "
192 "./bin-xa/c4che/default{}".format(*([CACHE_SUFFIX]*3)), "text/plain")],
194 # test build with -O3 -- catches extra warnings and bugs, tests the ad_dc environments
195 "samba-o3": [("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
196 ("configure", "ADDITIONAL_CFLAGS='-O3' ./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params, "text/plain"),
197 ("make", "make -j", "text/plain"),
198 ("test", "make quicktest FAIL_IMMEDIATELY=1 "
199 "TESTS='${PY3_ONLY}"
200 "--include-env=ad_dc'", "text/plain"),
201 ("install", "make install", "text/plain"),
202 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
203 ("clean", "make clean", "text/plain")],
205 "samba-ctdb": [("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
207 # make sure we have tdb around:
208 ("tdb-configure", "cd lib/tdb && PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig ./configure --bundled-libraries=NONE --abi-check --enable-debug -C ${PREFIX}", "text/plain"),
209 ("tdb-make", "cd lib/tdb && make", "text/plain"),
210 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
213 # build samba with cluster support (also building ctdb):
214 ("samba-configure", "PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH PKG_CONFIG_PATH=${PREFIX_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure.developer --picky-developer ${PREFIX} --with-selftest-prefix=./bin/ab --with-cluster-support --bundled-libraries=!tdb", "text/plain"),
215 ("samba-make", "make", "text/plain"),
216 ("samba-check", "./bin/smbd -b | grep CLUSTER_SUPPORT", "text/plain"),
217 ("samba-install", "make install", "text/plain"),
218 ("ctdb-check", "test -e ${PREFIX_DIR}/sbin/ctdbd", "text/plain"),
220 # clean up:
221 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
222 ("clean", "make clean", "text/plain"),
223 ("ctdb-clean", "cd ./ctdb && make clean", "text/plain")],
225 "samba-libs": [
226 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
227 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_libs, "text/plain"),
228 ("talloc-make", "cd lib/talloc && make", "text/plain"),
229 ("talloc-install", "cd lib/talloc && make install", "text/plain"),
231 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_libs, "text/plain"),
232 ("tdb-make", "cd lib/tdb && make", "text/plain"),
233 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
235 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_libs, "text/plain"),
236 ("tevent-make", "cd lib/tevent && make", "text/plain"),
237 ("tevent-install", "cd lib/tevent && make install", "text/plain"),
239 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_libs, "text/plain"),
240 ("ldb-make", "cd lib/ldb && make", "text/plain"),
241 ("ldb-install", "cd lib/ldb && make install", "text/plain"),
243 ("nondevel-configure", "./configure ${PREFIX}", "text/plain"),
244 ("nondevel-make", "make -j", "text/plain"),
245 ("nondevel-check", "./bin/smbd -b | grep WITH_NTVFS_FILESERVER && exit 1; exit 0", "text/plain"),
246 ("nondevel-install", "make install", "text/plain"),
247 ("nondevel-dist", "make dist", "text/plain"),
249 # retry with all modules shared
250 ("allshared-distclean", "make distclean", "text/plain"),
251 ("allshared-configure", samba_libs_configure_samba + " --with-shared-modules=ALL", "text/plain"),
252 ("allshared-make", "make -j", "text/plain")],
254 "samba-none-env": [
255 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
256 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
257 ("make", "make -j", "text/plain"),
258 ("test", "make test "
259 "FAIL_IMMEDIATELY=1 "
260 "TESTS='${PY3_ONLY}"
261 "--include-env=none'",
262 "text/plain")],
264 "samba-static": [
265 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
266 # build with all modules static
267 ("allstatic-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=ALL", "text/plain"),
268 ("allstatic-make", "make -j", "text/plain"),
269 ("allstatic-test", "make test "
270 "FAIL_IMMEDIATELY=1 "
271 "TESTS='samba3.smb2.create.*nt4_dc'",
272 "text/plain"),
274 # retry without any required modules
275 ("none-distclean", "make distclean", "text/plain"),
276 ("none-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=!FORCED,!DEFAULT --with-shared-modules=!FORCED,!DEFAULT", "text/plain"),
277 ("none-make", "make -j", "text/plain"),
279 # retry with nonshared smbd and smbtorture
280 ("nonshared-distclean", "make distclean", "text/plain"),
281 ("nonshared-configure", "./configure.developer " + samba_configure_params + " --bundled-libraries=talloc,tdb,pytdb,ldb,pyldb,tevent,pytevent --with-static-modules=ALL --nonshared-binary=smbtorture,smbd/smbd", "text/plain"),
282 ("nonshared-make", "make -j", "text/plain")],
284 "samba-systemkrb5": [
285 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
286 ("configure", "./configure.developer " + samba_configure_params + " --with-system-mitkrb5 --without-ad-dc", "text/plain"),
287 ("make", "make -j", "text/plain"),
288 # we currently cannot run a full make test, a limited list of tests could be run
289 # via "make test TESTS=sometests"
290 ("test", "make test FAIL_IMMEDIATELY=1 "
291 "TESTS='${PY3_ONLY}"
292 "--include-env=ktest'", "text/plain"),
293 ("install", "make install", "text/plain"),
294 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
295 ("clean", "make clean", "text/plain")
298 # Test Samba without python still builds. When this test fails
299 # due to more use of Python, the expectations is that the newly
300 # failing part of the code should be disabled when
301 # --disable-python is set (rather than major work being done to
302 # support this environment). The target here is for vendors
303 # shipping a minimal smbd.
304 "samba-nopython": [
305 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
306 ("configure", "./configure.developer --picky-developer ${PREFIX} --with-profiling-data --disable-python --without-ad-dc", "text/plain"),
307 ("make", "make -j", "text/plain"),
308 ("install", "make install", "text/plain"),
309 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
310 ("clean", "make clean", "text/plain"),
312 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python", "text/plain"),
313 ("talloc-make", "cd lib/talloc && make", "text/plain"),
314 ("talloc-install", "cd lib/talloc && make install", "text/plain"),
316 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python", "text/plain"),
317 ("tdb-make", "cd lib/tdb && make", "text/plain"),
318 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
320 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python", "text/plain"),
321 ("tevent-make", "cd lib/tevent && make", "text/plain"),
322 ("tevent-install", "cd lib/tevent && make install", "text/plain"),
324 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python", "text/plain"),
325 ("ldb-make", "cd lib/ldb && make", "text/plain"),
326 ("ldb-install", "cd lib/ldb && make install", "text/plain"),
328 # retry against installed library packages
329 ("libs-configure", samba_libs_configure_base + samba_libs_configure_bundled_libs + " --disable-python --without-ad-dc", "text/plain"),
330 ("libs-make", "make -j", "text/plain"),
331 ("libs-install", "make install", "text/plain"),
332 ("libs-check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
333 ("libs-clean", "make clean", "text/plain")
338 "ldb": [
339 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
340 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
341 ("make", "make", "text/plain"),
342 ("install", "make install", "text/plain"),
343 ("test", "make test", "text/plain"),
344 ("configure-no-lmdb", "./configure --enable-developer --without-ldb-lmdb -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
345 ("make-no-lmdb", "make", "text/plain"),
346 ("install-no-lmdb", "make install", "text/plain"),
347 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
348 ("distcheck", "make distcheck", "text/plain"),
349 ("clean", "make clean", "text/plain")],
351 "tdb": [
352 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
353 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
354 ("make", "make", "text/plain"),
355 ("install", "make install", "text/plain"),
356 ("test", "make test", "text/plain"),
357 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
358 ("distcheck", "make distcheck", "text/plain"),
359 ("clean", "make clean", "text/plain")],
361 "talloc": [
362 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
363 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
364 ("make", "make", "text/plain"),
365 ("install", "make install", "text/plain"),
366 ("test", "make test", "text/plain"),
367 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
368 ("distcheck", "make distcheck", "text/plain"),
369 ("clean", "make clean", "text/plain")],
371 "replace": [
372 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
373 ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
374 ("make", "make", "text/plain"),
375 ("install", "make install", "text/plain"),
376 ("test", "make test", "text/plain"),
377 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
378 ("distcheck", "make distcheck", "text/plain"),
379 ("clean", "make clean", "text/plain")],
381 "tevent": [
382 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
383 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
384 ("make", "make", "text/plain"),
385 ("install", "make install", "text/plain"),
386 ("test", "make test", "text/plain"),
387 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
388 ("distcheck", "make distcheck", "text/plain"),
389 ("clean", "make clean", "text/plain")],
391 "pidl": [
392 ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
393 ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}", "text/plain"),
394 ("touch", "touch *.yp", "text/plain"),
395 ("make", "make", "text/plain"),
396 ("test", "make test", "text/plain"),
397 ("install", "make install", "text/plain"),
398 ("checkout-yapp-generated", "git checkout lib/Parse/Pidl/IDL.pm lib/Parse/Pidl/Expr.pm", "text/plain"),
399 ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
400 ("clean", "make clean", "text/plain")],
402 # these are useful for debugging autobuild
403 'pass': [("pass", 'echo passing && /bin/true', "text/plain")],
404 'fail': [("fail", 'echo failing && /bin/false', "text/plain")]
408 def do_print(msg):
409 print("%s" % msg)
410 sys.stdout.flush()
411 sys.stderr.flush()
414 def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
415 if show is None:
416 show = options.verbose
417 if show:
418 do_print("Running: '%s' in '%s'" % (cmd, dir))
419 if output:
420 return Popen([cmd], shell=True, stdout=PIPE, cwd=dir, close_fds=True).communicate()[0]
421 elif checkfail:
422 return check_call(cmd, shell=True, cwd=dir)
423 else:
424 return call(cmd, shell=True, cwd=dir)
427 class builder(object):
428 '''handle build of one directory'''
430 def __init__(self, name, sequence, cp=True, py3=False):
431 self.name = name
432 self.py3 = py3
433 if name in builddirs:
434 self.dir = builddirs[name]
435 else:
436 self.dir = "."
438 self.tag = self.name.replace('/', '_')
439 self.sequence = sequence
440 self.next = 0
441 self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
442 self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
443 if options.verbose:
444 do_print("stdout for %s in %s" % (self.name, self.stdout_path))
445 do_print("stderr for %s in %s" % (self.name, self.stderr_path))
446 run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
447 self.stdout = open(self.stdout_path, 'w')
448 self.stderr = open(self.stderr_path, 'w')
449 self.stdin = open("/dev/null", 'r')
450 self.sdir = "%s/%s" % (testbase, self.tag)
451 self.prefix = "%s/%s" % (test_prefix, self.tag)
452 run_cmd("rm -rf %s" % self.sdir)
453 run_cmd("rm -rf %s" % self.prefix)
454 if cp:
455 run_cmd("cp --recursive --link --archive %s %s" % (test_master, self.sdir), dir=test_master, show=True)
456 else:
457 run_cmd("git clone --recursive --shared %s %s" % (test_master, self.sdir), dir=test_master, show=True)
458 self.start_next()
460 def start_next(self):
461 if self.next == len(self.sequence):
462 if not options.nocleanup:
463 run_cmd("rm -rf %s" % self.sdir)
464 run_cmd("rm -rf %s" % self.prefix)
465 do_print('%s: Completed OK' % self.name)
466 self.done = True
467 return
468 (self.stage, self.cmd, self.output_mime_type) = self.sequence[self.next]
469 self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(standard_lib=1, prefix=self.prefix))
470 self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
471 if self.py3:
472 self.cmd = self.cmd.replace("${EXTRA_PYTHON}", "%s" % extra_python)
473 # The trailing space is important
474 self.cmd = self.cmd.replace("${PY3_ONLY}", "python3 ")
475 else:
476 self.cmd = self.cmd.replace("${EXTRA_PYTHON}", "")
477 self.cmd = self.cmd.replace("${PY3_ONLY}", "")
478 self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
479 self.cmd = self.cmd.replace("${TESTS}", options.restrict_tests)
480 # if self.output_mime_type == "text/x-subunit":
481 # self.cmd += " | %s --immediate" % (os.path.join(os.path.dirname(__file__), "selftest/format-subunit"))
482 cwd = os.getcwd()
483 os.chdir("%s/%s" % (self.sdir, self.dir))
484 do_print('%s: [%s] Running %s in %r' % (self.name, self.stage, self.cmd, os.getcwd()))
485 self.proc = Popen(self.cmd, shell=True,
486 close_fds=True,
487 stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
488 os.chdir(cwd)
489 self.next += 1
492 class buildlist(object):
493 '''handle build of multiple directories'''
495 def __init__(self, tasknames, rebase_url, rebase_branch="master"):
496 global tasks
497 self.tlist = []
498 self.tail_proc = None
499 self.retry = None
500 if tasknames == []:
501 if options.restrict_tests:
502 tasknames = ["samba-test-only"]
503 else:
504 tasknames = defaulttasks
505 else:
506 # If we are only running one test,
507 # do not sleep randomly to wait for it to start
508 os.environ['AUTOBUILD_RANDOM_SLEEP_OVERRIDE'] = '1'
510 for n in tasknames:
511 if n not in tasks and n.endswith("-py3"):
512 b = builder(n,
513 tasks[n[:-4]],
514 cp=n is not "pidl",
515 py3=True)
516 else:
517 b = builder(n, tasks[n], cp=n is not "pidl")
518 self.tlist.append(b)
519 if options.retry:
520 rebase_remote = "rebaseon"
521 retry_task = [("retry",
522 '''set -e
523 git remote add -t %s %s %s
524 git fetch %s
525 while :; do
526 sleep 60
527 git describe %s/%s > old_remote_branch.desc
528 git fetch %s
529 git describe %s/%s > remote_branch.desc
530 diff old_remote_branch.desc remote_branch.desc
531 done
532 ''' % (
533 rebase_branch, rebase_remote, rebase_url,
534 rebase_remote,
535 rebase_remote, rebase_branch,
536 rebase_remote,
537 rebase_remote, rebase_branch
539 "test/plain")]
541 self.retry = builder('retry', retry_task, cp=False)
542 self.need_retry = False
544 def kill_kids(self):
545 if self.tail_proc is not None:
546 self.tail_proc.terminate()
547 self.tail_proc.wait()
548 self.tail_proc = None
549 if self.retry is not None:
550 self.retry.proc.terminate()
551 self.retry.proc.wait()
552 self.retry = None
553 for b in self.tlist:
554 if b.proc is not None:
555 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.sdir, checkfail=False)
556 b.proc.terminate()
557 b.proc.wait()
558 b.proc = None
560 def wait_one(self):
561 while True:
562 none_running = True
563 for b in self.tlist:
564 if b.proc is None:
565 continue
566 none_running = False
567 b.status = b.proc.poll()
568 if b.status is None:
569 continue
570 b.proc = None
571 return b
572 if options.retry:
573 ret = self.retry.proc.poll()
574 if ret is not None:
575 self.need_retry = True
576 self.retry = None
577 return None
578 if none_running:
579 return None
580 time.sleep(0.1)
582 def run(self):
583 while True:
584 b = self.wait_one()
585 if options.retry and self.need_retry:
586 self.kill_kids()
587 do_print("retry needed")
588 return (0, None, None, None, "retry")
589 if b is None:
590 break
591 if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
592 self.kill_kids()
593 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
594 b.start_next()
595 self.kill_kids()
596 return (0, None, None, None, "All OK")
598 def write_system_info(self):
599 filename = 'system-info.txt'
600 f = open(filename, 'w')
601 for cmd in ['uname -a', 'free', 'cat /proc/cpuinfo',
602 'cc --version', 'df -m .', 'df -m %s' % testbase]:
603 print('### %s' % cmd, file=f)
604 print(run_cmd(cmd, output=True, checkfail=False), file=f)
605 print(file=f)
606 f.close()
607 return filename
609 def tarlogs(self, fname):
610 tar = tarfile.open(fname, "w:gz")
611 for b in self.tlist:
612 tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
613 tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
614 if os.path.exists("autobuild.log"):
615 tar.add("autobuild.log")
616 sys_info = self.write_system_info()
617 tar.add(sys_info)
618 tar.close()
620 def remove_logs(self):
621 for b in self.tlist:
622 os.unlink(b.stdout_path)
623 os.unlink(b.stderr_path)
625 def start_tail(self):
626 cmd = ["tail", "-f"]
627 for b in self.tlist:
628 cmd.append(b.stdout_path)
629 cmd.append(b.stderr_path)
630 self.tail_proc = Popen(cmd, close_fds=True)
633 def cleanup():
634 if options.nocleanup:
635 return
636 run_cmd("stat %s || true" % test_tmpdir, show=True)
637 run_cmd("stat %s" % testbase, show=True)
638 do_print("Cleaning up %r" % cleanup_list)
639 for d in cleanup_list:
640 run_cmd("rm -rf %s" % d)
643 def find_git_root():
644 '''get to the top of the git repo'''
645 p = os.getcwd()
646 while p != '/':
647 if os.path.isdir(os.path.join(p, ".git")):
648 return p
649 p = os.path.abspath(os.path.join(p, '..'))
650 return None
653 def daemonize(logfile):
654 pid = os.fork()
655 if pid == 0: # Parent
656 os.setsid()
657 pid = os.fork()
658 if pid != 0: # Actual daemon
659 os._exit(0)
660 else: # Grandparent
661 os._exit(0)
663 import resource # Resource usage information.
664 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
665 if maxfd == resource.RLIM_INFINITY:
666 maxfd = 1024 # Rough guess at maximum number of open file descriptors.
667 for fd in range(0, maxfd):
668 try:
669 os.close(fd)
670 except OSError:
671 pass
672 os.open(logfile, os.O_RDWR | os.O_CREAT)
673 os.dup2(0, 1)
674 os.dup2(0, 2)
677 def write_pidfile(fname):
678 '''write a pid file, cleanup on exit'''
679 f = open(fname, mode='w')
680 f.write("%u\n" % os.getpid())
681 f.close()
684 def rebase_tree(rebase_url, rebase_branch="master"):
685 rebase_remote = "rebaseon"
686 do_print("Rebasing on %s" % rebase_url)
687 run_cmd("git describe HEAD", show=True, dir=test_master)
688 run_cmd("git remote add -t %s %s %s" %
689 (rebase_branch, rebase_remote, rebase_url),
690 show=True, dir=test_master)
691 run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
692 if options.fix_whitespace:
693 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
694 (rebase_remote, rebase_branch),
695 show=True, dir=test_master)
696 else:
697 run_cmd("git rebase --force-rebase %s/%s" %
698 (rebase_remote, rebase_branch),
699 show=True, dir=test_master)
700 diff = run_cmd("git --no-pager diff HEAD %s/%s" %
701 (rebase_remote, rebase_branch),
702 dir=test_master, output=True)
703 if diff == '':
704 do_print("No differences between HEAD and %s/%s - exiting" %
705 (rebase_remote, rebase_branch))
706 sys.exit(0)
707 run_cmd("git describe %s/%s" %
708 (rebase_remote, rebase_branch),
709 show=True, dir=test_master)
710 run_cmd("git describe HEAD", show=True, dir=test_master)
711 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
712 (rebase_remote, rebase_branch),
713 show=True, dir=test_master)
716 def push_to(push_url, push_branch="master"):
717 push_remote = "pushto"
718 do_print("Pushing to %s" % push_url)
719 if options.mark:
720 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
721 run_cmd("git commit --amend -c HEAD", dir=test_master)
722 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
723 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
724 run_cmd("git remote add -t %s %s %s" %
725 (push_branch, push_remote, push_url),
726 show=True, dir=test_master)
727 run_cmd("git push %s +HEAD:%s" %
728 (push_remote, push_branch),
729 show=True, dir=test_master)
732 def_testbase = os.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os.getenv('USER'))
734 gitroot = find_git_root()
735 if gitroot is None:
736 raise Exception("Failed to find git root")
738 parser = OptionParser()
739 parser.add_option("", "--tail", help="show output while running", default=False, action="store_true")
740 parser.add_option("", "--keeplogs", help="keep logs", default=False, action="store_true")
741 parser.add_option("", "--nocleanup", help="don't remove test tree", default=False, action="store_true")
742 parser.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase,
743 default=def_testbase)
744 parser.add_option("", "--passcmd", help="command to run on success", default=None)
745 parser.add_option("", "--verbose", help="show all commands as they are run",
746 default=False, action="store_true")
747 parser.add_option("", "--rebase", help="rebase on the given tree before testing",
748 default=None, type='str')
749 parser.add_option("", "--pushto", help="push to a git url on success",
750 default=None, type='str')
751 parser.add_option("", "--mark", help="add a Tested-By signoff before pushing",
752 default=False, action="store_true")
753 parser.add_option("", "--fix-whitespace", help="fix whitespace on rebase",
754 default=False, action="store_true")
755 parser.add_option("", "--retry", help="automatically retry if master changes",
756 default=False, action="store_true")
757 parser.add_option("", "--email", help="send email to the given address on failure",
758 type='str', default=None)
759 parser.add_option("", "--email-from", help="send email from the given address",
760 type='str', default="autobuild@samba.org")
761 parser.add_option("", "--email-server", help="send email via the given server",
762 type='str', default='localhost')
763 parser.add_option("", "--always-email", help="always send email, even on success",
764 action="store_true")
765 parser.add_option("", "--daemon", help="daemonize after initial setup",
766 action="store_true")
767 parser.add_option("", "--branch", help="the branch to work on (default=master)",
768 default="master", type='str')
769 parser.add_option("", "--log-base", help="location where the logs can be found (default=cwd)",
770 default=gitroot, type='str')
771 parser.add_option("", "--attach-logs", help="Attach logs to mails sent on success/failure?",
772 default=False, action="store_true")
773 parser.add_option("", "--restrict-tests", help="run as make test with this TESTS= regex",
774 default='')
777 def send_email(subject, text, log_tar):
778 if options.email is None:
779 do_print("not sending email because the recipient is not set")
780 do_print("the text content would have been:\n\nSubject: %s\n\n%s" %
781 (subject, text))
782 return
783 outer = MIMEMultipart()
784 outer['Subject'] = subject
785 outer['To'] = options.email
786 outer['From'] = options.email_from
787 outer['Date'] = email.utils.formatdate(localtime=True)
788 outer.preamble = 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
789 outer.attach(MIMEText(text, 'plain'))
790 if options.attach_logs:
791 fp = open(log_tar, 'rb')
792 msg = MIMEApplication(fp.read(), 'gzip', email.encoders.encode_base64)
793 fp.close()
794 # Set the filename parameter
795 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(log_tar))
796 outer.attach(msg)
797 content = outer.as_string()
798 s = smtplib.SMTP(options.email_server)
799 s.sendmail(options.email_from, [options.email], content)
800 s.set_debuglevel(1)
801 s.quit()
804 def email_failure(status, failed_task, failed_stage, failed_tag, errstr,
805 elapsed_time, log_base=None, add_log_tail=True):
806 '''send an email to options.email about the failure'''
807 elapsed_minutes = elapsed_time / 60.0
808 user = os.getenv("USER")
809 if log_base is None:
810 log_base = gitroot
811 text = '''
812 Dear Developer,
814 Your autobuild on %s failed after %.1f minutes
815 when trying to test %s with the following error:
819 the autobuild has been abandoned. Please fix the error and resubmit.
821 A summary of the autobuild process is here:
823 %s/autobuild.log
824 ''' % (platform.node(), elapsed_minutes, failed_task, errstr, log_base)
826 if options.restrict_tests:
827 text += """
828 The build was restricted to tests matching %s\n""" % options.restrict_tests
830 if failed_task != 'rebase':
831 text += '''
832 You can see logs of the failed task here:
834 %s/%s.stdout
835 %s/%s.stderr
837 or you can get full logs of all tasks in this job here:
839 %s/logs.tar.gz
841 The top commit for the tree that was built was:
845 ''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
847 if add_log_tail:
848 f = open("%s/%s.stdout" % (gitroot, failed_tag), 'r')
849 lines = f.readlines()
850 log_tail = "".join(lines[-50:])
851 num_lines = len(lines)
852 if num_lines < 50:
853 # Also include stderr (compile failures) if < 50 lines of stdout
854 f = open("%s/%s.stderr" % (gitroot, failed_tag), 'r')
855 log_tail += "".join(f.readlines()[-(50 - num_lines):])
857 text += '''
858 The last 50 lines of log messages:
861 ''' % log_tail
862 f.close()
864 logs = os.path.join(gitroot, 'logs.tar.gz')
865 send_email('autobuild[%s] failure on %s for task %s during %s'
866 % (options.branch, platform.node(), failed_task, failed_stage),
867 text, logs)
870 def email_success(elapsed_time, log_base=None):
871 '''send an email to options.email about a successful build'''
872 user = os.getenv("USER")
873 if log_base is None:
874 log_base = gitroot
875 text = '''
876 Dear Developer,
878 Your autobuild on %s has succeeded after %.1f minutes.
880 ''' % (platform.node(), elapsed_time / 60.)
882 if options.restrict_tests:
883 text += """
884 The build was restricted to tests matching %s\n""" % options.restrict_tests
886 if options.keeplogs:
887 text += '''
889 you can get full logs of all tasks in this job here:
891 %s/logs.tar.gz
893 ''' % log_base
895 text += '''
896 The top commit for the tree that was built was:
899 ''' % top_commit_msg
901 logs = os.path.join(gitroot, 'logs.tar.gz')
902 send_email('autobuild[%s] success on %s' % (options.branch, platform.node()),
903 text, logs)
906 (options, args) = parser.parse_args()
908 if options.retry:
909 if options.rebase is None:
910 raise Exception('You can only use --retry if you also rebase')
912 testbase = "%s/b%u" % (options.testbase, os.getpid())
913 test_master = "%s/master" % testbase
914 test_prefix = "%s/prefix" % testbase
915 test_tmpdir = "%s/tmp" % testbase
916 os.environ['TMPDIR'] = test_tmpdir
918 # get the top commit message, for emails
919 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
921 try:
922 os.makedirs(testbase)
923 except Exception as reason:
924 raise Exception("Unable to create %s : %s" % (testbase, reason))
925 cleanup_list.append(testbase)
927 if options.daemon:
928 logfile = os.path.join(testbase, "log")
929 do_print("Forking into the background, writing progress to %s" % logfile)
930 daemonize(logfile)
932 write_pidfile(gitroot + "/autobuild.pid")
934 start_time = time.time()
936 while True:
937 try:
938 run_cmd("rm -rf %s" % test_tmpdir, show=True)
939 os.makedirs(test_tmpdir)
940 # The waf uninstall code removes empty directories all the way
941 # up the tree. Creating a file in test_tmpdir stops it from
942 # being removed.
943 run_cmd("touch %s" % os.path.join(test_tmpdir,
944 ".directory-is-not-empty"), show=True)
945 run_cmd("stat %s" % test_tmpdir, show=True)
946 run_cmd("stat %s" % testbase, show=True)
947 run_cmd("git clone --recursive --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
948 except Exception:
949 cleanup()
950 raise
952 try:
953 try:
954 if options.rebase is not None:
955 rebase_tree(options.rebase, rebase_branch=options.branch)
956 except Exception:
957 cleanup_list.append(gitroot + "/autobuild.pid")
958 cleanup()
959 elapsed_time = time.time() - start_time
960 email_failure(-1, 'rebase', 'rebase', 'rebase',
961 'rebase on %s failed' % options.branch,
962 elapsed_time, log_base=options.log_base)
963 sys.exit(1)
964 blist = buildlist(args, options.rebase, rebase_branch=options.branch)
965 if options.tail:
966 blist.start_tail()
967 (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
968 if status != 0 or errstr != "retry":
969 break
970 cleanup()
971 except Exception:
972 cleanup()
973 raise
975 cleanup_list.append(gitroot + "/autobuild.pid")
977 do_print(errstr)
979 blist.kill_kids()
980 if options.tail:
981 do_print("waiting for tail to flush")
982 time.sleep(1)
984 elapsed_time = time.time() - start_time
985 if status == 0:
986 if options.passcmd is not None:
987 do_print("Running passcmd: %s" % options.passcmd)
988 run_cmd(options.passcmd, dir=test_master)
989 if options.pushto is not None:
990 push_to(options.pushto, push_branch=options.branch)
991 if options.keeplogs or options.attach_logs:
992 blist.tarlogs("logs.tar.gz")
993 do_print("Logs in logs.tar.gz")
994 if options.always_email:
995 email_success(elapsed_time, log_base=options.log_base)
996 blist.remove_logs()
997 cleanup()
998 do_print(errstr)
999 sys.exit(0)
1001 # something failed, gather a tar of the logs
1002 blist.tarlogs("logs.tar.gz")
1004 if options.email is not None:
1005 email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1006 elapsed_time, log_base=options.log_base)
1007 else:
1008 elapsed_minutes = elapsed_time / 60.0
1009 print('''
1011 ####################################################################
1013 AUTOBUILD FAILURE
1015 Your autobuild[%s] on %s failed after %.1f minutes
1016 when trying to test %s with the following error:
1020 the autobuild has been abandoned. Please fix the error and resubmit.
1022 ####################################################################
1024 ''' % (options.branch, platform.node(), elapsed_minutes, failed_task, errstr))
1026 cleanup()
1027 do_print(errstr)
1028 do_print("Logs in logs.tar.gz")
1029 sys.exit(status)