script/autobuild: fix path to random-sleep.sh
[sfrench/samba-autobuild/.git] / script / autobuild.py
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
6 from subprocess import call, check_call,Popen, PIPE
7 import os, tarfile, sys, time
8 from optparse import OptionParser
9 import smtplib
10 from email.mime.text import MIMEText
11 from distutils.sysconfig import get_python_lib
12
13 samba_master = os.getenv('SAMBA_MASTER', 'git://git.samba.org/samba.git')
14 samba_master_ssh = os.getenv('SAMBA_MASTER_SSH', 'git+ssh://git.samba.org/data/git/samba.git')
15
16 # This speeds up testing remarkably.
17 os.environ['TDB_NO_FSYNC'] = '1'
18
19 cleanup_list = []
20
21 builddirs = {
22     "samba3"  : "source3",
23     "samba3-ctdb" : "source3",
24     "samba"  : ".",
25     "samba-ctdb" : ".",
26     "samba-libs"  : ".",
27     "ldb"     : "lib/ldb",
28     "tdb"     : "lib/tdb",
29     "ntdb"    : "lib/ntdb",
30     "talloc"  : "lib/talloc",
31     "replace" : "lib/replace",
32     "tevent"  : "lib/tevent",
33     "pidl"    : "pidl",
34     "pass"    : ".",
35     "fail"    : ".",
36     "retry"   : "."
37     }
38
39 defaulttasks = [ "samba3", "samba3-ctdb", "samba", "samba-ctdb", "samba-libs", "ldb", "tdb", "ntdb", "talloc", "replace", "tevent", "pidl" ]
40
41 tasks = {
42     "samba3" : [ ("autogen", "./autogen.sh", "text/plain"),
43                  ("configure", "./configure.developer ${PREFIX}", "text/plain"),
44                  ("make basics", "make basics", "text/plain"),
45                  # we split 'make -j 4', 'make bin/smbtorture4' and 'make -j 4 everything'
46                  # because it makes it much easier to find errors.
47                  ("make", "make -j 4", "text/plain"), # don't use too many processes
48                  ("make bin/smbtorture4", "make bin/smbtorture4", "text/plain"),
49                  ("make everything", "make -j 4 everything", "text/plain"),
50                  ("install", "make install", "text/plain"),
51                  ("test", "make test FAIL_IMMEDIATELY=1", "text/plain"),
52                  ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
53                  ("clean", "make clean", "text/plain") ],
54
55     "samba3-ctdb" : [ ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
56                       ("autogen", "./autogen.sh", "text/plain"),
57                       ("configure", "./configure.developer ${PREFIX} --with-cluster-support --with-ctdb=../ctdb", "text/plain"),
58                       ("make basics", "make basics", "text/plain"),
59                       ("make", "make all", "text/plain"), # don't use too many processes
60                       ("check", "LD_LIBRARY_PATH=./bin ./bin/smbd -b | grep CLUSTER_SUPPORT", "text/plain"),
61                       ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
62                       ("clean", "make clean", "text/plain") ],
63
64     # We have 'test' before 'install' because, 'test' should work without 'install'
65     "samba" : [ ("configure", "./configure.developer ${PREFIX} --with-selftest-prefix=./bin/ab", "text/plain"),
66                 ("make", "make -j", "text/plain"),
67                 ("test", "make test FAIL_IMMEDIATELY=1", "text/plain"),
68                 ("install", "make install", "text/plain"),
69                 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
70                 ("clean", "make clean", "text/plain") ],
71
72     "samba-ctdb" : [ ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
73
74                      # make sure we have tdb around:
75                      ("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"),
76                      ("tdb-make", "cd lib/tdb && make", "text/plain"),
77                      ("tdb-install", "cd lib/tdb && make install", "text/plain"),
78
79                      # install the ctdb headers under the prefix:
80                      ("ctdb-header-install", "cp ./ctdb/include/* ${PREFIX_DIR}/include", "text/plain"),
81                      ("ctdb-header-ls", "ls ${PREFIX_DIR}/include/ctdb.h", "text/plain"),
82
83                      ("configure", "PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig ./configure.developer ${PREFIX} --with-selftest-prefix=./bin/ab --with-cluster-support --with-ctdb-dir=${PREFIX_DIR} --bundled-libraries=!tdb", "text/plain"),
84                      ("make", "make", "text/plain"),
85                      ("check", "./bin/smbd -b | grep CLUSTER_SUPPORT", "text/plain"),
86                      ("install", "make install", "text/plain"),
87                      ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
88                      ("clean", "make clean", "text/plain") ],
89
90     "samba-libs" : [
91                       ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
92                       ("talloc-configure", "cd lib/talloc && 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"),
93                       ("talloc-make", "cd lib/talloc && make", "text/plain"),
94                       ("talloc-install", "cd lib/talloc && make install", "text/plain"),
95
96                       ("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"),
97                       ("tdb-make", "cd lib/tdb && make", "text/plain"),
98                       ("tdb-install", "cd lib/tdb && make install", "text/plain"),
99
100                       ("tevent-configure", "cd lib/tevent && 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"),
101                       ("tevent-make", "cd lib/tevent && make", "text/plain"),
102                       ("tevent-install", "cd lib/tevent && make install", "text/plain"),
103
104                       ("ldb-configure", "cd lib/ldb && 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"),
105                       ("ldb-make", "cd lib/ldb && make", "text/plain"),
106                       ("ldb-install", "cd lib/ldb && make install", "text/plain"),
107
108                       ("configure", "PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig ./configure --bundled-libraries=!talloc,!tdb,!pytdb,!ldb,!pyldb,!tevent,!pytevent --abi-check --enable-debug -C ${PREFIX}", "text/plain"),
109                       ("make", "make", "text/plain"),
110                       ("install", "make install", "text/plain")],
111
112     "ldb" : [
113               ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
114               ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
115               ("make", "make", "text/plain"),
116               ("install", "make install", "text/plain"),
117               ("test", "make test", "text/plain"),
118               ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
119               ("distcheck", "make distcheck", "text/plain"),
120               ("clean", "make clean", "text/plain") ],
121
122     "tdb" : [
123               ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
124               ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
125               ("make", "make", "text/plain"),
126               ("install", "make install", "text/plain"),
127               ("test", "make test", "text/plain"),
128               ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
129               ("distcheck", "make distcheck", "text/plain"),
130               ("clean", "make clean", "text/plain") ],
131
132     "ntdb" : [
133                ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
134                ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
135                ("make", "make", "text/plain"),
136                ("install", "make install", "text/plain"),
137                ("test", "make test", "text/plain"),
138                ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
139                ("distcheck", "make distcheck", "text/plain"),
140                ("clean", "make clean", "text/plain") ],
141
142     "talloc" : [
143                  ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
144                  ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
145                  ("make", "make", "text/plain"),
146                  ("install", "make install", "text/plain"),
147                  ("test", "make test", "text/plain"),
148                  ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
149                  ("distcheck", "make distcheck", "text/plain"),
150                  ("clean", "make clean", "text/plain") ],
151
152     "replace" : [
153                   ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
154                   ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
155                   ("make", "make", "text/plain"),
156                   ("install", "make install", "text/plain"),
157                   ("test", "make test", "text/plain"),
158                   ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
159                   ("distcheck", "make distcheck", "text/plain"),
160                   ("clean", "make clean", "text/plain") ],
161
162     "tevent" : [
163                  ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
164                  ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
165                  ("make", "make", "text/plain"),
166                  ("install", "make install", "text/plain"),
167                  ("test", "make test", "text/plain"),
168                  ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
169                  ("distcheck", "make distcheck", "text/plain"),
170                  ("clean", "make clean", "text/plain") ],
171
172     "pidl" : [
173                ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
174                ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}", "text/plain"),
175                ("touch", "touch *.yp", "text/plain"),
176                ("make", "make", "text/plain"),
177                ("test", "make test", "text/plain"),
178                ("install", "make install", "text/plain"),
179                ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
180                ("clean", "make clean", "text/plain") ],
181
182     # these are useful for debugging autobuild
183     'pass' : [ ("pass", 'echo passing && /bin/true', "text/plain") ],
184     'fail' : [ ("fail", 'echo failing && /bin/false', "text/plain") ]
185 }
186
187 retry_task = [ ( "retry",
188                  '''set -e
189                 git remote add -t master master %s
190                 git fetch master
191                 while :; do
192                   sleep 60
193                   git describe master/master > old_master.desc
194                   git fetch master
195                   git describe master/master > master.desc
196                   diff old_master.desc master.desc
197                 done
198                ''' % samba_master, "test/plain" ) ]
199
200 def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
201     if show is None:
202         show = options.verbose
203     if show:
204         print("Running: '%s' in '%s'" % (cmd, dir))
205     if output:
206         return Popen([cmd], shell=True, stdout=PIPE, cwd=dir).communicate()[0]
207     elif checkfail:
208         return check_call(cmd, shell=True, cwd=dir)
209     else:
210         return call(cmd, shell=True, cwd=dir)
211
212
213 class builder(object):
214     '''handle build of one directory'''
215
216     def __init__(self, name, sequence):
217         self.name = name
218         self.dir = builddirs[name]
219
220         self.tag = self.name.replace('/', '_')
221         self.sequence = sequence
222         self.next = 0
223         self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
224         self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
225         if options.verbose:
226             print("stdout for %s in %s" % (self.name, self.stdout_path))
227             print("stderr for %s in %s" % (self.name, self.stderr_path))
228         run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
229         self.stdout = open(self.stdout_path, 'w')
230         self.stderr = open(self.stderr_path, 'w')
231         self.stdin  = open("/dev/null", 'r')
232         self.sdir = "%s/%s" % (testbase, self.tag)
233         self.prefix = "%s/prefix/%s" % (testbase, self.tag)
234         run_cmd("rm -rf %s" % self.sdir)
235         cleanup_list.append(self.sdir)
236         cleanup_list.append(self.prefix)
237         os.makedirs(self.sdir)
238         run_cmd("rm -rf %s" % self.sdir)
239         run_cmd("git clone --shared %s %s" % (test_master, self.sdir), dir=test_master, show=True)
240         self.start_next()
241
242     def start_next(self):
243         if self.next == len(self.sequence):
244             print '%s: Completed OK' % self.name
245             self.done = True
246             return
247         (self.stage, self.cmd, self.output_mime_type) = self.sequence[self.next]
248         self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(standard_lib=1, prefix=self.prefix))
249         self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
250         self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
251 #        if self.output_mime_type == "text/x-subunit":
252 #            self.cmd += " | %s --immediate" % (os.path.join(os.path.dirname(__file__), "selftest/format-subunit"))
253         print '%s: [%s] Running %s' % (self.name, self.stage, self.cmd)
254         cwd = os.getcwd()
255         os.chdir("%s/%s" % (self.sdir, self.dir))
256         self.proc = Popen(self.cmd, shell=True,
257                           stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
258         os.chdir(cwd)
259         self.next += 1
260
261
262 class buildlist(object):
263     '''handle build of multiple directories'''
264
265     def __init__(self, tasklist, tasknames):
266         global tasks
267         self.tlist = []
268         self.tail_proc = None
269         self.retry = None
270         if tasknames == []:
271             tasknames = defaulttasks
272         for n in tasknames:
273             b = builder(n, tasks[n])
274             self.tlist.append(b)
275         if options.retry:
276             self.retry = builder('retry', retry_task)
277             self.need_retry = False
278
279     def kill_kids(self):
280         if self.tail_proc is not None:
281             self.tail_proc.terminate()
282             self.tail_proc.wait()
283             self.tail_proc = None
284         if self.retry is not None:
285             self.retry.proc.terminate()
286             self.retry.proc.wait()
287             self.retry = None
288         for b in self.tlist:
289             if b.proc is not None:
290                 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.sdir, checkfail=False)
291                 b.proc.terminate()
292                 b.proc.wait()
293                 b.proc = None
294
295     def wait_one(self):
296         while True:
297             none_running = True
298             for b in self.tlist:
299                 if b.proc is None:
300                     continue
301                 none_running = False
302                 b.status = b.proc.poll()
303                 if b.status is None:
304                     continue
305                 b.proc = None
306                 return b
307             if options.retry:
308                 ret = self.retry.proc.poll()
309                 if ret is not None:
310                     self.need_retry = True
311                     self.retry = None
312                     return None
313             if none_running:
314                 return None
315             time.sleep(0.1)
316
317     def run(self):
318         while True:
319             b = self.wait_one()
320             if options.retry and self.need_retry:
321                 self.kill_kids()
322                 print("retry needed")
323                 return (0, None, None, None, "retry")
324             if b is None:
325                 break
326             if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
327                 self.kill_kids()
328                 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
329             b.start_next()
330         self.kill_kids()
331         return (0, None, None, None, "All OK")
332
333     def tarlogs(self, fname):
334         tar = tarfile.open(fname, "w:gz")
335         for b in self.tlist:
336             tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
337             tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
338         if os.path.exists("autobuild.log"):
339             tar.add("autobuild.log")
340         tar.close()
341
342     def remove_logs(self):
343         for b in self.tlist:
344             os.unlink(b.stdout_path)
345             os.unlink(b.stderr_path)
346
347     def start_tail(self):
348         cwd = os.getcwd()
349         cmd = "tail -f *.stdout *.stderr"
350         os.chdir(gitroot)
351         self.tail_proc = Popen(cmd, shell=True)
352         os.chdir(cwd)
353
354
355 def cleanup():
356     if options.nocleanup:
357         return
358     print("Cleaning up ....")
359     for d in cleanup_list:
360         run_cmd("rm -rf %s" % d)
361
362
363 def find_git_root():
364     '''get to the top of the git repo'''
365     p=os.getcwd()
366     while p != '/':
367         if os.path.isdir(os.path.join(p, ".git")):
368             return p
369         p = os.path.abspath(os.path.join(p, '..'))
370     return None
371
372
373 def daemonize(logfile):
374     pid = os.fork()
375     if pid == 0: # Parent
376         os.setsid()
377         pid = os.fork()
378         if pid != 0: # Actual daemon
379             os._exit(0)
380     else: # Grandparent
381         os._exit(0)
382
383     import resource      # Resource usage information.
384     maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
385     if maxfd == resource.RLIM_INFINITY:
386         maxfd = 1024 # Rough guess at maximum number of open file descriptors.
387     for fd in range(0, maxfd):
388         try:
389             os.close(fd)
390         except OSError:
391             pass
392     os.open(logfile, os.O_RDWR | os.O_CREAT)
393     os.dup2(0, 1)
394     os.dup2(0, 2)
395
396 def write_pidfile(fname):
397     '''write a pid file, cleanup on exit'''
398     f = open(fname, mode='w')
399     f.write("%u\n" % os.getpid())
400     f.close()
401
402
403 def rebase_tree(url):
404     print("Rebasing on %s" % url)
405     run_cmd("git describe HEAD", show=True, dir=test_master)
406     run_cmd("git remote add -t master master %s" % url, show=True, dir=test_master)
407     run_cmd("git fetch master", show=True, dir=test_master)
408     if options.fix_whitespace:
409         run_cmd("git rebase --whitespace=fix master/master", show=True, dir=test_master)
410     else:
411         run_cmd("git rebase master/master", show=True, dir=test_master)
412     diff = run_cmd("git --no-pager diff HEAD master/master", dir=test_master, output=True)
413     if diff == '':
414         print("No differences between HEAD and master/master - exiting")
415         sys.exit(0)
416     run_cmd("git describe master/master", show=True, dir=test_master)
417     run_cmd("git describe HEAD", show=True, dir=test_master)
418     run_cmd("git --no-pager diff --stat HEAD master/master", show=True, dir=test_master)
419
420 def push_to(url):
421     print("Pushing to %s" % url)
422     if options.mark:
423         run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
424         run_cmd("git commit --amend -c HEAD", dir=test_master)
425         # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
426         # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
427     run_cmd("git remote add -t master pushto %s" % url, show=True, dir=test_master)
428     run_cmd("git push pushto +HEAD:master", show=True, dir=test_master)
429
430 def_testbase = os.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os.getenv('USER'))
431
432 parser = OptionParser()
433 parser.add_option("", "--tail", help="show output while running", default=False, action="store_true")
434 parser.add_option("", "--keeplogs", help="keep logs", default=False, action="store_true")
435 parser.add_option("", "--nocleanup", help="don't remove test tree", default=False, action="store_true")
436 parser.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase,
437                   default=def_testbase)
438 parser.add_option("", "--passcmd", help="command to run on success", default=None)
439 parser.add_option("", "--verbose", help="show all commands as they are run",
440                   default=False, action="store_true")
441 parser.add_option("", "--rebase", help="rebase on the given tree before testing",
442                   default=None, type='str')
443 parser.add_option("", "--rebase-master", help="rebase on %s before testing" % samba_master,
444                   default=False, action='store_true')
445 parser.add_option("", "--pushto", help="push to a git url on success",
446                   default=None, type='str')
447 parser.add_option("", "--push-master", help="push to %s on success" % samba_master_ssh,
448                   default=False, action='store_true')
449 parser.add_option("", "--mark", help="add a Tested-By signoff before pushing",
450                   default=False, action="store_true")
451 parser.add_option("", "--fix-whitespace", help="fix whitespace on rebase",
452                   default=False, action="store_true")
453 parser.add_option("", "--retry", help="automatically retry if master changes",
454                   default=False, action="store_true")
455 parser.add_option("", "--email", help="send email to the given address on failure",
456                   type='str', default=None)
457 parser.add_option("", "--always-email", help="always send email, even on success",
458                   action="store_true")
459 parser.add_option("", "--daemon", help="daemonize after initial setup",
460                   action="store_true")
461
462
463 def email_failure(status, failed_task, failed_stage, failed_tag, errstr):
464     '''send an email to options.email about the failure'''
465     user = os.getenv("USER")
466     text = '''
467 Dear Developer,
468
469 Your autobuild failed when trying to test %s with the following error:
470    %s
471
472 the autobuild has been abandoned. Please fix the error and resubmit.
473
474 A summary of the autobuild process is here:
475
476   http://git.samba.org/%s/samba-autobuild/autobuild.log
477 ''' % (failed_task, errstr, user)
478     
479     if failed_task != 'rebase':
480         text += '''
481 You can see logs of the failed task here:
482
483   http://git.samba.org/%s/samba-autobuild/%s.stdout
484   http://git.samba.org/%s/samba-autobuild/%s.stderr
485
486 or you can get full logs of all tasks in this job here:
487
488   http://git.samba.org/%s/samba-autobuild/logs.tar.gz
489
490 The top commit for the tree that was built was:
491
492 %s
493
494 ''' % (user, failed_tag, user, failed_tag, user, top_commit_msg)
495     msg = MIMEText(text)
496     msg['Subject'] = 'autobuild failure for task %s during %s' % (failed_task, failed_stage)
497     msg['From'] = 'autobuild@samba.org'
498     msg['To'] = options.email
499
500     s = smtplib.SMTP()
501     s.connect()
502     s.sendmail(msg['From'], [msg['To']], msg.as_string())
503     s.quit()
504
505 def email_success():
506     '''send an email to options.email about a successful build'''
507     user = os.getenv("USER")
508     text = '''
509 Dear Developer,
510
511 Your autobuild has succeeded.
512
513 '''
514
515     if options.keeplogs:
516         text += '''
517
518 you can get full logs of all tasks in this job here:
519
520   http://git.samba.org/%s/samba-autobuild/logs.tar.gz
521
522 ''' % user
523
524     text += '''
525 The top commit for the tree that was built was:
526
527 %s
528 ''' % top_commit_msg
529
530     msg = MIMEText(text)
531     msg['Subject'] = 'autobuild success'
532     msg['From'] = 'autobuild@samba.org'
533     msg['To'] = options.email
534
535     s = smtplib.SMTP()
536     s.connect()
537     s.sendmail(msg['From'], [msg['To']], msg.as_string())
538     s.quit()
539
540
541 (options, args) = parser.parse_args()
542
543 if options.retry:
544     if not options.rebase_master and options.rebase is None:
545         raise Exception('You can only use --retry if you also rebase')
546
547 testbase = "%s/b%u" % (options.testbase, os.getpid())
548 test_master = "%s/master" % testbase
549
550 gitroot = find_git_root()
551 if gitroot is None:
552     raise Exception("Failed to find git root")
553
554 # get the top commit message, for emails
555 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
556
557 try:
558     os.makedirs(testbase)
559 except Exception, reason:
560     raise Exception("Unable to create %s : %s" % (testbase, reason))
561 cleanup_list.append(testbase)
562
563 if options.daemon:
564     logfile = os.path.join(testbase, "log")
565     print "Forking into the background, writing progress to %s" % logfile
566     daemonize(logfile)
567
568 write_pidfile(gitroot + "/autobuild.pid")
569
570 while True:
571     try:
572         run_cmd("rm -rf %s" % test_master)
573         cleanup_list.append(test_master)
574         run_cmd("git clone --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
575     except Exception:
576         cleanup()
577         raise
578
579     try:
580         try:
581             if options.rebase is not None:
582                 rebase_tree(options.rebase)
583             elif options.rebase_master:
584                 rebase_tree(samba_master)
585         except Exception:
586             cleanup_list.append(gitroot + "/autobuild.pid")
587             cleanup()
588             email_failure(-1, 'rebase', 'rebase', 'rebase', 'rebase on master failed')
589             sys.exit(1)
590         blist = buildlist(tasks, args)
591         if options.tail:
592             blist.start_tail()
593         (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
594         if status != 0 or errstr != "retry":
595             break
596         cleanup()
597     except Exception:
598         cleanup()
599         raise
600
601 cleanup_list.append(gitroot + "/autobuild.pid")
602
603 blist.kill_kids()
604 if options.tail:
605     print("waiting for tail to flush")
606     time.sleep(1)
607
608 if status == 0:
609     print errstr
610     if options.passcmd is not None:
611         print("Running passcmd: %s" % options.passcmd)
612         run_cmd(options.passcmd, dir=test_master)
613     if options.pushto is not None:
614         push_to(options.pushto)
615     elif options.push_master:
616         push_to(samba_master_ssh)
617     if options.keeplogs:
618         blist.tarlogs("logs.tar.gz")
619         print("Logs in logs.tar.gz")
620     if options.always_email:
621         email_success()
622     blist.remove_logs()
623     cleanup()
624     print(errstr)
625     sys.exit(0)
626
627 # something failed, gather a tar of the logs
628 blist.tarlogs("logs.tar.gz")
629
630 if options.email is not None:
631     email_failure(status, failed_task, failed_stage, failed_tag, errstr)
632
633 cleanup()
634 print(errstr)
635 print("Logs in logs.tar.gz")
636 sys.exit(status)