lib:tevent: Use correct C99 initializer for tevent_req
[Samba.git] / script / autobuild.py
blob2ea9e55b932775b3b83097fcafe3d6783305d8db
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',
612 'free',
613 'cat /proc/cpuinfo',
614 'cc --version',
615 'df -m .',
616 'df -m %s' % testbase]:
617 out = run_cmd(cmd, output=True, checkfail=False)
618 print('### %s' % cmd, file=f)
619 print(out.decode('utf8', 'backslashreplace'), file=f)
620 print(file=f)
621 f.close()
622 return filename
624 def tarlogs(self, fname):
625 tar = tarfile.open(fname, "w:gz")
626 for b in self.tlist:
627 tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
628 tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
629 if os.path.exists("autobuild.log"):
630 tar.add("autobuild.log")
631 sys_info = self.write_system_info()
632 tar.add(sys_info)
633 tar.close()
635 def remove_logs(self):
636 for b in self.tlist:
637 os.unlink(b.stdout_path)
638 os.unlink(b.stderr_path)
640 def start_tail(self):
641 cmd = ["tail", "-f"]
642 for b in self.tlist:
643 cmd.append(b.stdout_path)
644 cmd.append(b.stderr_path)
645 self.tail_proc = Popen(cmd, close_fds=True)
648 def cleanup():
649 if options.nocleanup:
650 return
651 run_cmd("stat %s || true" % test_tmpdir, show=True)
652 run_cmd("stat %s" % testbase, show=True)
653 do_print("Cleaning up %r" % cleanup_list)
654 for d in cleanup_list:
655 run_cmd("rm -rf %s" % d)
658 def find_git_root():
659 '''get to the top of the git repo'''
660 p = os.getcwd()
661 while p != '/':
662 if os.path.isdir(os.path.join(p, ".git")):
663 return p
664 p = os.path.abspath(os.path.join(p, '..'))
665 return None
668 def daemonize(logfile):
669 pid = os.fork()
670 if pid == 0: # Parent
671 os.setsid()
672 pid = os.fork()
673 if pid != 0: # Actual daemon
674 os._exit(0)
675 else: # Grandparent
676 os._exit(0)
678 import resource # Resource usage information.
679 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
680 if maxfd == resource.RLIM_INFINITY:
681 maxfd = 1024 # Rough guess at maximum number of open file descriptors.
682 for fd in range(0, maxfd):
683 try:
684 os.close(fd)
685 except OSError:
686 pass
687 os.open(logfile, os.O_RDWR | os.O_CREAT)
688 os.dup2(0, 1)
689 os.dup2(0, 2)
692 def write_pidfile(fname):
693 '''write a pid file, cleanup on exit'''
694 f = open(fname, mode='w')
695 f.write("%u\n" % os.getpid())
696 f.close()
699 def rebase_tree(rebase_url, rebase_branch="master"):
700 rebase_remote = "rebaseon"
701 do_print("Rebasing on %s" % rebase_url)
702 run_cmd("git describe HEAD", show=True, dir=test_master)
703 run_cmd("git remote add -t %s %s %s" %
704 (rebase_branch, rebase_remote, rebase_url),
705 show=True, dir=test_master)
706 run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
707 if options.fix_whitespace:
708 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
709 (rebase_remote, rebase_branch),
710 show=True, dir=test_master)
711 else:
712 run_cmd("git rebase --force-rebase %s/%s" %
713 (rebase_remote, rebase_branch),
714 show=True, dir=test_master)
715 diff = run_cmd("git --no-pager diff HEAD %s/%s" %
716 (rebase_remote, rebase_branch),
717 dir=test_master, output=True)
718 if diff == '':
719 do_print("No differences between HEAD and %s/%s - exiting" %
720 (rebase_remote, rebase_branch))
721 sys.exit(0)
722 run_cmd("git describe %s/%s" %
723 (rebase_remote, rebase_branch),
724 show=True, dir=test_master)
725 run_cmd("git describe HEAD", show=True, dir=test_master)
726 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
727 (rebase_remote, rebase_branch),
728 show=True, dir=test_master)
731 def push_to(push_url, push_branch="master"):
732 push_remote = "pushto"
733 do_print("Pushing to %s" % push_url)
734 if options.mark:
735 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
736 run_cmd("git commit --amend -c HEAD", dir=test_master)
737 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
738 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
739 run_cmd("git remote add -t %s %s %s" %
740 (push_branch, push_remote, push_url),
741 show=True, dir=test_master)
742 run_cmd("git push %s +HEAD:%s" %
743 (push_remote, push_branch),
744 show=True, dir=test_master)
747 def_testbase = os.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os.getenv('USER'))
749 gitroot = find_git_root()
750 if gitroot is None:
751 raise Exception("Failed to find git root")
753 parser = OptionParser()
754 parser.add_option("", "--tail", help="show output while running", default=False, action="store_true")
755 parser.add_option("", "--keeplogs", help="keep logs", default=False, action="store_true")
756 parser.add_option("", "--nocleanup", help="don't remove test tree", default=False, action="store_true")
757 parser.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase,
758 default=def_testbase)
759 parser.add_option("", "--passcmd", help="command to run on success", default=None)
760 parser.add_option("", "--verbose", help="show all commands as they are run",
761 default=False, action="store_true")
762 parser.add_option("", "--rebase", help="rebase on the given tree before testing",
763 default=None, type='str')
764 parser.add_option("", "--pushto", help="push to a git url on success",
765 default=None, type='str')
766 parser.add_option("", "--mark", help="add a Tested-By signoff before pushing",
767 default=False, action="store_true")
768 parser.add_option("", "--fix-whitespace", help="fix whitespace on rebase",
769 default=False, action="store_true")
770 parser.add_option("", "--retry", help="automatically retry if master changes",
771 default=False, action="store_true")
772 parser.add_option("", "--email", help="send email to the given address on failure",
773 type='str', default=None)
774 parser.add_option("", "--email-from", help="send email from the given address",
775 type='str', default="autobuild@samba.org")
776 parser.add_option("", "--email-server", help="send email via the given server",
777 type='str', default='localhost')
778 parser.add_option("", "--always-email", help="always send email, even on success",
779 action="store_true")
780 parser.add_option("", "--daemon", help="daemonize after initial setup",
781 action="store_true")
782 parser.add_option("", "--branch", help="the branch to work on (default=master)",
783 default="master", type='str')
784 parser.add_option("", "--log-base", help="location where the logs can be found (default=cwd)",
785 default=gitroot, type='str')
786 parser.add_option("", "--attach-logs", help="Attach logs to mails sent on success/failure?",
787 default=False, action="store_true")
788 parser.add_option("", "--restrict-tests", help="run as make test with this TESTS= regex",
789 default='')
792 def send_email(subject, text, log_tar):
793 if options.email is None:
794 do_print("not sending email because the recipient is not set")
795 do_print("the text content would have been:\n\nSubject: %s\n\n%s" %
796 (subject, text))
797 return
798 outer = MIMEMultipart()
799 outer['Subject'] = subject
800 outer['To'] = options.email
801 outer['From'] = options.email_from
802 outer['Date'] = email.utils.formatdate(localtime=True)
803 outer.preamble = 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
804 outer.attach(MIMEText(text, 'plain'))
805 if options.attach_logs:
806 fp = open(log_tar, 'rb')
807 msg = MIMEApplication(fp.read(), 'gzip', email.encoders.encode_base64)
808 fp.close()
809 # Set the filename parameter
810 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(log_tar))
811 outer.attach(msg)
812 content = outer.as_string()
813 s = smtplib.SMTP(options.email_server)
814 s.sendmail(options.email_from, [options.email], content)
815 s.set_debuglevel(1)
816 s.quit()
819 def email_failure(status, failed_task, failed_stage, failed_tag, errstr,
820 elapsed_time, log_base=None, add_log_tail=True):
821 '''send an email to options.email about the failure'''
822 elapsed_minutes = elapsed_time / 60.0
823 if log_base is None:
824 log_base = gitroot
825 text = '''
826 Dear Developer,
828 Your autobuild on %s failed after %.1f minutes
829 when trying to test %s with the following error:
833 the autobuild has been abandoned. Please fix the error and resubmit.
835 A summary of the autobuild process is here:
837 %s/autobuild.log
838 ''' % (platform.node(), elapsed_minutes, failed_task, errstr, log_base)
840 if options.restrict_tests:
841 text += """
842 The build was restricted to tests matching %s\n""" % options.restrict_tests
844 if failed_task != 'rebase':
845 text += '''
846 You can see logs of the failed task here:
848 %s/%s.stdout
849 %s/%s.stderr
851 or you can get full logs of all tasks in this job here:
853 %s/logs.tar.gz
855 The top commit for the tree that was built was:
859 ''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
861 if add_log_tail:
862 f = open("%s/%s.stdout" % (gitroot, failed_tag), 'r')
863 lines = f.readlines()
864 log_tail = "".join(lines[-50:])
865 num_lines = len(lines)
866 if num_lines < 50:
867 # Also include stderr (compile failures) if < 50 lines of stdout
868 f = open("%s/%s.stderr" % (gitroot, failed_tag), 'r')
869 log_tail += "".join(f.readlines()[-(50 - num_lines):])
871 text += '''
872 The last 50 lines of log messages:
875 ''' % log_tail
876 f.close()
878 logs = os.path.join(gitroot, 'logs.tar.gz')
879 send_email('autobuild[%s] failure on %s for task %s during %s'
880 % (options.branch, platform.node(), failed_task, failed_stage),
881 text, logs)
884 def email_success(elapsed_time, log_base=None):
885 '''send an email to options.email about a successful build'''
886 if log_base is None:
887 log_base = gitroot
888 text = '''
889 Dear Developer,
891 Your autobuild on %s has succeeded after %.1f minutes.
893 ''' % (platform.node(), elapsed_time / 60.)
895 if options.restrict_tests:
896 text += """
897 The build was restricted to tests matching %s\n""" % options.restrict_tests
899 if options.keeplogs:
900 text += '''
902 you can get full logs of all tasks in this job here:
904 %s/logs.tar.gz
906 ''' % log_base
908 text += '''
909 The top commit for the tree that was built was:
912 ''' % top_commit_msg
914 logs = os.path.join(gitroot, 'logs.tar.gz')
915 send_email('autobuild[%s] success on %s' % (options.branch, platform.node()),
916 text, logs)
919 (options, args) = parser.parse_args()
921 if options.retry:
922 if options.rebase is None:
923 raise Exception('You can only use --retry if you also rebase')
925 testbase = "%s/b%u" % (options.testbase, os.getpid())
926 test_master = "%s/master" % testbase
927 test_prefix = "%s/prefix" % testbase
928 test_tmpdir = "%s/tmp" % testbase
929 os.environ['TMPDIR'] = test_tmpdir
931 # get the top commit message, for emails
932 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
933 top_commit_msg = top_commit_msg.decode('utf-8', 'backslashreplace')
935 try:
936 os.makedirs(testbase)
937 except Exception as reason:
938 raise Exception("Unable to create %s : %s" % (testbase, reason))
939 cleanup_list.append(testbase)
941 if options.daemon:
942 logfile = os.path.join(testbase, "log")
943 do_print("Forking into the background, writing progress to %s" % logfile)
944 daemonize(logfile)
946 write_pidfile(gitroot + "/autobuild.pid")
948 start_time = time.time()
950 while True:
951 try:
952 run_cmd("rm -rf %s" % test_tmpdir, show=True)
953 os.makedirs(test_tmpdir)
954 # The waf uninstall code removes empty directories all the way
955 # up the tree. Creating a file in test_tmpdir stops it from
956 # being removed.
957 run_cmd("touch %s" % os.path.join(test_tmpdir,
958 ".directory-is-not-empty"), show=True)
959 run_cmd("stat %s" % test_tmpdir, show=True)
960 run_cmd("stat %s" % testbase, show=True)
961 run_cmd("git clone --recursive --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
962 except Exception:
963 cleanup()
964 raise
966 try:
967 try:
968 if options.rebase is not None:
969 rebase_tree(options.rebase, rebase_branch=options.branch)
970 except Exception:
971 cleanup_list.append(gitroot + "/autobuild.pid")
972 cleanup()
973 elapsed_time = time.time() - start_time
974 email_failure(-1, 'rebase', 'rebase', 'rebase',
975 'rebase on %s failed' % options.branch,
976 elapsed_time, log_base=options.log_base)
977 sys.exit(1)
978 blist = buildlist(args, options.rebase, rebase_branch=options.branch)
979 if options.tail:
980 blist.start_tail()
981 (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
982 if status != 0 or errstr != "retry":
983 break
984 cleanup()
985 except Exception:
986 cleanup()
987 raise
989 cleanup_list.append(gitroot + "/autobuild.pid")
991 do_print(errstr)
993 blist.kill_kids()
994 if options.tail:
995 do_print("waiting for tail to flush")
996 time.sleep(1)
998 elapsed_time = time.time() - start_time
999 if status == 0:
1000 if options.passcmd is not None:
1001 do_print("Running passcmd: %s" % options.passcmd)
1002 run_cmd(options.passcmd, dir=test_master)
1003 if options.pushto is not None:
1004 push_to(options.pushto, push_branch=options.branch)
1005 if options.keeplogs or options.attach_logs:
1006 blist.tarlogs("logs.tar.gz")
1007 do_print("Logs in logs.tar.gz")
1008 if options.always_email:
1009 email_success(elapsed_time, log_base=options.log_base)
1010 blist.remove_logs()
1011 cleanup()
1012 do_print(errstr)
1013 sys.exit(0)
1015 # something failed, gather a tar of the logs
1016 blist.tarlogs("logs.tar.gz")
1018 if options.email is not None:
1019 email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1020 elapsed_time, log_base=options.log_base)
1021 else:
1022 elapsed_minutes = elapsed_time / 60.0
1023 print('''
1025 ####################################################################
1027 AUTOBUILD FAILURE
1029 Your autobuild[%s] on %s failed after %.1f minutes
1030 when trying to test %s with the following error:
1034 the autobuild has been abandoned. Please fix the error and resubmit.
1036 ####################################################################
1038 ''' % (options.branch, platform.node(), elapsed_minutes, failed_task, errstr))
1040 cleanup()
1041 do_print(errstr)
1042 do_print("Logs in logs.tar.gz")
1043 sys.exit(status)