1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import sys, os, re, time, random
18 from twisted.internet import utils, protocol, defer, reactor, task
19 from twisted.spread import pb
20 from twisted.cred import credentials
21 from twisted.python import log
22 from twisted.python.procutils import which
23
24 from buildbot.sourcestamp import SourceStamp
25 from buildbot.util import now
26 from buildbot.status import builder
27
29
35
37 """This accepts the arguments of a command, without the actual
38 command itself."""
39 env = os.environ.copy()
40 env['LC_ALL'] = "C"
41 d = utils.getProcessOutputAndValue(self.exe, cmd, env=env,
42 path=self.treetop)
43 d.addCallback(self._didvc, cmd)
44 return d
46 (stdout, stderr, code) = res
47
48
49 return stdout
50
52 """Return a Deferred that fires with a SourceStamp instance."""
53 d = self.getBaseRevision()
54 d.addCallback(self.getPatch)
55 d.addCallback(self.done)
56 return d
68
70 patchlevel = 0
71 vcexe = "cvs"
73
74
75
76
77 self.baserev = time.strftime("%Y-%m-%d %H:%M:%S +0000",
78 time.gmtime(now()))
79 return defer.succeed(None)
80
82
83 if self.branch is not None:
84
85
86
87
88
89
90 raise RuntimeError("Sorry, CVS 'try' builds don't work with "
91 "branches")
92 args = ['-q', 'diff', '-u', '-D', self.baserev]
93 d = self.dovc(args)
94 d.addCallback(self.readPatch, self.patchlevel)
95 return d
96
98 patchlevel = 0
99 vcexe = "svn"
100
102 d = self.dovc(["status", "-u"])
103 d.addCallback(self.parseStatus)
104 return d
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 for line in res.split("\n"):
124 m = re.search(r'^Status against revision:\s+(\d+)', line)
125 if m:
126 self.baserev = int(m.group(1))
127 return
128 raise IndexError("Could not find 'Status against revision' in "
129 "SVN output: %s" % res)
134
136 patchlevel = 0
137 vcexe = "bzr"
142
144 revno, revid= out.split()
145 self.baserev = 'revid:' + revid
146 return
147
149 d = self.dovc(["diff","-r%s.." % self.baserev])
150 d.addCallback(self.readPatch, self.patchlevel)
151 return d
152
154 patchlevel = 1
155 vcexe = "hg"
157 d = self.dovc(["identify", "--id", "--debug"])
158 d.addCallback(self.parseStatus)
159 return d
161 m = re.search(r'^(\w+)', output)
162 self.baserev = m.group(0)
167
168
170 patchlevel = 0
171 vcexe = "p4"
173 d = self.dovc(["changes", "-m1", "..."])
174 d.addCallback(self.parseStatus)
175 return d
176
178
179
180
181 m = re.search(r'Change (\d+)',res)
182 if m:
183 self.baserev = m.group(1)
184 return
185
186 raise IndexError("Could not find change number in output: %s" % res)
187
189
190
191
192 assert self.branch, "you must specify a branch"
193 mpatch = ""
194 found = False
195 for line in res.split("\n"):
196 m = re.search('==== //depot/' + self.branch + r'/([\w\/\.\d\-\_]+)#(\d+) -',line)
197 if m:
198 mpatch += "--- %s#%s\n" % (m.group(1), m.group(2) )
199 mpatch += "+++ %s\n" % (m.group(1) )
200 found = True
201 else:
202 mpatch += line
203 mpatch += "\n"
204 assert found, "could not parse patch file"
205 self.patch = (patchlevel, mpatch)
210
211
225
227 patchlevel = 1
228 vcexe = "git"
229 config = None
230
232
233
234
235
236
237 if self.branch:
238 d = self.dovc(["rev-parse", self.branch])
239 if '/' in self.branch:
240 self.branch = self.branch.split('/', 1)[1]
241 d.addCallback(self.override_baserev)
242 return d
243 d = self.dovc(["branch", "--no-color", "-v", "--no-abbrev"])
244 d.addCallback(self.parseStatus)
245 return d
246
248 if self.config:
249 return defer.succeed(self.config)
250 d = self.dovc(["config", "-l"])
251 d.addCallback(self.parseConfig)
252 return d
253
255 self.config = {}
256 for l in res.split("\n"):
257 if l.strip():
258 parts = l.strip().split("=", 2)
259 self.config[parts[0]] = parts[1]
260 return self.config
261
263
264 remote = self.config.get("branch." + self.branch + ".remote")
265 ref = self.config.get("branch." + self.branch + ".merge")
266 if remote and ref:
267 remote_branch = ref.split("/", 3)[-1]
268 d = self.dovc(["rev-parse", remote + "/" + remote_branch])
269 d.addCallback(self.override_baserev)
270 return d
271
273 self.baserev = res.strip()
274
276
277
278
279
280 m = re.search(r'^\* (\S+)\s+([0-9a-f]{40})', res, re.MULTILINE)
281 if m:
282 self.baserev = m.group(2)
283 self.branch = m.group(1)
284 d = self.readConfig()
285 d.addCallback(self.parseTrackingBranch)
286 return d
287 raise IndexError("Could not find current GIT branch: %s" % res)
288
290 d = self.dovc(["diff", self.baserev])
291 d.addCallback(self.readPatch, self.patchlevel)
292 return d
293
295 patchlevel = 0
296 vcexe = "mtn"
298 d = self.dovc(["automate", "get_base_revision_id"])
299 d.addCallback(self.parseStatus)
300 return d
302 hash = output.strip()
303 if len(hash) != 40:
304 self.baserev = None
305 self.baserev = hash
310
311
332
333
335 return "%d:%s," % (len(s), s)
336
337 -def createJobfile(bsid, branch, baserev, patchlevel, diff, repository,
338 project, who, comment, builderNames):
368
370 """walk upwards from the current directory until we find this topfile"""
371 if not start:
372 start = os.getcwd()
373 here = start
374 toomany = 20
375 while toomany > 0:
376 if os.path.exists(os.path.join(here, topfile)):
377 return here
378 next = os.path.dirname(here)
379 if next == here:
380 break
381 here = next
382 toomany -= 1
383 raise ValueError("Unable to find topfile '%s' anywhere from %s upwards"
384 % (topfile, start))
385
388 self.job = job
389 self.d = defer.Deferred()
394 sys.stdout.write(data)
396 sys.stderr.write(data)
398 sig = status_object.value.signal
399 rc = status_object.value.exitCode
400 if sig != None or rc != 0:
401 self.d.errback(RuntimeError("remote 'buildbot tryserver' failed"
402 ": sig=%s, rc=%s" % (sig, rc)))
403 return
404 self.d.callback((sig, rc))
405
407 retryCount = 5
408 retryDelay = 3
409
413
415
416
417 self.d = defer.Deferred()
418
419
420 reactor.callLater(1, self.go)
421 return self.d
422
423 - def go(self, dummy=None):
424 if self.retryCount == 0:
425 raise RuntimeError("couldn't find matching buildset")
426 self.retryCount -= 1
427 d = self.status.callRemote("getBuildSets")
428 d.addCallback(self._gotSets)
429
431 for bs,bsid in buildsets:
432 if bsid == self.bsid:
433
434 self.d.callback(bs)
435 return
436 d = defer.Deferred()
437 d.addCallback(self.go)
438 reactor.callLater(self.retryDelay, d.callback, None)
439
440
441 -class Try(pb.Referenceable):
442 buildsetStatus = None
443 quiet = False
444 printloop = False
445
447 self.config = config
448 self.connect = self.getopt('connect')
449 assert self.connect, "you must specify a connect style: ssh or pb"
450 self.builderNames = self.getopt('builders')
451 self.project = self.getopt('project', '')
452 self.who = self.getopt('who')
453 self.comment = self.getopt('comment')
454
455 - def getopt(self, config_name, default=None):
456 value = self.config.get(config_name)
457 if value is None or value == []:
458 value = default
459 return value
460
462
463
464
465
466
467
468 self.bsid = "%d-%s" % (time.time(), random.randint(0, 1000000))
469
470
471 branch = self.getopt("branch")
472
473 difffile = self.config.get("diff")
474 if difffile:
475 baserev = self.config.get("baserev")
476 if difffile == "-":
477 diff = sys.stdin.read()
478 else:
479 diff = open(difffile,"r").read()
480 if not diff:
481 diff = None
482 patch = (self.config['patchlevel'], diff)
483 ss = SourceStamp(branch, baserev, patch, repository = self.getopt("repository"))
484 d = defer.succeed(ss)
485 else:
486 vc = self.getopt("vc")
487 if vc in ("cvs", "svn"):
488
489 topdir = self.getopt("topdir")
490 if topdir:
491 treedir = os.path.expanduser(topdir)
492 else:
493 topfile = self.getopt("topfile")
494 treedir = getTopdir(topfile)
495 else:
496 treedir = os.getcwd()
497 d = getSourceStamp(vc, treedir, branch, self.getopt("repository"))
498 d.addCallback(self._createJob_1)
499 return d
500
502 self.sourcestamp = ss
503 if self.connect == "ssh":
504 patchlevel, diff = ss.patch
505 revspec = ss.revision
506 if revspec is None:
507 revspec = ""
508 self.jobfile = createJobfile(self.bsid,
509 ss.branch or "", revspec,
510 patchlevel, diff, ss.repository,
511 self.project, self.who, self.comment,
512 self.builderNames)
513
515
516 ss = self.sourcestamp
517 print ("Job:\n\tRepository: %s\n\tProject: %s\n\tBranch: %s\n\t"
518 "Revision: %s\n\tBuilders: %s\n%s"
519 % (ss.repository, self.project, ss.branch,
520 ss.revision,
521 self.builderNames,
522 ss.patch[1]))
523 d = defer.Deferred()
524 d.callback(True)
525 return d
526
528
529
530 if self.connect == "ssh":
531 tryhost = self.getopt("host")
532 tryuser = self.getopt("username")
533 trydir = self.getopt("jobdir")
534
535 argv = ["ssh", "-l", tryuser, tryhost,
536 "buildbot", "tryserver", "--jobdir", trydir]
537
538
539 pp = RemoteTryPP(self.jobfile)
540 reactor.spawnProcess(pp, argv[0], argv, os.environ)
541 d = pp.d
542 return d
543 if self.connect == "pb":
544 user = self.getopt("username")
545 passwd = self.getopt("passwd")
546 master = self.getopt("master")
547 tryhost, tryport = master.split(":")
548 tryport = int(tryport)
549 f = pb.PBClientFactory()
550 d = f.login(credentials.UsernamePassword(user, passwd))
551 reactor.connectTCP(tryhost, tryport, f)
552 d.addCallback(self._deliverJob_pb)
553 return d
554 raise RuntimeError("unknown connecttype '%s', should be 'ssh' or 'pb'"
555 % self.connect)
556
558 ss = self.sourcestamp
559 print "Delivering job; comment=", self.comment
560
561 d = remote.callRemote("try",
562 ss.branch,
563 ss.revision,
564 ss.patch,
565 ss.repository,
566 self.project,
567 self.builderNames,
568 self.who,
569 self.comment,
570 self.config.get('properties', {}))
571 d.addCallback(self._deliverJob_pb2)
572 return d
576
578
579
580 wait = bool(self.getopt("wait"))
581 if not wait:
582
583
584
585
586
587 print "not waiting for builds to finish"
588 return
589 d = self.running = defer.Deferred()
590 if self.buildsetStatus:
591 self._getStatus_1()
592 return self.running
593
594
595 master = self.getopt("master")
596 host, port = master.split(":")
597 port = int(port)
598 self.announce("contacting the status port at %s:%d" % (host, port))
599 f = pb.PBClientFactory()
600 creds = credentials.UsernamePassword("statusClient", "clientpw")
601 d = f.login(creds)
602 reactor.connectTCP(host, port, f)
603 d.addCallback(self._getStatus_ssh_1)
604 return self.running
605
613
615 if res:
616 self.buildsetStatus = res
617
618 d = self.buildsetStatus.callRemote("getBuildRequests")
619 d.addCallback(self._getStatus_2)
620
622 self.builderNames = []
623 self.buildRequests = {}
624
625
626 self.builds = {}
627
628
629
630 self.outstanding = []
631
632
633
634 self.results = {}
635
636
637
638 self.currentStep = {}
639
640
641
642 self.ETA = {}
643
644 for n,br in brs:
645 self.builderNames.append(n)
646 self.buildRequests[n] = br
647 self.builds[n] = None
648 self.outstanding.append(n)
649 self.results[n] = [None,None]
650 self.currentStep[n] = None
651 self.ETA[n] = None
652
653
654 br.callRemote("subscribe", self)
655
656
657
658 if not self.getopt("quiet"):
659 self.printloop = task.LoopingCall(self.printStatus)
660 self.printloop.start(3, now=False)
661
662
663
665 if self.builds[builderName]:
666 self.builds[builderName].callRemote("unsubscribe", self)
667 self.builds[builderName] = bs
668 bs.callRemote("subscribe", self, 20)
669 d = bs.callRemote("waitUntilFinished")
670 d.addCallback(self._build_finished, builderName)
671
674
677
679 self.ETA[buildername] = now() + eta
680
682
683
684
685 self.builds[builderName] = None
686 self.ETA[builderName] = None
687 self.currentStep[builderName] = "finished"
688 d = bs.callRemote("getResults")
689 d.addCallback(self._build_finished_2, bs, builderName)
690 return d
692 self.results[builderName][0] = results
693 d = bs.callRemote("getText")
694 d.addCallback(self._build_finished_3, builderName)
695 return d
697 self.results[builderName][1] = text
698
699 self.outstanding.remove(builderName)
700 if not self.outstanding:
701
702 return self.statusDone()
703
705 names = self.buildRequests.keys()
706 names.sort()
707 for n in names:
708 if n not in self.outstanding:
709
710 code,text = self.results[n]
711 t = builder.Results[code]
712 if text:
713 t += " (%s)" % " ".join(text)
714 elif self.builds[n]:
715 t = self.currentStep[n] or "building"
716 if self.ETA[n]:
717 t += " [ETA %ds]" % (self.ETA[n] - now())
718 else:
719 t = "no build"
720 self.announce("%s: %s" % (n, t))
721 self.announce("")
722
724 if self.printloop:
725 self.printloop.stop()
726 print "All Builds Complete"
727
728 names = self.buildRequests.keys()
729 names.sort()
730 happy = True
731 for n in names:
732 code,text = self.results[n]
733 t = "%s: %s" % (n, builder.Results[code])
734 if text:
735 t += " (%s)" % " ".join(text)
736 print t
737 if code != builder.SUCCESS:
738 happy = False
739
740 if happy:
741 self.exitcode = 0
742 else:
743 self.exitcode = 1
744 self.running.callback(self.exitcode)
745
747
748
749
750 if self.connect == "pb":
751 user = self.getopt("username")
752 passwd = self.getopt("passwd")
753 master = self.getopt("master")
754 tryhost, tryport = master.split(":")
755 tryport = int(tryport)
756 f = pb.PBClientFactory()
757 d = f.login(credentials.UsernamePassword(user, passwd))
758 reactor.connectTCP(tryhost, tryport, f)
759 d.addCallback(self._getBuilderNames, self._getBuilderNames2)
760 return d
761 if self.connect == "ssh":
762 raise RuntimeError("ssh connection type not supported for this command")
763 raise RuntimeError("unknown connecttype '%s', should be 'pb'" % self.connect)
764
766 d = remote.callRemote("getAvailableBuilderNames")
767 d.addCallback(self._getBuilderNames2)
768 return d
769
771 print "The following builders are available for the try scheduler: "
772 for buildername in buildernames:
773 print buildername
774
776 if not self.quiet:
777 print message
778
803
805 log.err(why)
806 print "error during 'try' processing"
807 print why
808
812