Package buildbot :: Package clients :: Module tryclient
[frames] | no frames]

Source Code for Module buildbot.clients.tryclient

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 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   
28 -class SourceStampExtractor:
29
30 - def __init__(self, treetop, branch, repository):
31 self.treetop = treetop 32 self.repository = repository 33 self.branch = branch 34 self.exe = which(self.vcexe)[0]
35
36 - def dovc(self, cmd):
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
45 - def _didvc(self, res, cmd):
46 (stdout, stderr, code) = res 47 # 'bzr diff' sets rc=1 if there were any differences. 48 # cvs does something similar, so don't bother requring rc=0. 49 return stdout
50
51 - def get(self):
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
57 - def readPatch(self, diff, patchlevel):
58 if not diff: 59 diff = None 60 self.patch = (patchlevel, diff)
61 - def done(self, res):
62 if not self.repository: 63 self.repository = self.treetop 64 # TODO: figure out the branch and project too 65 ss = SourceStamp(self.branch, self.baserev, self.patch, 66 repository=self.repository) 67 return ss
68
69 -class CVSExtractor(SourceStampExtractor):
70 patchlevel = 0 71 vcexe = "cvs"
72 - def getBaseRevision(self):
73 # this depends upon our local clock and the repository's clock being 74 # reasonably synchronized with each other. We express everything in 75 # UTC because the '%z' format specifier for strftime doesn't always 76 # work. 77 self.baserev = time.strftime("%Y-%m-%d %H:%M:%S +0000", 78 time.gmtime(now())) 79 return defer.succeed(None)
80
81 - def getPatch(self, res):
82 # the -q tells CVS to not announce each directory as it works 83 if self.branch is not None: 84 # 'cvs diff' won't take both -r and -D at the same time (it 85 # ignores the -r). As best I can tell, there is no way to make 86 # cvs give you a diff relative to a timestamp on the non-trunk 87 # branch. A bare 'cvs diff' will tell you about the changes 88 # relative to your checked-out versions, but I know of no way to 89 # find out what those checked-out versions are. 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
97 -class SVNExtractor(SourceStampExtractor):
98 patchlevel = 0 99 vcexe = "svn" 100
101 - def getBaseRevision(self):
102 d = self.dovc(["status", "-u"]) 103 d.addCallback(self.parseStatus) 104 return d
105 - def parseStatus(self, res):
106 # svn shows the base revision for each file that has been modified or 107 # which needs an update. You can update each file to a different 108 # version, so each file is displayed with its individual base 109 # revision. It also shows the repository-wide latest revision number 110 # on the last line ("Status against revision: \d+"). 111 112 # for our purposes, we use the latest revision number as the "base" 113 # revision, and get a diff against that. This means we will get 114 # reverse-diffs for local files that need updating, but the resulting 115 # tree will still be correct. The only weirdness is that the baserev 116 # that we emit may be different than the version of the tree that we 117 # first checked out. 118 119 # to do this differently would probably involve scanning the revision 120 # numbers to find the max (or perhaps the min) revision, and then 121 # using that as a base. 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)
130 - def getPatch(self, res):
131 d = self.dovc(["diff", "-r%d" % self.baserev]) 132 d.addCallback(self.readPatch, self.patchlevel) 133 return d
134
135 -class BzrExtractor(SourceStampExtractor):
136 patchlevel = 0 137 vcexe = "bzr"
138 - def getBaseRevision(self):
139 d = self.dovc(["revision-info","-rsubmit:"]) 140 d.addCallback(self.get_revision_number) 141 return d
142
143 - def get_revision_number(self, out):
144 revno, revid= out.split() 145 self.baserev = 'revid:' + revid 146 return
147
148 - def getPatch(self, res):
149 d = self.dovc(["diff","-r%s.." % self.baserev]) 150 d.addCallback(self.readPatch, self.patchlevel) 151 return d
152
153 -class MercurialExtractor(SourceStampExtractor):
154 patchlevel = 1 155 vcexe = "hg"
156 - def getBaseRevision(self):
157 d = self.dovc(["identify", "--id", "--debug"]) 158 d.addCallback(self.parseStatus) 159 return d
160 - def parseStatus(self, output):
161 m = re.search(r'^(\w+)', output) 162 self.baserev = m.group(0)
163 - def getPatch(self, res):
164 d = self.dovc(["diff"]) 165 d.addCallback(self.readPatch, self.patchlevel) 166 return d
167 168
169 -class PerforceExtractor(SourceStampExtractor):
170 patchlevel = 0 171 vcexe = "p4"
172 - def getBaseRevision(self):
173 d = self.dovc(["changes", "-m1", "..."]) 174 d.addCallback(self.parseStatus) 175 return d
176
177 - def parseStatus(self, res):
178 # 179 # extract the base change number 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
188 - def readPatch(self, res, patchlevel):
189 # 190 # extract the actual patch from "res" 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)
206 - def getPatch(self, res):
207 d = self.dovc(["diff", "-du"]) 208 d.addCallback(self.readPatch, self.patchlevel) 209 return d
210 211
212 -class DarcsExtractor(SourceStampExtractor):
213 patchlevel = 1 214 vcexe = "darcs"
215 - def getBaseRevision(self):
216 d = self.dovc(["changes", "--context"]) 217 d.addCallback(self.parseStatus) 218 return d
219 - def parseStatus(self, res):
220 self.baserev = res # the whole context file
221 - def getPatch(self, res):
222 d = self.dovc(["diff", "-u"]) 223 d.addCallback(self.readPatch, self.patchlevel) 224 return d
225
226 -class GitExtractor(SourceStampExtractor):
227 patchlevel = 1 228 vcexe = "git" 229 config = None 230
231 - def getBaseRevision(self):
232 # If a branch is specified, parse out the rev it points to 233 # and extract the local name (assuming it has a slash). 234 # This may break if someone specifies the name of a local 235 # branch that has a slash in it and has no corresponding 236 # remote branch (or something similarly contrived). 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
247 - def readConfig(self):
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
254 - def parseConfig(self, res):
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
262 - def parseTrackingBranch(self, res):
263 # If we're tracking a remote, consider that the base. 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
272 - def override_baserev(self, res):
273 self.baserev = res.strip()
274
275 - def parseStatus(self, res):
276 # The current branch is marked by '*' at the start of the 277 # line, followed by the branch name and the SHA1. 278 # 279 # Branch names may contain pretty much anything but whitespace. 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
289 - def getPatch(self, res):
290 d = self.dovc(["diff", self.baserev]) 291 d.addCallback(self.readPatch, self.patchlevel) 292 return d
293
294 -class MonotoneExtractor(SourceStampExtractor):
295 patchlevel = 0 296 vcexe = "mtn"
297 - def getBaseRevision(self):
298 d = self.dovc(["automate", "get_base_revision_id"]) 299 d.addCallback(self.parseStatus) 300 return d
301 - def parseStatus(self, output):
302 hash = output.strip() 303 if len(hash) != 40: 304 self.baserev = None 305 self.baserev = hash
306 - def getPatch(self, res):
307 d = self.dovc(["diff"]) 308 d.addCallback(self.readPatch, self.patchlevel) 309 return d
310 311
312 -def getSourceStamp(vctype, treetop, branch=None, repository=None):
313 if vctype == "cvs": 314 cls = CVSExtractor 315 elif vctype == "svn": 316 cls = SVNExtractor 317 elif vctype == "bzr": 318 cls = BzrExtractor 319 elif vctype == "hg": 320 cls = MercurialExtractor 321 elif vctype == "p4": 322 cls = PerforceExtractor 323 elif vctype == "darcs": 324 cls = DarcsExtractor 325 elif vctype == "git": 326 cls = GitExtractor 327 elif vctype == "mtn": 328 cls = MonotoneExtractor 329 else: 330 raise KeyError("unknown vctype '%s'" % vctype) 331 return cls(treetop, branch, repository).get()
332 333
334 -def ns(s):
335 return "%d:%s," % (len(s), s)
336
337 -def createJobfile(bsid, branch, baserev, patchlevel, diff, repository, 338 project, who, comment, builderNames):
339 340 job = "" 341 342 #Determine job file version from provided arguments 343 if comment: 344 version = 4 345 elif who: 346 version = 3 347 else: 348 version = 2 349 350 job += ns(str(version)) 351 job += ns(bsid) 352 job += ns(branch) 353 job += ns(str(baserev)) 354 job += ns("%d" % patchlevel) 355 job += ns(diff) 356 job += ns(repository) 357 job += ns(project) 358 359 if (version >= 3): 360 job += ns(who) 361 if (version >= 4): 362 job += ns(comment) 363 364 for bn in builderNames: 365 job += ns(bn) 366 367 return job
368
369 -def getTopdir(topfile, start=None):
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 # we've hit the root 381 here = next 382 toomany -= 1 383 raise ValueError("Unable to find topfile '%s' anywhere from %s upwards" 384 % (topfile, start))
385
386 -class RemoteTryPP(protocol.ProcessProtocol):
387 - def __init__(self, job):
388 self.job = job 389 self.d = defer.Deferred()
390 - def connectionMade(self):
391 self.transport.write(self.job) 392 self.transport.closeStdin()
393 - def outReceived(self, data):
394 sys.stdout.write(data)
395 - def errReceived(self, data):
396 sys.stderr.write(data)
397 - def processEnded(self, status_object):
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
406 -class BuildSetStatusGrabber:
407 retryCount = 5 # how many times to we try to grab the BuildSetStatus? 408 retryDelay = 3 # seconds to wait between attempts 409
410 - def __init__(self, status, bsid):
411 self.status = status 412 self.bsid = bsid
413
414 - def grab(self):
415 # return a Deferred that either fires with the BuildSetStatus 416 # reference or errbacks because we were unable to grab it 417 self.d = defer.Deferred() 418 # wait a second before querying to give the master's maildir watcher 419 # a chance to see the job 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
430 - def _gotSets(self, buildsets):
431 for bs,bsid in buildsets: 432 if bsid == self.bsid: 433 # got it 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
446 - def __init__(self, config):
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
461 - def createJob(self):
462 # returns a Deferred which fires when the job parameters have been 463 # created 464 465 # generate a random (unique) string. It would make sense to add a 466 # hostname and process ID here, but a) I suspect that would cause 467 # windows portability problems, and b) really this is good enough 468 self.bsid = "%d-%s" % (time.time(), random.randint(0, 1000000)) 469 470 # common options 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 # we need to find the tree-top 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
501 - def _createJob_1(self, ss):
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
514 - def fakeDeliverJob(self):
515 # Display the job to be delivered, but don't perform delivery. 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
527 - def deliverJob(self):
528 # returns a Deferred that fires when the job has been delivered 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 # now run this command and feed the contents of 'job' into stdin 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
557 - def _deliverJob_pb(self, remote):
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
573 - def _deliverJob_pb2(self, status):
574 self.buildsetStatus = status 575 return status
576
577 - def getStatus(self):
578 # returns a Deferred that fires when the builds have finished, and 579 # may emit status messages while we wait 580 wait = bool(self.getopt("wait")) 581 if not wait: 582 # TODO: emit the URL where they can follow the builds. This 583 # requires contacting the Status server over PB and doing 584 # getURLForThing() on the BuildSetStatus. To get URLs for 585 # individual builds would require we wait for the builds to 586 # start. 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 # contact the status port 594 # we're probably using the ssh style 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
606 - def _getStatus_ssh_1(self, remote):
607 # find a remotereference to the corresponding BuildSetStatus object 608 self.announce("waiting for job to be accepted") 609 g = BuildSetStatusGrabber(remote, self.bsid) 610 d = g.grab() 611 d.addCallback(self._getStatus_1) 612 return d
613
614 - def _getStatus_1(self, res=None):
615 if res: 616 self.buildsetStatus = res 617 # gather the set of BuildRequests 618 d = self.buildsetStatus.callRemote("getBuildRequests") 619 d.addCallback(self._getStatus_2)
620
621 - def _getStatus_2(self, brs):
622 self.builderNames = [] 623 self.buildRequests = {} 624 625 # self.builds holds the current BuildStatus object for each one 626 self.builds = {} 627 628 # self.outstanding holds the list of builderNames which haven't 629 # finished yet 630 self.outstanding = [] 631 632 # self.results holds the list of build results. It holds a tuple of 633 # (result, text) 634 self.results = {} 635 636 # self.currentStep holds the name of the Step that each build is 637 # currently running 638 self.currentStep = {} 639 640 # self.ETA holds the expected finishing time (absolute time since 641 # epoch) 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 # get new Builds for this buildrequest. We follow each one until 653 # it finishes or is interrupted. 654 br.callRemote("subscribe", self) 655 656 # now that those queries are in transit, we can start the 657 # display-status-every-30-seconds loop 658 if not self.getopt("quiet"): 659 self.printloop = task.LoopingCall(self.printStatus) 660 self.printloop.start(3, now=False)
661 662 # these methods are invoked by the status objects we've subscribed to 663
664 - def remote_newbuild(self, bs, builderName):
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
672 - def remote_stepStarted(self, buildername, build, stepname, step):
673 self.currentStep[buildername] = stepname
674
675 - def remote_stepFinished(self, buildername, build, stepname, step, results):
676 pass
677
678 - def remote_buildETAUpdate(self, buildername, build, eta):
679 self.ETA[buildername] = now() + eta
680
681 - def _build_finished(self, bs, builderName):
682 # we need to collect status from the newly-finished build. We don't 683 # remove the build from self.outstanding until we've collected 684 # everything we want. 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
691 - def _build_finished_2(self, results, bs, builderName):
692 self.results[builderName][0] = results 693 d = bs.callRemote("getText") 694 d.addCallback(self._build_finished_3, builderName) 695 return d
696 - def _build_finished_3(self, text, builderName):
697 self.results[builderName][1] = text 698 699 self.outstanding.remove(builderName) 700 if not self.outstanding: 701 # all done 702 return self.statusDone()
703
704 - def printStatus(self):
705 names = self.buildRequests.keys() 706 names.sort() 707 for n in names: 708 if n not in self.outstanding: 709 # the build is finished, and we have results 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
723 - def statusDone(self):
724 if self.printloop: 725 self.printloop.stop() 726 print "All Builds Complete" 727 # TODO: include a URL for all failing builds 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
746 - def getAvailableBuilderNames(self):
747 # This logs into the master using the PB protocol to 748 # get the names of the configured builders that can 749 # be used for the --builder argument 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
765 - def _getBuilderNames(self, remote, output):
766 d = remote.callRemote("getAvailableBuilderNames") 767 d.addCallback(self._getBuilderNames2) 768 return d
769
770 - def _getBuilderNames2(self, buildernames):
771 print "The following builders are available for the try scheduler: " 772 for buildername in buildernames: 773 print buildername
774
775 - def announce(self, message):
776 if not self.quiet: 777 print message
778
779 - def run(self):
780 # we can't do spawnProcess until we're inside reactor.run(), so get 781 # funky 782 print "using '%s' connect method" % self.connect 783 self.exitcode = 0 784 d = defer.Deferred() 785 if bool(self.config.get("get-builder-names")): 786 d.addCallback(lambda res: self.getAvailableBuilderNames()) 787 else: 788 d.addCallback(lambda res: self.createJob()) 789 d.addCallback(lambda res: self.announce("job created")) 790 deliver = self.deliverJob 791 if bool(self.config.get("dryrun")): 792 deliver = self.fakeDeliverJob 793 d.addCallback(lambda res: deliver()) 794 d.addCallback(lambda res: self.announce("job has been delivered")) 795 d.addCallback(lambda res: self.getStatus()) 796 d.addErrback(log.err) 797 d.addCallback(self.cleanup) 798 d.addCallback(lambda res: reactor.stop()) 799 800 reactor.callLater(0, d.callback, None) 801 reactor.run() 802 sys.exit(self.exitcode)
803
804 - def logErr(self, why):
805 log.err(why) 806 print "error during 'try' processing" 807 print why
808
809 - def cleanup(self, res=None):
810 if self.buildsetStatus: 811 self.buildsetStatus.broker.transport.loseConnection()
812