1
2
3 import sys, os, re, time, random
4 from twisted.internet import utils, protocol, defer, reactor, task
5 from twisted.spread import pb
6 from twisted.cred import credentials
7 from twisted.python import log
8 from twisted.python.procutils import which
9
10 from buildbot.sourcestamp import SourceStamp
11 from buildbot.scripts import runner
12 from buildbot.util import now
13 from buildbot.status import builder
14
16
21
23 """This accepts the arguments of a command, without the actual
24 command itself."""
25 env = os.environ.copy()
26 env['LC_ALL'] = "C"
27 d = utils.getProcessOutputAndValue(self.exe, cmd, env=env,
28 path=self.treetop)
29 d.addCallback(self._didvc, cmd)
30 return d
32 (stdout, stderr, code) = res
33
34
35 return stdout
36
38 """Return a Deferred that fires with a SourceStamp instance."""
39 d = self.getBaseRevision()
40 d.addCallback(self.getPatch)
41 d.addCallback(self.done)
42 return d
49
51 patchlevel = 0
52 vcexe = "cvs"
54
55
56
57
58 self.baserev = time.strftime("%Y-%m-%d %H:%M:%S +0000",
59 time.gmtime(now()))
60 return defer.succeed(None)
61
63
64 if self.branch is not None:
65
66
67
68
69
70
71 raise RuntimeError("Sorry, CVS 'try' builds don't work with "
72 "branches")
73 args = ['-q', 'diff', '-u', '-D', self.baserev]
74 d = self.dovc(args)
75 d.addCallback(self.readPatch, self.patchlevel)
76 return d
77
79 patchlevel = 0
80 vcexe = "svn"
81
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 for line in res.split("\n"):
105 m = re.search(r'^Status against revision:\s+(\d+)', line)
106 if m:
107 self.baserev = int(m.group(1))
108 return
109 raise IndexError("Could not find 'Status against revision' in "
110 "SVN output: %s" % res)
115
117 patchlevel = 1
118 vcexe = "baz"
124 tid = res.strip()
125 slash = tid.index("/")
126 dd = tid.rindex("--")
127 self.branch = tid[slash+1:dd]
128 self.baserev = tid[dd+2:]
133
135 patchlevel = 1
136 vcexe = "tla"
138
139
140 d = self.dovc(["logs", "--full", "--reverse"])
141 d.addCallback(self.parseStatus)
142 return d
144 tid = res.split("\n")[0].strip()
145 slash = tid.index("/")
146 dd = tid.rindex("--")
147 self.branch = tid[slash+1:dd]
148 self.baserev = tid[dd+2:]
149
154
156 patchlevel = 0
157 vcexe = "bzr"
163 for line in out.split("\n"):
164 colon = line.find(":")
165 if colon != -1:
166 key, value = line[:colon], line[colon+2:]
167 if key == "revno":
168 self.baserev = int(value)
169 return
170 raise ValueError("unable to find revno: in bzr output: '%s'" % out)
171
176
178 patchlevel = 1
179 vcexe = "hg"
185 m = re.search(r'^(\w+)', output)
186 self.baserev = m.group(0)
191
205
207 patchlevel = 1
208 vcexe = "git"
209
211 d = self.dovc(["branch", "--no-color", "-v", "--no-abbrev"])
212 d.addCallback(self.parseStatus)
213 return d
214
216 d = self.dovc(["config", "-l"])
217 d.addCallback(self.parseConfig)
218 return d
219
221 git_config = {}
222 for l in res.split("\n"):
223 if l.strip():
224 parts = l.strip().split("=", 2)
225 git_config[parts[0]] = parts[1]
226
227
228 remote = git_config.get("branch." + self.branch + ".remote")
229 ref = git_config.get("branch." + self.branch + ".merge")
230 if remote and ref:
231 remote_branch = ref.split("/", 3)[-1]
232 d = self.dovc(["rev-parse", remote + "/" + remote_branch])
233 d.addCallback(self.override_baserev)
234 return d
235
237 self.baserev = res.strip()
238
240
241
242
243
244 m = re.search(r'^\* (\S+)\s+([0-9a-f]{40})', res, re.MULTILINE)
245 if m:
246 self.baserev = m.group(2)
247
248
249
250
251
252 if self.branch:
253 d = self.dovc(["rev-parse", self.branch])
254 if '/' in self.branch:
255 self.branch = self.branch.split('/', 1)[1]
256 d.addCallback(self.override_baserev)
257 return d
258 else:
259 self.branch = m.group(1)
260 return self.readConfig()
261 raise IndexError("Could not find current GIT branch: %s" % res)
262
264 d = self.dovc(["diff", self.baserev])
265 d.addCallback(self.readPatch, self.patchlevel)
266 return d
267
288
289
291 return "%d:%s," % (len(s), s)
292
293 -def createJobfile(bsid, branch, baserev, patchlevel, diff, builderNames):
294 job = ""
295 job += ns("1")
296 job += ns(bsid)
297 job += ns(branch)
298 job += ns(str(baserev))
299 job += ns("%d" % patchlevel)
300 job += ns(diff)
301 for bn in builderNames:
302 job += ns(bn)
303 return job
304
306 """walk upwards from the current directory until we find this topfile"""
307 if not start:
308 start = os.getcwd()
309 here = start
310 toomany = 20
311 while toomany > 0:
312 if os.path.exists(os.path.join(here, topfile)):
313 return here
314 next = os.path.dirname(here)
315 if next == here:
316 break
317 here = next
318 toomany -= 1
319 raise ValueError("Unable to find topfile '%s' anywhere from %s upwards"
320 % (topfile, start))
321
324 self.job = job
325 self.d = defer.Deferred()
327 self.transport.write(self.job)
328 self.transport.closeStdin()
330 sys.stdout.write(data)
332 sys.stderr.write(data)
334 sig = status_object.value.signal
335 rc = status_object.value.exitCode
336 if sig != None or rc != 0:
337 self.d.errback(RuntimeError("remote 'buildbot tryserver' failed"
338 ": sig=%s, rc=%s" % (sig, rc)))
339 return
340 self.d.callback((sig, rc))
341
343 retryCount = 5
344 retryDelay = 3
345
349
351
352
353 self.d = defer.Deferred()
354
355
356 reactor.callLater(1, self.go)
357 return self.d
358
359 - def go(self, dummy=None):
360 if self.retryCount == 0:
361 raise RuntimeError("couldn't find matching buildset")
362 self.retryCount -= 1
363 d = self.status.callRemote("getBuildSets")
364 d.addCallback(self._gotSets)
365
367 for bs,bsid in buildsets:
368 if bsid == self.bsid:
369
370 self.d.callback(bs)
371 return
372 d = defer.Deferred()
373 d.addCallback(self.go)
374 reactor.callLater(self.retryDelay, d.callback, None)
375
376
377 -class Try(pb.Referenceable):
378 buildsetStatus = None
379 quiet = False
380
382 self.config = config
383 self.opts = runner.loadOptions()
384 self.connect = self.getopt('connect', 'try_connect')
385 assert self.connect, "you must specify a connect style: ssh or pb"
386 self.builderNames = self.getopt('builders', 'try_builders')
387
388 - def getopt(self, config_name, options_name, default=None):
389 value = self.config.get(config_name)
390 if value is None or value == []:
391 value = self.opts.get(options_name)
392 if value is None or value == []:
393 value = default
394 return value
395
397
398
399 opts = self.opts
400
401
402
403 self.bsid = "%d-%s" % (time.time(), random.randint(0, 1000000))
404
405
406 branch = self.getopt("branch", "try_branch")
407
408 difffile = self.config.get("diff")
409 if difffile:
410 baserev = self.config.get("baserev")
411 if difffile == "-":
412 diff = sys.stdin.read()
413 else:
414 diff = open(difffile,"r").read()
415 patch = (self.config['patchlevel'], diff)
416 ss = SourceStamp(branch, baserev, patch)
417 d = defer.succeed(ss)
418 else:
419 vc = self.getopt("vc", "try_vc")
420 if vc in ("cvs", "svn"):
421
422 topdir = self.getopt("try_topdir", "try_topdir")
423 if topdir:
424 treedir = os.path.expanduser(topdir)
425 else:
426 topfile = self.getopt("try-topfile", "try_topfile")
427 treedir = getTopdir(topfile)
428 else:
429 treedir = os.getcwd()
430 d = getSourceStamp(vc, treedir, branch)
431 d.addCallback(self._createJob_1)
432 return d
433
445
447
448 ss = self.sourcestamp
449 print ("Job:\n\tBranch: %s\n\tRevision: %s\n\tBuilders: %s\n%s"
450 % (ss.branch,
451 ss.revision,
452 self.builderNames,
453 ss.patch[1]))
454 d = defer.Deferred()
455 d.callback(True)
456 return d
457
459
460 opts = self.opts
461
462 if self.connect == "ssh":
463 tryhost = self.getopt("tryhost", "try_host")
464 tryuser = self.getopt("username", "try_username")
465 trydir = self.getopt("trydir", "try_dir")
466
467 argv = ["ssh", "-l", tryuser, tryhost,
468 "buildbot", "tryserver", "--jobdir", trydir]
469
470
471 pp = RemoteTryPP(self.jobfile)
472 p = reactor.spawnProcess(pp, argv[0], argv, os.environ)
473 d = pp.d
474 return d
475 if self.connect == "pb":
476 user = self.getopt("username", "try_username")
477 passwd = self.getopt("passwd", "try_password")
478 master = self.getopt("master", "try_master")
479 tryhost, tryport = master.split(":")
480 tryport = int(tryport)
481 f = pb.PBClientFactory()
482 d = f.login(credentials.UsernamePassword(user, passwd))
483 reactor.connectTCP(tryhost, tryport, f)
484 d.addCallback(self._deliverJob_pb)
485 return d
486 raise RuntimeError("unknown connecttype '%s', should be 'ssh' or 'pb'"
487 % self.connect)
488
503
505
506
507 wait = bool(self.getopt("wait", "try_wait", False))
508 if not wait:
509
510
511
512
513
514 print "not waiting for builds to finish"
515 return
516 d = self.running = defer.Deferred()
517 if self.buildsetStatus:
518 self._getStatus_1()
519
520
521 master = self.getopt("master", "masterstatus")
522 host, port = master.split(":")
523 port = int(port)
524 self.announce("contacting the status port at %s:%d" % (host, port))
525 f = pb.PBClientFactory()
526 creds = credentials.UsernamePassword("statusClient", "clientpw")
527 d = f.login(creds)
528 reactor.connectTCP(host, port, f)
529 d.addCallback(self._getStatus_ssh_1)
530 return self.running
531
539
546
548 self.builderNames = []
549 self.buildRequests = {}
550
551
552 self.builds = {}
553
554
555
556 self.outstanding = []
557
558
559
560 self.results = {}
561
562
563
564 self.currentStep = {}
565
566
567
568 self.ETA = {}
569
570 for n,br in brs:
571 self.builderNames.append(n)
572 self.buildRequests[n] = br
573 self.builds[n] = None
574 self.outstanding.append(n)
575 self.results[n] = [None,None]
576 self.currentStep[n] = None
577 self.ETA[n] = None
578
579
580 br.callRemote("subscribe", self)
581
582
583
584 self.printloop = task.LoopingCall(self.printStatus)
585 self.printloop.start(3, now=False)
586
587
588
589
591 if self.builds[builderName]:
592 self.builds[builderName].callRemote("unsubscribe", self)
593 self.builds[builderName] = bs
594 bs.callRemote("subscribe", self, 20)
595 d = bs.callRemote("waitUntilFinished")
596 d.addCallback(self._build_finished, builderName)
597
600
603
605 self.ETA[buildername] = now() + eta
606
608
609
610
611 self.builds[builderName] = None
612 self.ETA[builderName] = None
613 self.currentStep[builderName] = "finished"
614 d = bs.callRemote("getResults")
615 d.addCallback(self._build_finished_2, bs, builderName)
616 return d
623 self.results[builderName][1] = text
624
625 self.outstanding.remove(builderName)
626 if not self.outstanding:
627
628 return self.statusDone()
629
631 names = self.buildRequests.keys()
632 names.sort()
633 for n in names:
634 if n not in self.outstanding:
635
636 code,text = self.results[n]
637 t = builder.Results[code]
638 if text:
639 t += " (%s)" % " ".join(text)
640 elif self.builds[n]:
641 t = self.currentStep[n] or "building"
642 if self.ETA[n]:
643 t += " [ETA %ds]" % (self.ETA[n] - now())
644 else:
645 t = "no build"
646 self.announce("%s: %s" % (n, t))
647 self.announce("")
648
650 self.printloop.stop()
651 print "All Builds Complete"
652
653 names = self.buildRequests.keys()
654 names.sort()
655 happy = True
656 for n in names:
657 code,text = self.results[n]
658 t = "%s: %s" % (n, builder.Results[code])
659 if text:
660 t += " (%s)" % " ".join(text)
661 print t
662 if self.results[n] != builder.SUCCESS:
663 happy = False
664
665 if happy:
666 self.exitcode = 0
667 else:
668 self.exitcode = 1
669 self.running.callback(self.exitcode)
670
674
676
677
678 print "using '%s' connect method" % self.connect
679 self.exitcode = 0
680 d = defer.Deferred()
681 d.addCallback(lambda res: self.createJob())
682 d.addCallback(lambda res: self.announce("job created"))
683 deliver = self.deliverJob
684 if bool(self.config.get("dryrun")):
685 deliver = self.fakeDeliverJob
686 d.addCallback(lambda res: deliver())
687 d.addCallback(lambda res: self.announce("job has been delivered"))
688 d.addCallback(lambda res: self.getStatus())
689 d.addErrback(log.err)
690 d.addCallback(self.cleanup)
691 d.addCallback(lambda res: reactor.stop())
692
693 reactor.callLater(0, d.callback, None)
694 reactor.run()
695 sys.exit(self.exitcode)
696
698 log.err(why)
699 print "error during 'try' processing"
700 print why
701
705