lib: Remove sid_string_tos
[Samba.git] / script / autobuild.py
blobc71b85a85b460c4685b1e8b2a19036f95633d15a
1 #!/usr/bin/env python3
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-py2": ".",
40 "samba-nt4": ".",
41 "samba-nt4-py2": ".",
42 "samba-fileserver": ".",
43 "samba-xc": ".",
44 "samba-o3": ".",
45 "samba-ctdb": ".",
46 "samba-libs": ".",
47 "samba-libs-py2": ".",
48 "samba-static": ".",
49 "samba-none-env": ".",
50 "samba-ad-dc": ".",
51 "samba-ad-dc-py2": ".",
52 "samba-ad-dc-2": ".",
53 "samba-ad-dc-2-py2": ".",
54 "samba-systemkrb5": ".",
55 "samba-nopython": ".",
56 "samba-buildpy2-only": ".",
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}:$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/python2"
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 --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 -Wp,-D_FORTIFY_SOURCE=2' ./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}:$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}:$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 "samba-buildpy2-only": [("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
403 ("configure", "PYTHON='python' ./configure.developer --with-selftest-prefix=./bin/ab " + samba_configure_params, "text/plain"),
404 ("make", "PYTHON='python' make -j", "text/plain"),
405 ("install", "PYTHON='python' make install", "text/plain"),
406 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
407 ("clean", "PYTHON='python' make clean", "text/plain")],
410 # these are useful for debugging autobuild
411 'pass': [("pass", 'echo passing && /bin/true', "text/plain")],
412 'fail': [("fail", 'echo failing && /bin/false', "text/plain")]
418 def do_print(msg):
419 print("%s" % msg)
420 sys.stdout.flush()
421 sys.stderr.flush()
424 def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
425 if show is None:
426 show = options.verbose
427 if show:
428 do_print("Running: '%s' in '%s'" % (cmd, dir))
429 if output:
430 return Popen([cmd], shell=True, stdout=PIPE, cwd=dir, close_fds=True).communicate()[0]
431 elif checkfail:
432 return check_call(cmd, shell=True, cwd=dir)
433 else:
434 return call(cmd, shell=True, cwd=dir)
437 class builder(object):
438 '''handle build of one directory'''
440 def __init__(self, name, sequence, cp=True, py3=False):
441 self.name = name
442 self.py3 = py3
443 if name in builddirs:
444 self.dir = builddirs[name]
445 else:
446 self.dir = "."
448 self.tag = self.name.replace('/', '_')
449 self.sequence = sequence
450 self.next = 0
451 self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
452 self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
453 if options.verbose:
454 do_print("stdout for %s in %s" % (self.name, self.stdout_path))
455 do_print("stderr for %s in %s" % (self.name, self.stderr_path))
456 run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
457 self.stdout = open(self.stdout_path, 'w')
458 self.stderr = open(self.stderr_path, 'w')
459 self.stdin = open("/dev/null", 'r')
460 self.sdir = "%s/%s" % (testbase, self.tag)
461 self.prefix = "%s/%s" % (test_prefix, self.tag)
462 run_cmd("rm -rf %s" % self.sdir)
463 run_cmd("rm -rf %s" % self.prefix)
464 if cp:
465 run_cmd("cp --recursive --link --archive %s %s" % (test_master, self.sdir), dir=test_master, show=True)
466 else:
467 run_cmd("git clone --recursive --shared %s %s" % (test_master, self.sdir), dir=test_master, show=True)
468 self.start_next()
470 def start_next(self):
471 if self.next == len(self.sequence):
472 if not options.nocleanup:
473 run_cmd("rm -rf %s" % self.sdir)
474 run_cmd("rm -rf %s" % self.prefix)
475 do_print('%s: Completed OK' % self.name)
476 self.done = True
477 return
478 (self.stage, self.cmd, self.output_mime_type) = self.sequence[self.next]
479 self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(plat_specific=1, standard_lib=0, prefix=self.prefix))
480 self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
481 if self.py3:
482 self.cmd = self.cmd.replace("${EXTRA_PYTHON}", "%s" % extra_python)
483 # The trailing space is important
484 self.cmd = self.cmd.replace("${PY3_ONLY}", "python2 ")
485 else:
486 self.cmd = self.cmd.replace("${EXTRA_PYTHON}", "")
487 self.cmd = self.cmd.replace("${PY3_ONLY}", "")
488 self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
489 self.cmd = self.cmd.replace("${TESTS}", options.restrict_tests)
490 # if self.output_mime_type == "text/x-subunit":
491 # self.cmd += " | %s --immediate" % (os.path.join(os.path.dirname(__file__), "selftest/format-subunit"))
492 cwd = os.getcwd()
493 os.chdir("%s/%s" % (self.sdir, self.dir))
494 do_print('%s: [%s] Running %s in %r' % (self.name, self.stage, self.cmd, os.getcwd()))
495 self.proc = Popen(self.cmd, shell=True,
496 close_fds=True,
497 stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
498 os.chdir(cwd)
499 self.next += 1
502 class buildlist(object):
503 '''handle build of multiple directories'''
505 def __init__(self, tasknames, rebase_url, rebase_branch="master"):
506 global tasks
507 self.tlist = []
508 self.tail_proc = None
509 self.retry = None
510 if tasknames == []:
511 if options.restrict_tests:
512 tasknames = ["samba-test-only"]
513 else:
514 tasknames = defaulttasks
515 else:
516 # If we are only running one test,
517 # do not sleep randomly to wait for it to start
518 os.environ['AUTOBUILD_RANDOM_SLEEP_OVERRIDE'] = '1'
520 for n in tasknames:
521 if n not in tasks and n.endswith("-py2"):
522 b = builder(n,
523 tasks[n[:-4]],
524 cp=n is not "pidl",
525 py3=True)
526 else:
527 b = builder(n, tasks[n], cp=n is not "pidl")
528 self.tlist.append(b)
529 if options.retry:
530 rebase_remote = "rebaseon"
531 retry_task = [("retry",
532 '''set -e
533 git remote add -t %s %s %s
534 git fetch %s
535 while :; do
536 sleep 60
537 git describe %s/%s > old_remote_branch.desc
538 git fetch %s
539 git describe %s/%s > remote_branch.desc
540 diff old_remote_branch.desc remote_branch.desc
541 done
542 ''' % (
543 rebase_branch, rebase_remote, rebase_url,
544 rebase_remote,
545 rebase_remote, rebase_branch,
546 rebase_remote,
547 rebase_remote, rebase_branch
549 "test/plain")]
551 self.retry = builder('retry', retry_task, cp=False)
552 self.need_retry = False
554 def kill_kids(self):
555 if self.tail_proc is not None:
556 self.tail_proc.terminate()
557 self.tail_proc.wait()
558 self.tail_proc = None
559 if self.retry is not None:
560 self.retry.proc.terminate()
561 self.retry.proc.wait()
562 self.retry = None
563 for b in self.tlist:
564 if b.proc is not None:
565 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.sdir, checkfail=False)
566 b.proc.terminate()
567 b.proc.wait()
568 b.proc = None
570 def wait_one(self):
571 while True:
572 none_running = True
573 for b in self.tlist:
574 if b.proc is None:
575 continue
576 none_running = False
577 b.status = b.proc.poll()
578 if b.status is None:
579 continue
580 b.proc = None
581 return b
582 if options.retry:
583 ret = self.retry.proc.poll()
584 if ret is not None:
585 self.need_retry = True
586 self.retry = None
587 return None
588 if none_running:
589 return None
590 time.sleep(0.1)
592 def run(self):
593 while True:
594 b = self.wait_one()
595 if options.retry and self.need_retry:
596 self.kill_kids()
597 do_print("retry needed")
598 return (0, None, None, None, "retry")
599 if b is None:
600 break
601 if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
602 self.kill_kids()
603 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
604 b.start_next()
605 self.kill_kids()
606 return (0, None, None, None, "All OK")
608 def write_system_info(self):
609 filename = 'system-info.txt'
610 f = open(filename, 'w')
611 for cmd in ['uname -a', 'free', 'cat /proc/cpuinfo',
612 'cc --version', 'df -m .', 'df -m %s' % testbase]:
613 print('### %s' % cmd, file=f)
614 print(run_cmd(cmd, output=True, checkfail=False), file=f)
615 print(file=f)
616 f.close()
617 return filename
619 def tarlogs(self, fname):
620 tar = tarfile.open(fname, "w:gz")
621 for b in self.tlist:
622 tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
623 tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
624 if os.path.exists("autobuild.log"):
625 tar.add("autobuild.log")
626 sys_info = self.write_system_info()
627 tar.add(sys_info)
628 tar.close()
630 def remove_logs(self):
631 for b in self.tlist:
632 os.unlink(b.stdout_path)
633 os.unlink(b.stderr_path)
635 def start_tail(self):
636 cmd = ["tail", "-f"]
637 for b in self.tlist:
638 cmd.append(b.stdout_path)
639 cmd.append(b.stderr_path)
640 self.tail_proc = Popen(cmd, close_fds=True)
643 def cleanup():
644 if options.nocleanup:
645 return
646 run_cmd("stat %s || true" % test_tmpdir, show=True)
647 run_cmd("stat %s" % testbase, show=True)
648 do_print("Cleaning up %r" % cleanup_list)
649 for d in cleanup_list:
650 run_cmd("rm -rf %s" % d)
653 def find_git_root():
654 '''get to the top of the git repo'''
655 p = os.getcwd()
656 while p != '/':
657 if os.path.isdir(os.path.join(p, ".git")):
658 return p
659 p = os.path.abspath(os.path.join(p, '..'))
660 return None
663 def daemonize(logfile):
664 pid = os.fork()
665 if pid == 0: # Parent
666 os.setsid()
667 pid = os.fork()
668 if pid != 0: # Actual daemon
669 os._exit(0)
670 else: # Grandparent
671 os._exit(0)
673 import resource # Resource usage information.
674 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
675 if maxfd == resource.RLIM_INFINITY:
676 maxfd = 1024 # Rough guess at maximum number of open file descriptors.
677 for fd in range(0, maxfd):
678 try:
679 os.close(fd)
680 except OSError:
681 pass
682 os.open(logfile, os.O_RDWR | os.O_CREAT)
683 os.dup2(0, 1)
684 os.dup2(0, 2)
687 def write_pidfile(fname):
688 '''write a pid file, cleanup on exit'''
689 f = open(fname, mode='w')
690 f.write("%u\n" % os.getpid())
691 f.close()
694 def rebase_tree(rebase_url, rebase_branch="master"):
695 rebase_remote = "rebaseon"
696 do_print("Rebasing on %s" % rebase_url)
697 run_cmd("git describe HEAD", show=True, dir=test_master)
698 run_cmd("git remote add -t %s %s %s" %
699 (rebase_branch, rebase_remote, rebase_url),
700 show=True, dir=test_master)
701 run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
702 if options.fix_whitespace:
703 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
704 (rebase_remote, rebase_branch),
705 show=True, dir=test_master)
706 else:
707 run_cmd("git rebase --force-rebase %s/%s" %
708 (rebase_remote, rebase_branch),
709 show=True, dir=test_master)
710 diff = run_cmd("git --no-pager diff HEAD %s/%s" %
711 (rebase_remote, rebase_branch),
712 dir=test_master, output=True)
713 if diff == '':
714 do_print("No differences between HEAD and %s/%s - exiting" %
715 (rebase_remote, rebase_branch))
716 sys.exit(0)
717 run_cmd("git describe %s/%s" %
718 (rebase_remote, rebase_branch),
719 show=True, dir=test_master)
720 run_cmd("git describe HEAD", show=True, dir=test_master)
721 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
722 (rebase_remote, rebase_branch),
723 show=True, dir=test_master)
726 def push_to(push_url, push_branch="master"):
727 push_remote = "pushto"
728 do_print("Pushing to %s" % push_url)
729 if options.mark:
730 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
731 run_cmd("git commit --amend -c HEAD", dir=test_master)
732 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
733 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
734 run_cmd("git remote add -t %s %s %s" %
735 (push_branch, push_remote, push_url),
736 show=True, dir=test_master)
737 run_cmd("git push %s +HEAD:%s" %
738 (push_remote, push_branch),
739 show=True, dir=test_master)
742 def_testbase = os.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os.getenv('USER'))
744 gitroot = find_git_root()
745 if gitroot is None:
746 raise Exception("Failed to find git root")
748 parser = OptionParser()
749 parser.add_option("", "--tail", help="show output while running", default=False, action="store_true")
750 parser.add_option("", "--keeplogs", help="keep logs", default=False, action="store_true")
751 parser.add_option("", "--nocleanup", help="don't remove test tree", default=False, action="store_true")
752 parser.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase,
753 default=def_testbase)
754 parser.add_option("", "--passcmd", help="command to run on success", default=None)
755 parser.add_option("", "--verbose", help="show all commands as they are run",
756 default=False, action="store_true")
757 parser.add_option("", "--rebase", help="rebase on the given tree before testing",
758 default=None, type='str')
759 parser.add_option("", "--pushto", help="push to a git url on success",
760 default=None, type='str')
761 parser.add_option("", "--mark", help="add a Tested-By signoff before pushing",
762 default=False, action="store_true")
763 parser.add_option("", "--fix-whitespace", help="fix whitespace on rebase",
764 default=False, action="store_true")
765 parser.add_option("", "--retry", help="automatically retry if master changes",
766 default=False, action="store_true")
767 parser.add_option("", "--email", help="send email to the given address on failure",
768 type='str', default=None)
769 parser.add_option("", "--email-from", help="send email from the given address",
770 type='str', default="autobuild@samba.org")
771 parser.add_option("", "--email-server", help="send email via the given server",
772 type='str', default='localhost')
773 parser.add_option("", "--always-email", help="always send email, even on success",
774 action="store_true")
775 parser.add_option("", "--daemon", help="daemonize after initial setup",
776 action="store_true")
777 parser.add_option("", "--branch", help="the branch to work on (default=master)",
778 default="master", type='str')
779 parser.add_option("", "--log-base", help="location where the logs can be found (default=cwd)",
780 default=gitroot, type='str')
781 parser.add_option("", "--attach-logs", help="Attach logs to mails sent on success/failure?",
782 default=False, action="store_true")
783 parser.add_option("", "--restrict-tests", help="run as make test with this TESTS= regex",
784 default='')
787 def send_email(subject, text, log_tar):
788 if options.email is None:
789 do_print("not sending email because the recipient is not set")
790 do_print("the text content would have been:\n\nSubject: %s\n\n%s" %
791 (subject, text))
792 return
793 outer = MIMEMultipart()
794 outer['Subject'] = subject
795 outer['To'] = options.email
796 outer['From'] = options.email_from
797 outer['Date'] = email.utils.formatdate(localtime=True)
798 outer.preamble = 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
799 outer.attach(MIMEText(text, 'plain'))
800 if options.attach_logs:
801 fp = open(log_tar, 'rb')
802 msg = MIMEApplication(fp.read(), 'gzip', email.encoders.encode_base64)
803 fp.close()
804 # Set the filename parameter
805 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(log_tar))
806 outer.attach(msg)
807 content = outer.as_string()
808 s = smtplib.SMTP(options.email_server)
809 s.sendmail(options.email_from, [options.email], content)
810 s.set_debuglevel(1)
811 s.quit()
814 def email_failure(status, failed_task, failed_stage, failed_tag, errstr,
815 elapsed_time, log_base=None, add_log_tail=True):
816 '''send an email to options.email about the failure'''
817 elapsed_minutes = elapsed_time / 60.0
818 if log_base is None:
819 log_base = gitroot
820 text = '''
821 Dear Developer,
823 Your autobuild on %s failed after %.1f minutes
824 when trying to test %s with the following error:
828 the autobuild has been abandoned. Please fix the error and resubmit.
830 A summary of the autobuild process is here:
832 %s/autobuild.log
833 ''' % (platform.node(), elapsed_minutes, failed_task, errstr, log_base)
835 if options.restrict_tests:
836 text += """
837 The build was restricted to tests matching %s\n""" % options.restrict_tests
839 if failed_task != 'rebase':
840 text += '''
841 You can see logs of the failed task here:
843 %s/%s.stdout
844 %s/%s.stderr
846 or you can get full logs of all tasks in this job here:
848 %s/logs.tar.gz
850 The top commit for the tree that was built was:
854 ''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
856 if add_log_tail:
857 f = open("%s/%s.stdout" % (gitroot, failed_tag), 'r')
858 lines = f.readlines()
859 log_tail = "".join(lines[-50:])
860 num_lines = len(lines)
861 if num_lines < 50:
862 # Also include stderr (compile failures) if < 50 lines of stdout
863 f = open("%s/%s.stderr" % (gitroot, failed_tag), 'r')
864 log_tail += "".join(f.readlines()[-(50 - num_lines):])
866 text += '''
867 The last 50 lines of log messages:
870 ''' % log_tail
871 f.close()
873 logs = os.path.join(gitroot, 'logs.tar.gz')
874 send_email('autobuild[%s] failure on %s for task %s during %s'
875 % (options.branch, platform.node(), failed_task, failed_stage),
876 text, logs)
879 def email_success(elapsed_time, log_base=None):
880 '''send an email to options.email about a successful build'''
881 if log_base is None:
882 log_base = gitroot
883 text = '''
884 Dear Developer,
886 Your autobuild on %s has succeeded after %.1f minutes.
888 ''' % (platform.node(), elapsed_time / 60.)
890 if options.restrict_tests:
891 text += """
892 The build was restricted to tests matching %s\n""" % options.restrict_tests
894 if options.keeplogs:
895 text += '''
897 you can get full logs of all tasks in this job here:
899 %s/logs.tar.gz
901 ''' % log_base
903 text += '''
904 The top commit for the tree that was built was:
907 ''' % top_commit_msg
909 logs = os.path.join(gitroot, 'logs.tar.gz')
910 send_email('autobuild[%s] success on %s' % (options.branch, platform.node()),
911 text, logs)
914 (options, args) = parser.parse_args()
916 if options.retry:
917 if options.rebase is None:
918 raise Exception('You can only use --retry if you also rebase')
920 testbase = "%s/b%u" % (options.testbase, os.getpid())
921 test_master = "%s/master" % testbase
922 test_prefix = "%s/prefix" % testbase
923 test_tmpdir = "%s/tmp" % testbase
924 os.environ['TMPDIR'] = test_tmpdir
926 # get the top commit message, for emails
927 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
929 try:
930 os.makedirs(testbase)
931 except Exception as reason:
932 raise Exception("Unable to create %s : %s" % (testbase, reason))
933 cleanup_list.append(testbase)
935 if options.daemon:
936 logfile = os.path.join(testbase, "log")
937 do_print("Forking into the background, writing progress to %s" % logfile)
938 daemonize(logfile)
940 write_pidfile(gitroot + "/autobuild.pid")
942 start_time = time.time()
944 while True:
945 try:
946 run_cmd("rm -rf %s" % test_tmpdir, show=True)
947 os.makedirs(test_tmpdir)
948 # The waf uninstall code removes empty directories all the way
949 # up the tree. Creating a file in test_tmpdir stops it from
950 # being removed.
951 run_cmd("touch %s" % os.path.join(test_tmpdir,
952 ".directory-is-not-empty"), show=True)
953 run_cmd("stat %s" % test_tmpdir, show=True)
954 run_cmd("stat %s" % testbase, show=True)
955 run_cmd("git clone --recursive --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
956 except Exception:
957 cleanup()
958 raise
960 try:
961 try:
962 if options.rebase is not None:
963 rebase_tree(options.rebase, rebase_branch=options.branch)
964 except Exception:
965 cleanup_list.append(gitroot + "/autobuild.pid")
966 cleanup()
967 elapsed_time = time.time() - start_time
968 email_failure(-1, 'rebase', 'rebase', 'rebase',
969 'rebase on %s failed' % options.branch,
970 elapsed_time, log_base=options.log_base)
971 sys.exit(1)
972 blist = buildlist(args, options.rebase, rebase_branch=options.branch)
973 if options.tail:
974 blist.start_tail()
975 (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
976 if status != 0 or errstr != "retry":
977 break
978 cleanup()
979 except Exception:
980 cleanup()
981 raise
983 cleanup_list.append(gitroot + "/autobuild.pid")
985 do_print(errstr)
987 blist.kill_kids()
988 if options.tail:
989 do_print("waiting for tail to flush")
990 time.sleep(1)
992 elapsed_time = time.time() - start_time
993 if status == 0:
994 if options.passcmd is not None:
995 do_print("Running passcmd: %s" % options.passcmd)
996 run_cmd(options.passcmd, dir=test_master)
997 if options.pushto is not None:
998 push_to(options.pushto, push_branch=options.branch)
999 if options.keeplogs or options.attach_logs:
1000 blist.tarlogs("logs.tar.gz")
1001 do_print("Logs in logs.tar.gz")
1002 if options.always_email:
1003 email_success(elapsed_time, log_base=options.log_base)
1004 blist.remove_logs()
1005 cleanup()
1006 do_print(errstr)
1007 sys.exit(0)
1009 # something failed, gather a tar of the logs
1010 blist.tarlogs("logs.tar.gz")
1012 if options.email is not None:
1013 email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1014 elapsed_time, log_base=options.log_base)
1015 else:
1016 elapsed_minutes = elapsed_time / 60.0
1017 print('''
1019 ####################################################################
1021 AUTOBUILD FAILURE
1023 Your autobuild[%s] on %s failed after %.1f minutes
1024 when trying to test %s with the following error:
1028 the autobuild has been abandoned. Please fix the error and resubmit.
1030 ####################################################################
1032 ''' % (options.branch, platform.node(), elapsed_minutes, failed_task, errstr))
1034 cleanup()
1035 do_print(errstr)
1036 do_print("Logs in logs.tar.gz")
1037 sys.exit(status)