1 # -*- test-case-name: buildbot.test.test_vc -*-
3 from warnings
import warn
4 from email
.Utils
import formatdate
5 from twisted
.python
import log
6 from buildbot
.process
.buildstep
import LoggingBuildStep
, LoggedRemoteCommand
7 from buildbot
.interfaces
import BuildSlaveTooOldError
8 from buildbot
.status
.builder
import SKIPPED
11 class Source(LoggingBuildStep
):
12 """This is a base class to generate a source tree in the buildslave.
13 Each version control system has a specialized subclass, and is expected
14 to override __init__ and implement computeSourceRevision() and
15 startVC(). The class as a whole builds up the self.args dictionary, then
16 starts a LoggedRemoteCommand with those arguments.
19 # if the checkout fails, there's no point in doing anything else
24 branch
= None # the default branch, should be set in __init__
26 def __init__(self
, workdir
=None, mode
='update', alwaysUseLatest
=False,
27 timeout
=20*60, retry
=None, **kwargs
):
30 @param workdir: local directory (relative to the Builder's root)
31 where the tree should be placed
34 @param mode: the kind of VC operation that is desired:
35 - 'update': specifies that the checkout/update should be
36 performed directly into the workdir. Each build is performed
37 in the same directory, allowing for incremental builds. This
38 minimizes disk space, bandwidth, and CPU time. However, it
39 may encounter problems if the build process does not handle
40 dependencies properly (if you must sometimes do a 'clean
41 build' to make sure everything gets compiled), or if source
42 files are deleted but generated files can influence test
43 behavior (e.g. python's .pyc files), or when source
44 directories are deleted but generated files prevent CVS from
47 - 'copy': specifies that the source-controlled workspace
48 should be maintained in a separate directory (called the
49 'copydir'), using checkout or update as necessary. For each
50 build, a new workdir is created with a copy of the source
51 tree (rm -rf workdir; cp -R -P -p copydir workdir). This
52 doubles the disk space required, but keeps the bandwidth low
53 (update instead of a full checkout). A full 'clean' build
54 is performed each time. This avoids any generated-file
55 build problems, but is still occasionally vulnerable to
56 problems such as a CVS repository being manually rearranged
57 (causing CVS errors on update) which are not an issue with
60 - 'clobber': specifies that the working directory should be
61 deleted each time, necessitating a full checkout for each
62 build. This insures a clean build off a complete checkout,
63 avoiding any of the problems described above, but is
64 bandwidth intensive, as the whole source tree must be
65 pulled down for each build.
67 - 'export': is like 'clobber', except that e.g. the 'cvs
68 export' command is used to create the working directory.
69 This command removes all VC metadata files (the
70 CVS/.svn/{arch} directories) from the tree, which is
71 sometimes useful for creating source tarballs (to avoid
72 including the metadata in the tar file). Not all VC systems
75 @type alwaysUseLatest: boolean
76 @param alwaysUseLatest: whether to always update to the most
77 recent available sources for this build.
79 Normally the Source step asks its Build for a list of all
80 Changes that are supposed to go into the build, then computes a
81 'source stamp' (revision number or timestamp) that will cause
82 exactly that set of changes to be present in the checked out
83 tree. This is turned into, e.g., 'cvs update -D timestamp', or
84 'svn update -r revnum'. If alwaysUseLatest=True, bypass this
85 computation and always update to the latest available sources
88 The source stamp helps avoid a race condition in which someone
89 commits a change after the master has decided to start a build
90 but before the slave finishes checking out the sources. At best
91 this results in a build which contains more changes than the
92 buildmaster thinks it has (possibly resulting in the wrong
93 person taking the blame for any problems that result), at worst
94 is can result in an incoherent set of sources (splitting a
95 non-atomic commit) which may not build at all.
97 @type retry: tuple of ints (delay, repeats) (or None)
98 @param retry: if provided, VC update failures are re-attempted up
99 to REPEATS times, with DELAY seconds between each
100 attempt. Some users have slaves with poor connectivity
101 to their VC repository, and they say that up to 80% of
102 their build failures are due to transient network
103 failures that could be handled by simply retrying a
108 LoggingBuildStep
.__init
__(self
, **kwargs
)
109 self
.addFactoryArguments(workdir
=workdir
,
111 alwaysUseLatest
=alwaysUseLatest
,
116 assert mode
in ("update", "copy", "clobber", "export")
118 delay
, repeats
= retry
119 assert isinstance(repeats
, int)
121 self
.args
= {'mode': mode
,
125 'patch': None, # set during .start
127 self
.alwaysUseLatest
= alwaysUseLatest
129 # Compute defaults for descriptions:
130 description
= ["updating"]
131 descriptionDone
= ["update"]
132 if mode
== "clobber":
133 description
= ["checkout"]
134 # because checkingouting takes too much space
135 descriptionDone
= ["checkout"]
136 elif mode
== "export":
137 description
= ["exporting"]
138 descriptionDone
= ["export"]
139 self
.description
= description
140 self
.descriptionDone
= descriptionDone
142 def setDefaultWorkdir(self
, workdir
):
143 self
.args
['workdir'] = self
.args
['workdir'] or workdir
145 def describe(self
, done
=False):
147 return self
.descriptionDone
148 return self
.description
150 def computeSourceRevision(self
, changes
):
151 """Each subclass must implement this method to do something more
152 precise than -rHEAD every time. For version control systems that use
153 repository-wide change numbers (SVN, P4), this can simply take the
154 maximum such number from all the changes involved in this build. For
155 systems that do not (CVS), it needs to create a timestamp based upon
156 the latest Change, the Build's treeStableTimer, and an optional
157 self.checkoutDelay value."""
162 log
.msg("faking %s checkout/update" % self
.name
)
163 self
.step_status
.setText(["fake", self
.name
, "successful"])
164 self
.addCompleteLog("log",
165 "Faked %s checkout/update 'successful'\n" \
169 # what source stamp would this build like to use?
170 s
= self
.build
.getSourceStamp()
171 # if branch is None, then use the Step's "default" branch
172 branch
= s
.branch
or self
.branch
173 # if revision is None, use the latest sources (-rHEAD)
174 revision
= s
.revision
175 if not revision
and not self
.alwaysUseLatest
:
176 revision
= self
.computeSourceRevision(s
.changes
)
177 # if patch is None, then do not patch the tree after checkout
179 # 'patch' is None or a tuple of (patchlevel, diff)
182 self
.addCompleteLog("patch", patch
[1])
184 self
.startVC(branch
, revision
, patch
)
186 def commandComplete(self
, cmd
):
187 if cmd
.updates
.has_key("got_revision"):
188 got_revision
= cmd
.updates
["got_revision"][-1]
189 if got_revision
is not None:
190 self
.setProperty("got_revision", str(got_revision
), "Source")
195 """I do CVS checkout/update operations.
197 Note: if you are doing anonymous/pserver CVS operations, you will need
198 to manually do a 'cvs login' on each buildslave before the slave has any
199 hope of success. XXX: fix then, take a cvs password as an argument and
200 figure out how to do a 'cvs login' on each build
205 #progressMetrics = ('output',)
207 # additional things to track: update gives one stderr line per directory
208 # (starting with 'cvs server: Updating ') (and is fairly stable if files
209 # is empty), export gives one line per directory (starting with 'cvs
210 # export: Updating ') and another line per file (starting with U). Would
211 # be nice to track these, requires grepping LogFile data for lines,
212 # parsing each line. Might be handy to have a hook in LogFile that gets
213 # called with each complete line.
215 def __init__(self
, cvsroot
, cvsmodule
,
216 global_options
=[], branch
=None, checkoutDelay
=None,
221 @type cvsroot: string
222 @param cvsroot: CVS Repository from which the source tree should
223 be obtained. '/home/warner/Repository' for local
224 or NFS-reachable repositories,
225 ':pserver:anon@foo.com:/cvs' for anonymous CVS,
226 'user@host.com:/cvs' for non-anonymous CVS or
227 CVS over ssh. Lots of possibilities, check the
228 CVS documentation for more.
230 @type cvsmodule: string
231 @param cvsmodule: subdirectory of CVS repository that should be
234 @type login: string or None
235 @param login: if not None, a string which will be provided as a
236 password to the 'cvs login' command, used when a
237 :pserver: method is used to access the repository.
238 This login is only needed once, but must be run
239 each time (just before the CVS operation) because
240 there is no way for the buildslave to tell whether
241 it was previously performed or not.
244 @param branch: the default branch name, will be used in a '-r'
245 argument to specify which branch of the source tree
246 should be used for this checkout. Defaults to None,
247 which means to use 'HEAD'.
249 @type checkoutDelay: int or None
250 @param checkoutDelay: if not None, the number of seconds to put
251 between the last known Change and the
252 timestamp given to the -D argument. This
253 defaults to exactly half of the parent
254 Build's .treeStableTimer, but it could be
255 set to something else if your CVS change
256 notification has particularly weird
257 latency characteristics.
259 @type global_options: list of strings
260 @param global_options: these arguments are inserted in the cvs
261 command line, before the
262 'checkout'/'update' command word. See
263 'cvs --help-options' for a list of what
264 may be accepted here. ['-r'] will make
265 the checked out files read only. ['-r',
266 '-R'] will also assume the repository is
267 read-only (I assume this means it won't
268 use locks to insure atomic access to the
271 self
.checkoutDelay
= checkoutDelay
274 Source
.__init
__(self
, **kwargs
)
275 self
.addFactoryArguments(cvsroot
=cvsroot
,
277 global_options
=global_options
,
279 checkoutDelay
=checkoutDelay
,
283 self
.args
.update({'cvsroot': cvsroot
,
284 'cvsmodule': cvsmodule
,
285 'global_options': global_options
,
289 def computeSourceRevision(self
, changes
):
292 lastChange
= max([c
.when
for c
in changes
])
293 if self
.checkoutDelay
is not None:
294 when
= lastChange
+ self
.checkoutDelay
296 lastSubmit
= max([r
.submittedAt
for r
in self
.build
.requests
])
297 when
= (lastChange
+ lastSubmit
) / 2
298 return formatdate(when
)
300 def startVC(self
, branch
, revision
, patch
):
301 if self
.slaveVersionIsOlderThan("cvs", "1.39"):
302 # the slave doesn't know to avoid re-using the same sourcedir
303 # when the branch changes. We have no way of knowing which branch
304 # the last build used, so if we're using a non-default branch and
305 # either 'update' or 'copy' modes, it is safer to refuse to
306 # build, and tell the user they need to upgrade the buildslave.
307 if (branch
!= self
.branch
308 and self
.args
['mode'] in ("update", "copy")):
309 m
= ("This buildslave (%s) does not know about multiple "
310 "branches, and using mode=%s would probably build the "
312 "Refusing to build. Please upgrade the buildslave to "
313 "buildbot-0.7.0 or newer." % (self
.build
.slavename
,
316 raise BuildSlaveTooOldError(m
)
320 self
.args
['branch'] = branch
321 self
.args
['revision'] = revision
322 self
.args
['patch'] = patch
324 if self
.args
['branch'] == "HEAD" and self
.args
['revision']:
325 # special case. 'cvs update -r HEAD -D today' gives no files
326 # TODO: figure out why, see if it applies to -r BRANCH
327 self
.args
['branch'] = None
329 # deal with old slaves
331 slavever
= self
.slaveVersion("cvs", "old")
333 if slavever
== "old":
335 if self
.args
['mode'] == "export":
336 self
.args
['export'] = 1
337 elif self
.args
['mode'] == "clobber":
338 self
.args
['clobber'] = 1
339 elif self
.args
['mode'] == "copy":
340 self
.args
['copydir'] = "source"
341 self
.args
['tag'] = self
.args
['branch']
342 assert not self
.args
['patch'] # 0.5.0 slave can't do patch
344 cmd
= LoggedRemoteCommand("cvs", self
.args
)
345 self
.startCommand(cmd
, warnings
)
349 """I perform Subversion checkout/update operations."""
353 def __init__(self
, svnurl
=None, baseURL
=None, defaultBranch
=None,
354 directory
=None, username
=None, password
=None, **kwargs
):
357 @param svnurl: the URL which points to the Subversion server,
358 combining the access method (HTTP, ssh, local file),
359 the repository host/port, the repository path, the
360 sub-tree within the repository, and the branch to
361 check out. Using C{svnurl} does not enable builds of
362 alternate branches: use C{baseURL} to enable this.
363 Use exactly one of C{svnurl} and C{baseURL}.
365 @param baseURL: if branches are enabled, this is the base URL to
366 which a branch name will be appended. It should
367 probably end in a slash. Use exactly one of
368 C{svnurl} and C{baseURL}.
370 @param defaultBranch: if branches are enabled, this is the branch
371 to use if the Build does not specify one
372 explicitly. It will simply be appended
373 to C{baseURL} and the result handed to
376 @param username: username to pass to svn's --username
377 @param password: username to pass to svn's --password
380 if not kwargs
.has_key('workdir') and directory
is not None:
381 # deal with old configs
382 warn("Please use workdir=, not directory=", DeprecationWarning)
383 kwargs
['workdir'] = directory
386 self
.baseURL
= baseURL
387 self
.branch
= defaultBranch
388 self
.username
= username
389 self
.password
= password
391 Source
.__init
__(self
, **kwargs
)
392 self
.addFactoryArguments(svnurl
=svnurl
,
394 defaultBranch
=defaultBranch
,
400 if not svnurl
and not baseURL
:
401 raise ValueError("you must use exactly one of svnurl and baseURL")
404 def computeSourceRevision(self
, changes
):
405 if not changes
or None in [c
.revision
for c
in changes
]:
407 lastChange
= max([int(c
.revision
) for c
in changes
])
410 def startVC(self
, branch
, revision
, patch
):
414 slavever
= self
.slaveVersion("svn", "old")
416 m
= "slave does not have the 'svn' command"
417 raise BuildSlaveTooOldError(m
)
419 if self
.slaveVersionIsOlderThan("svn", "1.39"):
420 # the slave doesn't know to avoid re-using the same sourcedir
421 # when the branch changes. We have no way of knowing which branch
422 # the last build used, so if we're using a non-default branch and
423 # either 'update' or 'copy' modes, it is safer to refuse to
424 # build, and tell the user they need to upgrade the buildslave.
425 if (branch
!= self
.branch
426 and self
.args
['mode'] in ("update", "copy")):
427 m
= ("This buildslave (%s) does not know about multiple "
428 "branches, and using mode=%s would probably build the "
430 "Refusing to build. Please upgrade the buildslave to "
431 "buildbot-0.7.0 or newer." % (self
.build
.slavename
,
433 raise BuildSlaveTooOldError(m
)
435 if slavever
== "old":
436 # 0.5.0 compatibility
437 if self
.args
['mode'] in ("clobber", "copy"):
438 # TODO: use some shell commands to make up for the
439 # deficiency, by blowing away the old directory first (thus
440 # forcing a full checkout)
441 warnings
.append("WARNING: this slave can only do SVN updates"
442 ", not mode=%s\n" % self
.args
['mode'])
443 log
.msg("WARNING: this slave only does mode=update")
444 if self
.args
['mode'] == "export":
445 raise BuildSlaveTooOldError("old slave does not have "
447 self
.args
['directory'] = self
.args
['workdir']
448 if revision
is not None:
449 # 0.5.0 can only do HEAD. We have no way of knowing whether
450 # the requested revision is HEAD or not, and for
451 # slowly-changing trees this will probably do the right
452 # thing, so let it pass with a warning
453 m
= ("WARNING: old slave can only update to HEAD, not "
454 "revision=%s" % revision
)
456 warnings
.append(m
+ "\n")
457 revision
= "HEAD" # interprets this key differently
459 raise BuildSlaveTooOldError("old slave can't do patch")
462 assert not branch
# we need baseURL= to use branches
463 self
.args
['svnurl'] = self
.svnurl
465 self
.args
['svnurl'] = self
.baseURL
+ branch
466 self
.args
['revision'] = revision
467 self
.args
['patch'] = patch
469 if self
.username
is not None or self
.password
is not None:
470 if self
.slaveVersionIsOlderThan("svn", "2.8"):
471 m
= ("This buildslave (%s) does not support svn usernames "
473 "Refusing to build. Please upgrade the buildslave to "
474 "buildbot-0.7.10 or newer." % (self
.build
.slavename
,))
475 raise BuildSlaveTooOldError(m
)
476 if self
.username
is not None: self
.args
['username'] = self
.username
477 if self
.password
is not None: self
.args
['password'] = self
.password
480 if branch
is not None and branch
!= self
.branch
:
481 revstuff
.append("[branch]")
482 if revision
is not None:
483 revstuff
.append("r%s" % revision
)
484 if patch
is not None:
485 revstuff
.append("[patch]")
486 self
.description
.extend(revstuff
)
487 self
.descriptionDone
.extend(revstuff
)
489 cmd
= LoggedRemoteCommand("svn", self
.args
)
490 self
.startCommand(cmd
, warnings
)
494 """Check out a source tree from a Darcs repository at 'repourl'.
496 Darcs has no concept of file modes. This means the eXecute-bit will be
497 cleared on all source files. As a result, you may need to invoke
498 configuration scripts with something like:
500 C{s(step.Configure, command=['/bin/sh', './configure'])}
505 def __init__(self
, repourl
=None, baseURL
=None, defaultBranch
=None,
508 @type repourl: string
509 @param repourl: the URL which points at the Darcs repository. This
510 is used as the default branch. Using C{repourl} does
511 not enable builds of alternate branches: use
512 C{baseURL} to enable this. Use either C{repourl} or
513 C{baseURL}, not both.
515 @param baseURL: if branches are enabled, this is the base URL to
516 which a branch name will be appended. It should
517 probably end in a slash. Use exactly one of
518 C{repourl} and C{baseURL}.
520 @param defaultBranch: if branches are enabled, this is the branch
521 to use if the Build does not specify one
522 explicitly. It will simply be appended to
523 C{baseURL} and the result handed to the
524 'darcs pull' command.
526 self
.repourl
= repourl
527 self
.baseURL
= baseURL
528 self
.branch
= defaultBranch
529 Source
.__init
__(self
, **kwargs
)
530 self
.addFactoryArguments(repourl
=repourl
,
532 defaultBranch
=defaultBranch
,
534 assert self
.args
['mode'] != "export", \
535 "Darcs does not have an 'export' mode"
536 if (not repourl
and not baseURL
) or (repourl
and baseURL
):
537 raise ValueError("you must provide exactly one of repourl and"
540 def startVC(self
, branch
, revision
, patch
):
541 slavever
= self
.slaveVersion("darcs")
543 m
= "slave is too old, does not know about darcs"
544 raise BuildSlaveTooOldError(m
)
546 if self
.slaveVersionIsOlderThan("darcs", "1.39"):
548 # TODO: revisit this once we implement computeSourceRevision
549 m
= "0.6.6 slaves can't handle args['revision']"
550 raise BuildSlaveTooOldError(m
)
552 # the slave doesn't know to avoid re-using the same sourcedir
553 # when the branch changes. We have no way of knowing which branch
554 # the last build used, so if we're using a non-default branch and
555 # either 'update' or 'copy' modes, it is safer to refuse to
556 # build, and tell the user they need to upgrade the buildslave.
557 if (branch
!= self
.branch
558 and self
.args
['mode'] in ("update", "copy")):
559 m
= ("This buildslave (%s) does not know about multiple "
560 "branches, and using mode=%s would probably build the "
562 "Refusing to build. Please upgrade the buildslave to "
563 "buildbot-0.7.0 or newer." % (self
.build
.slavename
,
565 raise BuildSlaveTooOldError(m
)
568 assert not branch
# we need baseURL= to use branches
569 self
.args
['repourl'] = self
.repourl
571 self
.args
['repourl'] = self
.baseURL
+ branch
572 self
.args
['revision'] = revision
573 self
.args
['patch'] = patch
576 if branch
is not None and branch
!= self
.branch
:
577 revstuff
.append("[branch]")
578 self
.description
.extend(revstuff
)
579 self
.descriptionDone
.extend(revstuff
)
581 cmd
= LoggedRemoteCommand("darcs", self
.args
)
582 self
.startCommand(cmd
)
586 """Check out a source tree from a git repository 'repourl'."""
590 def __init__(self
, repourl
, branch
="master", **kwargs
):
592 @type repourl: string
593 @param repourl: the URL which points at the git repository
596 @param branch: The branch or tag to check out by default. If
597 a build specifies a different branch, it will
598 be used instead of this.
600 Source
.__init
__(self
, **kwargs
)
601 self
.addFactoryArguments(repourl
=repourl
, branch
=branch
)
602 self
.args
.update({'repourl': repourl
,
605 def computeSourceRevision(self
, changes
):
608 return changes
[-1].revision
610 def startVC(self
, branch
, revision
, patch
):
611 if branch
is not None:
612 self
.args
['branch'] = branch
614 self
.args
['revision'] = revision
615 self
.args
['patch'] = patch
616 slavever
= self
.slaveVersion("git")
618 raise BuildSlaveTooOldError("slave is too old, does not know "
620 cmd
= LoggedRemoteCommand("git", self
.args
)
621 self
.startCommand(cmd
)
625 """Check out a source tree from an Arch repository named 'archive'
626 available at 'url'. 'version' specifies which version number (development
627 line) will be used for the checkout: this is mostly equivalent to a
628 branch name. This version uses the 'tla' tool to do the checkout, to use
629 'baz' see L{Bazaar} instead.
633 # TODO: slaves >0.6.6 will accept args['build-config'], so use it
635 def __init__(self
, url
, version
, archive
=None, **kwargs
):
638 @param url: the Arch coordinates of the repository. This is
639 typically an http:// URL, but could also be the absolute
640 pathname of a local directory instead.
642 @type version: string
643 @param version: the category--branch--version to check out. This is
644 the default branch. If a build specifies a different
645 branch, it will be used instead of this.
647 @type archive: string
648 @param archive: The archive name. If provided, it must match the one
649 that comes from the repository. If not, the
650 repository's default will be used.
652 self
.branch
= version
653 Source
.__init
__(self
, **kwargs
)
654 self
.addFactoryArguments(url
=url
,
658 self
.args
.update({'url': url
,
662 def computeSourceRevision(self
, changes
):
663 # in Arch, fully-qualified revision numbers look like:
664 # arch@buildbot.sourceforge.net--2004/buildbot--dev--0--patch-104
665 # For any given builder, all of this is fixed except the patch-104.
666 # The Change might have any part of the fully-qualified string, so we
667 # just look for the last part. We return the "patch-NN" string.
674 if c
.revision
.endswith("--base-0"):
677 i
= c
.revision
.rindex("patch")
678 rev
= int(c
.revision
[i
+len("patch-"):])
679 lastChange
= max(lastChange
, rev
)
680 if lastChange
is None:
684 return "patch-%d" % lastChange
686 def checkSlaveVersion(self
, cmd
, branch
):
688 slavever
= self
.slaveVersion(cmd
)
690 m
= "slave is too old, does not know about %s" % cmd
691 raise BuildSlaveTooOldError(m
)
693 # slave 1.28 and later understand 'revision'
694 if self
.slaveVersionIsOlderThan(cmd
, "1.28"):
695 if not self
.alwaysUseLatest
:
696 # we don't know whether our requested revision is the latest
697 # or not. If the tree does not change very quickly, this will
698 # probably build the right thing, so emit a warning rather
699 # than refuse to build at all
700 m
= "WARNING, buildslave is too old to use a revision"
702 warnings
.append(m
+ "\n")
704 if self
.slaveVersionIsOlderThan(cmd
, "1.39"):
705 # the slave doesn't know to avoid re-using the same sourcedir
706 # when the branch changes. We have no way of knowing which branch
707 # the last build used, so if we're using a non-default branch and
708 # either 'update' or 'copy' modes, it is safer to refuse to
709 # build, and tell the user they need to upgrade the buildslave.
710 if (branch
!= self
.branch
711 and self
.args
['mode'] in ("update", "copy")):
712 m
= ("This buildslave (%s) does not know about multiple "
713 "branches, and using mode=%s would probably build the "
715 "Refusing to build. Please upgrade the buildslave to "
716 "buildbot-0.7.0 or newer." % (self
.build
.slavename
,
719 raise BuildSlaveTooOldError(m
)
723 def startVC(self
, branch
, revision
, patch
):
724 self
.args
['version'] = branch
725 self
.args
['revision'] = revision
726 self
.args
['patch'] = patch
727 warnings
= self
.checkSlaveVersion("arch", branch
)
730 if branch
is not None and branch
!= self
.branch
:
731 revstuff
.append("[branch]")
732 if revision
is not None:
733 revstuff
.append("patch%s" % revision
)
734 self
.description
.extend(revstuff
)
735 self
.descriptionDone
.extend(revstuff
)
737 cmd
= LoggedRemoteCommand("arch", self
.args
)
738 self
.startCommand(cmd
, warnings
)
742 """Bazaar is an alternative client for Arch repositories. baz is mostly
743 compatible with tla, but archive registration is slightly different."""
745 # TODO: slaves >0.6.6 will accept args['build-config'], so use it
747 def __init__(self
, url
, version
, archive
, **kwargs
):
750 @param url: the Arch coordinates of the repository. This is
751 typically an http:// URL, but could also be the absolute
752 pathname of a local directory instead.
754 @type version: string
755 @param version: the category--branch--version to check out
757 @type archive: string
758 @param archive: The archive name (required). This must always match
759 the one that comes from the repository, otherwise the
760 buildslave will attempt to get sources from the wrong
763 self
.branch
= version
764 Source
.__init
__(self
, **kwargs
)
765 self
.addFactoryArguments(url
=url
,
769 self
.args
.update({'url': url
,
773 def startVC(self
, branch
, revision
, patch
):
774 self
.args
['version'] = branch
775 self
.args
['revision'] = revision
776 self
.args
['patch'] = patch
777 warnings
= self
.checkSlaveVersion("bazaar", branch
)
780 if branch
is not None and branch
!= self
.branch
:
781 revstuff
.append("[branch]")
782 if revision
is not None:
783 revstuff
.append("patch%s" % revision
)
784 self
.description
.extend(revstuff
)
785 self
.descriptionDone
.extend(revstuff
)
787 cmd
= LoggedRemoteCommand("bazaar", self
.args
)
788 self
.startCommand(cmd
, warnings
)
791 """Check out a source tree from a bzr (Bazaar) repository at 'repourl'.
797 def __init__(self
, repourl
=None, baseURL
=None, defaultBranch
=None,
800 @type repourl: string
801 @param repourl: the URL which points at the bzr repository. This
802 is used as the default branch. Using C{repourl} does
803 not enable builds of alternate branches: use
804 C{baseURL} to enable this. Use either C{repourl} or
805 C{baseURL}, not both.
807 @param baseURL: if branches are enabled, this is the base URL to
808 which a branch name will be appended. It should
809 probably end in a slash. Use exactly one of
810 C{repourl} and C{baseURL}.
812 @param defaultBranch: if branches are enabled, this is the branch
813 to use if the Build does not specify one
814 explicitly. It will simply be appended to
815 C{baseURL} and the result handed to the
816 'bzr checkout pull' command.
818 self
.repourl
= repourl
819 self
.baseURL
= baseURL
820 self
.branch
= defaultBranch
821 Source
.__init
__(self
, **kwargs
)
822 self
.addFactoryArguments(repourl
=repourl
,
824 defaultBranch
=defaultBranch
,
826 if (not repourl
and not baseURL
) or (repourl
and baseURL
):
827 raise ValueError("you must provide exactly one of repourl and"
830 def computeSourceRevision(self
, changes
):
833 lastChange
= max([int(c
.revision
) for c
in changes
])
836 def startVC(self
, branch
, revision
, patch
):
837 slavever
= self
.slaveVersion("bzr")
839 m
= "slave is too old, does not know about bzr"
840 raise BuildSlaveTooOldError(m
)
843 assert not branch
# we need baseURL= to use branches
844 self
.args
['repourl'] = self
.repourl
846 self
.args
['repourl'] = self
.baseURL
+ branch
847 self
.args
['revision'] = revision
848 self
.args
['patch'] = patch
851 if branch
is not None and branch
!= self
.branch
:
852 revstuff
.append("[branch]")
853 self
.description
.extend(revstuff
)
854 self
.descriptionDone
.extend(revstuff
)
856 cmd
= LoggedRemoteCommand("bzr", self
.args
)
857 self
.startCommand(cmd
)
860 class Mercurial(Source
):
861 """Check out a source tree from a mercurial repository 'repourl'."""
865 def __init__(self
, repourl
=None, baseURL
=None, defaultBranch
=None,
866 branchType
='dirname', **kwargs
):
868 @type repourl: string
869 @param repourl: the URL which points at the Mercurial repository.
870 This uses the 'default' branch unless defaultBranch is
871 specified below and the C{branchType} is set to
872 'inrepo'. It is an error to specify a branch without
873 setting the C{branchType} to 'inrepo'.
875 @param baseURL: if 'dirname' branches are enabled, this is the base URL
876 to which a branch name will be appended. It should
877 probably end in a slash. Use exactly one of C{repourl}
880 @param defaultBranch: if branches are enabled, this is the branch
881 to use if the Build does not specify one
883 For 'dirname' branches, It will simply be
884 appended to C{baseURL} and the result handed to
885 the 'hg update' command.
886 For 'inrepo' branches, this specifies the named
887 revision to which the tree will update after a
890 @param branchType: either 'dirname' or 'inrepo' depending on whether
891 the branch name should be appended to the C{baseURL}
892 or the branch is a mercurial named branch and can be
893 found within the C{repourl}
895 self
.repourl
= repourl
896 self
.baseURL
= baseURL
897 self
.branch
= defaultBranch
898 self
.branchType
= branchType
899 Source
.__init
__(self
, **kwargs
)
900 self
.addFactoryArguments(repourl
=repourl
,
902 defaultBranch
=defaultBranch
,
903 branchType
=branchType
,
905 if (not repourl
and not baseURL
) or (repourl
and baseURL
):
906 raise ValueError("you must provide exactly one of repourl and"
909 def startVC(self
, branch
, revision
, patch
):
910 slavever
= self
.slaveVersion("hg")
912 raise BuildSlaveTooOldError("slave is too old, does not know "
916 # we need baseURL= to use dirname branches
917 assert self
.branchType
== 'inrepo' or not branch
918 self
.args
['repourl'] = self
.repourl
920 self
.args
['branch'] = branch
922 self
.args
['repourl'] = self
.baseURL
+ branch
923 self
.args
['revision'] = revision
924 self
.args
['patch'] = patch
927 if branch
is not None and branch
!= self
.branch
:
928 revstuff
.append("[branch]")
929 self
.description
.extend(revstuff
)
930 self
.descriptionDone
.extend(revstuff
)
932 cmd
= LoggedRemoteCommand("hg", self
.args
)
933 self
.startCommand(cmd
)
935 def computeSourceRevision(self
, changes
):
938 # without knowing the revision ancestry graph, we can't sort the
939 # changes at all. So for now, assume they were given to us in sorted
940 # order, and just pay attention to the last one. See ticket #103 for
943 log
.msg("Mercurial.computeSourceRevision: warning: "
944 "there are %d changes here, assuming the last one is "
945 "the most recent" % len(changes
))
946 return changes
[-1].revision
950 """ P4 is a class for accessing perforce revision control"""
953 def __init__(self
, p4base
, defaultBranch
=None, p4port
=None, p4user
=None,
954 p4passwd
=None, p4extra_views
=[],
955 p4client
='buildbot_%(slave)s_%(builder)s', **kwargs
):
958 @param p4base: A view into a perforce depot, typically
961 @type defaultBranch: string
962 @param defaultBranch: Identify a branch to build by default. Perforce
963 is a view based branching system. So, the branch
964 is normally the name after the base. For example,
965 branch=1.0 is view=//depot/proj/1.0/...
966 branch=1.1 is view=//depot/proj/1.1/...
969 @param p4port: Specify the perforce server to connection in the format
970 <host>:<port>. Example "perforce.example.com:1666"
973 @param p4user: The perforce user to run the command as.
975 @type p4passwd: string
976 @param p4passwd: The password for the perforce user.
978 @type p4extra_views: list of tuples
979 @param p4extra_views: Extra views to be added to
980 the client that is being used.
982 @type p4client: string
983 @param p4client: The perforce client to use for this buildslave.
986 self
.branch
= defaultBranch
987 Source
.__init
__(self
, **kwargs
)
988 self
.addFactoryArguments(p4base
=p4base
,
989 defaultBranch
=defaultBranch
,
993 p4extra_views
=p4extra_views
,
996 self
.args
['p4port'] = p4port
997 self
.args
['p4user'] = p4user
998 self
.args
['p4passwd'] = p4passwd
999 self
.args
['p4base'] = p4base
1000 self
.args
['p4extra_views'] = p4extra_views
1001 self
.p4client
= p4client
1003 def setBuild(self
, build
):
1004 Source
.setBuild(self
, build
)
1005 self
.args
['p4client'] = self
.p4client
% {
1006 'slave': build
.slavename
,
1007 'builder': build
.builder
.name
,
1010 def computeSourceRevision(self
, changes
):
1013 lastChange
= max([int(c
.revision
) for c
in changes
])
1016 def startVC(self
, branch
, revision
, patch
):
1017 slavever
= self
.slaveVersion("p4")
1018 assert slavever
, "slave is too old, does not know about p4"
1019 args
= dict(self
.args
)
1020 args
['branch'] = branch
or self
.branch
1021 args
['revision'] = revision
1022 args
['patch'] = patch
1023 cmd
= LoggedRemoteCommand("p4", args
)
1024 self
.startCommand(cmd
)
1026 class P4Sync(Source
):
1027 """This is a partial solution for using a P4 source repository. You are
1028 required to manually set up each build slave with a useful P4
1029 environment, which means setting various per-slave environment variables,
1030 and creating a P4 client specification which maps the right files into
1031 the slave's working directory. Once you have done that, this step merely
1032 performs a 'p4 sync' to update that workspace with the newest files.
1034 Each slave needs the following environment:
1036 - PATH: the 'p4' binary must be on the slave's PATH
1037 - P4USER: each slave needs a distinct user account
1038 - P4CLIENT: each slave needs a distinct client specification
1040 You should use 'p4 client' (?) to set up a client view spec which maps
1041 the desired files into $SLAVEBASE/$BUILDERBASE/source .
1046 def __init__(self
, p4port
, p4user
, p4passwd
, p4client
, **kwargs
):
1047 assert kwargs
['mode'] == "copy", "P4Sync can only be used in mode=copy"
1049 Source
.__init
__(self
, **kwargs
)
1050 self
.addFactoryArguments(p4port
=p4port
,
1055 self
.args
['p4port'] = p4port
1056 self
.args
['p4user'] = p4user
1057 self
.args
['p4passwd'] = p4passwd
1058 self
.args
['p4client'] = p4client
1060 def computeSourceRevision(self
, changes
):
1063 lastChange
= max([int(c
.revision
) for c
in changes
])
1066 def startVC(self
, branch
, revision
, patch
):
1067 slavever
= self
.slaveVersion("p4sync")
1068 assert slavever
, "slave is too old, does not know about p4"
1069 cmd
= LoggedRemoteCommand("p4sync", self
.args
)
1070 self
.startCommand(cmd
)
1072 class Monotone(Source
):
1073 """Check out a revision from a monotone server at 'server_addr',
1074 branch 'branch'. 'revision' specifies which revision id to check
1077 This step will first create a local database, if necessary, and then pull
1078 the contents of the server into the database. Then it will do the
1079 checkout/update from this database."""
1083 def __init__(self
, server_addr
, branch
, db_path
="monotone.db",
1084 monotone
="monotone",
1086 Source
.__init
__(self
, **kwargs
)
1087 self
.addFactoryArguments(server_addr
=server_addr
,
1092 self
.args
.update({"server_addr": server_addr
,
1095 "monotone": monotone
})
1097 def computeSourceRevision(self
, changes
):
1100 return changes
[-1].revision
1103 slavever
= self
.slaveVersion("monotone")
1104 assert slavever
, "slave is too old, does not know about monotone"
1105 cmd
= LoggedRemoteCommand("monotone", self
.args
)
1106 self
.startCommand(cmd
)