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