autobuild: added --rebase 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 Popen, PIPE
7 import os, signal, tarfile, sys, time
8 from optparse import OptionParser
9
10
11 cleanup_list = []
12
13 tasks = {
14     "source3" : [ "./autogen.sh",
15                   "./configure.developer ${PREFIX}",
16                   "make basics",
17                   "make -j",
18                   "make test" ],
19
20     "source4" : [ "./autogen.sh",
21                   "./configure.developer ${PREFIX}",
22                   "make -j",
23                   "make test" ],
24
25     "source4/lib/ldb" : [ "./autogen-waf.sh",
26                           "./configure --enable-developer -C ${PREFIX}",
27                           "make -j",
28                           "make test" ],
29
30     "lib/tdb" : [ "./autogen-waf.sh",
31                   "./configure --enable-developer -C ${PREFIX}",
32                   "make -j",
33                   "make test" ],
34
35     "lib/talloc" : [ "./autogen-waf.sh",
36                      "./configure --enable-developer -C ${PREFIX}",
37                      "make -j",
38                      "make test" ],
39
40     "lib/replace" : [ "./autogen-waf.sh",
41                       "./configure --enable-developer -C ${PREFIX}",
42                       "make -j",
43                       "make test" ],
44
45     "lib/tevent" : [ "./autogen-waf.sh",
46                      "./configure --enable-developer -C ${PREFIX}",
47                      "make -j",
48                      "make test" ],
49 }
50
51 def run_cmd(cmd, dir=".", show=None):
52     cwd = os.getcwd()
53     os.chdir(dir)
54     if show is None:
55         show = options.verbose
56     if show:
57         print("Running: '%s' in '%s'" % (cmd, dir))
58     ret = os.system(cmd)
59     os.chdir(cwd)
60     if ret != 0:
61         raise Exception("FAILED %s: %d" % (cmd, ret))
62
63 class builder:
64     '''handle build of one directory'''
65     def __init__(self, name, sequence):
66         self.name = name
67         self.tag = self.name.replace('/', '_')
68         self.sequence = sequence
69         self.next = 0
70         self.stdout_path = "%s/%s.stdout" % (testbase, self.tag)
71         self.stderr_path = "%s/%s.stderr" % (testbase, self.tag)
72         cleanup_list.append(self.stdout_path)
73         cleanup_list.append(self.stderr_path)
74         run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
75         self.stdout = open(self.stdout_path, 'w')
76         self.stderr = open(self.stderr_path, 'w')
77         self.stdin  = open("/dev/null", 'r')
78         self.sdir = "%s/%s" % (testbase, self.tag)
79         self.prefix = "%s/prefix/%s" % (testbase, self.tag)
80         run_cmd("rm -rf %s" % self.sdir)
81         cleanup_list.append(self.sdir)
82         cleanup_list.append(self.prefix)
83         os.makedirs(self.sdir)
84         run_cmd("rm -rf %s" % self.sdir)
85         run_cmd("git clone --shared %s %s" % (gitroot, self.sdir))
86         self.start_next()
87
88     def start_next(self):
89         if self.next == len(self.sequence):
90             print '%s: Completed OK' % self.name
91             self.done = True
92             return
93         self.cmd = self.sequence[self.next].replace("${PREFIX}", "--prefix=%s" % self.prefix)
94         print '%s: Running %s' % (self.name, self.cmd)
95         cwd = os.getcwd()
96         os.chdir("%s/%s" % (self.sdir, self.name))
97         self.proc = Popen(self.cmd, shell=True,
98                           stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
99         os.chdir(cwd)
100         self.next += 1
101
102
103 class buildlist:
104     '''handle build of multiple directories'''
105     def __init__(self, tasklist, tasknames):
106         self.tlist = []
107         self.tail_proc = None
108         if tasknames == []:
109             tasknames = tasklist
110         for n in tasknames:
111             b = builder(n, tasks[n])
112             self.tlist.append(b)
113
114     def kill_kids(self):
115         for b in self.tlist:
116             if b.proc is not None:
117                 b.proc.terminate()
118                 b.proc.wait()
119                 b.proc = None
120         if self.tail_proc is not None:
121             self.tail_proc.terminate()
122             self.tail_proc.wait()
123             self.tail_proc = None
124
125     def wait_one(self):
126         while True:
127             none_running = True
128             for b in self.tlist:
129                 if b.proc is None:
130                     continue
131                 none_running = False
132                 b.status = b.proc.poll()
133                 if b.status is None:
134                     continue
135                 b.proc = None
136                 return b
137             if none_running:
138                 return None
139             time.sleep(0.1)
140
141     def run(self):
142         while True:
143             b = self.wait_one()
144             if b is None:
145                 break
146             if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
147                 self.kill_kids()
148                 return (b.status, "%s: failed '%s' with status %d" % (b.name, b.cmd, b.status))
149             b.start_next()
150         self.kill_kids()
151         return (0, "All OK")
152
153     def tarlogs(self, fname):
154         tar = tarfile.open(fname, "w:gz")
155         for b in self.tlist:
156             tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
157             tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
158         tar.close()
159
160     def remove_logs(self):
161         for b in self.tlist:
162             os.unlink(b.stdout_path)
163             os.unlink(b.stderr_path)
164
165     def start_tail(self):
166         cwd = os.getcwd()
167         cmd = "tail -f *.stdout *.stderr"
168         os.chdir(testbase)
169         self.tail_proc = Popen(cmd, shell=True)
170         os.chdir(cwd)
171
172
173 def cleanup():
174     print("Cleaning up ....")
175     for d in cleanup_list:
176         run_cmd("rm -rf %s" % d)
177
178
179 def find_git_root():
180     '''get to the top of the git repo'''
181     cwd=os.getcwd()
182     while os.getcwd() != '/':
183         try:
184             os.stat(".git")
185             ret = os.getcwd()
186             os.chdir(cwd)
187             return ret
188         except:
189             os.chdir("..")
190             pass
191     os.chdir(cwd)
192     return None
193
194 def rebase_tree(url):
195     print("Rebasing on %s" % url)
196     run_cmd("git remote add -t master master %s" % url, show=True, dir=test_master)
197     run_cmd("git fetch master", show=True, dir=test_master)
198     run_cmd("git rebase master/master", show=True, dir=test_master)
199
200 def_testbase = os.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os.getenv('USER'))
201 def_passcmd  = os.getenv("AUTOBUILD_PASSCMD",
202                          "git push %s/master-passed +HEAD:master" % os.getenv("HOME"))
203
204 parser = OptionParser()
205 parser.add_option("", "--tail", help="show output while running", default=False, action="store_true")
206 parser.add_option("", "--keeplogs", help="keep logs", default=False, action="store_true")
207 parser.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase,
208                   default=def_testbase)
209 parser.add_option("", "--passcmd", help="command to run on success (default %s)" % def_passcmd,
210                   default=def_passcmd)
211 parser.add_option("", "--verbose", help="show all commands as they are run",
212                   default=False, action="store_true")
213 parser.add_option("", "--rebase", help="rebase on the given tree before testing",
214                   default=None, type='str')
215
216
217 (options, args) = parser.parse_args()
218
219 testbase = "%s/build.%u" % (options.testbase, os.getpid())
220 test_master = "%s/master" % testbase
221
222 gitroot = find_git_root()
223 if gitroot is None:
224     raise Exception("Failed to find git root")
225
226 try:
227     os.makedirs(testbase)
228 except Exception, reason:
229     raise Exception("Unable to create %s : %s" % (testbase, reason))
230 cleanup_list.append(testbase)
231
232 try:
233     run_cmd("rm -rf %s" % test_master)
234     cleanup_list.append(test_master)
235     run_cmd("git clone --shared %s %s" % (gitroot, test_master))
236 except:
237     cleanup()
238     raise
239
240 try:
241     if options.rebase is not None:
242         rebase_tree(options.rebase)
243     blist = buildlist(tasks, args)
244     if options.tail:
245         blist.start_tail()
246     (status, errstr) = blist.run()
247 except:
248     cleanup()
249     raise
250
251 blist.kill_kids()
252 if options.tail:
253     print("waiting for tail to flush")
254     time.sleep(1)
255
256 if status == 0:
257     print errstr
258     print("Running passcmd: %s" % options.passcmd)
259     run_cmd(options.passcmd, dir=test_master)
260     if options.keeplogs:
261         blist.tarlogs("logs.tar.gz")
262         print("Logs in logs.tar.gz")
263     blist.remove_logs()
264     cleanup()
265     print(errstr)
266     sys.exit(0)
267
268 # something failed, gather a tar of the logs
269 blist.tarlogs("logs.tar.gz")
270 blist.remove_logs()
271 cleanup()
272 print(errstr)
273 print("Logs in logs.tar.gz")
274 sys.exit(os.WEXITSTATUS(status))