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
40
42
48
50 """This accepts the arguments of a command, without the actual
51 command itself."""
52 env = os.environ.copy()
53 env['LC_ALL'] = "C"
54 d = utils.getProcessOutputAndValue(self.exe, cmd, env=env,
55 path=self.treetop)
56 d.addCallback(self._didvc, cmd)
57 return d
58
60 (stdout, stderr, code) = res
61
62
63 return stdout
64
66 """Return a Deferred that fires with a SourceStamp instance."""
67 d = self.getBaseRevision()
68 d.addCallback(self.getPatch)
69 d.addCallback(self.done)
70 return d
71
76
84
85
87 patchlevel = 0
88 vcexe = "cvs"
89
91
92
93
94
95 self.baserev = time.strftime("%Y-%m-%d %H:%M:%S +0000",
96 time.gmtime(now()))
97 return defer.succeed(None)
98
100
101 if self.branch is not None:
102
103
104
105
106
107
108 raise RuntimeError("Sorry, CVS 'try' builds don't work with "
109 "branches")
110 args = ['-q', 'diff', '-u', '-D', self.baserev]
111 d = self.dovc(args)
112 d.addCallback(self.readPatch, self.patchlevel)
113 return d
114
115
117 patchlevel = 0
118 vcexe = "svn"
119
121 d = self.dovc(["status", "-u"])
122 d.addCallback(self.parseStatus)
123 return d
124
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143 for line in res.split("\n"):
144 m = re.search(r'^Status against revision:\s+(\d+)', line)
145 if m:
146 self.baserev = int(m.group(1))
147 return
148 raise IndexError("Could not find 'Status against revision' in "
149 "SVN output: %s" % res)
150
155
156
158 patchlevel = 0
159 vcexe = "bzr"
160
165
167 revno, revid = out.split()
168 self.baserev = 'revid:' + revid
169 return
170
172 d = self.dovc(["diff", "-r%s.." % self.baserev])
173 d.addCallback(self.readPatch, self.patchlevel)
174 return d
175
176
178 patchlevel = 1
179 vcexe = "hg"
180
182 upstream = ""
183 if self.repository:
184 upstream = "r'%s'" % self.repository
185 d = self.dovc(["log", "--template", "{node}\\n", "-r", "limit(parents(outgoing(%s) and branch(parents())) or parents(), 1)" % upstream])
186 d.addCallback(self.parseStatus)
187 return d
188
190 m = re.search(r'^(\w+)', output)
191 self.baserev = m.group(0)
192
197
198
200 patchlevel = 0
201 vcexe = "p4"
202
204 d = self.dovc(["changes", "-m1", "..."])
205 d.addCallback(self.parseStatus)
206 return d
207
209
210
211
212 m = re.search(r'Change (\d+)', res)
213 if m:
214 self.baserev = m.group(1)
215 return
216
217 raise IndexError("Could not find change number in output: %s" % res)
218
220
221
222
223 assert self.branch, "you must specify a branch"
224 mpatch = ""
225 found = False
226 for line in res.split("\n"):
227 m = re.search('==== //depot/' + self.branch
228 + r'/([\w\/\.\d\-\_]+)#(\d+) -', line)
229 if m:
230 mpatch += "--- %s#%s\n" % (m.group(1), m.group(2))
231 mpatch += "+++ %s\n" % (m.group(1))
232 found = True
233 else:
234 mpatch += line
235 mpatch += "\n"
236 assert found, "could not parse patch file"
237 self.patch = (patchlevel, mpatch)
238
243
244
261
262
264 patchlevel = 1
265 vcexe = "git"
266 config = None
267
269
270
271
272
273
274 if self.branch:
275 d = self.dovc(["rev-parse", self.branch])
276 if '/' in self.branch:
277 self.branch = self.branch.split('/', 1)[1]
278 d.addCallback(self.override_baserev)
279 return d
280 d = self.dovc(["branch", "--no-color", "-v", "--no-abbrev"])
281 d.addCallback(self.parseStatus)
282 return d
283
285 if self.config:
286 return defer.succeed(self.config)
287 d = self.dovc(["config", "-l"])
288 d.addCallback(self.parseConfig)
289 return d
290
292 self.config = {}
293 for l in res.split("\n"):
294 if l.strip():
295 parts = l.strip().split("=", 2)
296 self.config[parts[0]] = parts[1]
297 return self.config
298
300
301 remote = self.config.get("branch." + self.branch + ".remote")
302 ref = self.config.get("branch." + self.branch + ".merge")
303 if remote and ref:
304 remote_branch = ref.split("/", 3)[-1]
305 d = self.dovc(["rev-parse", remote + "/" + remote_branch])
306 d.addCallback(self.override_baserev)
307 return d
308
310 self.baserev = res.strip()
311
313
314
315
316
317 m = re.search(r'^\* (\S+)\s+([0-9a-f]{40})', res, re.MULTILINE)
318 if m:
319 self.baserev = m.group(2)
320 self.branch = m.group(1)
321 d = self.readConfig()
322 d.addCallback(self.parseTrackingBranch)
323 return d
324 raise IndexError("Could not find current GIT branch: %s" % res)
325
327 d = self.dovc(["diff", self.baserev])
328 d.addCallback(self.readPatch, self.patchlevel)
329 return d
330
331
333 patchlevel = 0
334 vcexe = "mtn"
335
337 d = self.dovc(["automate", "get_base_revision_id"])
338 d.addCallback(self.parseStatus)
339 return d
340
342 hash = output.strip()
343 if len(hash) != 40:
344 self.baserev = None
345 self.baserev = hash
346
351
352
373
374
376 return "%d:%s," % (len(s), s)
377
378
379 -def createJobfile(jobid, branch, baserev, patch_level, patch_body, repository,
380 project, who, comment, builderNames, properties):
381
382 if properties:
383 version = 5
384 elif comment:
385 version = 4
386 elif who:
387 version = 3
388 else:
389 version = 2
390 job = ""
391 job += ns(str(version))
392 if version < 5:
393 job += ns(jobid)
394 job += ns(branch)
395 job += ns(str(baserev))
396 job += ns("%d" % patch_level)
397 job += ns(patch_body)
398 job += ns(repository)
399 job += ns(project)
400 if (version >= 3):
401 job += ns(who)
402 if (version >= 4):
403 job += ns(comment)
404 for bn in builderNames:
405 job += ns(bn)
406 else:
407 job += ns(
408 json.dumps({
409 'jobid': jobid, 'branch': branch, 'baserev': str(baserev),
410 'patch_level': patch_level, 'patch_body': patch_body,
411 'repository': repository, 'project': project, 'who': who,
412 'comment': comment, 'builderNames': builderNames,
413 'properties': properties,
414 }))
415 return job
416
417
419 """walk upwards from the current directory until we find this topfile"""
420 if not start:
421 start = os.getcwd()
422 here = start
423 toomany = 20
424 while toomany > 0:
425 if os.path.exists(os.path.join(here, topfile)):
426 return here
427 next = os.path.dirname(here)
428 if next == here:
429 break
430 here = next
431 toomany -= 1
432 raise ValueError("Unable to find topfile '%s' anywhere from %s upwards"
433 % (topfile, start))
434
435
438 self.job = job
439 self.d = defer.Deferred()
440
444
446 sys.stdout.write(data)
447
449 sys.stderr.write(data)
450
452 sig = status_object.value.signal
453 rc = status_object.value.exitCode
454 if sig != None or rc != 0:
455 self.d.errback(RuntimeError("remote 'buildbot tryserver' failed"
456 ": sig=%s, rc=%s" % (sig, rc)))
457 return
458 self.d.callback((sig, rc))
459
460
462 retryCount = 5
463 retryDelay = 3
464
468
470
471
472 self.d = defer.Deferred()
473
474
475 reactor.callLater(1, self.go)
476 return self.d
477
478 - def go(self, dummy=None):
479 if self.retryCount == 0:
480 raise RuntimeError("couldn't find matching buildset")
481 self.retryCount -= 1
482 d = self.status.callRemote("getBuildSets")
483 d.addCallback(self._gotSets)
484
486 for bs, bsid in buildsets:
487 if bsid == self.bsid:
488
489 self.d.callback(bs)
490 return
491 d = defer.Deferred()
492 d.addCallback(self.go)
493 reactor.callLater(self.retryDelay, d.callback, None)
494
495
496 -class Try(pb.Referenceable):
497 buildsetStatus = None
498 quiet = False
499 printloop = False
500
502 self.config = config
503 self.connect = self.getopt('connect')
504 assert self.connect, "you must specify a connect style: ssh or pb"
505 self.builderNames = self.getopt('builders')
506 self.project = self.getopt('project', '')
507 self.who = self.getopt('who')
508 self.comment = self.getopt('comment')
509
510 - def getopt(self, config_name, default=None):
511 value = self.config.get(config_name)
512 if value is None or value == []:
513 value = default
514 return value
515
517
518
519
520
521
522
523 self.bsid = "%d-%s" % (time.time(), random.randint(0, 1000000))
524
525
526 branch = self.getopt("branch")
527
528 difffile = self.config.get("diff")
529 if difffile:
530 baserev = self.config.get("baserev")
531 if difffile == "-":
532 diff = sys.stdin.read()
533 else:
534 with open(difffile, "r") as f:
535 diff = f.read()
536 if not diff:
537 diff = None
538 patch = (self.config['patchlevel'], diff)
539 ss = SourceStamp(
540 branch, baserev, patch, repository=self.getopt("repository"))
541 d = defer.succeed(ss)
542 else:
543 vc = self.getopt("vc")
544 if vc in ("cvs", "svn"):
545
546 topdir = self.getopt("topdir")
547 if topdir:
548 treedir = os.path.expanduser(topdir)
549 else:
550 topfile = self.getopt("topfile")
551 treedir = getTopdir(topfile)
552 else:
553 treedir = os.getcwd()
554 d = getSourceStamp(vc, treedir, branch, self.getopt("repository"))
555 d.addCallback(self._createJob_1)
556 return d
557
559 self.sourcestamp = ss
560 if self.connect == "ssh":
561 patchlevel, diff = ss.patch
562 revspec = ss.revision
563 if revspec is None:
564 revspec = ""
565 self.jobfile = createJobfile(
566 self.bsid, ss.branch or "", revspec, patchlevel, diff,
567 ss.repository, self.project, self.who, self.comment,
568 self.builderNames, self.config.get('properties', {}))
569
571
572 ss = self.sourcestamp
573 print ("Job:\n\tRepository: %s\n\tProject: %s\n\tBranch: %s\n\t"
574 "Revision: %s\n\tBuilders: %s\n%s"
575 % (ss.repository, self.project, ss.branch,
576 ss.revision,
577 self.builderNames,
578 ss.patch[1]))
579 d = defer.Deferred()
580 d.callback(True)
581 return d
582
584
585 if self.connect == "ssh":
586 tryhost = self.getopt("host")
587 tryuser = self.getopt("username")
588 trydir = self.getopt("jobdir")
589 buildbotbin = self.getopt("buildbotbin")
590 argv = ["ssh", "-l", tryuser, tryhost,
591 buildbotbin, "tryserver", "--jobdir", trydir]
592 pp = RemoteTryPP(self.jobfile)
593 reactor.spawnProcess(pp, argv[0], argv, os.environ)
594 d = pp.d
595 return d
596 if self.connect == "pb":
597 user = self.getopt("username")
598 passwd = self.getopt("passwd")
599 master = self.getopt("master")
600 tryhost, tryport = master.split(":")
601 tryport = int(tryport)
602 f = pb.PBClientFactory()
603 d = f.login(credentials.UsernamePassword(user, passwd))
604 reactor.connectTCP(tryhost, tryport, f)
605 d.addCallback(self._deliverJob_pb)
606 return d
607 raise RuntimeError("unknown connecttype '%s', should be 'ssh' or 'pb'"
608 % self.connect)
609
611 ss = self.sourcestamp
612 print "Delivering job; comment=", self.comment
613
614 d = remote.callRemote("try",
615 ss.branch,
616 ss.revision,
617 ss.patch,
618 ss.repository,
619 self.project,
620 self.builderNames,
621 self.who,
622 self.comment,
623 self.config.get('properties', {}))
624 d.addCallback(self._deliverJob_pb2)
625 return d
626
630
632
633
634 wait = bool(self.getopt("wait"))
635 if not wait:
636
637
638
639
640
641 print "not waiting for builds to finish"
642 return
643 d = self.running = defer.Deferred()
644 if self.buildsetStatus:
645 self._getStatus_1()
646 return self.running
647
648
649 master = self.getopt("master")
650 host, port = master.split(":")
651 port = int(port)
652 self.announce("contacting the status port at %s:%d" % (host, port))
653 f = pb.PBClientFactory()
654 creds = credentials.UsernamePassword("statusClient", "clientpw")
655 d = f.login(creds)
656 reactor.connectTCP(host, port, f)
657 d.addCallback(self._getStatus_ssh_1)
658 return self.running
659
667
669 if res:
670 self.buildsetStatus = res
671
672 d = self.buildsetStatus.callRemote("getBuildRequests")
673 d.addCallback(self._getStatus_2)
674
676 self.builderNames = []
677 self.buildRequests = {}
678
679
680 self.builds = {}
681
682
683
684 self.outstanding = []
685
686
687
688 self.results = {}
689
690
691
692 self.currentStep = {}
693
694
695
696 self.ETA = {}
697
698 for n, br in brs:
699 self.builderNames.append(n)
700 self.buildRequests[n] = br
701 self.builds[n] = None
702 self.outstanding.append(n)
703 self.results[n] = [None, None]
704 self.currentStep[n] = None
705 self.ETA[n] = None
706
707
708 br.callRemote("subscribe", self)
709
710
711
712 if not self.getopt("quiet"):
713 self.printloop = task.LoopingCall(self.printStatus)
714 self.printloop.start(3, now=False)
715
716
717
719 if self.builds[builderName]:
720 self.builds[builderName].callRemote("unsubscribe", self)
721 self.builds[builderName] = bs
722 bs.callRemote("subscribe", self, 20)
723 d = bs.callRemote("waitUntilFinished")
724 d.addCallback(self._build_finished, builderName)
725
728
731
733 self.ETA[buildername] = now() + eta
734
736
737
738
739 self.builds[builderName] = None
740 self.ETA[builderName] = None
741 self.currentStep[builderName] = "finished"
742 d = bs.callRemote("getResults")
743 d.addCallback(self._build_finished_2, bs, builderName)
744 return d
745
747 self.results[builderName][0] = results
748 d = bs.callRemote("getText")
749 d.addCallback(self._build_finished_3, builderName)
750 return d
751
753 self.results[builderName][1] = text
754
755 self.outstanding.remove(builderName)
756 if not self.outstanding:
757
758 return self.statusDone()
759
761 names = self.buildRequests.keys()
762 names.sort()
763 for n in names:
764 if n not in self.outstanding:
765
766 code, text = self.results[n]
767 t = builder.Results[code]
768 if text:
769 t += " (%s)" % " ".join(text)
770 elif self.builds[n]:
771 t = self.currentStep[n] or "building"
772 if self.ETA[n]:
773 t += " [ETA %ds]" % (self.ETA[n] - now())
774 else:
775 t = "no build"
776 self.announce("%s: %s" % (n, t))
777 self.announce("")
778
780 if self.printloop:
781 self.printloop.stop()
782 print "All Builds Complete"
783
784 names = self.buildRequests.keys()
785 names.sort()
786 happy = True
787 for n in names:
788 code, text = self.results[n]
789 t = "%s: %s" % (n, builder.Results[code])
790 if text:
791 t += " (%s)" % " ".join(text)
792 print t
793 if code != builder.SUCCESS:
794 happy = False
795
796 if happy:
797 self.exitcode = 0
798 else:
799 self.exitcode = 1
800 self.running.callback(self.exitcode)
801
803
804
805
806 if self.connect == "pb":
807 user = self.getopt("username")
808 passwd = self.getopt("passwd")
809 master = self.getopt("master")
810 tryhost, tryport = master.split(":")
811 tryport = int(tryport)
812 f = pb.PBClientFactory()
813 d = f.login(credentials.UsernamePassword(user, passwd))
814 reactor.connectTCP(tryhost, tryport, f)
815 d.addCallback(self._getBuilderNames, self._getBuilderNames2)
816 return d
817 if self.connect == "ssh":
818 raise RuntimeError(
819 "ssh connection type not supported for this command")
820 raise RuntimeError(
821 "unknown connecttype '%s', should be 'pb'" % self.connect)
822
824 d = remote.callRemote("getAvailableBuilderNames")
825 d.addCallback(self._getBuilderNames2)
826 return d
827
829 print "The following builders are available for the try scheduler: "
830 for buildername in buildernames:
831 print buildername
832
834 if not self.quiet:
835 print message
836
861
863 log.err(why)
864 print "error during 'try' processing"
865 print why
866
870