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)) 
 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