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

Source Code for Module buildbot.clients.tryclient

  1  # -*- test-case-name: buildbot.test.test_scheduler,buildbot.test.test_vc -*- 
  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   
14 -class SourceStampExtractor:
15
16 - def __init__(self, treetop, branch):
17 self.treetop = treetop # also is repository 18 self.branch = branch 19 self.exe = which(self.vcexe)[0]
20
21 - def dovc(self, cmd):
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
30 - def _didvc(self, res, cmd):
31 (stdout, stderr, code) = res 32 # 'bzr diff' sets rc=1 if there were any differences. tla, baz, and 33 # cvs do something similar, so don't bother requring rc=0. 34 return stdout
35
36 - def get(self):
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
42 - def readPatch(self, res, patchlevel):
43 self.patch = (patchlevel, res)
44 - def done(self, res):
45 # TODO: figure out the branch and project too 46 ss = SourceStamp(self.branch, self.baserev, self.patch, 47 repository=self.treetop) 48 return ss
49
50 -class CVSExtractor(SourceStampExtractor):
51 patchlevel = 0 52 vcexe = "cvs"
53 - def getBaseRevision(self):
54 # this depends upon our local clock and the repository's clock being 55 # reasonably synchronized with each other. We express everything in 56 # UTC because the '%z' format specifier for strftime doesn't always 57 # work. 58 self.baserev = time.strftime("%Y-%m-%d %H:%M:%S +0000", 59 time.gmtime(now())) 60 return defer.succeed(None)
61
62 - def getPatch(self, res):
63 # the -q tells CVS to not announce each directory as it works 64 if self.branch is not None: 65 # 'cvs diff' won't take both -r and -D at the same time (it 66 # ignores the -r). As best I can tell, there is no way to make 67 # cvs give you a diff relative to a timestamp on the non-trunk 68 # branch. A bare 'cvs diff' will tell you about the changes 69 # relative to your checked-out versions, but I know of no way to 70 # find out what those checked-out versions are. 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
78 -class SVNExtractor(SourceStampExtractor):
79 patchlevel = 0 80 vcexe = "svn" 81
82 - def getBaseRevision(self):
83 d = self.dovc(["status", "-u"]) 84 d.addCallback(self.parseStatus) 85 return d
86 - def parseStatus(self, res):
87 # svn shows the base revision for each file that has been modified or 88 # which needs an update. You can update each file to a different 89 # version, so each file is displayed with its individual base 90 # revision. It also shows the repository-wide latest revision number 91 # on the last line ("Status against revision: \d+"). 92 93 # for our purposes, we use the latest revision number as the "base" 94 # revision, and get a diff against that. This means we will get 95 # reverse-diffs for local files that need updating, but the resulting 96 # tree will still be correct. The only weirdness is that the baserev 97 # that we emit may be different than the version of the tree that we 98 # first checked out. 99 100 # to do this differently would probably involve scanning the revision 101 # numbers to find the max (or perhaps the min) revision, and then 102 # using that as a base. 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)
111 - def getPatch(self, res):
112 d = self.dovc(["diff", "-r%d" % self.baserev]) 113 d.addCallback(self.readPatch, self.patchlevel) 114 return d
115
116 -class BazExtractor(SourceStampExtractor):
117 patchlevel = 1 118 vcexe = "baz"
119 - def getBaseRevision(self):
120 d = self.dovc(["tree-id"]) 121 d.addCallback(self.parseStatus) 122 return d
123 - def parseStatus(self, res):
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:]
129 - def getPatch(self, res):
130 d = self.dovc(["diff"]) 131 d.addCallback(self.readPatch, self.patchlevel) 132 return d
133
134 -class TlaExtractor(SourceStampExtractor):
135 patchlevel = 1 136 vcexe = "tla"
137 - def getBaseRevision(self):
138 # 'tla logs --full' gives us ARCHIVE/BRANCH--REVISION 139 # 'tla logs' gives us REVISION 140 d = self.dovc(["logs", "--full", "--reverse"]) 141 d.addCallback(self.parseStatus) 142 return d
143 - def parseStatus(self, res):
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
150 - def getPatch(self, res):
151 d = self.dovc(["changes", "--diffs"]) 152 d.addCallback(self.readPatch, self.patchlevel) 153 return d
154
155 -class BzrExtractor(SourceStampExtractor):
156 patchlevel = 0 157 vcexe = "bzr"
158 - def getBaseRevision(self):
159 d = self.dovc(["revision-info","-rsubmit:"]) 160 d.addCallback(self.get_revision_number) 161 return d
162
163 - def get_revision_number(self, out):
164 revno, revid= out.split() 165 self.baserev = 'revid:' + revid 166 return
167
168 - def getPatch(self, res):
169 d = self.dovc(["diff","-r%s.." % self.baserev]) 170 d.addCallback(self.readPatch, self.patchlevel) 171 return d
172
173 -class MercurialExtractor(SourceStampExtractor):
174 patchlevel = 1 175 vcexe = "hg"
176 - def getBaseRevision(self):
177 d = self.dovc(["identify", "--id", "--debug"]) 178 d.addCallback(self.parseStatus) 179 return d
180 - def parseStatus(self, output):
181 m = re.search(r'^(\w+)', output) 182 self.baserev = m.group(0)
183 - def getPatch(self, res):
184 d = self.dovc(["diff"]) 185 d.addCallback(self.readPatch, self.patchlevel) 186 return d
187 188
189 -class PerforceExtractor(SourceStampExtractor):
190 patchlevel = 0 191 vcexe = "p4"
192 - def getBaseRevision(self):
193 d = self.dovc(["changes", "-m1", "..."]) 194 d.addCallback(self.parseStatus) 195 return d
196
197 - def parseStatus(self, res):
198 # 199 # extract the base change number 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
208 - def readPatch(self, res, patchlevel):
209 # 210 # extract the actual patch from "res" 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)
226 - def getPatch(self, res):
227 d = self.dovc(["diff", "-du"]) 228 d.addCallback(self.readPatch, self.patchlevel) 229 return d
230 231
232 -class DarcsExtractor(SourceStampExtractor):
233 patchlevel = 1 234 vcexe = "darcs"
235 - def getBaseRevision(self):
236 d = self.dovc(["changes", "--context"]) 237 d.addCallback(self.parseStatus) 238 return d
239 - def parseStatus(self, res):
240 self.baserev = res # the whole context file
241 - def getPatch(self, res):
242 d = self.dovc(["diff", "-u"]) 243 d.addCallback(self.readPatch, self.patchlevel) 244 return d
245
246 -class GitExtractor(SourceStampExtractor):
247 patchlevel = 1 248 vcexe = "git" 249
250 - def getBaseRevision(self):
251 d = self.dovc(["branch", "--no-color", "-v", "--no-abbrev"]) 252 d.addCallback(self.parseStatus) 253 return d
254
255 - def readConfig(self):
256 d = self.dovc(["config", "-l"]) 257 d.addCallback(self.parseConfig) 258 return d
259
260 - def parseConfig(self, res):
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 # If we're tracking a remote, consider that the base. 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
276 - def override_baserev(self, res):
277 self.baserev = res.strip()
278
279 - def parseStatus(self, res):
280 # The current branch is marked by '*' at the start of the 281 # line, followed by the branch name and the SHA1. 282 # 283 # Branch names may contain pretty much anything but whitespace. 284 m = re.search(r'^\* (\S+)\s+([0-9a-f]{40})', res, re.MULTILINE) 285 if m: 286 self.baserev = m.group(2) 287 # If a branch is specified, parse out the rev it points to 288 # and extract the local name (assuming it has a slash). 289 # This may break if someone specifies the name of a local 290 # branch that has a slash in it and has no corresponding 291 # remote branch (or something similarly contrived). 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
303 - def getPatch(self, res):
304 d = self.dovc(["diff", self.baserev]) 305 d.addCallback(self.readPatch, self.patchlevel) 306 return d
307
308 -def getSourceStamp(vctype, treetop, branch=None):
309 if vctype == "cvs": 310 e = CVSExtractor(treetop, branch) 311 elif vctype == "svn": 312 e = SVNExtractor(treetop, branch) 313 elif vctype == "baz": 314 e = BazExtractor(treetop, branch) 315 elif vctype == "bzr": 316 e = BzrExtractor(treetop, branch) 317 elif vctype == "tla": 318 e = TlaExtractor(treetop, branch) 319 elif vctype == "hg": 320 e = MercurialExtractor(treetop, branch) 321 elif vctype == "p4": 322 e = PerforceExtractor(treetop, branch) 323 elif vctype == "darcs": 324 e = DarcsExtractor(treetop, branch) 325 elif vctype == "git": 326 e = GitExtractor(treetop, branch) 327 else: 328 raise KeyError("unknown vctype '%s'" % vctype) 329 return e.get()
330 331
332 -def ns(s):
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
350 -def getTopdir(topfile, start=None):
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 # we've hit the root 362 here = next 363 toomany -= 1 364 raise ValueError("Unable to find topfile '%s' anywhere from %s upwards" 365 % (topfile, start))
366
367 -class RemoteTryPP(protocol.ProcessProtocol):
368 - def __init__(self, job):
369 self.job = job 370 self.d = defer.Deferred()
371 - def connectionMade(self):
372 self.transport.write(self.job) 373 self.transport.closeStdin()
374 - def outReceived(self, data):
375 sys.stdout.write(data)
376 - def errReceived(self, data):
377 sys.stderr.write(data)
378 - def processEnded(self, status_object):
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
387 -class BuildSetStatusGrabber:
388 retryCount = 5 # how many times to we try to grab the BuildSetStatus? 389 retryDelay = 3 # seconds to wait between attempts 390
391 - def __init__(self, status, bsid):
392 self.status = status 393 self.bsid = bsid
394
395 - def grab(self):
396 # return a Deferred that either fires with the BuildSetStatus 397 # reference or errbacks because we were unable to grab it 398 self.d = defer.Deferred() 399 # wait a second before querying to give the master's maildir watcher 400 # a chance to see the job 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
411 - def _gotSets(self, buildsets):
412 for bs,bsid in buildsets: 413 if bsid == self.bsid: 414 # got it 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
426 - def __init__(self, config):
427 self.config = config 428 self.connect = self.getopt('connect') 429 assert self.connect, "you must specify a connect style: ssh or pb" 430 self.builderNames = self.getopt('builders') 431 self.project = self.getopt('project', '')
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
439 - def createJob(self):
440 # returns a Deferred which fires when the job parameters have been 441 # created 442 443 # generate a random (unique) string. It would make sense to add a 444 # hostname and process ID here, but a) I suspect that would cause 445 # windows portability problems, and b) really this is good enough 446 self.bsid = "%d-%s" % (time.time(), random.randint(0, 1000000)) 447 448 # common options 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 # we need to find the tree-top 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
477 - def _createJob_1(self, ss):
478 self.sourcestamp = ss 479 if self.connect == "ssh": 480 patchlevel, diff = ss.patch 481 revspec = ss.revision 482 if revspec is None: 483 revspec = "" 484 self.jobfile = createJobfile(self.bsid, 485 ss.branch or "", revspec, 486 patchlevel, diff, ss.repository, 487 self.project, self.builderNames)
488
489 - def fakeDeliverJob(self):
490 # Display the job to be delivered, but don't perform delivery. 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
502 - def deliverJob(self):
503 # returns a Deferred that fires when the job has been delivered 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 # now run this command and feed the contents of 'job' into stdin 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
532 - def _deliverJob_pb(self, remote):
533 ss = self.sourcestamp 534 535 d = remote.callRemote("try", 536 ss.branch, 537 ss.revision, 538 ss.patch, 539 ss.repository, 540 self.project, 541 self.builderNames, 542 self.config.get('properties', {})) 543 d.addCallback(self._deliverJob_pb2) 544 return d
545 - def _deliverJob_pb2(self, status):
546 self.buildsetStatus = status 547 return status
548
549 - def getStatus(self):
550 # returns a Deferred that fires when the builds have finished, and 551 # may emit status messages while we wait 552 wait = bool(self.getopt("wait", "try_wait")) 553 if not wait: 554 # TODO: emit the URL where they can follow the builds. This 555 # requires contacting the Status server over PB and doing 556 # getURLForThing() on the BuildSetStatus. To get URLs for 557 # individual builds would require we wait for the builds to 558 # start. 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 # contact the status port 566 # we're probably using the ssh style 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
578 - def _getStatus_ssh_1(self, remote):
579 # find a remotereference to the corresponding BuildSetStatus object 580 self.announce("waiting for job to be accepted") 581 g = BuildSetStatusGrabber(remote, self.bsid) 582 d = g.grab() 583 d.addCallback(self._getStatus_1) 584 return d
585
586 - def _getStatus_1(self, res=None):
587 if res: 588 self.buildsetStatus = res 589 # gather the set of BuildRequests 590 d = self.buildsetStatus.callRemote("getBuildRequests") 591 d.addCallback(self._getStatus_2)
592
593 - def _getStatus_2(self, brs):
594 self.builderNames = [] 595 self.buildRequests = {} 596 597 # self.builds holds the current BuildStatus object for each one 598 self.builds = {} 599 600 # self.outstanding holds the list of builderNames which haven't 601 # finished yet 602 self.outstanding = [] 603 604 # self.results holds the list of build results. It holds a tuple of 605 # (result, text) 606 self.results = {} 607 608 # self.currentStep holds the name of the Step that each build is 609 # currently running 610 self.currentStep = {} 611 612 # self.ETA holds the expected finishing time (absolute time since 613 # epoch) 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 # get new Builds for this buildrequest. We follow each one until 625 # it finishes or is interrupted. 626 br.callRemote("subscribe", self) 627 628 # now that those queries are in transit, we can start the 629 # display-status-every-30-seconds loop 630 self.printloop = task.LoopingCall(self.printStatus) 631 self.printloop.start(3, now=False)
632 633 634 # these methods are invoked by the status objects we've subscribed to 635
636 - def remote_newbuild(self, bs, builderName):
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
644 - def remote_stepStarted(self, buildername, build, stepname, step):
645 self.currentStep[buildername] = stepname
646
647 - def remote_stepFinished(self, buildername, build, stepname, step, results):
648 pass
649
650 - def remote_buildETAUpdate(self, buildername, build, eta):
651 self.ETA[buildername] = now() + eta
652
653 - def _build_finished(self, bs, builderName):
654 # we need to collect status from the newly-finished build. We don't 655 # remove the build from self.outstanding until we've collected 656 # everything we want. 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
663 - def _build_finished_2(self, results, bs, builderName):
664 self.results[builderName][0] = results 665 d = bs.callRemote("getText") 666 d.addCallback(self._build_finished_3, builderName) 667 return d
668 - def _build_finished_3(self, text, builderName):
669 self.results[builderName][1] = text 670 671 self.outstanding.remove(builderName) 672 if not self.outstanding: 673 # all done 674 return self.statusDone()
675
676 - def printStatus(self):
677 names = self.buildRequests.keys() 678 names.sort() 679 for n in names: 680 if n not in self.outstanding: 681 # the build is finished, and we have results 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
695 - def statusDone(self):
696 self.printloop.stop() 697 print "All Builds Complete" 698 # TODO: include a URL for all failing builds 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
717 - def getAvailableBuilderNames(self):
718 # This logs into the master using the PB protocol to 719 # get the names of the configured builders that can 720 # be used for the --builder argument 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
736 - def _getBuilderNames(self, remote, output):
737 d = remote.callRemote("getAvailableBuilderNames") 738 d.addCallback(self._getBuilderNames2) 739 return d
740
741 - def _getBuilderNames2(self, buildernames):
742 print "The following builders are available for the try scheduler: " 743 for buildername in buildernames: 744 print buildername
745
746 - def announce(self, message):
747 if not self.quiet: 748 print message
749
750 - def run(self):
751 # we can't do spawnProcess until we're inside reactor.run(), so get 752 # funky 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
775 - def logErr(self, why):
776 log.err(why) 777 print "error during 'try' processing" 778 print why
779
780 - def cleanup(self, res=None):
781 if self.buildsetStatus: 782 self.buildsetStatus.broker.transport.loseConnection()
783