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):
31 self.treetop = treetop # also is repository 32 self.branch = branch 33 self.exe = which(self.vcexe)[0]
34
35 - def dovc(self, cmd):
36 """This accepts the arguments of a command, without the actual 37 command itself.""" 38 env = os.environ.copy() 39 env['LC_ALL'] = "C" 40 d = utils.getProcessOutputAndValue(self.exe, cmd, env=env, 41 path=self.treetop) 42 d.addCallback(self._didvc, cmd) 43 return d
44 - def _didvc(self, res, cmd):
45 (stdout, stderr, code) = res 46 # 'bzr diff' sets rc=1 if there were any differences. 47 # cvs does something similar, so don't bother requring rc=0. 48 return stdout
49
50 - def get(self):
51 """Return a Deferred that fires with a SourceStamp instance.""" 52 d = self.getBaseRevision() 53 d.addCallback(self.getPatch) 54 d.addCallback(self.done) 55 return d
56 - def readPatch(self, res, patchlevel):
57 self.patch = (patchlevel, res)
58 - def done(self, res):
59 # TODO: figure out the branch and project too 60 ss = SourceStamp(self.branch, self.baserev, self.patch, 61 repository=self.treetop) 62 return ss
63
64 -class CVSExtractor(SourceStampExtractor):
65 patchlevel = 0 66 vcexe = "cvs"
67 - def getBaseRevision(self):
68 # this depends upon our local clock and the repository's clock being 69 # reasonably synchronized with each other. We express everything in 70 # UTC because the '%z' format specifier for strftime doesn't always 71 # work. 72 self.baserev = time.strftime("%Y-%m-%d %H:%M:%S +0000", 73 time.gmtime(now())) 74 return defer.succeed(None)
75
76 - def getPatch(self, res):
77 # the -q tells CVS to not announce each directory as it works 78 if self.branch is not None: 79 # 'cvs diff' won't take both -r and -D at the same time (it 80 # ignores the -r). As best I can tell, there is no way to make 81 # cvs give you a diff relative to a timestamp on the non-trunk 82 # branch. A bare 'cvs diff' will tell you about the changes 83 # relative to your checked-out versions, but I know of no way to 84 # find out what those checked-out versions are. 85 raise RuntimeError("Sorry, CVS 'try' builds don't work with " 86 "branches") 87 args = ['-q', 'diff', '-u', '-D', self.baserev] 88 d = self.dovc(args) 89 d.addCallback(self.readPatch, self.patchlevel) 90 return d
91
92 -class SVNExtractor(SourceStampExtractor):
93 patchlevel = 0 94 vcexe = "svn" 95
96 - def getBaseRevision(self):
97 d = self.dovc(["status", "-u"]) 98 d.addCallback(self.parseStatus) 99 return d
100 - def parseStatus(self, res):
101 # svn shows the base revision for each file that has been modified or 102 # which needs an update. You can update each file to a different 103 # version, so each file is displayed with its individual base 104 # revision. It also shows the repository-wide latest revision number 105 # on the last line ("Status against revision: \d+"). 106 107 # for our purposes, we use the latest revision number as the "base" 108 # revision, and get a diff against that. This means we will get 109 # reverse-diffs for local files that need updating, but the resulting 110 # tree will still be correct. The only weirdness is that the baserev 111 # that we emit may be different than the version of the tree that we 112 # first checked out. 113 114 # to do this differently would probably involve scanning the revision 115 # numbers to find the max (or perhaps the min) revision, and then 116 # using that as a base. 117 118 for line in res.split("\n"): 119 m = re.search(r'^Status against revision:\s+(\d+)', line) 120 if m: 121 self.baserev = int(m.group(1)) 122 return 123 raise IndexError("Could not find 'Status against revision' in " 124 "SVN output: %s" % res)
125 - def getPatch(self, res):
126 d = self.dovc(["diff", "-r%d" % self.baserev]) 127 d.addCallback(self.readPatch, self.patchlevel) 128 return d
129
130 -class BzrExtractor(SourceStampExtractor):
131 patchlevel = 0 132 vcexe = "bzr"
133 - def getBaseRevision(self):
134 d = self.dovc(["revision-info","-rsubmit:"]) 135 d.addCallback(self.get_revision_number) 136 return d
137
138 - def get_revision_number(self, out):
139 revno, revid= out.split() 140 self.baserev = 'revid:' + revid 141 return
142
143 - def getPatch(self, res):
144 d = self.dovc(["diff","-r%s.." % self.baserev]) 145 d.addCallback(self.readPatch, self.patchlevel) 146 return d
147
148 -class MercurialExtractor(SourceStampExtractor):
149 patchlevel = 1 150 vcexe = "hg"
151 - def getBaseRevision(self):
152 d = self.dovc(["identify", "--id", "--debug"]) 153 d.addCallback(self.parseStatus) 154 return d
155 - def parseStatus(self, output):
156 m = re.search(r'^(\w+)', output) 157 self.baserev = m.group(0)
158 - def getPatch(self, res):
159 d = self.dovc(["diff"]) 160 d.addCallback(self.readPatch, self.patchlevel) 161 return d
162 163
164 -class PerforceExtractor(SourceStampExtractor):
165 patchlevel = 0 166 vcexe = "p4"
167 - def getBaseRevision(self):
168 d = self.dovc(["changes", "-m1", "..."]) 169 d.addCallback(self.parseStatus) 170 return d
171
172 - def parseStatus(self, res):
173 # 174 # extract the base change number 175 # 176 m = re.search(r'Change (\d+)',res) 177 if m: 178 self.baserev = m.group(1) 179 return 180 181 raise IndexError("Could not find change number in output: %s" % res)
182
183 - def readPatch(self, res, patchlevel):
184 # 185 # extract the actual patch from "res" 186 # 187 assert self.branch, "you must specify a branch" 188 mpatch = "" 189 found = False 190 for line in res.split("\n"): 191 m = re.search('==== //depot/' + self.branch + r'/([\w\/\.\d\-\_]+)#(\d+) -',line) 192 if m: 193 mpatch += "--- %s#%s\n" % (m.group(1), m.group(2) ) 194 mpatch += "+++ %s\n" % (m.group(1) ) 195 found = True 196 else: 197 mpatch += line 198 mpatch += "\n" 199 assert found, "could not parse patch file" 200 self.patch = (patchlevel, mpatch)
201 - def getPatch(self, res):
202 d = self.dovc(["diff", "-du"]) 203 d.addCallback(self.readPatch, self.patchlevel) 204 return d
205 206
207 -class DarcsExtractor(SourceStampExtractor):
208 patchlevel = 1 209 vcexe = "darcs"
210 - def getBaseRevision(self):
211 d = self.dovc(["changes", "--context"]) 212 d.addCallback(self.parseStatus) 213 return d
214 - def parseStatus(self, res):
215 self.baserev = res # the whole context file
216 - def getPatch(self, res):
217 d = self.dovc(["diff", "-u"]) 218 d.addCallback(self.readPatch, self.patchlevel) 219 return d
220
221 -class GitExtractor(SourceStampExtractor):
222 patchlevel = 1 223 vcexe = "git" 224
225 - def getBaseRevision(self):
226 d = self.dovc(["branch", "--no-color", "-v", "--no-abbrev"]) 227 d.addCallback(self.parseStatus) 228 return d
229
230 - def readConfig(self):
231 d = self.dovc(["config", "-l"]) 232 d.addCallback(self.parseConfig) 233 return d
234
235 - def parseConfig(self, res):
236 git_config = {} 237 for l in res.split("\n"): 238 if l.strip(): 239 parts = l.strip().split("=", 2) 240 git_config[parts[0]] = parts[1] 241 242 # If we're tracking a remote, consider that the base. 243 remote = git_config.get("branch." + self.branch + ".remote") 244 ref = git_config.get("branch." + self.branch + ".merge") 245 if remote and ref: 246 remote_branch = ref.split("/", 3)[-1] 247 d = self.dovc(["rev-parse", remote + "/" + remote_branch]) 248 d.addCallback(self.override_baserev) 249 return d
250
251 - def override_baserev(self, res):
252 self.baserev = res.strip()
253
254 - def parseStatus(self, res):
255 # The current branch is marked by '*' at the start of the 256 # line, followed by the branch name and the SHA1. 257 # 258 # Branch names may contain pretty much anything but whitespace. 259 m = re.search(r'^\* (\S+)\s+([0-9a-f]{40})', res, re.MULTILINE) 260 if m: 261 self.baserev = m.group(2) 262 # If a branch is specified, parse out the rev it points to 263 # and extract the local name (assuming it has a slash). 264 # This may break if someone specifies the name of a local 265 # branch that has a slash in it and has no corresponding 266 # remote branch (or something similarly contrived). 267 if self.branch: 268 d = self.dovc(["rev-parse", self.branch]) 269 if '/' in self.branch: 270 self.branch = self.branch.split('/', 1)[1] 271 d.addCallback(self.override_baserev) 272 return d 273 else: 274 self.branch = m.group(1) 275 return self.readConfig() 276 raise IndexError("Could not find current GIT branch: %s" % res)
277
278 - def getPatch(self, res):
279 d = self.dovc(["diff", self.baserev]) 280 d.addCallback(self.readPatch, self.patchlevel) 281 return d
282
283 -def getSourceStamp(vctype, treetop, branch=None):
284 if vctype == "cvs": 285 e = CVSExtractor(treetop, branch) 286 elif vctype == "svn": 287 e = SVNExtractor(treetop, branch) 288 elif vctype == "bzr": 289 e = BzrExtractor(treetop, branch) 290 elif vctype == "hg": 291 e = MercurialExtractor(treetop, branch) 292 elif vctype == "p4": 293 e = PerforceExtractor(treetop, branch) 294 elif vctype == "darcs": 295 e = DarcsExtractor(treetop, branch) 296 elif vctype == "git": 297 e = GitExtractor(treetop, branch) 298 else: 299 raise KeyError("unknown vctype '%s'" % vctype) 300 return e.get()
301 302
303 -def ns(s):
304 return "%d:%s," % (len(s), s)
305
306 -def createJobfile(bsid, branch, baserev, patchlevel, diff, repository, 307 project, builderNames):
308 job = "" 309 job += ns("2") 310 job += ns(bsid) 311 job += ns(branch) 312 job += ns(str(baserev)) 313 job += ns("%d" % patchlevel) 314 job += ns(diff) 315 job += ns(repository) 316 job += ns(project) 317 for bn in builderNames: 318 job += ns(bn) 319 return job
320
321 -def getTopdir(topfile, start=None):
322 """walk upwards from the current directory until we find this topfile""" 323 if not start: 324 start = os.getcwd() 325 here = start 326 toomany = 20 327 while toomany > 0: 328 if os.path.exists(os.path.join(here, topfile)): 329 return here 330 next = os.path.dirname(here) 331 if next == here: 332 break # we've hit the root 333 here = next 334 toomany -= 1 335 raise ValueError("Unable to find topfile '%s' anywhere from %s upwards" 336 % (topfile, start))
337
338 -class RemoteTryPP(protocol.ProcessProtocol):
339 - def __init__(self, job):
340 self.job = job 341 self.d = defer.Deferred()
342 - def connectionMade(self):
343 self.transport.write(self.job) 344 self.transport.closeStdin()
345 - def outReceived(self, data):
346 sys.stdout.write(data)
347 - def errReceived(self, data):
348 sys.stderr.write(data)
349 - def processEnded(self, status_object):
350 sig = status_object.value.signal 351 rc = status_object.value.exitCode 352 if sig != None or rc != 0: 353 self.d.errback(RuntimeError("remote 'buildbot tryserver' failed" 354 ": sig=%s, rc=%s" % (sig, rc))) 355 return 356 self.d.callback((sig, rc))
357
358 -class BuildSetStatusGrabber:
359 retryCount = 5 # how many times to we try to grab the BuildSetStatus? 360 retryDelay = 3 # seconds to wait between attempts 361
362 - def __init__(self, status, bsid):
363 self.status = status 364 self.bsid = bsid
365
366 - def grab(self):
367 # return a Deferred that either fires with the BuildSetStatus 368 # reference or errbacks because we were unable to grab it 369 self.d = defer.Deferred() 370 # wait a second before querying to give the master's maildir watcher 371 # a chance to see the job 372 reactor.callLater(1, self.go) 373 return self.d
374
375 - def go(self, dummy=None):
376 if self.retryCount == 0: 377 raise RuntimeError("couldn't find matching buildset") 378 self.retryCount -= 1 379 d = self.status.callRemote("getBuildSets") 380 d.addCallback(self._gotSets)
381
382 - def _gotSets(self, buildsets):
383 for bs,bsid in buildsets: 384 if bsid == self.bsid: 385 # got it 386 self.d.callback(bs) 387 return 388 d = defer.Deferred() 389 d.addCallback(self.go) 390 reactor.callLater(self.retryDelay, d.callback, None)
391 392
393 -class Try(pb.Referenceable):
394 buildsetStatus = None 395 quiet = False 396
397 - def __init__(self, config):
398 self.config = config 399 self.connect = self.getopt('connect') 400 assert self.connect, "you must specify a connect style: ssh or pb" 401 self.builderNames = self.getopt('builders') 402 self.project = self.getopt('project', '')
403
404 - def getopt(self, config_name, default=None):
405 value = self.config.get(config_name) 406 if value is None or value == []: 407 value = default 408 return value
409
410 - def createJob(self):
411 # returns a Deferred which fires when the job parameters have been 412 # created 413 414 # generate a random (unique) string. It would make sense to add a 415 # hostname and process ID here, but a) I suspect that would cause 416 # windows portability problems, and b) really this is good enough 417 self.bsid = "%d-%s" % (time.time(), random.randint(0, 1000000)) 418 419 # common options 420 branch = self.getopt("branch") 421 422 difffile = self.config.get("diff") 423 if difffile: 424 baserev = self.config.get("baserev") 425 if difffile == "-": 426 diff = sys.stdin.read() 427 else: 428 diff = open(difffile,"r").read() 429 patch = (self.config['patchlevel'], diff) 430 ss = SourceStamp(branch, baserev, patch) 431 d = defer.succeed(ss) 432 else: 433 vc = self.getopt("vc") 434 if vc in ("cvs", "svn"): 435 # we need to find the tree-top 436 topdir = self.getopt("try-topdir") 437 if topdir: 438 treedir = os.path.expanduser(topdir) 439 else: 440 topfile = self.getopt("try-topfile") 441 treedir = getTopdir(topfile) 442 else: 443 treedir = os.getcwd() 444 d = getSourceStamp(vc, treedir, branch) 445 d.addCallback(self._createJob_1) 446 return d
447
448 - def _createJob_1(self, ss):
449 self.sourcestamp = ss 450 if self.connect == "ssh": 451 patchlevel, diff = ss.patch 452 revspec = ss.revision 453 if revspec is None: 454 revspec = "" 455 self.jobfile = createJobfile(self.bsid, 456 ss.branch or "", revspec, 457 patchlevel, diff, ss.repository, 458 self.project, self.builderNames)
459
460 - def fakeDeliverJob(self):
461 # Display the job to be delivered, but don't perform delivery. 462 ss = self.sourcestamp 463 print ("Job:\n\tRepository: %s\n\tProject: %s\n\tBranch: %s\n\t" 464 "Revision: %s\n\tBuilders: %s\n%s" 465 % (ss.repository, self.project, ss.branch, 466 ss.revision, 467 self.builderNames, 468 ss.patch[1])) 469 d = defer.Deferred() 470 d.callback(True) 471 return d
472
473 - def deliverJob(self):
474 # returns a Deferred that fires when the job has been delivered 475 476 if self.connect == "ssh": 477 tryhost = self.getopt("tryhost") 478 tryuser = self.getopt("username") 479 trydir = self.getopt("trydir") 480 481 argv = ["ssh", "-l", tryuser, tryhost, 482 "buildbot", "tryserver", "--jobdir", trydir] 483 # now run this command and feed the contents of 'job' into stdin 484 485 pp = RemoteTryPP(self.jobfile) 486 reactor.spawnProcess(pp, argv[0], argv, os.environ) 487 d = pp.d 488 return d 489 if self.connect == "pb": 490 user = self.getopt("username") 491 passwd = self.getopt("passwd") 492 master = self.getopt("master") 493 tryhost, tryport = master.split(":") 494 tryport = int(tryport) 495 f = pb.PBClientFactory() 496 d = f.login(credentials.UsernamePassword(user, passwd)) 497 reactor.connectTCP(tryhost, tryport, f) 498 d.addCallback(self._deliverJob_pb) 499 return d 500 raise RuntimeError("unknown connecttype '%s', should be 'ssh' or 'pb'" 501 % self.connect)
502
503 - def _deliverJob_pb(self, remote):
504 ss = self.sourcestamp 505 506 d = remote.callRemote("try", 507 ss.branch, 508 ss.revision, 509 ss.patch, 510 ss.repository, 511 self.project, 512 self.builderNames, 513 self.config.get('properties', {})) 514 d.addCallback(self._deliverJob_pb2) 515 return d
516 - def _deliverJob_pb2(self, status):
517 self.buildsetStatus = status 518 return status
519
520 - def getStatus(self):
521 # returns a Deferred that fires when the builds have finished, and 522 # may emit status messages while we wait 523 wait = bool(self.getopt("wait", "try_wait")) 524 if not wait: 525 # TODO: emit the URL where they can follow the builds. This 526 # requires contacting the Status server over PB and doing 527 # getURLForThing() on the BuildSetStatus. To get URLs for 528 # individual builds would require we wait for the builds to 529 # start. 530 print "not waiting for builds to finish" 531 return 532 d = self.running = defer.Deferred() 533 if self.buildsetStatus: 534 self._getStatus_1() 535 return self.running 536 # contact the status port 537 # we're probably using the ssh style 538 master = self.getopt("master") 539 host, port = master.split(":") 540 port = int(port) 541 self.announce("contacting the status port at %s:%d" % (host, port)) 542 f = pb.PBClientFactory() 543 creds = credentials.UsernamePassword("statusClient", "clientpw") 544 d = f.login(creds) 545 reactor.connectTCP(host, port, f) 546 d.addCallback(self._getStatus_ssh_1) 547 return self.running
548
549 - def _getStatus_ssh_1(self, remote):
550 # find a remotereference to the corresponding BuildSetStatus object 551 self.announce("waiting for job to be accepted") 552 g = BuildSetStatusGrabber(remote, self.bsid) 553 d = g.grab() 554 d.addCallback(self._getStatus_1) 555 return d
556
557 - def _getStatus_1(self, res=None):
558 if res: 559 self.buildsetStatus = res 560 # gather the set of BuildRequests 561 d = self.buildsetStatus.callRemote("getBuildRequests") 562 d.addCallback(self._getStatus_2)
563
564 - def _getStatus_2(self, brs):
565 self.builderNames = [] 566 self.buildRequests = {} 567 568 # self.builds holds the current BuildStatus object for each one 569 self.builds = {} 570 571 # self.outstanding holds the list of builderNames which haven't 572 # finished yet 573 self.outstanding = [] 574 575 # self.results holds the list of build results. It holds a tuple of 576 # (result, text) 577 self.results = {} 578 579 # self.currentStep holds the name of the Step that each build is 580 # currently running 581 self.currentStep = {} 582 583 # self.ETA holds the expected finishing time (absolute time since 584 # epoch) 585 self.ETA = {} 586 587 for n,br in brs: 588 self.builderNames.append(n) 589 self.buildRequests[n] = br 590 self.builds[n] = None 591 self.outstanding.append(n) 592 self.results[n] = [None,None] 593 self.currentStep[n] = None 594 self.ETA[n] = None 595 # get new Builds for this buildrequest. We follow each one until 596 # it finishes or is interrupted. 597 br.callRemote("subscribe", self) 598 599 # now that those queries are in transit, we can start the 600 # display-status-every-30-seconds loop 601 self.printloop = task.LoopingCall(self.printStatus) 602 self.printloop.start(3, now=False)
603 604 605 # these methods are invoked by the status objects we've subscribed to 606
607 - def remote_newbuild(self, bs, builderName):
608 if self.builds[builderName]: 609 self.builds[builderName].callRemote("unsubscribe", self) 610 self.builds[builderName] = bs 611 bs.callRemote("subscribe", self, 20) 612 d = bs.callRemote("waitUntilFinished") 613 d.addCallback(self._build_finished, builderName)
614
615 - def remote_stepStarted(self, buildername, build, stepname, step):
616 self.currentStep[buildername] = stepname
617
618 - def remote_stepFinished(self, buildername, build, stepname, step, results):
619 pass
620
621 - def remote_buildETAUpdate(self, buildername, build, eta):
622 self.ETA[buildername] = now() + eta
623
624 - def _build_finished(self, bs, builderName):
625 # we need to collect status from the newly-finished build. We don't 626 # remove the build from self.outstanding until we've collected 627 # everything we want. 628 self.builds[builderName] = None 629 self.ETA[builderName] = None 630 self.currentStep[builderName] = "finished" 631 d = bs.callRemote("getResults") 632 d.addCallback(self._build_finished_2, bs, builderName) 633 return d
634 - def _build_finished_2(self, results, bs, builderName):
635 self.results[builderName][0] = results 636 d = bs.callRemote("getText") 637 d.addCallback(self._build_finished_3, builderName) 638 return d
639 - def _build_finished_3(self, text, builderName):
640 self.results[builderName][1] = text 641 642 self.outstanding.remove(builderName) 643 if not self.outstanding: 644 # all done 645 return self.statusDone()
646
647 - def printStatus(self):
648 names = self.buildRequests.keys() 649 names.sort() 650 for n in names: 651 if n not in self.outstanding: 652 # the build is finished, and we have results 653 code,text = self.results[n] 654 t = builder.Results[code] 655 if text: 656 t += " (%s)" % " ".join(text) 657 elif self.builds[n]: 658 t = self.currentStep[n] or "building" 659 if self.ETA[n]: 660 t += " [ETA %ds]" % (self.ETA[n] - now()) 661 else: 662 t = "no build" 663 self.announce("%s: %s" % (n, t)) 664 self.announce("")
665
666 - def statusDone(self):
667 self.printloop.stop() 668 print "All Builds Complete" 669 # TODO: include a URL for all failing builds 670 names = self.buildRequests.keys() 671 names.sort() 672 happy = True 673 for n in names: 674 code,text = self.results[n] 675 t = "%s: %s" % (n, builder.Results[code]) 676 if text: 677 t += " (%s)" % " ".join(text) 678 print t 679 if code != builder.SUCCESS: 680 happy = False 681 682 if happy: 683 self.exitcode = 0 684 else: 685 self.exitcode = 1 686 self.running.callback(self.exitcode)
687
688 - def getAvailableBuilderNames(self):
689 # This logs into the master using the PB protocol to 690 # get the names of the configured builders that can 691 # be used for the --builder argument 692 if self.connect == "pb": 693 user = self.getopt("username", "try_username") 694 passwd = self.getopt("passwd", "try_password") 695 master = self.getopt("master", "try_master") 696 tryhost, tryport = master.split(":") 697 tryport = int(tryport) 698 f = pb.PBClientFactory() 699 d = f.login(credentials.UsernamePassword(user, passwd)) 700 reactor.connectTCP(tryhost, tryport, f) 701 d.addCallback(self._getBuilderNames, self._getBuilderNames2) 702 return d 703 if self.connect == "ssh": 704 raise RuntimeError("ssh connection type not supported for this command") 705 raise RuntimeError("unknown connecttype '%s', should be 'pb'" % self.connect)
706
707 - def _getBuilderNames(self, remote, output):
708 d = remote.callRemote("getAvailableBuilderNames") 709 d.addCallback(self._getBuilderNames2) 710 return d
711
712 - def _getBuilderNames2(self, buildernames):
713 print "The following builders are available for the try scheduler: " 714 for buildername in buildernames: 715 print buildername
716
717 - def announce(self, message):
718 if not self.quiet: 719 print message
720
721 - def run(self):
722 # we can't do spawnProcess until we're inside reactor.run(), so get 723 # funky 724 print "using '%s' connect method" % self.connect 725 self.exitcode = 0 726 d = defer.Deferred() 727 if bool(self.config.get("get-builder-names")): 728 d.addCallback(lambda res: self.getAvailableBuilderNames()) 729 else: 730 d.addCallback(lambda res: self.createJob()) 731 d.addCallback(lambda res: self.announce("job created")) 732 deliver = self.deliverJob 733 if bool(self.config.get("dryrun")): 734 deliver = self.fakeDeliverJob 735 d.addCallback(lambda res: deliver()) 736 d.addCallback(lambda res: self.announce("job has been delivered")) 737 d.addCallback(lambda res: self.getStatus()) 738 d.addErrback(log.err) 739 d.addCallback(self.cleanup) 740 d.addCallback(lambda res: reactor.stop()) 741 742 reactor.callLater(0, d.callback, None) 743 reactor.run() 744 sys.exit(self.exitcode)
745
746 - def logErr(self, why):
747 log.err(why) 748 print "error during 'try' processing" 749 print why
750
751 - def cleanup(self, res=None):
752 if self.buildsetStatus: 753 self.buildsetStatus.broker.transport.loseConnection()
754