source: vendor/current/script/autobuild.py

Last change on this file was 989, checked in by Silvan Scherrer, 9 years ago

Samba Server: update vendor to version 4.4.7

File size: 31.3 KB
Line 
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
5
6from subprocess import call, check_call,Popen, PIPE
7import os, tarfile, sys, time
8from optparse import OptionParser
9import smtplib
10import email
11from email.mime.text import MIMEText
12from email.mime.base import MIMEBase
13from email.mime.application import MIMEApplication
14from email.mime.multipart import MIMEMultipart
15from distutils.sysconfig import get_python_lib
16import platform
17
18# This speeds up testing remarkably.
19os.environ['TDB_NO_FSYNC'] = '1'
20
21cleanup_list = []
22
23builddirs = {
24 "ctdb" : "ctdb",
25 "samba" : ".",
26 "samba-xc" : ".",
27 "samba-ctdb" : ".",
28 "samba-libs" : ".",
29 "samba-static" : ".",
30 "ldb" : "lib/ldb",
31 "tdb" : "lib/tdb",
32 "talloc" : "lib/talloc",
33 "replace" : "lib/replace",
34 "tevent" : "lib/tevent",
35 "pidl" : "pidl",
36 "pass" : ".",
37 "fail" : ".",
38 "retry" : "."
39 }
40
41defaulttasks = [ "ctdb", "samba", "samba-xc", "samba-ctdb", "samba-libs", "samba-static", "ldb", "tdb", "talloc", "replace", "tevent", "pidl" ]
42
43samba_configure_params = " --picky-developer ${PREFIX} --with-profiling-data"
44
45samba_libs_envvars = "PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH"
46samba_libs_envvars += " PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig"
47samba_libs_envvars += " ADDITIONAL_CFLAGS='-Wmissing-prototypes'"
48samba_libs_configure_base = samba_libs_envvars + " ./configure --abi-check --enable-debug --picky-developer -C ${PREFIX}"
49samba_libs_configure_libs = samba_libs_configure_base + " --bundled-libraries=NONE"
50samba_libs_configure_samba = samba_libs_configure_base + " --bundled-libraries=!talloc,!pytalloc-util,!tdb,!pytdb,!ldb,!pyldb,!pyldb-util,!tevent,!pytevent"
51
52tasks = {
53 "ctdb" : [ ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
54 ("configure", "./configure ${PREFIX}", "text/plain"),
55 ("make", "make all", "text/plain"),
56 ("install", "make install", "text/plain"),
57 ("test", "make autotest", "text/plain"),
58 ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
59 ("clean", "make clean", "text/plain") ],
60
61 # We have 'test' before 'install' because, 'test' should work without 'install'
62 "samba" : [ ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
63 ("make", "make -j", "text/plain"),
64 ("test", "make test FAIL_IMMEDIATELY=1", "text/plain"),
65 ("install", "make install", "text/plain"),
66 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
67 ("clean", "make clean", "text/plain") ],
68
69 # Test cross-compile infrastructure
70 "samba-xc" : [ ("configure-native", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params, "text/plain"),
71 ("configure-cross-execute", "./configure.developer -b ./bin-xe --cross-compile --cross-execute=script/identity_cc.sh" \
72 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xe/ab" + samba_configure_params, "text/plain"),
73 ("configure-cross-answers", "./configure.developer -b ./bin-xa --cross-compile" \
74 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa/ab" + samba_configure_params, "text/plain"),
75 ("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")],
76
77
78 "samba-ctdb" : [ ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
79
80 # make sure we have tdb around:
81 ("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"),
82 ("tdb-make", "cd lib/tdb && make", "text/plain"),
83 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
84
85
86 # build samba with cluster support (also building ctdb):
87 ("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"),
88 ("samba-make", "make", "text/plain"),
89 ("samba-check", "./bin/smbd -b | grep CLUSTER_SUPPORT", "text/plain"),
90 ("samba-install", "make install", "text/plain"),
91 ("ctdb-check", "test -e ${PREFIX_DIR}/sbin/ctdbd", "text/plain"),
92
93 # clean up:
94 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
95 ("clean", "make clean", "text/plain"),
96 ("ctdb-clean", "cd ./ctdb && make clean", "text/plain") ],
97
98 "samba-libs" : [
99 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
100 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_libs, "text/plain"),
101 ("talloc-make", "cd lib/talloc && make", "text/plain"),
102 ("talloc-install", "cd lib/talloc && make install", "text/plain"),
103
104 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_libs, "text/plain"),
105 ("tdb-make", "cd lib/tdb && make", "text/plain"),
106 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
107
108 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_libs, "text/plain"),
109 ("tevent-make", "cd lib/tevent && make", "text/plain"),
110 ("tevent-install", "cd lib/tevent && make install", "text/plain"),
111
112 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_libs, "text/plain"),
113 ("ldb-make", "cd lib/ldb && make", "text/plain"),
114 ("ldb-install", "cd lib/ldb && make install", "text/plain"),
115
116 ("nondevel-configure", "./configure ${PREFIX}", "text/plain"),
117 ("nondevel-make", "make -j", "text/plain"),
118 ("nondevel-check", "./bin/smbd -b | grep WITH_NTVFS_FILESERVER && exit 1; exit 0", "text/plain"),
119 ("nondevel-install", "make install", "text/plain"),
120 ("nondevel-dist", "make dist", "text/plain"),
121
122 # retry with all modules shared
123 ("allshared-distclean", "make distclean", "text/plain"),
124 ("allshared-configure", samba_libs_configure_samba + " --with-shared-modules=ALL", "text/plain"),
125 ("allshared-make", "make -j", "text/plain")],
126
127 "samba-static" : [
128 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
129 # build with all modules static
130 ("allstatic-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=ALL", "text/plain"),
131 ("allstatic-make", "make -j", "text/plain"),
132
133 # retry without any required modules
134 ("none-distclean", "make distclean", "text/plain"),
135 ("none-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=!FORCED,!DEFAULT --with-shared-modules=!FORCED,!DEFAULT", "text/plain"),
136 ("none-make", "make -j", "text/plain"),
137
138 # retry with nonshared smbd and smbtorture
139 ("nonshared-distclean", "make distclean", "text/plain"),
140 ("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"),
141 ("nonshared-make", "make -j", "text/plain")],
142
143 "ldb" : [
144 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
145 ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
146 ("make", "make", "text/plain"),
147 ("install", "make install", "text/plain"),
148 ("test", "make test", "text/plain"),
149 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
150 ("distcheck", "make distcheck", "text/plain"),
151 ("clean", "make clean", "text/plain") ],
152
153 "tdb" : [
154 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
155 ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
156 ("make", "make", "text/plain"),
157 ("install", "make install", "text/plain"),
158 ("test", "make test", "text/plain"),
159 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
160 ("distcheck", "make distcheck", "text/plain"),
161 ("clean", "make clean", "text/plain") ],
162
163 "talloc" : [
164 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
165 ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
166 ("make", "make", "text/plain"),
167 ("install", "make install", "text/plain"),
168 ("test", "make test", "text/plain"),
169 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
170 ("distcheck", "make distcheck", "text/plain"),
171 ("clean", "make clean", "text/plain") ],
172
173 "replace" : [
174 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
175 ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
176 ("make", "make", "text/plain"),
177 ("install", "make install", "text/plain"),
178 ("test", "make test", "text/plain"),
179 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
180 ("distcheck", "make distcheck", "text/plain"),
181 ("clean", "make clean", "text/plain") ],
182
183 "tevent" : [
184 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
185 ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
186 ("make", "make", "text/plain"),
187 ("install", "make install", "text/plain"),
188 ("test", "make test", "text/plain"),
189 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
190 ("distcheck", "make distcheck", "text/plain"),
191 ("clean", "make clean", "text/plain") ],
192
193 "pidl" : [
194 ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
195 ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}", "text/plain"),
196 ("touch", "touch *.yp", "text/plain"),
197 ("make", "make", "text/plain"),
198 ("test", "make test", "text/plain"),
199 ("install", "make install", "text/plain"),
200 ("checkout-yapp-generated", "git checkout lib/Parse/Pidl/IDL.pm lib/Parse/Pidl/Expr.pm", "text/plain"),
201 ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
202 ("clean", "make clean", "text/plain") ],
203
204 # these are useful for debugging autobuild
205 'pass' : [ ("pass", 'echo passing && /bin/true', "text/plain") ],
206 'fail' : [ ("fail", 'echo failing && /bin/false', "text/plain") ]
207}
208
209def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
210 if show is None:
211 show = options.verbose
212 if show:
213 print("Running: '%s' in '%s'" % (cmd, dir))
214 if output:
215 return Popen([cmd], shell=True, stdout=PIPE, cwd=dir).communicate()[0]
216 elif checkfail:
217 return check_call(cmd, shell=True, cwd=dir)
218 else:
219 return call(cmd, shell=True, cwd=dir)
220
221
222class builder(object):
223 '''handle build of one directory'''
224
225 def __init__(self, name, sequence, cp=True):
226 self.name = name
227 self.dir = builddirs[name]
228
229 self.tag = self.name.replace('/', '_')
230 self.sequence = sequence
231 self.next = 0
232 self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
233 self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
234 if options.verbose:
235 print("stdout for %s in %s" % (self.name, self.stdout_path))
236 print("stderr for %s in %s" % (self.name, self.stderr_path))
237 run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
238 self.stdout = open(self.stdout_path, 'w')
239 self.stderr = open(self.stderr_path, 'w')
240 self.stdin = open("/dev/null", 'r')
241 self.sdir = "%s/%s" % (testbase, self.tag)
242 self.prefix = "%s/prefix/%s" % (testbase, self.tag)
243 run_cmd("rm -rf %s" % self.sdir)
244 cleanup_list.append(self.sdir)
245 cleanup_list.append(self.prefix)
246 os.makedirs(self.sdir)
247 run_cmd("rm -rf %s" % self.sdir)
248 if cp:
249 run_cmd("cp --recursive --link --archive %s %s" % (test_master, self.sdir), dir=test_master, show=True)
250 else:
251 run_cmd("git clone --recursive --shared %s %s" % (test_master, self.sdir), dir=test_master, show=True)
252 self.start_next()
253
254 def start_next(self):
255 if self.next == len(self.sequence):
256 print '%s: Completed OK' % self.name
257 self.done = True
258 return
259 (self.stage, self.cmd, self.output_mime_type) = self.sequence[self.next]
260 self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(standard_lib=1, prefix=self.prefix))
261 self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
262 self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
263# if self.output_mime_type == "text/x-subunit":
264# self.cmd += " | %s --immediate" % (os.path.join(os.path.dirname(__file__), "selftest/format-subunit"))
265 print '%s: [%s] Running %s' % (self.name, self.stage, self.cmd)
266 cwd = os.getcwd()
267 os.chdir("%s/%s" % (self.sdir, self.dir))
268 self.proc = Popen(self.cmd, shell=True,
269 stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
270 os.chdir(cwd)
271 self.next += 1
272
273
274class buildlist(object):
275 '''handle build of multiple directories'''
276
277 def __init__(self, tasklist, tasknames, rebase_url, rebase_branch="master"):
278 global tasks
279 self.tlist = []
280 self.tail_proc = None
281 self.retry = None
282 if tasknames == []:
283 tasknames = defaulttasks
284 else:
285 # If we are only running one test,
286 # do not sleep randomly to wait for it to start
287 os.environ['AUTOBUILD_RANDOM_SLEEP_OVERRIDE'] = '1'
288
289 for n in tasknames:
290 b = builder(n, tasks[n], cp=n is not "pidl")
291 self.tlist.append(b)
292 if options.retry:
293 rebase_remote = "rebaseon"
294 retry_task = [ ("retry",
295 '''set -e
296 git remote add -t %s %s %s
297 git fetch %s
298 while :; do
299 sleep 60
300 git describe %s/%s > old_remote_branch.desc
301 git fetch %s
302 git describe %s/%s > remote_branch.desc
303 diff old_remote_branch.desc remote_branch.desc
304 done
305 ''' % (
306 rebase_branch, rebase_remote, rebase_url,
307 rebase_remote,
308 rebase_remote, rebase_branch,
309 rebase_remote,
310 rebase_remote, rebase_branch
311 ),
312 "test/plain" ) ]
313
314 self.retry = builder('retry', retry_task, cp=False)
315 self.need_retry = False
316
317 def kill_kids(self):
318 if self.tail_proc is not None:
319 self.tail_proc.terminate()
320 self.tail_proc.wait()
321 self.tail_proc = None
322 if self.retry is not None:
323 self.retry.proc.terminate()
324 self.retry.proc.wait()
325 self.retry = None
326 for b in self.tlist:
327 if b.proc is not None:
328 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.sdir, checkfail=False)
329 b.proc.terminate()
330 b.proc.wait()
331 b.proc = None
332
333 def wait_one(self):
334 while True:
335 none_running = True
336 for b in self.tlist:
337 if b.proc is None:
338 continue
339 none_running = False
340 b.status = b.proc.poll()
341 if b.status is None:
342 continue
343 b.proc = None
344 return b
345 if options.retry:
346 ret = self.retry.proc.poll()
347 if ret is not None:
348 self.need_retry = True
349 self.retry = None
350 return None
351 if none_running:
352 return None
353 time.sleep(0.1)
354
355 def run(self):
356 while True:
357 b = self.wait_one()
358 if options.retry and self.need_retry:
359 self.kill_kids()
360 print("retry needed")
361 return (0, None, None, None, "retry")
362 if b is None:
363 break
364 if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
365 self.kill_kids()
366 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
367 b.start_next()
368 self.kill_kids()
369 return (0, None, None, None, "All OK")
370
371 def write_system_info(self):
372 filename = 'system-info.txt'
373 f = open(filename, 'w')
374 for cmd in ['uname -a', 'free', 'cat /proc/cpuinfo']:
375 print >>f, '### %s' % cmd
376 print >>f, run_cmd(cmd, output=True, checkfail=False)
377 print >>f
378 f.close()
379 return filename
380
381 def tarlogs(self, fname):
382 tar = tarfile.open(fname, "w:gz")
383 for b in self.tlist:
384 tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
385 tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
386 if os.path.exists("autobuild.log"):
387 tar.add("autobuild.log")
388 sys_info = self.write_system_info()
389 tar.add(sys_info)
390 tar.close()
391
392 def remove_logs(self):
393 for b in self.tlist:
394 os.unlink(b.stdout_path)
395 os.unlink(b.stderr_path)
396
397 def start_tail(self):
398 cwd = os.getcwd()
399 cmd = "tail -f *.stdout *.stderr"
400 os.chdir(gitroot)
401 self.tail_proc = Popen(cmd, shell=True)
402 os.chdir(cwd)
403
404
405def cleanup():
406 if options.nocleanup:
407 return
408 print("Cleaning up ....")
409 for d in cleanup_list:
410 run_cmd("rm -rf %s" % d)
411
412
413def find_git_root():
414 '''get to the top of the git repo'''
415 p=os.getcwd()
416 while p != '/':
417 if os.path.isdir(os.path.join(p, ".git")):
418 return p
419 p = os.path.abspath(os.path.join(p, '..'))
420 return None
421
422
423def daemonize(logfile):
424 pid = os.fork()
425 if pid == 0: # Parent
426 os.setsid()
427 pid = os.fork()
428 if pid != 0: # Actual daemon
429 os._exit(0)
430 else: # Grandparent
431 os._exit(0)
432
433 import resource # Resource usage information.
434 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
435 if maxfd == resource.RLIM_INFINITY:
436 maxfd = 1024 # Rough guess at maximum number of open file descriptors.
437 for fd in range(0, maxfd):
438 try:
439 os.close(fd)
440 except OSError:
441 pass
442 os.open(logfile, os.O_RDWR | os.O_CREAT)
443 os.dup2(0, 1)
444 os.dup2(0, 2)
445
446def write_pidfile(fname):
447 '''write a pid file, cleanup on exit'''
448 f = open(fname, mode='w')
449 f.write("%u\n" % os.getpid())
450 f.close()
451
452
453def rebase_tree(rebase_url, rebase_branch = "master"):
454 rebase_remote = "rebaseon"
455 print("Rebasing on %s" % rebase_url)
456 run_cmd("git describe HEAD", show=True, dir=test_master)
457 run_cmd("git remote add -t %s %s %s" %
458 (rebase_branch, rebase_remote, rebase_url),
459 show=True, dir=test_master)
460 run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
461 if options.fix_whitespace:
462 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
463 (rebase_remote, rebase_branch),
464 show=True, dir=test_master)
465 else:
466 run_cmd("git rebase --force-rebase %s/%s" %
467 (rebase_remote, rebase_branch),
468 show=True, dir=test_master)
469 diff = run_cmd("git --no-pager diff HEAD %s/%s" %
470 (rebase_remote, rebase_branch),
471 dir=test_master, output=True)
472 if diff == '':
473 print("No differences between HEAD and %s/%s - exiting" %
474 (rebase_remote, rebase_branch))
475 sys.exit(0)
476 run_cmd("git describe %s/%s" %
477 (rebase_remote, rebase_branch),
478 show=True, dir=test_master)
479 run_cmd("git describe HEAD", show=True, dir=test_master)
480 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
481 (rebase_remote, rebase_branch),
482 show=True, dir=test_master)
483
484def push_to(push_url, push_branch = "master"):
485 push_remote = "pushto"
486 print("Pushing to %s" % push_url)
487 if options.mark:
488 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
489 run_cmd("git commit --amend -c HEAD", dir=test_master)
490 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
491 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
492 run_cmd("git remote add -t %s %s %s" %
493 (push_branch, push_remote, push_url),
494 show=True, dir=test_master)
495 run_cmd("git push %s +HEAD:%s" %
496 (push_remote, push_branch),
497 show=True, dir=test_master)
498
499def_testbase = os.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os.getenv('USER'))
500
501gitroot = find_git_root()
502if gitroot is None:
503 raise Exception("Failed to find git root")
504
505parser = OptionParser()
506parser.add_option("", "--tail", help="show output while running", default=False, action="store_true")
507parser.add_option("", "--keeplogs", help="keep logs", default=False, action="store_true")
508parser.add_option("", "--nocleanup", help="don't remove test tree", default=False, action="store_true")
509parser.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase,
510 default=def_testbase)
511parser.add_option("", "--passcmd", help="command to run on success", default=None)
512parser.add_option("", "--verbose", help="show all commands as they are run",
513 default=False, action="store_true")
514parser.add_option("", "--rebase", help="rebase on the given tree before testing",
515 default=None, type='str')
516parser.add_option("", "--pushto", help="push to a git url on success",
517 default=None, type='str')
518parser.add_option("", "--mark", help="add a Tested-By signoff before pushing",
519 default=False, action="store_true")
520parser.add_option("", "--fix-whitespace", help="fix whitespace on rebase",
521 default=False, action="store_true")
522parser.add_option("", "--retry", help="automatically retry if master changes",
523 default=False, action="store_true")
524parser.add_option("", "--email", help="send email to the given address on failure",
525 type='str', default=None)
526parser.add_option("", "--email-from", help="send email from the given address",
527 type='str', default="autobuild@samba.org")
528parser.add_option("", "--email-server", help="send email via the given server",
529 type='str', default='localhost')
530parser.add_option("", "--always-email", help="always send email, even on success",
531 action="store_true")
532parser.add_option("", "--daemon", help="daemonize after initial setup",
533 action="store_true")
534parser.add_option("", "--branch", help="the branch to work on (default=master)",
535 default="master", type='str')
536parser.add_option("", "--log-base", help="location where the logs can be found (default=cwd)",
537 default=gitroot, type='str')
538parser.add_option("", "--attach-logs", help="Attach logs to mails sent on success/failure?",
539 default=False, action="store_true")
540
541def send_email(subject, text, log_tar):
542 outer = MIMEMultipart()
543 outer['Subject'] = subject
544 outer['To'] = options.email
545 outer['From'] = options.email_from
546 outer['Date'] = email.utils.formatdate(localtime = True)
547 outer.preamble = 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
548 outer.attach(MIMEText(text, 'plain'))
549 if options.attach_logs:
550 fp = open(log_tar, 'rb')
551 msg = MIMEApplication(fp.read(), 'gzip', email.encoders.encode_base64)
552 fp.close()
553 # Set the filename parameter
554 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(log_tar))
555 outer.attach(msg)
556 content = outer.as_string()
557 s = smtplib.SMTP(options.email_server)
558 s.sendmail(options.email_from, [options.email], content)
559 s.set_debuglevel(1)
560 s.quit()
561
562def email_failure(status, failed_task, failed_stage, failed_tag, errstr,
563 elapsed_time, log_base=None, add_log_tail=True):
564 '''send an email to options.email about the failure'''
565 elapsed_minutes = elapsed_time / 60.0
566 user = os.getenv("USER")
567 if log_base is None:
568 log_base = gitroot
569 text = '''
570Dear Developer,
571
572Your autobuild on %s failed after %.1f minutes
573when trying to test %s with the following error:
574
575 %s
576
577the autobuild has been abandoned. Please fix the error and resubmit.
578
579A summary of the autobuild process is here:
580
581 %s/autobuild.log
582''' % (platform.node(), elapsed_minutes, failed_task, errstr, log_base)
583
584 if failed_task != 'rebase':
585 text += '''
586You can see logs of the failed task here:
587
588 %s/%s.stdout
589 %s/%s.stderr
590
591or you can get full logs of all tasks in this job here:
592
593 %s/logs.tar.gz
594
595The top commit for the tree that was built was:
596
597%s
598
599''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
600
601 if add_log_tail:
602 f = open("%s/%s.stdout" % (gitroot, failed_tag), 'r')
603 lines = f.readlines()
604 log_tail = "".join(lines[-50:])
605 num_lines = len(lines)
606 if num_lines < 50:
607 # Also include stderr (compile failures) if < 50 lines of stdout
608 f = open("%s/%s.stderr" % (gitroot, failed_tag), 'r')
609 log_tail += "".join(f.readlines()[-(50-num_lines):])
610
611 text += '''
612The last 50 lines of log messages:
613
614%s
615 ''' % log_tail
616 f.close()
617
618 logs = os.path.join(gitroot, 'logs.tar.gz')
619 send_email('autobuild[%s] failure on %s for task %s during %s'
620 % (options.branch, platform.node(), failed_task, failed_stage),
621 text, logs)
622
623def email_success(elapsed_time, log_base=None):
624 '''send an email to options.email about a successful build'''
625 user = os.getenv("USER")
626 if log_base is None:
627 log_base = gitroot
628 text = '''
629Dear Developer,
630
631Your autobuild on %s has succeeded after %.1f minutes.
632
633''' % (platform.node(), elapsed_time / 60.)
634
635 if options.keeplogs:
636 text += '''
637
638you can get full logs of all tasks in this job here:
639
640 %s/logs.tar.gz
641
642''' % log_base
643
644 text += '''
645The top commit for the tree that was built was:
646
647%s
648''' % top_commit_msg
649
650 logs = os.path.join(gitroot, 'logs.tar.gz')
651 send_email('autobuild[%s] success on %s' % (options.branch, platform.node()),
652 text, logs)
653
654
655(options, args) = parser.parse_args()
656
657if options.retry:
658 if options.rebase is None:
659 raise Exception('You can only use --retry if you also rebase')
660
661testbase = "%s/b%u" % (options.testbase, os.getpid())
662test_master = "%s/master" % testbase
663
664# get the top commit message, for emails
665top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
666
667try:
668 os.makedirs(testbase)
669except Exception, reason:
670 raise Exception("Unable to create %s : %s" % (testbase, reason))
671cleanup_list.append(testbase)
672
673if options.daemon:
674 logfile = os.path.join(testbase, "log")
675 print "Forking into the background, writing progress to %s" % logfile
676 daemonize(logfile)
677
678write_pidfile(gitroot + "/autobuild.pid")
679
680start_time = time.time()
681
682while True:
683 try:
684 run_cmd("rm -rf %s" % test_master)
685 cleanup_list.append(test_master)
686 run_cmd("git clone --recursive --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
687 except Exception:
688 cleanup()
689 raise
690
691 try:
692 try:
693 if options.rebase is not None:
694 rebase_tree(options.rebase, rebase_branch=options.branch)
695 except Exception:
696 cleanup_list.append(gitroot + "/autobuild.pid")
697 cleanup()
698 elapsed_time = time.time() - start_time
699 email_failure(-1, 'rebase', 'rebase', 'rebase',
700 'rebase on %s failed' % options.branch,
701 elapsed_time, log_base=options.log_base)
702 sys.exit(1)
703 blist = buildlist(tasks, args, options.rebase, rebase_branch=options.branch)
704 if options.tail:
705 blist.start_tail()
706 (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
707 if status != 0 or errstr != "retry":
708 break
709 cleanup()
710 except Exception:
711 cleanup()
712 raise
713
714cleanup_list.append(gitroot + "/autobuild.pid")
715
716blist.kill_kids()
717if options.tail:
718 print("waiting for tail to flush")
719 time.sleep(1)
720
721elapsed_time = time.time() - start_time
722if status == 0:
723 print errstr
724 if options.passcmd is not None:
725 print("Running passcmd: %s" % options.passcmd)
726 run_cmd(options.passcmd, dir=test_master)
727 if options.pushto is not None:
728 push_to(options.pushto, push_branch=options.branch)
729 if options.keeplogs or options.attach_logs:
730 blist.tarlogs("logs.tar.gz")
731 print("Logs in logs.tar.gz")
732 if options.always_email:
733 email_success(elapsed_time, log_base=options.log_base)
734 blist.remove_logs()
735 cleanup()
736 print(errstr)
737 sys.exit(0)
738
739# something failed, gather a tar of the logs
740blist.tarlogs("logs.tar.gz")
741
742if options.email is not None:
743 email_failure(status, failed_task, failed_stage, failed_tag, errstr,
744 elapsed_time, log_base=options.log_base)
745else:
746 elapsed_minutes = elapsed_time / 60.0
747 print '''
748
749####################################################################
750
751AUTOBUILD FAILURE
752
753Your autobuild[%s] on %s failed after %.1f minutes
754when trying to test %s with the following error:
755
756 %s
757
758the autobuild has been abandoned. Please fix the error and resubmit.
759
760####################################################################
761
762''' % (options.branch, platform.node(), elapsed_minutes, failed_task, errstr)
763
764cleanup()
765print(errstr)
766print("Logs in logs.tar.gz")
767sys.exit(status)
Note: See TracBrowser for help on using the repository browser.