script/autobuild.py: add --branch option
[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 def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
188     if show is None:
189         show = options.verbose
190     if show:
191         print("Running: '%s' in '%s'" % (cmd, dir))
192     if output:
193         return Popen([cmd], shell=True, stdout=PIPE, cwd=dir).communicate()[0]
194     elif checkfail:
195         return check_call(cmd, shell=True, cwd=dir)
196     else:
197         return call(cmd, shell=True, cwd=dir)
198
199
200 class builder(object):
201     '''handle build of one directory'''
202
203     def __init__(self, name, sequence):
204         self.name = name
205         self.dir = builddirs[name]
206
207         self.tag = self.name.replace('/', '_')
208         self.sequence = sequence
209         self.next = 0
210         self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
211         self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
212         if options.verbose:
213             print("stdout for %s in %s" % (self.name, self.stdout_path))
214             print("stderr for %s in %s" % (self.name, self.stderr_path))
215         run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
216         self.stdout = open(self.stdout_path, 'w')
217         self.stderr = open(self.stderr_path, 'w')
218         self.stdin  = open("/dev/null", 'r')
219         self.sdir = "%s/%s" % (testbase, self.tag)
220         self.prefix = "%s/prefix/%s" % (testbase, self.tag)
221         run_cmd("rm -rf %s" % self.sdir)
222         cleanup_list.append(self.sdir)
223         cleanup_list.append(self.prefix)
224         os.makedirs(self.sdir)
225         run_cmd("rm -rf %s" % self.sdir)
226         run_cmd("git clone --shared %s %s" % (test_master, self.sdir), dir=test_master, show=True)
227         self.start_next()
228
229     def start_next(self):
230         if self.next == len(self.sequence):
231             print '%s: Completed OK' % self.name
232             self.done = True
233             return
234         (self.stage, self.cmd, self.output_mime_type) = self.sequence[self.next]
235         self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(standard_lib=1, prefix=self.prefix))
236         self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
237         self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
238 #        if self.output_mime_type == "text/x-subunit":
239 #            self.cmd += " | %s --immediate" % (os.path.join(os.path.dirname(__file__), "selftest/format-subunit"))
240         print '%s: [%s] Running %s' % (self.name, self.stage, self.cmd)
241         cwd = os.getcwd()
242         os.chdir("%s/%s" % (self.sdir, self.dir))
243         self.proc = Popen(self.cmd, shell=True,
244                           stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
245         os.chdir(cwd)
246         self.next += 1
247
248
249 class buildlist(object):
250     '''handle build of multiple directories'''
251
252     def __init__(self, tasklist, tasknames, rebase_url, rebase_branch="master"):
253         global tasks
254         self.tlist = []
255         self.tail_proc = None
256         self.retry = None
257         if tasknames == []:
258             tasknames = defaulttasks
259         for n in tasknames:
260             b = builder(n, tasks[n])
261             self.tlist.append(b)
262         if options.retry:
263             rebase_remote = "rebaseon"
264             retry_task = [ ("retry",
265                             '''set -e
266                             git remote add -t %s %s %s
267                             git fetch %s
268                             while :; do
269                               sleep 60
270                               git describe %s/%s > old_remote_branch.desc
271                               git fetch %s
272                               git describe %s/%s > remote_branch.desc
273                               diff old_remote_branch.desc remote_branch.desc
274                             done
275                            ''' % (
276                                rebase_branch, rebase_remote, rebase_url,
277                                rebase_remote,
278                                rebase_remote, rebase_branch,
279                                rebase_remote,
280                                rebase_remote, rebase_branch
281                            ),
282                            "test/plain" ) ]
283
284             self.retry = builder('retry', retry_task)
285             self.need_retry = False
286
287     def kill_kids(self):
288         if self.tail_proc is not None:
289             self.tail_proc.terminate()
290             self.tail_proc.wait()
291             self.tail_proc = None
292         if self.retry is not None:
293             self.retry.proc.terminate()
294             self.retry.proc.wait()
295             self.retry = None
296         for b in self.tlist:
297             if b.proc is not None:
298                 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.sdir, checkfail=False)
299                 b.proc.terminate()
300                 b.proc.wait()
301                 b.proc = None
302
303     def wait_one(self):
304         while True:
305             none_running = True
306             for b in self.tlist:
307                 if b.proc is None:
308                     continue
309                 none_running = False
310                 b.status = b.proc.poll()
311                 if b.status is None:
312                     continue
313                 b.proc = None
314                 return b
315             if options.retry:
316                 ret = self.retry.proc.poll()
317                 if ret is not None:
318                     self.need_retry = True
319                     self.retry = None
320                     return None
321             if none_running:
322                 return None
323             time.sleep(0.1)
324
325     def run(self):
326         while True:
327             b = self.wait_one()
328             if options.retry and self.need_retry:
329                 self.kill_kids()
330                 print("retry needed")
331                 return (0, None, None, None, "retry")
332             if b is None:
333                 break
334             if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
335                 self.kill_kids()
336                 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
337             b.start_next()
338         self.kill_kids()
339         return (0, None, None, None, "All OK")
340
341     def tarlogs(self, fname):
342         tar = tarfile.open(fname, "w:gz")
343         for b in self.tlist:
344             tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
345             tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
346         if os.path.exists("autobuild.log"):
347             tar.add("autobuild.log")
348         tar.close()
349
350     def remove_logs(self):
351         for b in self.tlist:
352             os.unlink(b.stdout_path)
353             os.unlink(b.stderr_path)
354
355     def start_tail(self):
356         cwd = os.getcwd()
357         cmd = "tail -f *.stdout *.stderr"
358         os.chdir(gitroot)
359         self.tail_proc = Popen(cmd, shell=True)
360         os.chdir(cwd)
361
362
363 def cleanup():
364     if options.nocleanup:
365         return
366     print("Cleaning up ....")
367     for d in cleanup_list:
368         run_cmd("rm -rf %s" % d)
369
370
371 def find_git_root():
372     '''get to the top of the git repo'''
373     p=os.getcwd()
374     while p != '/':
375         if os.path.isdir(os.path.join(p, ".git")):
376             return p
377         p = os.path.abspath(os.path.join(p, '..'))
378     return None
379
380
381 def daemonize(logfile):
382     pid = os.fork()
383     if pid == 0: # Parent
384         os.setsid()
385         pid = os.fork()
386         if pid != 0: # Actual daemon
387             os._exit(0)
388     else: # Grandparent
389         os._exit(0)
390
391     import resource      # Resource usage information.
392     maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
393     if maxfd == resource.RLIM_INFINITY:
394         maxfd = 1024 # Rough guess at maximum number of open file descriptors.
395     for fd in range(0, maxfd):
396         try:
397             os.close(fd)
398         except OSError:
399             pass
400     os.open(logfile, os.O_RDWR | os.O_CREAT)
401     os.dup2(0, 1)
402     os.dup2(0, 2)
403
404 def write_pidfile(fname):
405     '''write a pid file, cleanup on exit'''
406     f = open(fname, mode='w')
407     f.write("%u\n" % os.getpid())
408     f.close()
409
410
411 def rebase_tree(rebase_url, rebase_branch = "master"):
412     rebase_remote = "rebaseon"
413     print("Rebasing on %s" % rebase_url)
414     run_cmd("git describe HEAD", show=True, dir=test_master)
415     run_cmd("git remote add -t %s %s %s" %
416             (rebase_branch, rebase_remote, rebase_url),
417             show=True, dir=test_master)
418     run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
419     if options.fix_whitespace:
420         run_cmd("git rebase --whitespace=fix %s/%s" %
421                 (rebase_remote, rebase_branch),
422                 show=True, dir=test_master)
423     else:
424         run_cmd("git rebase %s/%s" %
425                 (rebase_remote, rebase_branch),
426                 show=True, dir=test_master)
427     diff = run_cmd("git --no-pager diff HEAD %s/%s" %
428                    (rebase_remote, rebase_branch),
429                    dir=test_master, output=True)
430     if diff == '':
431         print("No differences between HEAD and %s/%s - exiting" %
432               (rebase_remote, rebase_branch))
433         sys.exit(0)
434     run_cmd("git describe %s/%s" %
435             (rebase_remote, rebase_branch),
436             show=True, dir=test_master)
437     run_cmd("git describe HEAD", show=True, dir=test_master)
438     run_cmd("git --no-pager diff --stat HEAD %s/%s" %
439             (rebase_remote, rebase_branch),
440             show=True, dir=test_master)
441
442 def push_to(push_url, push_branch = "master"):
443     push_remote = "pushto"
444     print("Pushing to %s" % push_url)
445     if options.mark:
446         run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
447         run_cmd("git commit --amend -c HEAD", dir=test_master)
448         # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
449         # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
450     run_cmd("git remote add -t %s %s %s" %
451             (push_branch, push_remote, push_url),
452             show=True, dir=test_master)
453     run_cmd("git push %s +HEAD:%s" %
454             (push_remote, push_branch),
455             show=True, dir=test_master)
456
457 def_testbase = os.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os.getenv('USER'))
458
459 parser = OptionParser()
460 parser.add_option("", "--tail", help="show output while running", default=False, action="store_true")
461 parser.add_option("", "--keeplogs", help="keep logs", default=False, action="store_true")
462 parser.add_option("", "--nocleanup", help="don't remove test tree", default=False, action="store_true")
463 parser.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase,
464                   default=def_testbase)
465 parser.add_option("", "--passcmd", help="command to run on success", default=None)
466 parser.add_option("", "--verbose", help="show all commands as they are run",
467                   default=False, action="store_true")
468 parser.add_option("", "--rebase", help="rebase on the given tree before testing",
469                   default=None, type='str')
470 parser.add_option("", "--rebase-master", help="rebase on %s before testing" % samba_master,
471                   default=False, action='store_true')
472 parser.add_option("", "--pushto", help="push to a git url on success",
473                   default=None, type='str')
474 parser.add_option("", "--push-master", help="push to %s on success" % samba_master_ssh,
475                   default=False, action='store_true')
476 parser.add_option("", "--mark", help="add a Tested-By signoff before pushing",
477                   default=False, action="store_true")
478 parser.add_option("", "--fix-whitespace", help="fix whitespace on rebase",
479                   default=False, action="store_true")
480 parser.add_option("", "--retry", help="automatically retry if master changes",
481                   default=False, action="store_true")
482 parser.add_option("", "--email", help="send email to the given address on failure",
483                   type='str', default=None)
484 parser.add_option("", "--always-email", help="always send email, even on success",
485                   action="store_true")
486 parser.add_option("", "--daemon", help="daemonize after initial setup",
487                   action="store_true")
488 parser.add_option("", "--branch", help="the branch to work on (default=master)",
489                   default="master", type='str')
490
491
492 def email_failure(status, failed_task, failed_stage, failed_tag, errstr):
493     '''send an email to options.email about the failure'''
494     user = os.getenv("USER")
495     text = '''
496 Dear Developer,
497
498 Your autobuild failed when trying to test %s with the following error:
499    %s
500
501 the autobuild has been abandoned. Please fix the error and resubmit.
502
503 A summary of the autobuild process is here:
504
505   http://git.samba.org/%s/samba-autobuild/autobuild.log
506 ''' % (failed_task, errstr, user)
507     
508     if failed_task != 'rebase':
509         text += '''
510 You can see logs of the failed task here:
511
512   http://git.samba.org/%s/samba-autobuild/%s.stdout
513   http://git.samba.org/%s/samba-autobuild/%s.stderr
514
515 or you can get full logs of all tasks in this job here:
516
517   http://git.samba.org/%s/samba-autobuild/logs.tar.gz
518
519 The top commit for the tree that was built was:
520
521 %s
522
523 ''' % (user, failed_tag, user, failed_tag, user, top_commit_msg)
524     msg = MIMEText(text)
525     msg['Subject'] = 'autobuild failure for task %s during %s' % (failed_task, failed_stage)
526     msg['From'] = 'autobuild@samba.org'
527     msg['To'] = options.email
528
529     s = smtplib.SMTP()
530     s.connect()
531     s.sendmail(msg['From'], [msg['To']], msg.as_string())
532     s.quit()
533
534 def email_success():
535     '''send an email to options.email about a successful build'''
536     user = os.getenv("USER")
537     text = '''
538 Dear Developer,
539
540 Your autobuild has succeeded.
541
542 '''
543
544     if options.keeplogs:
545         text += '''
546
547 you can get full logs of all tasks in this job here:
548
549   http://git.samba.org/%s/samba-autobuild/logs.tar.gz
550
551 ''' % user
552
553     text += '''
554 The top commit for the tree that was built was:
555
556 %s
557 ''' % top_commit_msg
558
559     msg = MIMEText(text)
560     msg['Subject'] = 'autobuild success'
561     msg['From'] = 'autobuild@samba.org'
562     msg['To'] = options.email
563
564     s = smtplib.SMTP()
565     s.connect()
566     s.sendmail(msg['From'], [msg['To']], msg.as_string())
567     s.quit()
568
569
570 (options, args) = parser.parse_args()
571
572 if options.retry:
573     if not options.rebase_master and options.rebase is None:
574         raise Exception('You can only use --retry if you also rebase')
575
576 testbase = "%s/b%u" % (options.testbase, os.getpid())
577 test_master = "%s/master" % testbase
578
579 gitroot = find_git_root()
580 if gitroot is None:
581     raise Exception("Failed to find git root")
582
583 # get the top commit message, for emails
584 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
585
586 try:
587     os.makedirs(testbase)
588 except Exception, reason:
589     raise Exception("Unable to create %s : %s" % (testbase, reason))
590 cleanup_list.append(testbase)
591
592 if options.daemon:
593     logfile = os.path.join(testbase, "log")
594     print "Forking into the background, writing progress to %s" % logfile
595     daemonize(logfile)
596
597 write_pidfile(gitroot + "/autobuild.pid")
598
599 while True:
600     try:
601         run_cmd("rm -rf %s" % test_master)
602         cleanup_list.append(test_master)
603         run_cmd("git clone --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
604     except Exception:
605         cleanup()
606         raise
607
608     try:
609         try:
610             if options.rebase is not None:
611                 rebase_url = options.rebase
612             elif options.rebase_master:
613                 rebase_url = samba_master
614             else:
615                 rebase_url = None
616             if rebase_url is not None:
617                 rebase_tree(rebase_url, rebase_branch=options.branch)
618         except Exception:
619             cleanup_list.append(gitroot + "/autobuild.pid")
620             cleanup()
621             email_failure(-1, 'rebase', 'rebase', 'rebase',
622                           'rebase on %s failed' % options.branch)
623             sys.exit(1)
624         blist = buildlist(tasks, args, rebase_url, rebase_branch=options.branch)
625         if options.tail:
626             blist.start_tail()
627         (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
628         if status != 0 or errstr != "retry":
629             break
630         cleanup()
631     except Exception:
632         cleanup()
633         raise
634
635 cleanup_list.append(gitroot + "/autobuild.pid")
636
637 blist.kill_kids()
638 if options.tail:
639     print("waiting for tail to flush")
640     time.sleep(1)
641
642 if status == 0:
643     print errstr
644     if options.passcmd is not None:
645         print("Running passcmd: %s" % options.passcmd)
646         run_cmd(options.passcmd, dir=test_master)
647     if options.pushto is not None:
648         push_to(options.pushto, push_branch=options.branch)
649     elif options.push_master:
650         push_to(samba_master_ssh, push_branch=options.branch)
651     if options.keeplogs:
652         blist.tarlogs("logs.tar.gz")
653         print("Logs in logs.tar.gz")
654     if options.always_email:
655         email_success()
656     blist.remove_logs()
657     cleanup()
658     print(errstr)
659     sys.exit(0)
660
661 # something failed, gather a tar of the logs
662 blist.tarlogs("logs.tar.gz")
663
664 if options.email is not None:
665     email_failure(status, failed_task, failed_stage, failed_tag, errstr)
666
667 cleanup()
668 print(errstr)
669 print("Logs in logs.tar.gz")
670 sys.exit(status)