1 import os, re, sys, shutil, time
2
3 from xml.dom.minidom import parseString
4
5 from twisted.python import log, failure, runtime
6 from twisted.internet import defer, reactor
7
8 from buildbot.slave.commands.base import Command, ShellCommand, AbandonChain, command_version, Obfuscated
9 from buildbot.slave.commands.registry import registerSlaveCommand
10 from buildbot.slave.commands.utils import getCommand, rmdirRecursive
11 from buildbot.util import remove_userpassword
12
14 """Abstract base class for Version Control System operations (checkout
15 and update). This class extracts the following arguments from the
16 dictionary received from the master:
17
18 - ['workdir']: (required) the subdirectory where the buildable sources
19 should be placed
20
21 - ['mode']: one of update/copy/clobber/export, defaults to 'update'
22
23 - ['revision']: If not None, this is an int or string which indicates
24 which sources (along a time-like axis) should be used.
25 It is the thing you provide as the CVS -r or -D
26 argument.
27
28 - ['patch']: If not None, this is a tuple of (striplevel, patch)
29 which contains a patch that should be applied after the
30 checkout has occurred. Once applied, the tree is no
31 longer eligible for use with mode='update', and it only
32 makes sense to use this in conjunction with a
33 ['revision'] argument. striplevel is an int, and patch
34 is a string in standard unified diff format. The patch
35 will be applied with 'patch -p%d <PATCH', with
36 STRIPLEVEL substituted as %d. The command will fail if
37 the patch process fails (rejected hunks).
38
39 - ['timeout']: seconds of silence tolerated before we kill off the
40 command
41
42 - ['maxTime']: seconds before we kill off the command
43
44 - ['retry']: If not None, this is a tuple of (delay, repeats)
45 which means that any failed VC updates should be
46 reattempted, up to REPEATS times, after a delay of
47 DELAY seconds. This is intended to deal with slaves
48 that experience transient network failures.
49 """
50
51 sourcedata = ""
52
54
55
56
57 self.env = os.environ.copy()
58 self.env['LC_MESSAGES'] = "C"
59
60 self.workdir = args['workdir']
61 self.mode = args.get('mode', "update")
62 self.revision = args.get('revision')
63 self.patch = args.get('patch')
64 self.timeout = args.get('timeout', 120)
65 self.maxTime = args.get('maxTime', None)
66 self.retry = args.get('retry')
67
68
69
99
104
109
110 - def doVC(self, res):
123
125 try:
126 olddata = self.readSourcedata()
127 if olddata != self.sourcedata:
128 return False
129 except IOError:
130 return False
131 return True
132
137
139 d = defer.maybeDeferred(self.parseGotRevision)
140 d.addCallback(lambda got_revision:
141 self.sendStatus({'got_revision': got_revision}))
142 return d
143
145 """Override this in a subclass. It should return a string that
146 represents which revision was actually checked out, or a Deferred
147 that will fire with such a string. If, in a future build, you were to
148 pass this 'got_revision' string in as the 'revision' component of a
149 SourceStamp, you should wind up with the same source code as this
150 checkout just obtained.
151
152 It is probably most useful to scan self.command.stdout for a string
153 of some sort. Be sure to set keepStdout=True on the VC command that
154 you run, so that you'll have something available to look at.
155
156 If this information is unavailable, just return None."""
157
158 return None
159
161 return open(self.sourcedatafile, "r").read()
162
166
168 """Returns True if the tree can be updated."""
169 raise NotImplementedError("this must be implemented in a subclass")
170
172 """Returns a deferred with the steps to update a checkout."""
173 raise NotImplementedError("this must be implemented in a subclass")
174
176 """Returns a deferred with the steps to do a fresh checkout."""
177 raise NotImplementedError("this must be implemented in a subclass")
178
190
192 msg = "now retrying VC operation"
193 self.sendStatus({'header': msg + "\n"})
194 log.msg(msg)
195 d = self.doVCFull()
196 d.addBoth(self.maybeDoVCRetry)
197 d.addCallback(self._abandonOnFailure)
198 return d
199
201 """We get here somewhere after a VC chain has finished. res could
202 be::
203
204 - 0: the operation was successful
205 - nonzero: the operation failed. retry if possible
206 - AbandonChain: the operation failed, someone else noticed. retry.
207 - Failure: some other exception, re-raise
208 """
209
210 if isinstance(res, failure.Failure):
211 if self.interrupted:
212 return res
213 res.trap(AbandonChain)
214 else:
215 if type(res) is int and res == 0:
216 return res
217 if self.interrupted:
218 raise AbandonChain(1)
219
220 if self.retry:
221 delay, repeats = self.retry
222 if repeats >= 0:
223 self.retry = (delay, repeats-1)
224 msg = ("update failed, trying %d more times after %d seconds"
225 % (repeats, delay))
226 self.sendStatus({'header': msg + "\n"})
227 log.msg(msg)
228 d = defer.Deferred()
229
230
231 self.doClobber(d, self.workdir)
232 d.addCallback(lambda res: self.doVCFull())
233 d.addBoth(self.maybeDoVCRetry)
234 self._reactor.callLater(delay, d.callback, None)
235 return d
236 return res
237
238 - def doClobber(self, dummy, dirname, chmodDone=False):
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 d = os.path.join(self.builder.basedir, dirname)
258 if runtime.platformType != "posix":
259
260
261 rmdirRecursive(d)
262 return defer.succeed(0)
263 command = ["rm", "-rf", d]
264 c = ShellCommand(self.builder, command, self.builder.basedir,
265 sendRC=0, timeout=self.timeout, maxTime=self.maxTime,
266 usePTY=False)
267
268 self.command = c
269
270
271
272 d = c.start()
273
274
275
276 if chmodDone:
277 d.addCallback(self._abandonOnFailure)
278 else:
279 d.addCallback(lambda rc: self.doClobberTryChmodIfFail(rc, dirname))
280 return d
281
283 assert isinstance(rc, int)
284 if rc == 0:
285 return defer.succeed(0)
286
287
288 command = ["chmod", "-Rf", "u+rwx", os.path.join(self.builder.basedir, dirname)]
289 if sys.platform.startswith('freebsd'):
290
291
292
293 command = ["find", os.path.join(self.builder.basedir, dirname),
294 '-exec', 'chmod', 'u+rwx', '{}', ';' ]
295 c = ShellCommand(self.builder, command, self.builder.basedir,
296 sendRC=0, timeout=self.timeout, maxTime=self.maxTime,
297 usePTY=False)
298
299 self.command = c
300 d = c.start()
301 d.addCallback(self._abandonOnFailure)
302 d.addCallback(lambda dummy: self.doClobber(dummy, dirname, True))
303 return d
304
306
307 fromdir = os.path.join(self.builder.basedir, self.srcdir)
308 todir = os.path.join(self.builder.basedir, self.workdir)
309 if runtime.platformType != "posix":
310 self.sendStatus({'header': "Since we're on a non-POSIX platform, "
311 "we're not going to try to execute cp in a subprocess, but instead "
312 "use shutil.copytree(), which will block until it is complete. "
313 "fromdir: %s, todir: %s\n" % (fromdir, todir)})
314 shutil.copytree(fromdir, todir)
315 return defer.succeed(0)
316
317 if not os.path.exists(os.path.dirname(todir)):
318 os.makedirs(os.path.dirname(todir))
319 if os.path.exists(todir):
320
321 log.msg("cp target '%s' already exists -- cp will not do what you think!" % todir)
322
323 command = ['cp', '-R', '-P', '-p', fromdir, todir]
324 c = ShellCommand(self.builder, command, self.builder.basedir,
325 sendRC=False, timeout=self.timeout, maxTime=self.maxTime,
326 usePTY=False)
327 self.command = c
328 d = c.start()
329 d.addCallback(self._abandonOnFailure)
330 return d
331
333 patchlevel = self.patch[0]
334 diff = self.patch[1]
335 root = None
336 if len(self.patch) >= 3:
337 root = self.patch[2]
338 command = [
339 getCommand("patch"),
340 '-p%d' % patchlevel,
341 '--remove-empty-files',
342 '--force',
343 '--forward',
344 ]
345 dir = os.path.join(self.builder.basedir, self.workdir)
346
347
348 marker = open(os.path.join(dir, ".buildbot-patched"), "w")
349 marker.write("patched\n")
350 marker.close()
351
352
353
354 if (root and
355 os.path.abspath(os.path.join(dir, root)
356 ).startswith(os.path.abspath(dir))):
357 dir = os.path.join(dir, root)
358
359
360 c = ShellCommand(self.builder, command, dir,
361 sendRC=False, timeout=self.timeout,
362 maxTime=self.maxTime, initialStdin=diff, usePTY=False)
363 self.command = c
364 d = c.start()
365 d.addCallback(self._abandonOnFailure)
366 return d
367
368
369
370 -class BK(SourceBase):
371 """BitKeeper-specific VC operation. In addition to the arguments
372 handled by SourceBase, this command reads the following keys:
373
374 ['bkurl'] (required): the BK repository string
375 """
376
377 header = "bk operation"
378
380 SourceBase.setup(self, args)
381 self.vcexe = getCommand("bk")
382 self.bkurl = args['bkurl']
383 self.sourcedata = '"%s\n"' % self.bkurl
384
385 self.bk_args = []
386 if args.get('extra_args', None) is not None:
387 self.bk_args.extend(args['extra_args'])
388
395
408
424
426 """
427 Get the (shell) command used to determine BK revision number
428 of checked-out code
429
430 return: list of strings, passable as the command argument to ShellCommand
431 """
432 return [self.vcexe, "changes", "-r+", "-d:REV:"]
433
435 c = ShellCommand(self.builder,
436 self.getBKVersionCommand(),
437 os.path.join(self.builder.basedir, self.srcdir),
438 environ=self.env,
439 sendStdout=False, sendStderr=False, sendRC=False,
440 keepStdout=True, usePTY=False)
441 d = c.start()
442 def _parse(res):
443 r_raw = c.stdout.strip()
444 got_version = None
445 try:
446 r = r_raw
447 except:
448 msg = ("BK.parseGotRevision unable to parse output: (%s)" % r_raw)
449 log.msg(msg)
450 self.sendStatus({'header': msg + "\n"})
451 raise ValueError(msg)
452 return r
453 d.addCallback(_parse)
454 return d
455
456 registerSlaveCommand("bk", BK, command_version)
457
458
459
460
461 -class CVS(SourceBase):
462 """CVS-specific VC operation. In addition to the arguments handled by
463 SourceBase, this command reads the following keys:
464
465 ['cvsroot'] (required): the CVSROOT repository string
466 ['cvsmodule'] (required): the module to be retrieved
467 ['branch']: a '-r' tag or branch name to use for the checkout/update
468 ['login']: a string for use as a password to 'cvs login'
469 ['global_options']: a list of strings to use before the CVS verb
470 ['checkout_options']: a list of strings to use after checkout,
471 but before revision and branch specifiers
472 ['checkout_options']: a list of strings to use after export,
473 but before revision and branch specifiers
474 ['extra_options']: a list of strings to use after export and checkout,
475 but before revision and branch specifiers
476 """
477
478 header = "cvs operation"
479
481 SourceBase.setup(self, args)
482 self.vcexe = getCommand("cvs")
483 self.cvsroot = args['cvsroot']
484 self.cvsmodule = args['cvsmodule']
485 self.global_options = args.get('global_options', [])
486 self.checkout_options = args.get('checkout_options', [])
487 self.export_options = args.get('export_options', [])
488 self.extra_options = args.get('extra_options', [])
489 self.branch = args.get('branch')
490 self.login = args.get('login')
491 self.sourcedata = "%s\n%s\n%s\n" % (self.cvsroot, self.cvsmodule,
492 self.branch)
493
498
516
520
522 d = os.path.join(self.builder.basedir, self.srcdir)
523 command = [self.vcexe, '-z3'] + self.global_options + ['update', '-dP']
524 if self.branch:
525 command += ['-r', self.branch]
526 if self.revision:
527 command += ['-D', self.revision]
528 c = ShellCommand(self.builder, command, d,
529 sendRC=False, timeout=self.timeout,
530 maxTime=self.maxTime, usePTY=False)
531 self.command = c
532 return c.start()
533
535 d = self.builder.basedir
536 if self.mode == "export":
537 verb = "export"
538 else:
539 verb = "checkout"
540 command = ([self.vcexe, '-d', self.cvsroot, '-z3'] +
541 self.global_options +
542 [verb, '-d', self.srcdir])
543
544 if verb == "checkout":
545 command += self.checkout_options
546 else:
547 command += self.export_options
548 command += self.extra_options
549
550 if self.branch:
551 command += ['-r', self.branch]
552 if self.revision:
553 command += ['-D', self.revision]
554 command += [self.cvsmodule]
555
556 c = ShellCommand(self.builder, command, d,
557 sendRC=False, timeout=self.timeout,
558 maxTime=self.maxTime, usePTY=False)
559 self.command = c
560 return c.start()
561
563
564
565
566
567 return time.strftime("%Y-%m-%d %H:%M:%S +0000", time.gmtime())
568
569 registerSlaveCommand("cvs", CVS, command_version)
570
571 -class SVN(SourceBase):
572 """Subversion-specific VC operation. In addition to the arguments
573 handled by SourceBase, this command reads the following keys:
574
575 ['svnurl'] (required): the SVN repository string
576 ['username']: Username passed to the svn command
577 ['password']: Password passed to the svn command
578 ['keep_on_purge']: Files and directories to keep between updates
579 ['ignore_ignores']: Ignore ignores when purging changes
580 ['always_purge']: Always purge local changes after each build
581 ['depth']: Pass depth argument to subversion 1.5+
582 """
583
584 header = "svn operation"
585
587 SourceBase.setup(self, args)
588 self.vcexe = getCommand("svn")
589 self.svnurl = args['svnurl']
590 self.sourcedata = "%s\n" % self.svnurl
591 self.keep_on_purge = args.get('keep_on_purge', [])
592 self.keep_on_purge.append(".buildbot-sourcedata")
593 self.ignore_ignores = args.get('ignore_ignores', True)
594 self.always_purge = args.get('always_purge', False)
595
596 self.svn_args = []
597 if args.has_key('username'):
598 self.svn_args.extend(["--username", args['username']])
599 if args.has_key('password'):
600 self.svn_args.extend(["--password", Obfuscated(args['password'], "XXXX")])
601 if args.get('extra_args', None) is not None:
602 self.svn_args.extend(args['extra_args'])
603
604 if args.has_key('depth'):
605 self.svn_args.extend(["--depth",args['depth']])
606
607 - def _dovccmd(self, command, args, rootdir=None, cb=None, **kwargs):
608 if rootdir is None:
609 rootdir = os.path.join(self.builder.basedir, self.srcdir)
610 fullCmd = [self.vcexe, command, '--non-interactive', '--no-auth-cache']
611 fullCmd.extend(self.svn_args)
612 fullCmd.extend(args)
613 c = ShellCommand(self.builder, fullCmd, rootdir,
614 environ=self.env, sendRC=False, timeout=self.timeout,
615 maxTime=self.maxTime, usePTY=False, **kwargs)
616 self.command = c
617 d = c.start()
618 if cb:
619 d.addCallback(self._abandonOnFailure)
620 d.addCallback(cb)
621 return d
622
626
628 if self.sourcedirIsPatched() or self.always_purge:
629 return self._purgeAndUpdate()
630 revision = self.args['revision'] or 'HEAD'
631
632 return self._dovccmd('update', ['--revision', str(revision)],
633 keepStdout=True)
634
636 revision = self.args['revision'] or 'HEAD'
637 args = ['--revision', str(revision), self.svnurl, self.srcdir]
638 if self.mode == "export":
639 command = 'export'
640 else:
641
642 command = 'checkout'
643 return self._dovccmd(command, args, rootdir=self.builder.basedir,
644 keepStdout=True)
645
647 """svn revert has several corner cases that make it unpractical.
648
649 Use the Force instead and delete everything that shows up in status."""
650 args = ['--xml']
651 if self.ignore_ignores:
652 args.append('--no-ignore')
653 return self._dovccmd('status', args, keepStdout=True, sendStdout=False,
654 cb=self._purgeAndUpdate2)
655
657 """Delete everything that shown up on status."""
658 result_xml = parseString(self.command.stdout)
659 for entry in result_xml.getElementsByTagName('entry'):
660 filename = entry.getAttribute('path')
661 if filename in self.keep_on_purge:
662 continue
663 filepath = os.path.join(self.builder.basedir, self.workdir,
664 filename)
665 self.sendStatus({'stdout': "%s\n" % filepath})
666 if os.path.isfile(filepath):
667 os.chmod(filepath, 0700)
668 os.remove(filepath)
669 else:
670 rmdirRecursive(filepath)
671
672 revision = self.args['revision'] or 'HEAD'
673 return self._dovccmd('update', ['--revision', str(revision)],
674 keepStdout=True)
675
677 """
678 Get the (shell) command used to determine SVN revision number
679 of checked-out code
680
681 return: list of strings, passable as the command argument to ShellCommand
682 """
683
684
685
686 svnversion_command = getCommand("svnversion")
687
688
689 return [svnversion_command, "."]
690
692 c = ShellCommand(self.builder,
693 self.getSvnVersionCommand(),
694 os.path.join(self.builder.basedir, self.srcdir),
695 environ=self.env,
696 sendStdout=False, sendStderr=False, sendRC=False,
697 keepStdout=True, usePTY=False)
698 d = c.start()
699 def _parse(res):
700 r_raw = c.stdout.strip()
701
702 r = r_raw.rstrip('MS')
703 r = r.split(':')[-1]
704 got_version = None
705 try:
706 got_version = int(r)
707 except ValueError:
708 msg =("SVN.parseGotRevision unable to parse output "
709 "of svnversion: '%s'" % r_raw)
710 log.msg(msg)
711 self.sendStatus({'header': msg + "\n"})
712 return got_version
713 d.addCallback(_parse)
714 return d
715
716
717 registerSlaveCommand("svn", SVN, command_version)
718
720 """Darcs-specific VC operation. In addition to the arguments
721 handled by SourceBase, this command reads the following keys:
722
723 ['repourl'] (required): the Darcs repository string
724 """
725
726 header = "darcs operation"
727
734
741
743 assert not self.revision
744
745 d = os.path.join(self.builder.basedir, self.srcdir)
746 command = [self.vcexe, 'pull', '--all', '--verbose']
747 c = ShellCommand(self.builder, command, d,
748 sendRC=False, timeout=self.timeout,
749 maxTime=self.maxTime, usePTY=False)
750 self.command = c
751 return c.start()
752
754
755 d = self.builder.basedir
756 command = [self.vcexe, 'get', '--verbose', '--partial',
757 '--repo-name', self.srcdir]
758 if self.revision:
759
760 n = os.path.join(self.builder.basedir, ".darcs-context")
761 f = open(n, "wb")
762 f.write(self.revision)
763 f.close()
764
765 command.append('--context')
766 command.append(n)
767 command.append(self.repourl)
768
769 c = ShellCommand(self.builder, command, d,
770 sendRC=False, timeout=self.timeout,
771 maxTime=self.maxTime, usePTY=False)
772 self.command = c
773 d = c.start()
774 if self.revision:
775 d.addCallback(self.removeContextFile, n)
776 return d
777
778 - def removeContextFile(self, res, n):
779 os.unlink(n)
780 return res
781
783
784 command = [self.vcexe, "changes", "--context"]
785 c = ShellCommand(self.builder, command,
786 os.path.join(self.builder.basedir, self.srcdir),
787 environ=self.env,
788 sendStdout=False, sendStderr=False, sendRC=False,
789 keepStdout=True, usePTY=False)
790 d = c.start()
791 d.addCallback(lambda res: c.stdout)
792 return d
793
794 registerSlaveCommand("darcs", Darcs, command_version)
795
797 """Monotone-specific VC operation. In addition to the arguments handled
798 by SourceBase, this command reads the following keys:
799
800 ['server_addr'] (required): the address of the server to pull from
801 ['branch'] (required): the branch the revision is on
802 ['db_path'] (required): the local database path to use
803 ['revision'] (required): the revision to check out
804 ['monotone']: (required): path to monotone executable
805 """
806
807 header = "monotone operation"
808
810 SourceBase.setup(self, args)
811 self.server_addr = args["server_addr"]
812 self.branch = args["branch"]
813 self.db_path = args["db_path"]
814 self.revision = args["revision"]
815 self.monotone = args["monotone"]
816 self._made_fulls = False
817 self._pull_timeout = args["timeout"]
818
825
827 self._makefulls()
828 return (not self.sourcedirIsPatched() and
829 os.path.isfile(self.full_db_path) and
830 os.path.isdir(os.path.join(self.full_srcdir, "MT")))
831
833 return self._withFreshDb(self._doUpdate)
834
836
837 command = [self.monotone, "update",
838 "-r", self.revision,
839 "-b", self.branch]
840 c = ShellCommand(self.builder, command, self.full_srcdir,
841 sendRC=False, timeout=self.timeout,
842 maxTime=self.maxTime, usePTY=False)
843 self.command = c
844 return c.start()
845
847 return self._withFreshDb(self._doFull)
848
850 command = [self.monotone, "--db=" + self.full_db_path,
851 "checkout",
852 "-r", self.revision,
853 "-b", self.branch,
854 self.full_srcdir]
855 c = ShellCommand(self.builder, command, self.builder.basedir,
856 sendRC=False, timeout=self.timeout,
857 maxTime=self.maxTime, usePTY=False)
858 self.command = c
859 return c.start()
860
862 self._makefulls()
863
864 if os.path.isfile(self.full_db_path):
865
866
867 command = [self.monotone, "db", "migrate",
868 "--db=" + self.full_db_path]
869 else:
870
871
872 self._pull_timeout = max(self._pull_timeout, 3 * 60 * 60)
873 self.sendStatus({"header": "creating database %s\n"
874 % (self.full_db_path,)})
875 command = [self.monotone, "db", "init",
876 "--db=" + self.full_db_path]
877 c = ShellCommand(self.builder, command, self.builder.basedir,
878 sendRC=False, timeout=self.timeout,
879 maxTime=self.maxTime, usePTY=False)
880 self.command = c
881 d = c.start()
882 d.addCallback(self._abandonOnFailure)
883 d.addCallback(self._didDbInit)
884 d.addCallback(self._didPull, callback)
885 return d
886
888 command = [self.monotone, "--db=" + self.full_db_path,
889 "pull", "--ticker=dot", self.server_addr, self.branch]
890 c = ShellCommand(self.builder, command, self.builder.basedir,
891 sendRC=False, timeout=self._pull_timeout,
892 maxTime=self.maxTime, usePTY=False)
893 self.sendStatus({"header": "pulling %s from %s\n"
894 % (self.branch, self.server_addr)})
895 self.command = c
896 return c.start()
897
900
901 registerSlaveCommand("monotone", Monotone, command_version)
902
903
904 -class Git(SourceBase):
905 """Git specific VC operation. In addition to the arguments
906 handled by SourceBase, this command reads the following keys:
907
908 ['repourl'] (required): the upstream GIT repository string
909 ['branch'] (optional): which version (i.e. branch or tag) to
910 retrieve. Default: "master".
911 ['submodules'] (optional): whether to initialize and update
912 submodules. Default: False.
913 ['ignore_ignores']: ignore ignores when purging changes.
914 """
915
916 header = "git operation"
917
928
931
936
938 return os.path.isdir(os.path.join(self._fullSrcdir(), ".git"))
939
940 - def _dovccmd(self, command, cb=None, **kwargs):
950
951
952
953
954
955
957 try:
958 olddata = self.readSourcedata()
959 if not olddata.startswith(self.repourl+' '):
960 return False
961 except IOError:
962 return False
963 return True
964
966 command = ['submodule', 'foreach', 'git', 'clean', '-d', '-f']
967 if self.ignore_ignores:
968 command.append('-x')
969 return self._dovccmd(command)
970
972 return self._dovccmd(['submodule', 'update'], self._cleanSubmodules)
973
975 if self.submodules:
976 return self._dovccmd(['submodule', 'init'], self._updateSubmodules)
977 else:
978 return defer.succeed(0)
979
981
982
983 command = ['branch', '-M', self.branch]
984 return self._dovccmd(command, self._initSubmodules)
985
987 if self.revision:
988 head = self.revision
989 else:
990 head = 'FETCH_HEAD'
991
992
993
994 command = ['reset', '--hard', head]
995 return self._dovccmd(command, self._didHeadCheckout)
996
997
998
999
1000
1002 try:
1003
1004 diffbranch = self.sourcedata != self.readSourcedata()
1005 except IOError:
1006 diffbranch = False
1007 if diffbranch:
1008 command = ['clean', '-f', '-d']
1009 if self.ignore_ignores:
1010 command.append('-x')
1011 return self._dovccmd(command, self._didClean)
1012 return self._didClean(None)
1013
1015
1016
1017 command = ['fetch', '-t', self.repourl, '+%s' % self.branch]
1018 self.sendStatus({"header": "fetching branch %s from %s\n"
1019 % (self.branch, self.repourl)})
1020 return self._dovccmd(command, self._didFetch)
1021
1023
1024 if self.revision:
1025
1026 d = self._dovccmd(['reset', '--hard', self.revision],
1027 self._initSubmodules)
1028
1029
1030 d.addErrback(self._doFetch)
1031 return d
1032 else:
1033
1034 return self._doFetch(None)
1035
1038
1040
1041
1042 if not self.args.get('revision') and self.args.get('shallow'):
1043 cmd = [self.vcexe, 'clone', '--depth', '1', self.repourl,
1044 self._fullSrcdir()]
1045 c = ShellCommand(self.builder, cmd, self.builder.basedir,
1046 sendRC=False, timeout=self.timeout,
1047 maxTime=self.maxTime, usePTY=False)
1048 self.command = c
1049 cmdexec = c.start()
1050 cmdexec.addCallback(self._didInit)
1051 return cmdexec
1052 else:
1053 os.makedirs(self._fullSrcdir())
1054 return self._dovccmd(['init'], self._didInit)
1055
1057 command = ['rev-parse', 'HEAD']
1058 def _parse(res):
1059 hash = self.command.stdout.strip()
1060 if len(hash) != 40:
1061 return None
1062 return hash
1063 return self._dovccmd(command, _parse, keepStdout=True)
1064
1065 registerSlaveCommand("git", Git, command_version)
1066
1067 -class Arch(SourceBase):
1068 """Arch-specific (tla-specific) VC operation. In addition to the
1069 arguments handled by SourceBase, this command reads the following keys:
1070
1071 ['url'] (required): the repository string
1072 ['version'] (required): which version (i.e. branch) to retrieve
1073 ['revision'] (optional): the 'patch-NN' argument to check out
1074 ['archive']: the archive name to use. If None, use the archive's default
1075 ['build-config']: if present, give to 'tla build-config' after checkout
1076 """
1077
1078 header = "arch operation"
1079 buildconfig = None
1080
1091
1102
1114
1116
1117
1118
1119
1120
1121 command = [self.vcexe, 'register-archive', '--force', self.url]
1122 c = ShellCommand(self.builder, command, self.builder.basedir,
1123 sendRC=False, keepStdout=True, timeout=self.timeout,
1124 maxTime=self.maxTime, usePTY=False)
1125 self.command = c
1126 d = c.start()
1127 d.addCallback(self._abandonOnFailure)
1128 d.addCallback(self._didRegister, c)
1129 return d
1130
1132
1133
1134 r = re.search(r'Registering archive: (\S+)\s*$', c.stdout)
1135 if r:
1136 msg = "tla reports archive name is '%s'" % r.group(1)
1137 log.msg(msg)
1138 self.builder.sendUpdate({'header': msg+"\n"})
1139 if self.archive and r.group(1) != self.archive:
1140 msg = (" mismatch, we wanted an archive named '%s'"
1141 % self.archive)
1142 log.msg(msg)
1143 self.builder.sendUpdate({'header': msg+"\n"})
1144 raise AbandonChain(-1)
1145 self.archive = r.group(1)
1146 assert self.archive, "need archive name to continue"
1147 return self._doGet()
1148
1150 ver = self.version
1151 if self.revision:
1152 ver += "--%s" % self.revision
1153 command = [self.vcexe, 'get', '--archive', self.archive,
1154 '--no-pristine',
1155 ver, self.srcdir]
1156 c = ShellCommand(self.builder, command, self.builder.basedir,
1157 sendRC=False, timeout=self.timeout,
1158 maxTime=self.maxTime, usePTY=False)
1159 self.command = c
1160 d = c.start()
1161 d.addCallback(self._abandonOnFailure)
1162 if self.buildconfig:
1163 d.addCallback(self._didGet)
1164 return d
1165
1176
1178
1179
1180
1181 command = [self.vcexe, "logs", "--full", "--reverse"]
1182 c = ShellCommand(self.builder, command,
1183 os.path.join(self.builder.basedir, self.srcdir),
1184 environ=self.env,
1185 sendStdout=False, sendStderr=False, sendRC=False,
1186 keepStdout=True, usePTY=False)
1187 d = c.start()
1188 def _parse(res):
1189 tid = c.stdout.split("\n")[0].strip()
1190 slash = tid.index("/")
1191 dd = tid.rindex("--")
1192
1193 baserev = tid[dd+2:]
1194 return baserev
1195 d.addCallback(_parse)
1196 return d
1197
1198 registerSlaveCommand("arch", Arch, command_version)
1199
1201 """Bazaar (/usr/bin/baz) is an alternative client for Arch repositories.
1202 It is mostly option-compatible, but archive registration is different
1203 enough to warrant a separate Command.
1204
1205 ['archive'] (required): the name of the archive being used
1206 """
1207
1218
1219
1220
1221
1223
1224
1225 ver = self.archive + "/" + self.version
1226 if self.revision:
1227 ver += "--%s" % self.revision
1228 command = [self.vcexe, 'get', '--no-pristine',
1229 ver, self.srcdir]
1230 c = ShellCommand(self.builder, command, self.builder.basedir,
1231 sendRC=False, timeout=self.timeout,
1232 maxTime=self.maxTime, usePTY=False)
1233 self.command = c
1234 d = c.start()
1235 d.addCallback(self._abandonOnFailure)
1236 if self.buildconfig:
1237 d.addCallback(self._didGet)
1238 return d
1239
1241
1242 command = [self.vcexe, "tree-id"]
1243 c = ShellCommand(self.builder, command,
1244 os.path.join(self.builder.basedir, self.srcdir),
1245 environ=self.env,
1246 sendStdout=False, sendStderr=False, sendRC=False,
1247 keepStdout=True, usePTY=False)
1248 d = c.start()
1249 def _parse(res):
1250 tid = c.stdout.strip()
1251 slash = tid.index("/")
1252 dd = tid.rindex("--")
1253
1254 baserev = tid[dd+2:]
1255 return baserev
1256 d.addCallback(_parse)
1257 return d
1258
1259 registerSlaveCommand("bazaar", Bazaar, command_version)
1260
1261
1262 -class Bzr(SourceBase):
1263 """bzr-specific VC operation. In addition to the arguments
1264 handled by SourceBase, this command reads the following keys:
1265
1266 ['repourl'] (required): the Bzr repository string
1267 """
1268
1269 header = "bzr operation"
1270
1278
1285
1290
1291 if self.forceSharedRepo:
1292 d = self.doForceSharedRepo();
1293 d.addCallback(cont)
1294 return d
1295 else:
1296 return cont(None)
1297
1308
1338
1340 tmpdir = os.path.join(self.builder.basedir, "export-temp")
1341 srcdir = os.path.join(self.builder.basedir, self.srcdir)
1342 command = [self.vcexe, 'checkout', '--lightweight']
1343 if self.revision:
1344 command.append('--revision')
1345 command.append(str(self.revision))
1346 command.append(self.repourl)
1347 command.append(tmpdir)
1348 c = ShellCommand(self.builder, command, self.builder.basedir,
1349 sendRC=False, timeout=self.timeout,
1350 maxTime=self.maxTime, usePTY=False)
1351 self.command = c
1352 d = c.start()
1353 def _export(res):
1354 command = [self.vcexe, 'export', srcdir]
1355 c = ShellCommand(self.builder, command, tmpdir,
1356 sendRC=False, timeout=self.timeout,
1357 maxTime=self.maxTime, usePTY=False)
1358 self.command = c
1359 return c.start()
1360 d.addCallback(_export)
1361 return d
1362
1364
1365
1366
1367 c = ShellCommand(self.builder, [self.vcexe, 'info', '.'],
1368 self.builder.basedir,
1369 sendStderr=False, sendRC=False, usePTY=False)
1370 d = c.start()
1371 def afterCheckSharedRepo(res):
1372 if type(res) is int and res != 0:
1373 log.msg("No shared repo found, creating it")
1374
1375 c = ShellCommand(self.builder, [self.vcexe, 'init-repo', '.'],
1376 self.builder.basedir,
1377 sendRC=False, usePTY=False)
1378 self.command = c
1379 return c.start()
1380 else:
1381 return defer.succeed(res)
1382 d.addCallback(afterCheckSharedRepo)
1383 return d
1384
1386
1387
1388
1389 for line in out.split("\n"):
1390 colon = line.find(":")
1391 if colon != -1:
1392 key, value = line[:colon], line[colon+2:]
1393 if key == "revno":
1394 return int(value)
1395 raise ValueError("unable to find revno: in bzr output: '%s'" % out)
1396
1398 command = [self.vcexe, "version-info"]
1399 c = ShellCommand(self.builder, command,
1400 os.path.join(self.builder.basedir, self.srcdir),
1401 environ=self.env,
1402 sendStdout=False, sendStderr=False, sendRC=False,
1403 keepStdout=True, usePTY=False)
1404 d = c.start()
1405 def _parse(res):
1406 try:
1407 return self.get_revision_number(c.stdout)
1408 except ValueError:
1409 msg =("Bzr.parseGotRevision unable to parse output "
1410 "of bzr version-info: '%s'" % c.stdout.strip())
1411 log.msg(msg)
1412 self.sendStatus({'header': msg + "\n"})
1413 return None
1414 d.addCallback(_parse)
1415 return d
1416
1417 registerSlaveCommand("bzr", Bzr, command_version)
1418
1420 """Mercurial specific VC operation. In addition to the arguments
1421 handled by SourceBase, this command reads the following keys:
1422
1423 ['repourl'] (required): the Mercurial repository string
1424 ['clobberOnBranchChange']: Document me. See ticket #462.
1425 """
1426
1427 header = "mercurial operation"
1428
1430 SourceBase.setup(self, args)
1431 self.vcexe = getCommand("hg")
1432 self.repourl = args['repourl']
1433 self.clobberOnBranchChange = args.get('clobberOnBranchChange', True)
1434 self.sourcedata = "%s\n" % self.repourl
1435 self.branchType = args.get('branchType', 'dirname')
1436 self.stdout = ""
1437 self.stderr = ""
1438 self.clobbercount = 0
1439
1443
1445 d = os.path.join(self.builder.basedir, self.srcdir)
1446 command = [self.vcexe, 'pull', '--verbose', self.repourl]
1447 c = ShellCommand(self.builder, command, d,
1448 sendRC=False, timeout=self.timeout,
1449 maxTime=self.maxTime, keepStdout=True, usePTY=False)
1450 self.command = c
1451 d = c.start()
1452 d.addCallback(self._handleEmptyUpdate)
1453 d.addCallback(self._update)
1454 return d
1455
1457 if type(res) is int and res == 1:
1458 if self.command.stdout.find("no changes found") != -1:
1459
1460
1461
1462
1463 return 0
1464 return res
1465
1467 d = os.path.join(self.builder.basedir, self.srcdir)
1468 command = [self.vcexe, 'clone', '--verbose', '--noupdate']
1469
1470
1471
1472 if self.args.get('revision') and self.mode == 'clobber' and self.branchType == 'dirname':
1473 command.extend(['--rev', self.args.get('revision')])
1474 command.extend([self.repourl, d])
1475
1476 c = ShellCommand(self.builder, command, self.builder.basedir,
1477 sendRC=False, timeout=self.timeout,
1478 maxTime=self.maxTime, usePTY=False)
1479 self.command = c
1480 cmd1 = c.start()
1481 cmd1.addCallback(self._update)
1482 return cmd1
1483
1485 self.clobbercount += 1
1486
1487 if self.clobbercount > 3:
1488 raise Exception, "Too many clobber attempts. Aborting step"
1489
1490 def _vcfull(res):
1491 return self.doVCFull()
1492
1493 c = self.doClobber(dummy, dirname)
1494 c.addCallback(_vcfull)
1495
1496 return c
1497
1498 - def _purge(self, dummy, dirname):
1499 d = os.path.join(self.builder.basedir, self.srcdir)
1500 purge = [self.vcexe, 'purge', '--all']
1501 purgeCmd = ShellCommand(self.builder, purge, d,
1502 sendStdout=False, sendStderr=False,
1503 keepStdout=True, keepStderr=True, usePTY=False)
1504
1505 def _clobber(res):
1506 if res != 0:
1507
1508 msg = "'hg purge' failed: %s\n%s. Clobbering." % (purgeCmd.stdout, purgeCmd.stderr)
1509 self.sendStatus({'header': msg + "\n"})
1510 log.msg(msg)
1511
1512 return self._clobber(dummy, dirname)
1513
1514
1515 return self._update2(res)
1516
1517 p = purgeCmd.start()
1518 p.addCallback(_clobber)
1519 return p
1520
1522 if res != 0:
1523 return res
1524
1525
1526 self.update_branch = self.args.get('branch', 'default')
1527
1528 d = os.path.join(self.builder.basedir, self.srcdir)
1529 parentscmd = [self.vcexe, 'identify', '--num', '--branch']
1530 cmd = ShellCommand(self.builder, parentscmd, d,
1531 sendStdout=False, sendStderr=False,
1532 keepStdout=True, keepStderr=True, usePTY=False)
1533
1534 self.clobber = None
1535
1536 def _parseIdentify(res):
1537 if res != 0:
1538 msg = "'hg identify' failed: %s\n%s" % (cmd.stdout, cmd.stderr)
1539 self.sendStatus({'header': msg + "\n"})
1540 log.msg(msg)
1541 return res
1542
1543 log.msg('Output: %s' % cmd.stdout)
1544
1545 match = re.search(r'^(.+) (.+)$', cmd.stdout)
1546 assert match
1547
1548 rev = match.group(1)
1549 current_branch = match.group(2)
1550
1551 if rev == '-1':
1552 msg = "Fresh hg repo, don't worry about in-repo branch name"
1553 log.msg(msg)
1554
1555 elif self.sourcedirIsPatched():
1556 self.clobber = self._purge
1557
1558 elif self.update_branch != current_branch:
1559 msg = "Working dir is on in-repo branch '%s' and build needs '%s'." % (current_branch, self.update_branch)
1560 if self.clobberOnBranchChange:
1561 msg += ' Cloberring.'
1562 else:
1563 msg += ' Updating.'
1564
1565 self.sendStatus({'header': msg + "\n"})
1566 log.msg(msg)
1567
1568
1569 if self.clobberOnBranchChange:
1570 self.clobber = self._purge
1571
1572 else:
1573 msg = "Working dir on same in-repo branch as build (%s)." % (current_branch)
1574 log.msg(msg)
1575
1576 return 0
1577
1578 def _checkRepoURL(res):
1579 parentscmd = [self.vcexe, 'paths', 'default']
1580 cmd2 = ShellCommand(self.builder, parentscmd, d,
1581 sendStdout=False, sendStderr=False,
1582 keepStdout=True, keepStderr=True, usePTY=False)
1583
1584 def _parseRepoURL(res):
1585 if res == 1:
1586 if "not found!" == cmd2.stderr.strip():
1587 msg = "hg default path not set. Not checking repo url for clobber test"
1588 log.msg(msg)
1589 return 0
1590 else:
1591 msg = "'hg paths default' failed: %s\n%s" % (cmd2.stdout, cmd2.stderr)
1592 log.msg(msg)
1593 return 1
1594
1595 oldurl = cmd2.stdout.strip()
1596
1597 log.msg("Repo cloned from: '%s'" % oldurl)
1598
1599 if sys.platform == "win32":
1600 oldurl = oldurl.lower().replace('\\', '/')
1601 repourl = self.repourl.lower().replace('\\', '/')
1602 else:
1603 repourl = self.repourl
1604
1605 if repourl.startswith('file://'):
1606 repourl = repourl.split('file://')[1]
1607 if oldurl.startswith('file://'):
1608 oldurl = oldurl.split('file://')[1]
1609
1610 oldurl = remove_userpassword(oldurl)
1611 repourl = remove_userpassword(repourl)
1612
1613 if oldurl.rstrip('/') != repourl.rstrip('/'):
1614 self.clobber = self._clobber
1615 msg = "RepoURL changed from '%s' in wc to '%s' in update. Clobbering" % (oldurl, repourl)
1616 log.msg(msg)
1617
1618 return 0
1619
1620 c = cmd2.start()
1621 c.addCallback(_parseRepoURL)
1622 return c
1623
1624 def _maybeClobber(res):
1625 if self.clobber:
1626 msg = "Clobber flag set. Doing clobbering"
1627 log.msg(msg)
1628
1629 def _vcfull(res):
1630 return self.doVCFull()
1631
1632 return self.clobber(None, self.srcdir)
1633
1634 return 0
1635
1636 c = cmd.start()
1637 c.addCallback(_parseIdentify)
1638 c.addCallback(_checkRepoURL)
1639 c.addCallback(_maybeClobber)
1640 c.addCallback(self._update2)
1641 return c
1642
1644 d = os.path.join(self.builder.basedir, self.srcdir)
1645
1646 updatecmd=[self.vcexe, 'update', '--clean', '--repository', d]
1647 if self.args.get('revision'):
1648 updatecmd.extend(['--rev', self.args['revision']])
1649 else:
1650 updatecmd.extend(['--rev', self.args.get('branch', 'default')])
1651 self.command = ShellCommand(self.builder, updatecmd,
1652 self.builder.basedir, sendRC=False,
1653 timeout=self.timeout, maxTime=self.maxTime, usePTY=False)
1654 return self.command.start()
1655
1657
1658 command = [self.vcexe, "identify", "--id", "--debug"]
1659 c = ShellCommand(self.builder, command,
1660 os.path.join(self.builder.basedir, self.srcdir),
1661 environ=self.env,
1662 sendStdout=False, sendStderr=False, sendRC=False,
1663 keepStdout=True, usePTY=False)
1664 d = c.start()
1665 def _parse(res):
1666 m = re.search(r'^(\w+)', c.stdout)
1667 return m.group(1)
1668 d.addCallback(_parse)
1669 return d
1670
1671 registerSlaveCommand("hg", Mercurial, command_version)
1672
1673
1675 """Base class for P4 source-updaters
1676
1677 ['p4port'] (required): host:port for server to access
1678 ['p4user'] (optional): user to use for access
1679 ['p4passwd'] (optional): passwd to try for the user
1680 ['p4client'] (optional): client spec to use
1681 """
1683 SourceBase.setup(self, args)
1684 self.p4port = args['p4port']
1685 self.p4client = args['p4client']
1686 self.p4user = args['p4user']
1687 self.p4passwd = args['p4passwd']
1688
1690
1691
1692 command = ['p4']
1693 if self.p4port:
1694 command.extend(['-p', self.p4port])
1695 if self.p4user:
1696 command.extend(['-u', self.p4user])
1697 if self.p4passwd:
1698 command.extend(['-P', Obfuscated(self.p4passwd, "XXXXXXXX")])
1699 if self.p4client:
1700 command.extend(['-c', self.p4client])
1701
1702 command.extend(['changes', '-s', 'submitted', '-m', '1', '#have'])
1703 c = ShellCommand(self.builder, command, self.builder.basedir,
1704 environ=self.env, timeout=self.timeout,
1705 maxTime=self.maxTime, sendStdout=True,
1706 sendStderr=False, sendRC=False, keepStdout=True,
1707 usePTY=False)
1708 self.command = c
1709 d = c.start()
1710
1711 def _parse(res):
1712
1713
1714
1715 m = re.match('Change\s+(\d+)\s+', c.stdout)
1716 if m:
1717 return m.group(1)
1718 return None
1719 d.addCallback(_parse)
1720 return d
1721
1722
1724 """A P4 source-updater.
1725
1726 ['p4port'] (required): host:port for server to access
1727 ['p4user'] (optional): user to use for access
1728 ['p4passwd'] (optional): passwd to try for the user
1729 ['p4client'] (optional): client spec to use
1730 ['p4extra_views'] (optional): additional client views to use
1731 """
1732
1733 header = "p4"
1734
1736 P4Base.setup(self, args)
1737 self.p4base = args['p4base']
1738 self.p4extra_views = args['p4extra_views']
1739 self.p4mode = args['mode']
1740 self.p4branch = args['branch']
1741
1742 self.sourcedata = str([
1743
1744 self.p4port,
1745
1746
1747 self.p4client,
1748
1749
1750 self.p4base,
1751 self.p4branch,
1752 self.p4extra_views,
1753
1754
1755 self.builder.basedir,
1756 self.mode,
1757 self.workdir
1758 ])
1759
1760
1768
1770 return self._doP4Sync(force=False)
1771
1773 command = ['p4']
1774
1775 if self.p4port:
1776 command.extend(['-p', self.p4port])
1777 if self.p4user:
1778 command.extend(['-u', self.p4user])
1779 if self.p4passwd:
1780 command.extend(['-P', Obfuscated(self.p4passwd, "XXXXXXXX")])
1781 if self.p4client:
1782 command.extend(['-c', self.p4client])
1783 command.extend(['sync'])
1784 if force:
1785 command.extend(['-f'])
1786 if self.revision:
1787 command.extend(['@' + str(self.revision)])
1788 env = {}
1789 c = ShellCommand(self.builder, command, self.builder.basedir,
1790 environ=env, sendRC=False, timeout=self.timeout,
1791 maxTime=self.maxTime, keepStdout=True, usePTY=False)
1792 self.command = c
1793 d = c.start()
1794 d.addCallback(self._abandonOnFailure)
1795 return d
1796
1797
1799 env = {}
1800 command = ['p4']
1801 client_spec = ''
1802 client_spec += "Client: %s\n\n" % self.p4client
1803 client_spec += "Owner: %s\n\n" % self.p4user
1804 client_spec += "Description:\n\tCreated by %s\n\n" % self.p4user
1805 client_spec += "Root:\t%s\n\n" % self.builder.basedir
1806 client_spec += "Options:\tallwrite rmdir\n\n"
1807 client_spec += "LineEnd:\tlocal\n\n"
1808
1809
1810 client_spec += "View:\n\t%s" % (self.p4base)
1811 if self.p4branch:
1812 client_spec += "%s/" % (self.p4branch)
1813 client_spec += "... //%s/%s/...\n" % (self.p4client, self.srcdir)
1814 if self.p4extra_views:
1815 for k, v in self.p4extra_views:
1816 client_spec += "\t%s/... //%s/%s%s/...\n" % (k, self.p4client,
1817 self.srcdir, v)
1818 if self.p4port:
1819 command.extend(['-p', self.p4port])
1820 if self.p4user:
1821 command.extend(['-u', self.p4user])
1822 if self.p4passwd:
1823 command.extend(['-P', Obfuscated(self.p4passwd, "XXXXXXXX")])
1824 command.extend(['client', '-i'])
1825 log.msg(client_spec)
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837 client_spec=client_spec.encode('ascii','ignore')
1838
1839 c = ShellCommand(self.builder, command, self.builder.basedir,
1840 environ=env, sendRC=False, timeout=self.timeout,
1841 maxTime=self.maxTime, initialStdin=client_spec,
1842 usePTY=False)
1843 self.command = c
1844 d = c.start()
1845 d.addCallback(self._abandonOnFailure)
1846 d.addCallback(lambda _: self._doP4Sync(force=True))
1847 return d
1848
1854
1855 registerSlaveCommand("p4", P4, command_version)
1856
1857
1859 """A partial P4 source-updater. Requires manual setup of a per-slave P4
1860 environment. The only thing which comes from the master is P4PORT.
1861 'mode' is required to be 'copy'.
1862
1863 ['p4port'] (required): host:port for server to access
1864 ['p4user'] (optional): user to use for access
1865 ['p4passwd'] (optional): passwd to try for the user
1866 ['p4client'] (optional): client spec to use
1867 """
1868
1869 header = "p4 sync"
1870
1874
1877
1878 - def _doVC(self, force):
1879 d = os.path.join(self.builder.basedir, self.srcdir)
1880 command = [self.vcexe]
1881 if self.p4port:
1882 command.extend(['-p', self.p4port])
1883 if self.p4user:
1884 command.extend(['-u', self.p4user])
1885 if self.p4passwd:
1886 command.extend(['-P', Obfuscated(self.p4passwd, "XXXXXXXX")])
1887 if self.p4client:
1888 command.extend(['-c', self.p4client])
1889 command.extend(['sync'])
1890 if force:
1891 command.extend(['-f'])
1892 if self.revision:
1893 command.extend(['@' + self.revision])
1894 env = {}
1895 c = ShellCommand(self.builder, command, d, environ=env,
1896 sendRC=False, timeout=self.timeout,
1897 maxTime=self.maxTime, usePTY=False)
1898 self.command = c
1899 return c.start()
1900
1902 return self._doVC(force=False)
1903
1905 return self._doVC(force=True)
1906
1912
1913 registerSlaveCommand("p4sync", P4Sync, command_version)
1914