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.util import now
12 from buildbot.status import builder
13
15
20
22 """This accepts the arguments of a command, without the actual
23 command itself."""
24 env = os.environ.copy()
25 env['LC_ALL'] = "C"
26 d = utils.getProcessOutputAndValue(self.exe, cmd, env=env,
27 path=self.treetop)
28 d.addCallback(self._didvc, cmd)
29 return d
31 (stdout, stderr, code) = res
32
33
34 return stdout
35
37 """Return a Deferred that fires with a SourceStamp instance."""
38 d = self.getBaseRevision()
39 d.addCallback(self.getPatch)
40 d.addCallback(self.done)
41 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"
162
164 revno, revid= out.split()
165 self.baserev = 'revid:' + revid
166 return
167
172
174 patchlevel = 1
175 vcexe = "hg"
177 d = self.dovc(["identify", "--id", "--debug"])
178 d.addCallback(self.parseStatus)
179 return d
181 m = re.search(r'^(\w+)', output)
182 self.baserev = m.group(0)
187
188
190 patchlevel = 0
191 vcexe = "p4"
193 d = self.dovc(["changes", "-m1", "..."])
194 d.addCallback(self.parseStatus)
195 return d
196
198
199
200
201 m = re.search(r'Change (\d+)',res)
202 if m:
203 self.baserev = m.group(1)
204 return
205
206 raise IndexError("Could not find change number in output: %s" % res)
207
209
210
211
212 assert self.branch, "you must specify a branch"
213 mpatch = ""
214 found = False
215 for line in res.split("\n"):
216 m = re.search('==== //depot/' + self.branch + r'/([\w\/\.\d\-\_]+)#(\d+) -',line)
217 if m:
218 mpatch += "--- %s#%s\n" % (m.group(1), m.group(2) )
219 mpatch += "+++ %s\n" % (m.group(1) )
220 found = True
221 else:
222 mpatch += line
223 mpatch += "\n"
224 assert found, "could not parse patch file"
225 self.patch = (patchlevel, mpatch)
230
231
245
247 patchlevel = 1
248 vcexe = "git"
249
251 d = self.dovc(["branch", "--no-color", "-v", "--no-abbrev"])
252 d.addCallback(self.parseStatus)
253 return d
254
256 d = self.dovc(["config", "-l"])
257 d.addCallback(self.parseConfig)
258 return d
259
261 git_config = {}
262 for l in res.split("\n"):
263 if l.strip():
264 parts = l.strip().split("=", 2)
265 git_config[parts[0]] = parts[1]
266
267
268 remote = git_config.get("branch." + self.branch + ".remote")
269 ref = git_config.get("branch." + self.branch + ".merge")
270 if remote and ref:
271 remote_branch = ref.split("/", 3)[-1]
272 d = self.dovc(["rev-parse", remote + "/" + remote_branch])
273 d.addCallback(self.override_baserev)
274 return d
275
277 self.baserev = res.strip()
278
280
281
282
283
284 m = re.search(r'^\* (\S+)\s+([0-9a-f]{40})', res, re.MULTILINE)
285 if m:
286 self.baserev = m.group(2)
287
288
289
290
291
292 if self.branch:
293 d = self.dovc(["rev-parse", self.branch])
294 if '/' in self.branch:
295 self.branch = self.branch.split('/', 1)[1]
296 d.addCallback(self.override_baserev)
297 return d
298 else:
299 self.branch = m.group(1)
300 return self.readConfig()
301 raise IndexError("Could not find current GIT branch: %s" % res)
302
304 d = self.dovc(["diff", self.baserev])
305 d.addCallback(self.readPatch, self.patchlevel)
306 return d
307
330
331
333 return "%d:%s," % (len(s), s)
334
335 -def createJobfile(bsid, branch, baserev, patchlevel, diff, repository,
336 project, builderNames):
337 job = ""
338 job += ns("2")
339 job += ns(bsid)
340 job += ns(branch)
341 job += ns(str(baserev))
342 job += ns("%d" % patchlevel)
343 job += ns(diff)
344 job += ns(repository)
345 job += ns(project)
346 for bn in builderNames:
347 job += ns(bn)
348 return job
349
351 """walk upwards from the current directory until we find this topfile"""
352 if not start:
353 start = os.getcwd()
354 here = start
355 toomany = 20
356 while toomany > 0:
357 if os.path.exists(os.path.join(here, topfile)):
358 return here
359 next = os.path.dirname(here)
360 if next == here:
361 break
362 here = next
363 toomany -= 1
364 raise ValueError("Unable to find topfile '%s' anywhere from %s upwards"
365 % (topfile, start))
366
369 self.job = job
370 self.d = defer.Deferred()
375 sys.stdout.write(data)
377 sys.stderr.write(data)
379 sig = status_object.value.signal
380 rc = status_object.value.exitCode
381 if sig != None or rc != 0:
382 self.d.errback(RuntimeError("remote 'buildbot tryserver' failed"
383 ": sig=%s, rc=%s" % (sig, rc)))
384 return
385 self.d.callback((sig, rc))
386
388 retryCount = 5
389 retryDelay = 3
390
394
396
397
398 self.d = defer.Deferred()
399
400
401 reactor.callLater(1, self.go)
402 return self.d
403
404 - def go(self, dummy=None):
405 if self.retryCount == 0:
406 raise RuntimeError("couldn't find matching buildset")
407 self.retryCount -= 1
408 d = self.status.callRemote("getBuildSets")
409 d.addCallback(self._gotSets)
410
412 for bs,bsid in buildsets:
413 if bsid == self.bsid:
414
415 self.d.callback(bs)
416 return
417 d = defer.Deferred()
418 d.addCallback(self.go)
419 reactor.callLater(self.retryDelay, d.callback, None)
420
421
422 -class Try(pb.Referenceable):
423 buildsetStatus = None
424 quiet = False
425
432
433 - def getopt(self, config_name, default=None):
434 value = self.config.get(config_name)
435 if value is None or value == []:
436 value = default
437 return value
438
440
441
442
443
444
445
446 self.bsid = "%d-%s" % (time.time(), random.randint(0, 1000000))
447
448
449 branch = self.getopt("branch")
450
451 difffile = self.config.get("diff")
452 if difffile:
453 baserev = self.config.get("baserev")
454 if difffile == "-":
455 diff = sys.stdin.read()
456 else:
457 diff = open(difffile,"r").read()
458 patch = (self.config['patchlevel'], diff)
459 ss = SourceStamp(branch, baserev, patch)
460 d = defer.succeed(ss)
461 else:
462 vc = self.getopt("vc")
463 if vc in ("cvs", "svn"):
464
465 topdir = self.getopt("try-topdir")
466 if topdir:
467 treedir = os.path.expanduser(topdir)
468 else:
469 topfile = self.getopt("try-topfile")
470 treedir = getTopdir(topfile)
471 else:
472 treedir = os.getcwd()
473 d = getSourceStamp(vc, treedir, branch)
474 d.addCallback(self._createJob_1)
475 return d
476
488
490
491 ss = self.sourcestamp
492 print ("Job:\n\tRepository: %s\n\tProject: %s\n\tBranch: %s\n\t"
493 "Revision: %s\n\tBuilders: %s\n%s"
494 % (ss.repository, self.project, ss.branch,
495 ss.revision,
496 self.builderNames,
497 ss.patch[1]))
498 d = defer.Deferred()
499 d.callback(True)
500 return d
501
503
504
505 if self.connect == "ssh":
506 tryhost = self.getopt("tryhost")
507 tryuser = self.getopt("username")
508 trydir = self.getopt("trydir")
509
510 argv = ["ssh", "-l", tryuser, tryhost,
511 "buildbot", "tryserver", "--jobdir", trydir]
512
513
514 pp = RemoteTryPP(self.jobfile)
515 reactor.spawnProcess(pp, argv[0], argv, os.environ)
516 d = pp.d
517 return d
518 if self.connect == "pb":
519 user = self.getopt("username")
520 passwd = self.getopt("passwd")
521 master = self.getopt("master")
522 tryhost, tryport = master.split(":")
523 tryport = int(tryport)
524 f = pb.PBClientFactory()
525 d = f.login(credentials.UsernamePassword(user, passwd))
526 reactor.connectTCP(tryhost, tryport, f)
527 d.addCallback(self._deliverJob_pb)
528 return d
529 raise RuntimeError("unknown connecttype '%s', should be 'ssh' or 'pb'"
530 % self.connect)
531
548
550
551
552 wait = bool(self.getopt("wait", "try_wait"))
553 if not wait:
554
555
556
557
558
559 print "not waiting for builds to finish"
560 return
561 d = self.running = defer.Deferred()
562 if self.buildsetStatus:
563 self._getStatus_1()
564 return self.running
565
566
567 master = self.getopt("master")
568 host, port = master.split(":")
569 port = int(port)
570 self.announce("contacting the status port at %s:%d" % (host, port))
571 f = pb.PBClientFactory()
572 creds = credentials.UsernamePassword("statusClient", "clientpw")
573 d = f.login(creds)
574 reactor.connectTCP(host, port, f)
575 d.addCallback(self._getStatus_ssh_1)
576 return self.running
577
585
592
594 self.builderNames = []
595 self.buildRequests = {}
596
597
598 self.builds = {}
599
600
601
602 self.outstanding = []
603
604
605
606 self.results = {}
607
608
609
610 self.currentStep = {}
611
612
613
614 self.ETA = {}
615
616 for n,br in brs:
617 self.builderNames.append(n)
618 self.buildRequests[n] = br
619 self.builds[n] = None
620 self.outstanding.append(n)
621 self.results[n] = [None,None]
622 self.currentStep[n] = None
623 self.ETA[n] = None
624
625
626 br.callRemote("subscribe", self)
627
628
629
630 self.printloop = task.LoopingCall(self.printStatus)
631 self.printloop.start(3, now=False)
632
633
634
635
637 if self.builds[builderName]:
638 self.builds[builderName].callRemote("unsubscribe", self)
639 self.builds[builderName] = bs
640 bs.callRemote("subscribe", self, 20)
641 d = bs.callRemote("waitUntilFinished")
642 d.addCallback(self._build_finished, builderName)
643
646
649
651 self.ETA[buildername] = now() + eta
652
654
655
656
657 self.builds[builderName] = None
658 self.ETA[builderName] = None
659 self.currentStep[builderName] = "finished"
660 d = bs.callRemote("getResults")
661 d.addCallback(self._build_finished_2, bs, builderName)
662 return d
664 self.results[builderName][0] = results
665 d = bs.callRemote("getText")
666 d.addCallback(self._build_finished_3, builderName)
667 return d
669 self.results[builderName][1] = text
670
671 self.outstanding.remove(builderName)
672 if not self.outstanding:
673
674 return self.statusDone()
675
677 names = self.buildRequests.keys()
678 names.sort()
679 for n in names:
680 if n not in self.outstanding:
681
682 code,text = self.results[n]
683 t = builder.Results[code]
684 if text:
685 t += " (%s)" % " ".join(text)
686 elif self.builds[n]:
687 t = self.currentStep[n] or "building"
688 if self.ETA[n]:
689 t += " [ETA %ds]" % (self.ETA[n] - now())
690 else:
691 t = "no build"
692 self.announce("%s: %s" % (n, t))
693 self.announce("")
694
696 self.printloop.stop()
697 print "All Builds Complete"
698
699 names = self.buildRequests.keys()
700 names.sort()
701 happy = True
702 for n in names:
703 code,text = self.results[n]
704 t = "%s: %s" % (n, builder.Results[code])
705 if text:
706 t += " (%s)" % " ".join(text)
707 print t
708 if code != builder.SUCCESS:
709 happy = False
710
711 if happy:
712 self.exitcode = 0
713 else:
714 self.exitcode = 1
715 self.running.callback(self.exitcode)
716
718
719
720
721 if self.connect == "pb":
722 user = self.getopt("username", "try_username")
723 passwd = self.getopt("passwd", "try_password")
724 master = self.getopt("master", "try_master")
725 tryhost, tryport = master.split(":")
726 tryport = int(tryport)
727 f = pb.PBClientFactory()
728 d = f.login(credentials.UsernamePassword(user, passwd))
729 reactor.connectTCP(tryhost, tryport, f)
730 d.addCallback(self._getBuilderNames, self._getBuilderNames2)
731 return d
732 if self.connect == "ssh":
733 raise RuntimeError("ssh connection type not supported for this command")
734 raise RuntimeError("unknown connecttype '%s', should be 'pb'" % self.connect)
735
737 d = remote.callRemote("getAvailableBuilderNames")
738 d.addCallback(self._getBuilderNames2)
739 return d
740
742 print "The following builders are available for the try scheduler: "
743 for buildername in buildernames:
744 print buildername
745
747 if not self.quiet:
748 print message
749
751
752
753 print "using '%s' connect method" % self.connect
754 self.exitcode = 0
755 d = defer.Deferred()
756 if bool(self.config.get("get-builder-names")):
757 d.addCallback(lambda res: self.getAvailableBuilderNames())
758 else:
759 d.addCallback(lambda res: self.createJob())
760 d.addCallback(lambda res: self.announce("job created"))
761 deliver = self.deliverJob
762 if bool(self.config.get("dryrun")):
763 deliver = self.fakeDeliverJob
764 d.addCallback(lambda res: deliver())
765 d.addCallback(lambda res: self.announce("job has been delivered"))
766 d.addCallback(lambda res: self.getStatus())
767 d.addErrback(log.err)
768 d.addCallback(self.cleanup)
769 d.addCallback(lambda res: reactor.stop())
770
771 reactor.callLater(0, d.callback, None)
772 reactor.run()
773 sys.exit(self.exitcode)
774
776 log.err(why)
777 print "error during 'try' processing"
778 print why
779
783