1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from __future__ import with_statement
17
18
19 import os
20 import random
21 import re
22 import sys
23 import time
24
25 from twisted.cred import credentials
26 from twisted.internet import defer
27 from twisted.internet import protocol
28 from twisted.internet import reactor
29 from twisted.internet import task
30 from twisted.internet import utils
31 from twisted.python import log
32 from twisted.python.procutils import which
33 from twisted.spread import pb
34
35 from buildbot.sourcestamp import SourceStamp
36 from buildbot.status import builder
37 from buildbot.util import json
38 from buildbot.util import now
39 from buildbot.util.eventual import fireEventually
43
45 self.treetop = treetop
46 self.repository = repository
47 self.branch = branch
48 exes = which(self.vcexe)
49 if not exes:
50 print "Could not find executable '%s'." % self.vcexe
51 sys.exit(1)
52 self.exe = exes[0]
53
55 """This accepts the arguments of a command, without the actual
56 command itself."""
57 env = os.environ.copy()
58 env['LC_ALL'] = "C"
59 d = utils.getProcessOutputAndValue(self.exe, cmd, env=env,
60 path=self.treetop)
61 d.addCallback(self._didvc, cmd)
62 return d
63
65 (stdout, stderr, code) = res
66
67
68 return stdout
69
71 """Return a Deferred that fires with a SourceStamp instance."""
72 d = self.getBaseRevision()
73 d.addCallback(self.getPatch)
74 d.addCallback(self.done)
75 return d
76
81
89
92 patchlevel = 0
93 vcexe = "cvs"
94
96
97
98
99
100 self.baserev = time.strftime("%Y-%m-%d %H:%M:%S +0000",
101 time.gmtime(now()))
102 return defer.succeed(None)
103
105
106 if self.branch is not None:
107
108
109
110
111
112
113 print "Sorry, CVS 'try' builds don't work with branches"
114 sys.exit(1)
115 args = ['-q', 'diff', '-u', '-D', self.baserev]
116 d = self.dovc(args)
117 d.addCallback(self.readPatch, self.patchlevel)
118 return d
119
122 patchlevel = 0
123 vcexe = "svn"
124
126 d = self.dovc(["status", "-u"])
127 d.addCallback(self.parseStatus)
128 return d
129
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148 for line in res.split("\n"):
149 m = re.search(r'^Status against revision:\s+(\d+)', line)
150 if m:
151 self.baserev = int(m.group(1))
152 return
153 print "Could not find 'Status against revision' in SVN output: %s" % res
154 sys.exit(1)
155
160
163 patchlevel = 0
164 vcexe = "bzr"
165
170
172 revno, revid = out.split()
173 self.baserev = 'revid:' + revid
174 return
175
177 d = self.dovc(["diff", "-r%s.." % self.baserev])
178 d.addCallback(self.readPatch, self.patchlevel)
179 return d
180
183 patchlevel = 1
184 vcexe = "hg"
185
187 (stdout, stderr, code) = res
188
189 if code:
190 cs = ' '.join(['hg'] + cmd)
191 if stderr:
192 stderr = '\n' + stderr.rstrip()
193 raise RuntimeError("%s returned %d%s" % (cs, code, stderr))
194
195 return stdout
196
197 @defer.inlineCallbacks
199 upstream = ""
200 if self.repository:
201 upstream = "r'%s'" % self.repository
202 output = ''
203 try:
204 output = yield self.dovc(["log", "--template", "{node}\\n", "-r",
205 "max(::. - outgoing(%s))" % upstream])
206 except RuntimeError:
207
208 if upstream:
209 raise
210
211 output = yield self.dovc(["log", "--template", "{node}\\n", "-r", "p1()"])
212 m = re.search(r'^(\w+)', output)
213 if not m:
214 raise RuntimeError("Revision %r is not in the right format" % (output,))
215 self.baserev = m.group(0)
216
221
224 patchlevel = 0
225 vcexe = "p4"
226
228 d = self.dovc(["changes", "-m1", "..."])
229 d.addCallback(self.parseStatus)
230 return d
231
233
234
235
236 m = re.search(r'Change (\d+)', res)
237 if m:
238 self.baserev = m.group(1)
239 return
240
241 print "Could not find change number in output: %s" % res
242 sys.exit(1)
243
245
246
247
248 if not self.branch:
249 print "you must specify a branch"
250 sys.exit(1)
251 mpatch = ""
252 found = False
253 for line in res.split("\n"):
254 m = re.search('==== //depot/' + self.branch
255 + r'/([\w\/\.\d\-\_]+)#(\d+) -', line)
256 if m:
257 mpatch += "--- %s#%s\n" % (m.group(1), m.group(2))
258 mpatch += "+++ %s\n" % (m.group(1))
259 found = True
260 else:
261 mpatch += line
262 mpatch += "\n"
263 if not found:
264 print "could not parse patch file"
265 sys.exit(1)
266 self.patch = (patchlevel, mpatch)
267
272
290
293 patchlevel = 1
294 vcexe = "git"
295 config = None
296
298
299
300
301
302
303 if self.branch:
304 d = self.dovc(["rev-parse", self.branch])
305 if '/' in self.branch:
306 self.branch = self.branch.split('/', 1)[1]
307 d.addCallback(self.override_baserev)
308 return d
309 d = self.dovc(["branch", "--no-color", "-v", "--no-abbrev"])
310 d.addCallback(self.parseStatus)
311 return d
312
314 if self.config:
315 return defer.succeed(self.config)
316 d = self.dovc(["config", "-l"])
317 d.addCallback(self.parseConfig)
318 return d
319
321 self.config = {}
322 for l in res.split("\n"):
323 if l.strip():
324 parts = l.strip().split("=", 2)
325 self.config[parts[0]] = parts[1]
326 return self.config
327
329
330 remote = self.config.get("branch." + self.branch + ".remote")
331 ref = self.config.get("branch." + self.branch + ".merge")
332 if remote and ref:
333 remote_branch = ref.split("/", 3)[-1]
334 d = self.dovc(["rev-parse", remote + "/" + remote_branch])
335 d.addCallback(self.override_baserev)
336 return d
337
339 self.baserev = res.strip()
340
342
343
344
345
346 m = re.search(r'^\* (\S+)\s+([0-9a-f]{40})', res, re.MULTILINE)
347 if m:
348 self.baserev = m.group(2)
349 self.branch = m.group(1)
350 d = self.readConfig()
351 d.addCallback(self.parseTrackingBranch)
352 return d
353 print "Could not find current GIT branch: %s" % res
354 sys.exit(1)
355
357 d = self.dovc(["diff", self.baserev])
358 d.addCallback(self.readPatch, self.patchlevel)
359 return d
360
363 patchlevel = 0
364 vcexe = "mtn"
365
367 d = self.dovc(["automate", "get_base_revision_id"])
368 d.addCallback(self.parseStatus)
369 return d
370
372 hash = output.strip()
373 if len(hash) != 40:
374 self.baserev = None
375 self.baserev = hash
376
381
382
383 -def getSourceStamp(vctype, treetop, branch=None, repository=None):
404
407 return "%d:%s," % (len(s), s)
408
409
410 -def createJobfile(jobid, branch, baserev, patch_level, patch_body, repository,
411 project, who, comment, builderNames, properties):
412
413 if properties:
414 version = 5
415 elif comment:
416 version = 4
417 elif who:
418 version = 3
419 else:
420 version = 2
421 job = ""
422 job += ns(str(version))
423 if version < 5:
424 job += ns(jobid)
425 job += ns(branch)
426 job += ns(str(baserev))
427 job += ns("%d" % patch_level)
428 job += ns(patch_body)
429 job += ns(repository)
430 job += ns(project)
431 if (version >= 3):
432 job += ns(who)
433 if (version >= 4):
434 job += ns(comment)
435 for bn in builderNames:
436 job += ns(bn)
437 else:
438 job += ns(
439 json.dumps({
440 'jobid': jobid, 'branch': branch, 'baserev': str(baserev),
441 'patch_level': patch_level, 'patch_body': patch_body,
442 'repository': repository, 'project': project, 'who': who,
443 'comment': comment, 'builderNames': builderNames,
444 'properties': properties,
445 }))
446 return job
447
450 """walk upwards from the current directory until we find this topfile"""
451 if not start:
452 start = os.getcwd()
453 here = start
454 toomany = 20
455 while toomany > 0:
456 if os.path.exists(os.path.join(here, topfile)):
457 return here
458 next = os.path.dirname(here)
459 if next == here:
460 break
461 here = next
462 toomany -= 1
463 print ("Unable to find topfile '%s' anywhere from %s upwards"
464 % (topfile, start))
465 sys.exit(1)
466
470 self.job = job
471 self.d = defer.Deferred()
472
476
478 sys.stdout.write(data)
479
481 sys.stderr.write(data)
482
484 sig = status_object.value.signal
485 rc = status_object.value.exitCode
486 if sig != None or rc != 0:
487 self.d.errback(RuntimeError("remote 'buildbot tryserver' failed"
488 ": sig=%s, rc=%s" % (sig, rc)))
489 return
490 self.d.callback((sig, rc))
491
494 retryCount = 5
495 retryDelay = 3
496
500
502
503
504 self.d = defer.Deferred()
505
506
507 reactor.callLater(1, self.go)
508 return self.d
509
510 - def go(self, dummy=None):
511 if self.retryCount == 0:
512 print "couldn't find matching buildset"
513 sys.exit(1)
514 self.retryCount -= 1
515 d = self.status.callRemote("getBuildSets")
516 d.addCallback(self._gotSets)
517
519 for bs, bsid in buildsets:
520 if bsid == self.bsid:
521
522 self.d.callback(bs)
523 return
524 d = defer.Deferred()
525 d.addCallback(self.go)
526 reactor.callLater(self.retryDelay, d.callback, None)
527
528
529 -class Try(pb.Referenceable):
530 buildsetStatus = None
531 quiet = False
532 printloop = False
533
535 self.config = config
536 self.connect = self.getopt('connect')
537 if self.connect not in ['ssh', 'pb']:
538 print "you must specify a connect style: ssh or pb"
539 sys.exit(1)
540 self.builderNames = self.getopt('builders')
541 self.project = self.getopt('project', '')
542 self.who = self.getopt('who')
543 self.comment = self.getopt('comment')
544
545 - def getopt(self, config_name, default=None):
546 value = self.config.get(config_name)
547 if value is None or value == []:
548 value = default
549 return value
550
552
553
554
555
556
557
558 self.bsid = "%d-%s" % (time.time(), random.randint(0, 1000000))
559
560
561 branch = self.getopt("branch")
562
563 difffile = self.config.get("diff")
564 if difffile:
565 baserev = self.config.get("baserev")
566 if difffile == "-":
567 diff = sys.stdin.read()
568 else:
569 with open(difffile, "r") as f:
570 diff = f.read()
571 if not diff:
572 diff = None
573 patch = (self.config['patchlevel'], diff)
574 ss = SourceStamp(
575 branch, baserev, patch, repository=self.getopt("repository"))
576 d = defer.succeed(ss)
577 else:
578 vc = self.getopt("vc")
579 if vc in ("cvs", "svn"):
580
581 topdir = self.getopt("topdir")
582 if topdir:
583 treedir = os.path.expanduser(topdir)
584 else:
585 topfile = self.getopt("topfile")
586 if topfile:
587 treedir = getTopdir(topfile)
588 else:
589 print "Must specify topdir or topfile."
590 sys.exit(1)
591 else:
592 treedir = os.getcwd()
593 d = getSourceStamp(vc, treedir, branch, self.getopt("repository"))
594 d.addCallback(self._createJob_1)
595 return d
596
598 self.sourcestamp = ss
599 if self.connect == "ssh":
600 patchlevel, diff = ss.patch
601 revspec = ss.revision
602 if revspec is None:
603 revspec = ""
604 self.jobfile = createJobfile(
605 self.bsid, ss.branch or "", revspec, patchlevel, diff,
606 ss.repository, self.project, self.who, self.comment,
607 self.builderNames, self.config.get('properties', {}))
608
610
611 ss = self.sourcestamp
612 print ("Job:\n\tRepository: %s\n\tProject: %s\n\tBranch: %s\n\t"
613 "Revision: %s\n\tBuilders: %s\n%s"
614 % (ss.repository, self.project, ss.branch,
615 ss.revision,
616 self.builderNames,
617 ss.patch[1]))
618 d = defer.Deferred()
619 d.callback(True)
620 return d
621
623
624 if self.connect == "ssh":
625 tryhost = self.getopt("host")
626 tryuser = self.getopt("username")
627 trydir = self.getopt("jobdir")
628 buildbotbin = self.getopt("buildbotbin")
629 if tryuser:
630 argv = ["ssh", "-l", tryuser, tryhost,
631 buildbotbin, "tryserver", "--jobdir", trydir]
632 else:
633 argv = ["ssh", tryhost,
634 buildbotbin, "tryserver", "--jobdir", trydir]
635 pp = RemoteTryPP(self.jobfile)
636 reactor.spawnProcess(pp, argv[0], argv, os.environ)
637 d = pp.d
638 return d
639 if self.connect == "pb":
640 user = self.getopt("username")
641 passwd = self.getopt("passwd")
642 master = self.getopt("master")
643 tryhost, tryport = master.split(":")
644 tryport = int(tryport)
645 f = pb.PBClientFactory()
646 d = f.login(credentials.UsernamePassword(user, passwd))
647 reactor.connectTCP(tryhost, tryport, f)
648 d.addCallback(self._deliverJob_pb)
649 return d
650 raise RuntimeError("unknown connecttype '%s', should be 'ssh' or 'pb'"
651 % self.connect)
652
654 ss = self.sourcestamp
655 print "Delivering job; comment=", self.comment
656
657 d = remote.callRemote("try",
658 ss.branch,
659 ss.revision,
660 ss.patch,
661 ss.repository,
662 self.project,
663 self.builderNames,
664 self.who,
665 self.comment,
666 self.config.get('properties', {}))
667 d.addCallback(self._deliverJob_pb2)
668 return d
669
673
675
676
677 wait = bool(self.getopt("wait"))
678 if not wait:
679
680
681
682
683
684 print "not waiting for builds to finish"
685 return
686 d = self.running = defer.Deferred()
687 if self.buildsetStatus:
688 self._getStatus_1()
689 return self.running
690
691
692 master = self.getopt("master")
693 host, port = master.split(":")
694 port = int(port)
695 self.announce("contacting the status port at %s:%d" % (host, port))
696 f = pb.PBClientFactory()
697 creds = credentials.UsernamePassword("statusClient", "clientpw")
698 d = f.login(creds)
699 reactor.connectTCP(host, port, f)
700 d.addCallback(self._getStatus_ssh_1)
701 return self.running
702
710
712 if res:
713 self.buildsetStatus = res
714
715 d = self.buildsetStatus.callRemote("getBuildRequests")
716 d.addCallback(self._getStatus_2)
717
719 self.builderNames = []
720 self.buildRequests = {}
721
722
723 self.builds = {}
724
725
726
727 self.outstanding = []
728
729
730
731 self.results = {}
732
733
734
735 self.currentStep = {}
736
737
738
739 self.ETA = {}
740
741 for n, br in brs:
742 self.builderNames.append(n)
743 self.buildRequests[n] = br
744 self.builds[n] = None
745 self.outstanding.append(n)
746 self.results[n] = [None, None]
747 self.currentStep[n] = None
748 self.ETA[n] = None
749
750
751 br.callRemote("subscribe", self)
752
753
754
755 if not self.getopt("quiet"):
756 self.printloop = task.LoopingCall(self.printStatus)
757 self.printloop.start(3, now=False)
758
759
760
762 if self.builds[builderName]:
763 self.builds[builderName].callRemote("unsubscribe", self)
764 self.builds[builderName] = bs
765 bs.callRemote("subscribe", self, 20)
766 d = bs.callRemote("waitUntilFinished")
767 d.addCallback(self._build_finished, builderName)
768
771
774
776 self.ETA[buildername] = now() + eta
777
779
780
781
782 self.builds[builderName] = None
783 self.ETA[builderName] = None
784 self.currentStep[builderName] = "finished"
785 d = bs.callRemote("getResults")
786 d.addCallback(self._build_finished_2, bs, builderName)
787 return d
788
790 self.results[builderName][0] = results
791 d = bs.callRemote("getText")
792 d.addCallback(self._build_finished_3, builderName)
793 return d
794
796 self.results[builderName][1] = text
797
798 self.outstanding.remove(builderName)
799 if not self.outstanding:
800
801 return self.statusDone()
802
804 try:
805 names = self.buildRequests.keys()
806 names.sort()
807 for n in names:
808 if n not in self.outstanding:
809
810 code, text = self.results[n]
811 t = builder.Results[code]
812 if text:
813 t += " (%s)" % " ".join(text)
814 elif self.builds[n]:
815 t = self.currentStep[n] or "building"
816 if self.ETA[n]:
817 t += " [ETA %ds]" % (self.ETA[n] - now())
818 else:
819 t = "no build"
820 self.announce("%s: %s" % (n, t))
821 self.announce("")
822 except Exception:
823 log.err(None, "printing status")
824
826 if self.printloop:
827 self.printloop.stop()
828 print "All Builds Complete"
829
830 names = self.buildRequests.keys()
831 names.sort()
832 happy = True
833 for n in names:
834 code, text = self.results[n]
835 t = "%s: %s" % (n, builder.Results[code])
836 if text:
837 t += " (%s)" % " ".join(text)
838 print t
839 if code != builder.SUCCESS:
840 happy = False
841
842 if happy:
843 self.exitcode = 0
844 else:
845 self.exitcode = 1
846 self.running.callback(self.exitcode)
847
849
850
851
852 if self.connect == "pb":
853 user = self.getopt("username")
854 passwd = self.getopt("passwd")
855 master = self.getopt("master")
856 tryhost, tryport = master.split(":")
857 tryport = int(tryport)
858 f = pb.PBClientFactory()
859 d = f.login(credentials.UsernamePassword(user, passwd))
860 reactor.connectTCP(tryhost, tryport, f)
861 d.addCallback(self._getBuilderNames, self._getBuilderNames2)
862 return d
863 if self.connect == "ssh":
864 print "Cannot get availble builders over ssh."
865 sys.exit(1)
866 raise RuntimeError(
867 "unknown connecttype '%s', should be 'pb'" % self.connect)
868
870
871
872 properties = self.config.get('properties', {})
873 if properties:
874 d = remote.callRemote("getAvailableBuilderNames", properties)
875 else:
876 d = remote.callRemote("getAvailableBuilderNames")
877 d.addCallback(self._getBuilderNames2)
878 return d
879
881 print "The following builders are available for the try scheduler: "
882 for buildername in buildernames:
883 print buildername
884
886 if not self.quiet:
887 print message
888
913
915 why.trap(SystemExit)
916 self.exitcode = why.value.code
917
921