script/autobuild.py: add --log-base 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 parser.add_option("", "--log-base", help="location where the logs can be found (default=cwd)",
491                   default=None, type='str')
492
493 def email_failure(status, failed_task, failed_stage, failed_tag, errstr, log_base=None):
494     '''send an email to options.email about the failure'''
495     user = os.getenv("USER")
496     if log_base is None:
497         log_base = "http://git.samba.org/%s/samba-autobuild" % user
498     text = '''
499 Dear Developer,
500
501 Your autobuild failed when trying to test %s with the following error:
502    %s
503
504 the autobuild has been abandoned. Please fix the error and resubmit.
505
506 A summary of the autobuild process is here:
507
508   %s/autobuild.log
509 ''' % (failed_task, errstr, log_base)
510     
511     if failed_task != 'rebase':
512         text += '''
513 You can see logs of the failed task here:
514
515   %s/%s.stdout
516   %s/%s.stderr
517
518 or you can get full logs of all tasks in this job here:
519
520   %s/logs.tar.gz
521
522 The top commit for the tree that was built was:
523
524 %s
525
526 ''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
527     msg = MIMEText(text)
528     msg['Subject'] = 'autobuild failure for task %s during %s' % (failed_task, failed_stage)
529     msg['From'] = 'autobuild@samba.org'
530     msg['To'] = options.email
531
532     s = smtplib.SMTP()
533     s.connect()
534     s.sendmail(msg['From'], [msg['To']], msg.as_string())
535     s.quit()
536
537 def email_success(log_base=None):
538     '''send an email to options.email about a successful build'''
539     user = os.getenv("USER")
540     if log_base is None:
541         log_base = "http://git.samba.org/%s/samba-autobuild" % user
542     text = '''
543 Dear Developer,
544
545 Your autobuild has succeeded.
546
547 '''
548
549     if options.keeplogs:
550         text += '''
551
552 you can get full logs of all tasks in this job here:
553
554   %s/logs.tar.gz
555
556 ''' % log_base
557
558     text += '''
559 The top commit for the tree that was built was:
560
561 %s
562 ''' % top_commit_msg
563
564     msg = MIMEText(text)
565     msg['Subject'] = 'autobuild success'
566     msg['From'] = 'autobuild@samba.org'
567     msg['To'] = options.email
568
569     s = smtplib.SMTP()
570     s.connect()
571     s.sendmail(msg['From'], [msg['To']], msg.as_string())
572     s.quit()
573
574
575 (options, args) = parser.parse_args()
576
577 if options.retry:
578     if not options.rebase_master and options.rebase is None:
579         raise Exception('You can only use --retry if you also rebase')
580
581 testbase = "%s/b%u" % (options.testbase, os.getpid())
582 test_master = "%s/master" % testbase
583
584 gitroot = find_git_root()
585 if gitroot is None:
586     raise Exception("Failed to find git root")
587
588 # get the top commit message, for emails
589 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
590
591 try:
592     os.makedirs(testbase)
593 except Exception, reason:
594     raise Exception("Unable to create %s : %s" % (testbase, reason))
595 cleanup_list.append(testbase)
596
597 if options.daemon:
598     logfile = os.path.join(testbase, "log")
599     print "Forking into the background, writing progress to %s" % logfile
600     daemonize(logfile)
601
602 write_pidfile(gitroot + "/autobuild.pid")
603
604 while True:
605     try:
606         run_cmd("rm -rf %s" % test_master)
607         cleanup_list.append(test_master)
608         run_cmd("git clone --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
609     except Exception:
610         cleanup()
611         raise
612
613     try:
614         try:
615             if options.rebase is not None:
616                 rebase_url = options.rebase
617             elif options.rebase_master:
618                 rebase_url = samba_master
619             else:
620                 rebase_url = None
621             if rebase_url is not None:
622                 rebase_tree(rebase_url, rebase_branch=options.branch)
623         except Exception:
624             cleanup_list.append(gitroot + "/autobuild.pid")
625             cleanup()
626             email_failure(-1, 'rebase', 'rebase', 'rebase',
627                           'rebase on %s failed' % options.branch,
628                           log_base=options.log_base)
629             sys.exit(1)
630         blist = buildlist(tasks, args, rebase_url, rebase_branch=options.branch)
631         if options.tail:
632             blist.start_tail()
633         (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
634         if status != 0 or errstr != "retry":
635             break
636         cleanup()
637     except Exception:
638         cleanup()
639         raise
640
641 cleanup_list.append(gitroot + "/autobuild.pid")
642
643 blist.kill_kids()
644 if options.tail:
645     print("waiting for tail to flush")
646     time.sleep(1)
647
648 if status == 0:
649     print errstr
650     if options.passcmd is not None:
651         print("Running passcmd: %s" % options.passcmd)
652         run_cmd(options.passcmd, dir=test_master)
653     if options.pushto is not None:
654         push_to(options.pushto, push_branch=options.branch)
655     elif options.push_master:
656         push_to(samba_master_ssh, push_branch=options.branch)
657     if options.keeplogs:
658         blist.tarlogs("logs.tar.gz")
659         print("Logs in logs.tar.gz")
660     if options.always_email:
661         email_success(log_base=options.log_base)
662     blist.remove_logs()
663     cleanup()
664     print(errstr)
665     sys.exit(0)
666
667 # something failed, gather a tar of the logs
668 blist.tarlogs("logs.tar.gz")
669
670 if options.email is not None:
671     email_failure(status, failed_task, failed_stage, failed_tag, errstr, log_base=options.log_base)
672
673 cleanup()
674 print(errstr)
675 print("Logs in logs.tar.gz")
676 sys.exit(status)