| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: buildbot.test.test_vc -*- | |
| 2 | |
| 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 | |
| 9 | |
| 10 | |
| 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. | |
| 17 """ | |
| 18 | |
| 19 # if the checkout fails, there's no point in doing anything else | |
| 20 haltOnFailure = True | |
| 21 flunkOnFailure = True | |
| 22 notReally = False | |
| 23 | |
| 24 branch = None # the default branch, should be set in __init__ | |
| 25 | |
| 26 def __init__(self, workdir=None, mode='update', alwaysUseLatest=False, | |
| 27 timeout=20*60, retry=None, **kwargs): | |
| 28 """ | |
| 29 @type workdir: string | |
| 30 @param workdir: local directory (relative to the Builder's root) | |
| 31 where the tree should be placed | |
| 32 | |
| 33 @type mode: string | |
| 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 | |
| 45 removing them. When used with a patched checkout, from a | |
| 46 previous buildbot try for instance, it will try to "revert" | |
| 47 the changes first and will do a clobber if it is unable to | |
| 48 get a clean checkout. The behavior is SCM-dependent. | |
| 49 | |
| 50 - 'copy': specifies that the source-controlled workspace | |
| 51 should be maintained in a separate directory (called the | |
| 52 'copydir'), using checkout or update as necessary. For each | |
| 53 build, a new workdir is created with a copy of the source | |
| 54 tree (rm -rf workdir; cp -R -P -p copydir workdir). This | |
| 55 doubles the disk space required, but keeps the bandwidth low | |
| 56 (update instead of a full checkout). A full 'clean' build | |
| 57 is performed each time. This avoids any generated-file | |
| 58 build problems, but is still occasionally vulnerable to | |
| 59 problems such as a CVS repository being manually rearranged | |
| 60 (causing CVS errors on update) which are not an issue with | |
| 61 a full checkout. | |
| 62 | |
| 63 - 'clobber': specifies that the working directory should be | |
| 64 deleted each time, necessitating a full checkout for each | |
| 65 build. This insures a clean build off a complete checkout, | |
| 66 avoiding any of the problems described above, but is | |
| 67 bandwidth intensive, as the whole source tree must be | |
| 68 pulled down for each build. | |
| 69 | |
| 70 - 'export': is like 'clobber', except that e.g. the 'cvs | |
| 71 export' command is used to create the working directory. | |
| 72 This command removes all VC metadata files (the | |
| 73 CVS/.svn/{arch} directories) from the tree, which is | |
| 74 sometimes useful for creating source tarballs (to avoid | |
| 75 including the metadata in the tar file). Not all VC systems | |
| 76 support export. | |
| 77 | |
| 78 @type alwaysUseLatest: boolean | |
| 79 @param alwaysUseLatest: whether to always update to the most | |
| 80 recent available sources for this build. | |
| 81 | |
| 82 Normally the Source step asks its Build for a list of all | |
| 83 Changes that are supposed to go into the build, then computes a | |
| 84 'source stamp' (revision number or timestamp) that will cause | |
| 85 exactly that set of changes to be present in the checked out | |
| 86 tree. This is turned into, e.g., 'cvs update -D timestamp', or | |
| 87 'svn update -r revnum'. If alwaysUseLatest=True, bypass this | |
| 88 computation and always update to the latest available sources | |
| 89 for each build. | |
| 90 | |
| 91 The source stamp helps avoid a race condition in which someone | |
| 92 commits a change after the master has decided to start a build | |
| 93 but before the slave finishes checking out the sources. At best | |
| 94 this results in a build which contains more changes than the | |
| 95 buildmaster thinks it has (possibly resulting in the wrong | |
| 96 person taking the blame for any problems that result), at worst | |
| 97 is can result in an incoherent set of sources (splitting a | |
| 98 non-atomic commit) which may not build at all. | |
| 99 | |
| 100 @type retry: tuple of ints (delay, repeats) (or None) | |
| 101 @param retry: if provided, VC update failures are re-attempted up | |
| 102 to REPEATS times, with DELAY seconds between each | |
| 103 attempt. Some users have slaves with poor connectivity | |
| 104 to their VC repository, and they say that up to 80% of | |
| 105 their build failures are due to transient network | |
| 106 failures that could be handled by simply retrying a | |
| 107 couple times. | |
| 108 | |
| 109 """ | |
| 110 | |
| 111 LoggingBuildStep.__init__(self, **kwargs) | |
| 112 self.addFactoryArguments(workdir=workdir, | |
| 113 mode=mode, | |
| 114 alwaysUseLatest=alwaysUseLatest, | |
| 115 timeout=timeout, | |
| 116 retry=retry, | |
| 117 ) | |
| 118 | |
| 119 assert mode in ("update", "copy", "clobber", "export") | |
| 120 if retry: | |
| 121 delay, repeats = retry | |
| 122 assert isinstance(repeats, int) | |
| 123 assert repeats > 0 | |
| 124 self.args = {'mode': mode, | |
| 125 'workdir': workdir, | |
| 126 'timeout': timeout, | |
| 127 'retry': retry, | |
| 128 'patch': None, # set during .start | |
| 129 } | |
| 130 self.alwaysUseLatest = alwaysUseLatest | |
| 131 | |
| 132 # Compute defaults for descriptions: | |
| 133 description = ["updating"] | |
| 134 descriptionDone = ["update"] | |
| 135 if mode == "clobber": | |
| 136 description = ["checkout"] | |
| 137 # because checkingouting takes too much space | |
| 138 descriptionDone = ["checkout"] | |
| 139 elif mode == "export": | |
| 140 description = ["exporting"] | |
| 141 descriptionDone = ["export"] | |
| 142 self.description = description | |
| 143 self.descriptionDone = descriptionDone | |
| 144 | |
| 145 def setStepStatus(self, step_status): | |
| 146 LoggingBuildStep.setStepStatus(self, step_status) | |
| 147 | |
| 148 # start doesn't set text soon enough to capture our description in | |
| 149 # the stepStarted status notification. Set text here so it's included. | |
| 150 self.step_status.setText(self.describe(False)) | |
| 151 | |
| 152 def setDefaultWorkdir(self, workdir): | |
| 153 self.args['workdir'] = self.args['workdir'] or workdir | |
| 154 | |
| 155 def describe(self, done=False): | |
| 156 if done: | |
| 157 return self.descriptionDone | |
| 158 return self.description | |
| 159 | |
| 160 def computeSourceRevision(self, changes): | |
| 161 """Each subclass must implement this method to do something more | |
| 162 precise than -rHEAD every time. For version control systems that use | |
| 163 repository-wide change numbers (SVN, P4), this can simply take the | |
| 164 maximum such number from all the changes involved in this build. For | |
| 165 systems that do not (CVS), it needs to create a timestamp based upon | |
| 166 the latest Change, the Build's treeStableTimer, and an optional | |
| 167 self.checkoutDelay value.""" | |
| 168 return None | |
| 169 | |
| 170 def start(self): | |
| 171 if self.notReally: | |
| 172 log.msg("faking %s checkout/update" % self.name) | |
| 173 self.step_status.setText(["fake", self.name, "successful"]) | |
| 174 self.addCompleteLog("log", | |
| 175 "Faked %s checkout/update 'successful'\n" \ | |
| 176 % self.name) | |
| 177 return SKIPPED | |
| 178 | |
| 179 # what source stamp would this build like to use? | |
| 180 s = self.build.getSourceStamp() | |
| 181 # if branch is None, then use the Step's "default" branch | |
| 182 branch = s.branch or self.branch | |
| 183 # if revision is None, use the latest sources (-rHEAD) | |
| 184 revision = s.revision | |
| 185 if not revision and not self.alwaysUseLatest: | |
| 186 revision = self.computeSourceRevision(s.changes) | |
| 187 # if patch is None, then do not patch the tree after checkout | |
| 188 | |
| 189 # 'patch' is None or a tuple of (patchlevel, diff, root) | |
| 190 # root is optional. | |
| 191 patch = s.patch | |
| 192 if patch: | |
| 193 self.addCompleteLog("patch", patch[1]) | |
| 194 | |
| 195 if self.alwaysUseLatest: | |
| 196 revision = None | |
| 197 self.startVC(branch, revision, patch) | |
| 198 | |
| 199 def commandComplete(self, cmd): | |
| 200 if cmd.updates.has_key("got_revision"): | |
| 201 got_revision = cmd.updates["got_revision"][-1] | |
| 202 if got_revision is not None: | |
| 203 self.setProperty("got_revision", str(got_revision), "Source") | |
| 204 | |
| 205 | |
| 206 | |
| 207 class CVS(Source): | |
| 208 """I do CVS checkout/update operations. | |
| 209 | |
| 210 Note: if you are doing anonymous/pserver CVS operations, you will need | |
| 211 to manually do a 'cvs login' on each buildslave before the slave has any | |
| 212 hope of success. XXX: fix then, take a cvs password as an argument and | |
| 213 figure out how to do a 'cvs login' on each build | |
| 214 """ | |
| 215 | |
| 216 name = "cvs" | |
| 217 | |
| 218 #progressMetrics = ('output',) | |
| 219 # | |
| 220 # additional things to track: update gives one stderr line per directory | |
| 221 # (starting with 'cvs server: Updating ') (and is fairly stable if files | |
| 222 # is empty), export gives one line per directory (starting with 'cvs | |
| 223 # export: Updating ') and another line per file (starting with U). Would | |
| 224 # be nice to track these, requires grepping LogFile data for lines, | |
| 225 # parsing each line. Might be handy to have a hook in LogFile that gets | |
| 226 # called with each complete line. | |
| 227 | |
| 228 def __init__(self, cvsroot, cvsmodule, | |
| 229 global_options=[], branch=None, checkoutDelay=None, | |
| 230 checkout_options=[], | |
| 231 login=None, | |
| 232 **kwargs): | |
| 233 | |
| 234 """ | |
| 235 @type cvsroot: string | |
| 236 @param cvsroot: CVS Repository from which the source tree should | |
| 237 be obtained. '/home/warner/Repository' for local | |
| 238 or NFS-reachable repositories, | |
| 239 ':pserver:anon@foo.com:/cvs' for anonymous CVS, | |
| 240 'user@host.com:/cvs' for non-anonymous CVS or | |
| 241 CVS over ssh. Lots of possibilities, check the | |
| 242 CVS documentation for more. | |
| 243 | |
| 244 @type cvsmodule: string | |
| 245 @param cvsmodule: subdirectory of CVS repository that should be | |
| 246 retrieved | |
| 247 | |
| 248 @type login: string or None | |
| 249 @param login: if not None, a string which will be provided as a | |
| 250 password to the 'cvs login' command, used when a | |
| 251 :pserver: method is used to access the repository. | |
| 252 This login is only needed once, but must be run | |
| 253 each time (just before the CVS operation) because | |
| 254 there is no way for the buildslave to tell whether | |
| 255 it was previously performed or not. | |
| 256 | |
| 257 @type branch: string | |
| 258 @param branch: the default branch name, will be used in a '-r' | |
| 259 argument to specify which branch of the source tree | |
| 260 should be used for this checkout. Defaults to None, | |
| 261 which means to use 'HEAD'. | |
| 262 | |
| 263 @type checkoutDelay: int or None | |
| 264 @param checkoutDelay: if not None, the number of seconds to put | |
| 265 between the last known Change and the | |
| 266 timestamp given to the -D argument. This | |
| 267 defaults to exactly half of the parent | |
| 268 Build's .treeStableTimer, but it could be | |
| 269 set to something else if your CVS change | |
| 270 notification has particularly weird | |
| 271 latency characteristics. | |
| 272 | |
| 273 @type global_options: list of strings | |
| 274 @param global_options: these arguments are inserted in the cvs | |
| 275 command line, before the | |
| 276 'checkout'/'update' command word. See | |
| 277 'cvs --help-options' for a list of what | |
| 278 may be accepted here. ['-r'] will make | |
| 279 the checked out files read only. ['-r', | |
| 280 '-R'] will also assume the repository is | |
| 281 read-only (I assume this means it won't | |
| 282 use locks to insure atomic access to the | |
| 283 ,v files). | |
| 284 | |
| 285 @type checkout_options: list of strings | |
| 286 @param checkout_options: these arguments are inserted in the cvs | |
| 287 command line, after 'checkout' but before | |
| 288 branch or revision specifiers. | |
| 289 """ | |
| 290 | |
| 291 self.checkoutDelay = checkoutDelay | |
| 292 self.branch = branch | |
| 293 | |
| 294 Source.__init__(self, **kwargs) | |
| 295 self.addFactoryArguments(cvsroot=cvsroot, | |
| 296 cvsmodule=cvsmodule, | |
| 297 global_options=global_options, | |
| 298 checkout_options=checkout_options, | |
| 299 branch=branch, | |
| 300 checkoutDelay=checkoutDelay, | |
| 301 login=login, | |
| 302 ) | |
| 303 | |
| 304 self.args.update({'cvsroot': cvsroot, | |
| 305 'cvsmodule': cvsmodule, | |
| 306 'global_options': global_options, | |
| 307 'checkout_options':checkout_options, | |
| 308 'login': login, | |
| 309 }) | |
| 310 | |
| 311 def computeSourceRevision(self, changes): | |
| 312 if not changes: | |
| 313 return None | |
| 314 lastChange = max([c.when for c in changes]) | |
| 315 if self.checkoutDelay is not None: | |
| 316 when = lastChange + self.checkoutDelay | |
| 317 else: | |
| 318 lastSubmit = max([r.submittedAt for r in self.build.requests]) | |
| 319 when = (lastChange + lastSubmit) / 2 | |
| 320 return formatdate(when) | |
| 321 | |
| 322 def startVC(self, branch, revision, patch): | |
| 323 if self.slaveVersionIsOlderThan("cvs", "1.39"): | |
| 324 # the slave doesn't know to avoid re-using the same sourcedir | |
| 325 # when the branch changes. We have no way of knowing which branch | |
| 326 # the last build used, so if we're using a non-default branch and | |
| 327 # either 'update' or 'copy' modes, it is safer to refuse to | |
| 328 # build, and tell the user they need to upgrade the buildslave. | |
| 329 if (branch != self.branch | |
| 330 and self.args['mode'] in ("update", "copy")): | |
| 331 m = ("This buildslave (%s) does not know about multiple " | |
| 332 "branches, and using mode=%s would probably build the " | |
| 333 "wrong tree. " | |
| 334 "Refusing to build. Please upgrade the buildslave to " | |
| 335 "buildbot-0.7.0 or newer." % (self.build.slavename, | |
| 336 self.args['mode'])) | |
| 337 log.msg(m) | |
| 338 raise BuildSlaveTooOldError(m) | |
| 339 | |
| 340 if branch is None: | |
| 341 branch = "HEAD" | |
| 342 self.args['branch'] = branch | |
| 343 self.args['revision'] = revision | |
| 344 self.args['patch'] = patch | |
| 345 | |
| 346 if self.args['branch'] == "HEAD" and self.args['revision']: | |
| 347 # special case. 'cvs update -r HEAD -D today' gives no files | |
| 348 # TODO: figure out why, see if it applies to -r BRANCH | |
| 349 self.args['branch'] = None | |
| 350 | |
| 351 # deal with old slaves | |
| 352 warnings = [] | |
| 353 slavever = self.slaveVersion("cvs", "old") | |
| 354 | |
| 355 if slavever == "old": | |
| 356 # 0.5.0 | |
| 357 if self.args['mode'] == "export": | |
| 358 self.args['export'] = 1 | |
| 359 elif self.args['mode'] == "clobber": | |
| 360 self.args['clobber'] = 1 | |
| 361 elif self.args['mode'] == "copy": | |
| 362 self.args['copydir'] = "source" | |
| 363 self.args['tag'] = self.args['branch'] | |
| 364 assert not self.args['patch'] # 0.5.0 slave can't do patch | |
| 365 | |
| 366 cmd = LoggedRemoteCommand("cvs", self.args) | |
| 367 self.startCommand(cmd, warnings) | |
| 368 | |
| 369 | |
| 370 class SVN(Source): | |
| 371 """I perform Subversion checkout/update operations.""" | |
| 372 | |
| 373 name = 'svn' | |
| 374 | |
| 375 def __init__(self, svnurl=None, baseURL=None, defaultBranch=None, | |
| 376 directory=None, username=None, password=None, | |
| 377 extra_args=None, keep_on_purge=None, ignore_ignores=None, | |
| 378 always_purge=None, depth=None, **kwargs): | |
| 379 """ | |
| 380 @type svnurl: string | |
| 381 @param svnurl: the URL which points to the Subversion server, | |
| 382 combining the access method (HTTP, ssh, local file), | |
| 383 the repository host/port, the repository path, the | |
| 384 sub-tree within the repository, and the branch to | |
| 385 check out. Using C{svnurl} does not enable builds of | |
| 386 alternate branches: use C{baseURL} to enable this. | |
| 387 Use exactly one of C{svnurl} and C{baseURL}. | |
| 388 | |
| 389 @param baseURL: if branches are enabled, this is the base URL to | |
| 390 which a branch name will be appended. It should | |
| 391 probably end in a slash. Use exactly one of | |
| 392 C{svnurl} and C{baseURL}. | |
| 393 | |
| 394 @param defaultBranch: if branches are enabled, this is the branch | |
| 395 to use if the Build does not specify one | |
| 396 explicitly. It will simply be appended | |
| 397 to C{baseURL} and the result handed to | |
| 398 the SVN command. | |
| 399 | |
| 400 @param username: username to pass to svn's --username | |
| 401 @param password: username to pass to svn's --password | |
| 402 """ | |
| 403 | |
| 404 if not kwargs.has_key('workdir') and directory is not None: | |
| 405 # deal with old configs | |
| 406 warn("Please use workdir=, not directory=", DeprecationWarning) | |
| 407 kwargs['workdir'] = directory | |
| 408 | |
| 409 self.svnurl = svnurl | |
| 410 self.baseURL = baseURL | |
| 411 self.branch = defaultBranch | |
| 412 self.username = username | |
| 413 self.password = password | |
| 414 self.extra_args = extra_args | |
| 415 self.keep_on_purge = keep_on_purge | |
| 416 self.ignore_ignores = ignore_ignores | |
| 417 self.always_purge = always_purge | |
| 418 self.depth = depth | |
| 419 | |
| 420 Source.__init__(self, **kwargs) | |
| 421 self.addFactoryArguments(svnurl=svnurl, | |
| 422 baseURL=baseURL, | |
| 423 defaultBranch=defaultBranch, | |
| 424 directory=directory, | |
| 425 username=username, | |
| 426 password=password, | |
| 427 extra_args=extra_args, | |
| 428 keep_on_purge=keep_on_purge, | |
| 429 ignore_ignores=ignore_ignores, | |
| 430 always_purge=always_purge, | |
| 431 depth=depth, | |
| 432 ) | |
| 433 | |
| 434 if not svnurl and not baseURL: | |
| 435 raise ValueError("you must use exactly one of svnurl and baseURL") | |
| 436 | |
| 437 | |
| 438 def computeSourceRevision(self, changes): | |
| 439 if not changes or None in [c.revision for c in changes]: | |
| 440 return None | |
| 441 lastChange = max([int(c.revision) for c in changes]) | |
| 442 return lastChange | |
| 443 | |
| 444 def startVC(self, branch, revision, patch): | |
| 445 | |
| 446 # handle old slaves | |
| 447 warnings = [] | |
| 448 slavever = self.slaveVersion("svn", "old") | |
| 449 if not slavever: | |
| 450 m = "slave does not have the 'svn' command" | |
| 451 raise BuildSlaveTooOldError(m) | |
| 452 | |
| 453 if self.slaveVersionIsOlderThan("svn", "1.39"): | |
| 454 # the slave doesn't know to avoid re-using the same sourcedir | |
| 455 # when the branch changes. We have no way of knowing which branch | |
| 456 # the last build used, so if we're using a non-default branch and | |
| 457 # either 'update' or 'copy' modes, it is safer to refuse to | |
| 458 # build, and tell the user they need to upgrade the buildslave. | |
| 459 if (branch != self.branch | |
| 460 and self.args['mode'] in ("update", "copy")): | |
| 461 m = ("This buildslave (%s) does not know about multiple " | |
| 462 "branches, and using mode=%s would probably build the " | |
| 463 "wrong tree. " | |
| 464 "Refusing to build. Please upgrade the buildslave to " | |
| 465 "buildbot-0.7.0 or newer." % (self.build.slavename, | |
| 466 self.args['mode'])) | |
| 467 raise BuildSlaveTooOldError(m) | |
| 468 | |
| 469 if slavever == "old": | |
| 470 # 0.5.0 compatibility | |
| 471 if self.args['mode'] in ("clobber", "copy"): | |
| 472 # TODO: use some shell commands to make up for the | |
| 473 # deficiency, by blowing away the old directory first (thus | |
| 474 # forcing a full checkout) | |
| 475 warnings.append("WARNING: this slave can only do SVN updates" | |
| 476 ", not mode=%s\n" % self.args['mode']) | |
| 477 log.msg("WARNING: this slave only does mode=update") | |
| 478 if self.args['mode'] == "export": | |
| 479 raise BuildSlaveTooOldError("old slave does not have " | |
| 480 "mode=export") | |
| 481 self.args['directory'] = self.args['workdir'] | |
| 482 if revision is not None: | |
| 483 # 0.5.0 can only do HEAD. We have no way of knowing whether | |
| 484 # the requested revision is HEAD or not, and for | |
| 485 # slowly-changing trees this will probably do the right | |
| 486 # thing, so let it pass with a warning | |
| 487 m = ("WARNING: old slave can only update to HEAD, not " | |
| 488 "revision=%s" % revision) | |
| 489 log.msg(m) | |
| 490 warnings.append(m + "\n") | |
| 491 revision = "HEAD" # interprets this key differently | |
| 492 if patch: | |
| 493 raise BuildSlaveTooOldError("old slave can't do patch") | |
| 494 | |
| 495 if self.svnurl: | |
| 496 assert not branch # we need baseURL= to use branches | |
| 497 self.args['svnurl'] = self.svnurl | |
| 498 else: | |
| 499 self.args['svnurl'] = self.baseURL + branch | |
| 500 self.args['revision'] = revision | |
| 501 self.args['patch'] = patch | |
| 502 | |
| 503 #Set up depth if specified | |
| 504 if self.depth is not None: | |
| 505 if self.slaveVersionIsOlderThan("svn","2.9"): | |
| 506 m = ("This buildslave (%s) does not support svn depth " | |
| 507 "arguments. " | |
| 508 "Refusing to build. " | |
| 509 "Please upgrade the buildslave." % (self.build.slav
ename)) | |
| 510 raise BuildSlaveTooOldError(m) | |
| 511 else: | |
| 512 self.args['depth'] = self.depth | |
| 513 | |
| 514 if self.username is not None or self.password is not None: | |
| 515 if self.slaveVersionIsOlderThan("svn", "2.8"): | |
| 516 m = ("This buildslave (%s) does not support svn usernames " | |
| 517 "and passwords. " | |
| 518 "Refusing to build. Please upgrade the buildslave to " | |
| 519 "buildbot-0.7.10 or newer." % (self.build.slavename,)) | |
| 520 raise BuildSlaveTooOldError(m) | |
| 521 if self.username is not None: self.args['username'] = self.username | |
| 522 if self.password is not None: self.args['password'] = self.password | |
| 523 | |
| 524 if self.extra_args is not None: | |
| 525 self.args['extra_args'] = self.extra_args | |
| 526 | |
| 527 revstuff = [] | |
| 528 if branch is not None and branch != self.branch: | |
| 529 revstuff.append("[branch]") | |
| 530 if revision is not None: | |
| 531 revstuff.append("r%s" % revision) | |
| 532 if patch is not None: | |
| 533 revstuff.append("[patch]") | |
| 534 self.description.extend(revstuff) | |
| 535 self.descriptionDone.extend(revstuff) | |
| 536 | |
| 537 cmd = LoggedRemoteCommand("svn", self.args) | |
| 538 self.startCommand(cmd, warnings) | |
| 539 | |
| 540 | |
| 541 class Darcs(Source): | |
| 542 """Check out a source tree from a Darcs repository at 'repourl'. | |
| 543 | |
| 544 Darcs has no concept of file modes. This means the eXecute-bit will be | |
| 545 cleared on all source files. As a result, you may need to invoke | |
| 546 configuration scripts with something like: | |
| 547 | |
| 548 C{s(step.Configure, command=['/bin/sh', './configure'])} | |
| 549 """ | |
| 550 | |
| 551 name = "darcs" | |
| 552 | |
| 553 def __init__(self, repourl=None, baseURL=None, defaultBranch=None, | |
| 554 **kwargs): | |
| 555 """ | |
| 556 @type repourl: string | |
| 557 @param repourl: the URL which points at the Darcs repository. This | |
| 558 is used as the default branch. Using C{repourl} does | |
| 559 not enable builds of alternate branches: use | |
| 560 C{baseURL} to enable this. Use either C{repourl} or | |
| 561 C{baseURL}, not both. | |
| 562 | |
| 563 @param baseURL: if branches are enabled, this is the base URL to | |
| 564 which a branch name will be appended. It should | |
| 565 probably end in a slash. Use exactly one of | |
| 566 C{repourl} and C{baseURL}. | |
| 567 | |
| 568 @param defaultBranch: if branches are enabled, this is the branch | |
| 569 to use if the Build does not specify one | |
| 570 explicitly. It will simply be appended to | |
| 571 C{baseURL} and the result handed to the | |
| 572 'darcs pull' command. | |
| 573 """ | |
| 574 self.repourl = repourl | |
| 575 self.baseURL = baseURL | |
| 576 self.branch = defaultBranch | |
| 577 Source.__init__(self, **kwargs) | |
| 578 self.addFactoryArguments(repourl=repourl, | |
| 579 baseURL=baseURL, | |
| 580 defaultBranch=defaultBranch, | |
| 581 ) | |
| 582 assert self.args['mode'] != "export", \ | |
| 583 "Darcs does not have an 'export' mode" | |
| 584 if (not repourl and not baseURL) or (repourl and baseURL): | |
| 585 raise ValueError("you must provide exactly one of repourl and" | |
| 586 " baseURL") | |
| 587 | |
| 588 def startVC(self, branch, revision, patch): | |
| 589 slavever = self.slaveVersion("darcs") | |
| 590 if not slavever: | |
| 591 m = "slave is too old, does not know about darcs" | |
| 592 raise BuildSlaveTooOldError(m) | |
| 593 | |
| 594 if self.slaveVersionIsOlderThan("darcs", "1.39"): | |
| 595 if revision: | |
| 596 # TODO: revisit this once we implement computeSourceRevision | |
| 597 m = "0.6.6 slaves can't handle args['revision']" | |
| 598 raise BuildSlaveTooOldError(m) | |
| 599 | |
| 600 # the slave doesn't know to avoid re-using the same sourcedir | |
| 601 # when the branch changes. We have no way of knowing which branch | |
| 602 # the last build used, so if we're using a non-default branch and | |
| 603 # either 'update' or 'copy' modes, it is safer to refuse to | |
| 604 # build, and tell the user they need to upgrade the buildslave. | |
| 605 if (branch != self.branch | |
| 606 and self.args['mode'] in ("update", "copy")): | |
| 607 m = ("This buildslave (%s) does not know about multiple " | |
| 608 "branches, and using mode=%s would probably build the " | |
| 609 "wrong tree. " | |
| 610 "Refusing to build. Please upgrade the buildslave to " | |
| 611 "buildbot-0.7.0 or newer." % (self.build.slavename, | |
| 612 self.args['mode'])) | |
| 613 raise BuildSlaveTooOldError(m) | |
| 614 | |
| 615 if self.repourl: | |
| 616 assert not branch # we need baseURL= to use branches | |
| 617 self.args['repourl'] = self.repourl | |
| 618 else: | |
| 619 self.args['repourl'] = self.baseURL + branch | |
| 620 self.args['revision'] = revision | |
| 621 self.args['patch'] = patch | |
| 622 | |
| 623 revstuff = [] | |
| 624 if branch is not None and branch != self.branch: | |
| 625 revstuff.append("[branch]") | |
| 626 self.description.extend(revstuff) | |
| 627 self.descriptionDone.extend(revstuff) | |
| 628 | |
| 629 cmd = LoggedRemoteCommand("darcs", self.args) | |
| 630 self.startCommand(cmd) | |
| 631 | |
| 632 | |
| 633 class Git(Source): | |
| 634 """Check out a source tree from a git repository 'repourl'.""" | |
| 635 | |
| 636 name = "git" | |
| 637 | |
| 638 def __init__(self, repourl, | |
| 639 branch="master", | |
| 640 submodules=False, | |
| 641 ignore_ignores=None, | |
| 642 **kwargs): | |
| 643 """ | |
| 644 @type repourl: string | |
| 645 @param repourl: the URL which points at the git repository | |
| 646 | |
| 647 @type branch: string | |
| 648 @param branch: The branch or tag to check out by default. If | |
| 649 a build specifies a different branch, it will | |
| 650 be used instead of this. | |
| 651 | |
| 652 @type submodules: boolean | |
| 653 @param submodules: Whether or not to update (and initialize) | |
| 654 git submodules. | |
| 655 | |
| 656 """ | |
| 657 Source.__init__(self, **kwargs) | |
| 658 self.addFactoryArguments(repourl=repourl, | |
| 659 branch=branch, | |
| 660 submodules=submodules, | |
| 661 ignore_ignores=ignore_ignores, | |
| 662 ) | |
| 663 self.args.update({'repourl': repourl, | |
| 664 'branch': branch, | |
| 665 'submodules': submodules, | |
| 666 'ignore_ignores': ignore_ignores, | |
| 667 }) | |
| 668 | |
| 669 def computeSourceRevision(self, changes): | |
| 670 if not changes: | |
| 671 return None | |
| 672 return changes[-1].revision | |
| 673 | |
| 674 def startVC(self, branch, revision, patch): | |
| 675 if branch is not None: | |
| 676 self.args['branch'] = branch | |
| 677 | |
| 678 self.args['revision'] = revision | |
| 679 self.args['patch'] = patch | |
| 680 slavever = self.slaveVersion("git") | |
| 681 if not slavever: | |
| 682 raise BuildSlaveTooOldError("slave is too old, does not know " | |
| 683 "about git") | |
| 684 cmd = LoggedRemoteCommand("git", self.args) | |
| 685 self.startCommand(cmd) | |
| 686 | |
| 687 | |
| 688 class Arch(Source): | |
| 689 """Check out a source tree from an Arch repository named 'archive' | |
| 690 available at 'url'. 'version' specifies which version number (development | |
| 691 line) will be used for the checkout: this is mostly equivalent to a | |
| 692 branch name. This version uses the 'tla' tool to do the checkout, to use | |
| 693 'baz' see L{Bazaar} instead. | |
| 694 """ | |
| 695 | |
| 696 name = "arch" | |
| 697 # TODO: slaves >0.6.6 will accept args['build-config'], so use it | |
| 698 | |
| 699 def __init__(self, url, version, archive=None, **kwargs): | |
| 700 """ | |
| 701 @type url: string | |
| 702 @param url: the Arch coordinates of the repository. This is | |
| 703 typically an http:// URL, but could also be the absolute | |
| 704 pathname of a local directory instead. | |
| 705 | |
| 706 @type version: string | |
| 707 @param version: the category--branch--version to check out. This is | |
| 708 the default branch. If a build specifies a different | |
| 709 branch, it will be used instead of this. | |
| 710 | |
| 711 @type archive: string | |
| 712 @param archive: The archive name. If provided, it must match the one | |
| 713 that comes from the repository. If not, the | |
| 714 repository's default will be used. | |
| 715 """ | |
| 716 self.branch = version | |
| 717 Source.__init__(self, **kwargs) | |
| 718 self.addFactoryArguments(url=url, | |
| 719 version=version, | |
| 720 archive=archive, | |
| 721 ) | |
| 722 self.args.update({'url': url, | |
| 723 'archive': archive, | |
| 724 }) | |
| 725 | |
| 726 def computeSourceRevision(self, changes): | |
| 727 # in Arch, fully-qualified revision numbers look like: | |
| 728 # arch@buildbot.sourceforge.net--2004/buildbot--dev--0--patch-104 | |
| 729 # For any given builder, all of this is fixed except the patch-104. | |
| 730 # The Change might have any part of the fully-qualified string, so we | |
| 731 # just look for the last part. We return the "patch-NN" string. | |
| 732 if not changes: | |
| 733 return None | |
| 734 lastChange = None | |
| 735 for c in changes: | |
| 736 if not c.revision: | |
| 737 continue | |
| 738 if c.revision.endswith("--base-0"): | |
| 739 rev = 0 | |
| 740 else: | |
| 741 i = c.revision.rindex("patch") | |
| 742 rev = int(c.revision[i+len("patch-"):]) | |
| 743 lastChange = max(lastChange, rev) | |
| 744 if lastChange is None: | |
| 745 return None | |
| 746 if lastChange == 0: | |
| 747 return "base-0" | |
| 748 return "patch-%d" % lastChange | |
| 749 | |
| 750 def checkSlaveVersion(self, cmd, branch): | |
| 751 warnings = [] | |
| 752 slavever = self.slaveVersion(cmd) | |
| 753 if not slavever: | |
| 754 m = "slave is too old, does not know about %s" % cmd | |
| 755 raise BuildSlaveTooOldError(m) | |
| 756 | |
| 757 # slave 1.28 and later understand 'revision' | |
| 758 if self.slaveVersionIsOlderThan(cmd, "1.28"): | |
| 759 if not self.alwaysUseLatest: | |
| 760 # we don't know whether our requested revision is the latest | |
| 761 # or not. If the tree does not change very quickly, this will | |
| 762 # probably build the right thing, so emit a warning rather | |
| 763 # than refuse to build at all | |
| 764 m = "WARNING, buildslave is too old to use a revision" | |
| 765 log.msg(m) | |
| 766 warnings.append(m + "\n") | |
| 767 | |
| 768 if self.slaveVersionIsOlderThan(cmd, "1.39"): | |
| 769 # the slave doesn't know to avoid re-using the same sourcedir | |
| 770 # when the branch changes. We have no way of knowing which branch | |
| 771 # the last build used, so if we're using a non-default branch and | |
| 772 # either 'update' or 'copy' modes, it is safer to refuse to | |
| 773 # build, and tell the user they need to upgrade the buildslave. | |
| 774 if (branch != self.branch | |
| 775 and self.args['mode'] in ("update", "copy")): | |
| 776 m = ("This buildslave (%s) does not know about multiple " | |
| 777 "branches, and using mode=%s would probably build the " | |
| 778 "wrong tree. " | |
| 779 "Refusing to build. Please upgrade the buildslave to " | |
| 780 "buildbot-0.7.0 or newer." % (self.build.slavename, | |
| 781 self.args['mode'])) | |
| 782 log.msg(m) | |
| 783 raise BuildSlaveTooOldError(m) | |
| 784 | |
| 785 return warnings | |
| 786 | |
| 787 def startVC(self, branch, revision, patch): | |
| 788 self.args['version'] = branch | |
| 789 self.args['revision'] = revision | |
| 790 self.args['patch'] = patch | |
| 791 warnings = self.checkSlaveVersion("arch", branch) | |
| 792 | |
| 793 revstuff = [] | |
| 794 if branch is not None and branch != self.branch: | |
| 795 revstuff.append("[branch]") | |
| 796 if revision is not None: | |
| 797 revstuff.append("patch%s" % revision) | |
| 798 self.description.extend(revstuff) | |
| 799 self.descriptionDone.extend(revstuff) | |
| 800 | |
| 801 cmd = LoggedRemoteCommand("arch", self.args) | |
| 802 self.startCommand(cmd, warnings) | |
| 803 | |
| 804 | |
| 805 class Bazaar(Arch): | |
| 806 """Bazaar is an alternative client for Arch repositories. baz is mostly | |
| 807 compatible with tla, but archive registration is slightly different.""" | |
| 808 | |
| 809 # TODO: slaves >0.6.6 will accept args['build-config'], so use it | |
| 810 | |
| 811 def __init__(self, url, version, archive, **kwargs): | |
| 812 """ | |
| 813 @type url: string | |
| 814 @param url: the Arch coordinates of the repository. This is | |
| 815 typically an http:// URL, but could also be the absolute | |
| 816 pathname of a local directory instead. | |
| 817 | |
| 818 @type version: string | |
| 819 @param version: the category--branch--version to check out | |
| 820 | |
| 821 @type archive: string | |
| 822 @param archive: The archive name (required). This must always match | |
| 823 the one that comes from the repository, otherwise the | |
| 824 buildslave will attempt to get sources from the wrong | |
| 825 archive. | |
| 826 """ | |
| 827 self.branch = version | |
| 828 Source.__init__(self, **kwargs) | |
| 829 self.addFactoryArguments(url=url, | |
| 830 version=version, | |
| 831 archive=archive, | |
| 832 ) | |
| 833 self.args.update({'url': url, | |
| 834 'archive': archive, | |
| 835 }) | |
| 836 | |
| 837 def startVC(self, branch, revision, patch): | |
| 838 self.args['version'] = branch | |
| 839 self.args['revision'] = revision | |
| 840 self.args['patch'] = patch | |
| 841 warnings = self.checkSlaveVersion("bazaar", branch) | |
| 842 | |
| 843 revstuff = [] | |
| 844 if branch is not None and branch != self.branch: | |
| 845 revstuff.append("[branch]") | |
| 846 if revision is not None: | |
| 847 revstuff.append("patch%s" % revision) | |
| 848 self.description.extend(revstuff) | |
| 849 self.descriptionDone.extend(revstuff) | |
| 850 | |
| 851 cmd = LoggedRemoteCommand("bazaar", self.args) | |
| 852 self.startCommand(cmd, warnings) | |
| 853 | |
| 854 class Bzr(Source): | |
| 855 """Check out a source tree from a bzr (Bazaar) repository at 'repourl'. | |
| 856 | |
| 857 """ | |
| 858 | |
| 859 name = "bzr" | |
| 860 | |
| 861 def __init__(self, repourl=None, baseURL=None, defaultBranch=None, | |
| 862 forceSharedRepo=None, | |
| 863 **kwargs): | |
| 864 """ | |
| 865 @type repourl: string | |
| 866 @param repourl: the URL which points at the bzr repository. This | |
| 867 is used as the default branch. Using C{repourl} does | |
| 868 not enable builds of alternate branches: use | |
| 869 C{baseURL} to enable this. Use either C{repourl} or | |
| 870 C{baseURL}, not both. | |
| 871 | |
| 872 @param baseURL: if branches are enabled, this is the base URL to | |
| 873 which a branch name will be appended. It should | |
| 874 probably end in a slash. Use exactly one of | |
| 875 C{repourl} and C{baseURL}. | |
| 876 | |
| 877 @param defaultBranch: if branches are enabled, this is the branch | |
| 878 to use if the Build does not specify one | |
| 879 explicitly. It will simply be appended to | |
| 880 C{baseURL} and the result handed to the | |
| 881 'bzr checkout pull' command. | |
| 882 | |
| 883 | |
| 884 @param forceSharedRepo: Boolean, defaults to False. If set to True, | |
| 885 the working directory will be made into a | |
| 886 bzr shared repository if it is not already. | |
| 887 Shared repository greatly reduces the amount | |
| 888 of history data that needs to be downloaded | |
| 889 if not using update/copy mode, or if using | |
| 890 update/copy mode with multiple branches. | |
| 891 """ | |
| 892 self.repourl = repourl | |
| 893 self.baseURL = baseURL | |
| 894 self.branch = defaultBranch | |
| 895 Source.__init__(self, **kwargs) | |
| 896 self.addFactoryArguments(repourl=repourl, | |
| 897 baseURL=baseURL, | |
| 898 defaultBranch=defaultBranch, | |
| 899 forceSharedRepo=forceSharedRepo | |
| 900 ) | |
| 901 self.args.update({'forceSharedRepo': forceSharedRepo}) | |
| 902 if (not repourl and not baseURL) or (repourl and baseURL): | |
| 903 raise ValueError("you must provide exactly one of repourl and" | |
| 904 " baseURL") | |
| 905 | |
| 906 def computeSourceRevision(self, changes): | |
| 907 if not changes: | |
| 908 return None | |
| 909 lastChange = max([int(c.revision) for c in changes]) | |
| 910 return lastChange | |
| 911 | |
| 912 def startVC(self, branch, revision, patch): | |
| 913 slavever = self.slaveVersion("bzr") | |
| 914 if not slavever: | |
| 915 m = "slave is too old, does not know about bzr" | |
| 916 raise BuildSlaveTooOldError(m) | |
| 917 | |
| 918 if self.repourl: | |
| 919 assert not branch # we need baseURL= to use branches | |
| 920 self.args['repourl'] = self.repourl | |
| 921 else: | |
| 922 self.args['repourl'] = self.baseURL + branch | |
| 923 self.args['revision'] = revision | |
| 924 self.args['patch'] = patch | |
| 925 | |
| 926 revstuff = [] | |
| 927 if branch is not None and branch != self.branch: | |
| 928 revstuff.append("[" + branch + "]") | |
| 929 if revision is not None: | |
| 930 revstuff.append("r%s" % revision) | |
| 931 self.description.extend(revstuff) | |
| 932 self.descriptionDone.extend(revstuff) | |
| 933 | |
| 934 cmd = LoggedRemoteCommand("bzr", self.args) | |
| 935 self.startCommand(cmd) | |
| 936 | |
| 937 | |
| 938 class Mercurial(Source): | |
| 939 """Check out a source tree from a mercurial repository 'repourl'.""" | |
| 940 | |
| 941 name = "hg" | |
| 942 | |
| 943 def __init__(self, repourl=None, baseURL=None, defaultBranch=None, | |
| 944 branchType='dirname', clobberOnBranchChange=True, **kwargs): | |
| 945 """ | |
| 946 @type repourl: string | |
| 947 @param repourl: the URL which points at the Mercurial repository. | |
| 948 This uses the 'default' branch unless defaultBranch is | |
| 949 specified below and the C{branchType} is set to | |
| 950 'inrepo'. It is an error to specify a branch without | |
| 951 setting the C{branchType} to 'inrepo'. | |
| 952 | |
| 953 @param baseURL: if 'dirname' branches are enabled, this is the base URL | |
| 954 to which a branch name will be appended. It should | |
| 955 probably end in a slash. Use exactly one of C{repourl} | |
| 956 and C{baseURL}. | |
| 957 | |
| 958 @param defaultBranch: if branches are enabled, this is the branch | |
| 959 to use if the Build does not specify one | |
| 960 explicitly. | |
| 961 For 'dirname' branches, It will simply be | |
| 962 appended to C{baseURL} and the result handed to | |
| 963 the 'hg update' command. | |
| 964 For 'inrepo' branches, this specifies the named | |
| 965 revision to which the tree will update after a | |
| 966 clone. | |
| 967 | |
| 968 @param branchType: either 'dirname' or 'inrepo' depending on whether | |
| 969 the branch name should be appended to the C{baseURL} | |
| 970 or the branch is a mercurial named branch and can be | |
| 971 found within the C{repourl} | |
| 972 | |
| 973 @param clobberOnBranchChange: boolean, defaults to True. If set and | |
| 974 using inrepos branches, clobber the tree | |
| 975 at each branch change. Otherwise, just | |
| 976 update to the branch. | |
| 977 """ | |
| 978 self.repourl = repourl | |
| 979 self.baseURL = baseURL | |
| 980 self.branch = defaultBranch | |
| 981 self.branchType = branchType | |
| 982 self.clobberOnBranchChange = clobberOnBranchChange | |
| 983 Source.__init__(self, **kwargs) | |
| 984 self.addFactoryArguments(repourl=repourl, | |
| 985 baseURL=baseURL, | |
| 986 defaultBranch=defaultBranch, | |
| 987 branchType=branchType, | |
| 988 clobberOnBranchChange=clobberOnBranchChange, | |
| 989 ) | |
| 990 if (not repourl and not baseURL) or (repourl and baseURL): | |
| 991 raise ValueError("you must provide exactly one of repourl and" | |
| 992 " baseURL") | |
| 993 | |
| 994 def startVC(self, branch, revision, patch): | |
| 995 slavever = self.slaveVersion("hg") | |
| 996 if not slavever: | |
| 997 raise BuildSlaveTooOldError("slave is too old, does not know " | |
| 998 "about hg") | |
| 999 | |
| 1000 if self.repourl: | |
| 1001 # we need baseURL= to use dirname branches | |
| 1002 assert self.branchType == 'inrepo' or not branch | |
| 1003 self.args['repourl'] = self.repourl | |
| 1004 if branch: | |
| 1005 self.args['branch'] = branch | |
| 1006 else: | |
| 1007 self.args['repourl'] = self.baseURL + (branch or '') | |
| 1008 self.args['revision'] = revision | |
| 1009 self.args['patch'] = patch | |
| 1010 self.args['clobberOnBranchChange'] = self.clobberOnBranchChange | |
| 1011 self.args['branchType'] = self.branchType | |
| 1012 | |
| 1013 revstuff = [] | |
| 1014 if branch is not None and branch != self.branch: | |
| 1015 revstuff.append("[branch]") | |
| 1016 self.description.extend(revstuff) | |
| 1017 self.descriptionDone.extend(revstuff) | |
| 1018 | |
| 1019 cmd = LoggedRemoteCommand("hg", self.args) | |
| 1020 self.startCommand(cmd) | |
| 1021 | |
| 1022 def computeSourceRevision(self, changes): | |
| 1023 if not changes: | |
| 1024 return None | |
| 1025 # without knowing the revision ancestry graph, we can't sort the | |
| 1026 # changes at all. So for now, assume they were given to us in sorted | |
| 1027 # order, and just pay attention to the last one. See ticket #103 for | |
| 1028 # more details. | |
| 1029 if len(changes) > 1: | |
| 1030 log.msg("Mercurial.computeSourceRevision: warning: " | |
| 1031 "there are %d changes here, assuming the last one is " | |
| 1032 "the most recent" % len(changes)) | |
| 1033 return changes[-1].revision | |
| 1034 | |
| 1035 | |
| 1036 class P4(Source): | |
| 1037 """ P4 is a class for accessing perforce revision control""" | |
| 1038 name = "p4" | |
| 1039 | |
| 1040 def __init__(self, p4base, defaultBranch=None, p4port=None, p4user=None, | |
| 1041 p4passwd=None, p4extra_views=[], | |
| 1042 p4client='buildbot_%(slave)s_%(builder)s', **kwargs): | |
| 1043 """ | |
| 1044 @type p4base: string | |
| 1045 @param p4base: A view into a perforce depot, typically | |
| 1046 "//depot/proj/" | |
| 1047 | |
| 1048 @type defaultBranch: string | |
| 1049 @param defaultBranch: Identify a branch to build by default. Perforce | |
| 1050 is a view based branching system. So, the branch | |
| 1051 is normally the name after the base. For example, | |
| 1052 branch=1.0 is view=//depot/proj/1.0/... | |
| 1053 branch=1.1 is view=//depot/proj/1.1/... | |
| 1054 | |
| 1055 @type p4port: string | |
| 1056 @param p4port: Specify the perforce server to connection in the format | |
| 1057 <host>:<port>. Example "perforce.example.com:1666" | |
| 1058 | |
| 1059 @type p4user: string | |
| 1060 @param p4user: The perforce user to run the command as. | |
| 1061 | |
| 1062 @type p4passwd: string | |
| 1063 @param p4passwd: The password for the perforce user. | |
| 1064 | |
| 1065 @type p4extra_views: list of tuples | |
| 1066 @param p4extra_views: Extra views to be added to | |
| 1067 the client that is being used. | |
| 1068 | |
| 1069 @type p4client: string | |
| 1070 @param p4client: The perforce client to use for this buildslave. | |
| 1071 """ | |
| 1072 | |
| 1073 self.branch = defaultBranch | |
| 1074 Source.__init__(self, **kwargs) | |
| 1075 self.addFactoryArguments(p4base=p4base, | |
| 1076 defaultBranch=defaultBranch, | |
| 1077 p4port=p4port, | |
| 1078 p4user=p4user, | |
| 1079 p4passwd=p4passwd, | |
| 1080 p4extra_views=p4extra_views, | |
| 1081 p4client=p4client, | |
| 1082 ) | |
| 1083 self.args['p4port'] = p4port | |
| 1084 self.args['p4user'] = p4user | |
| 1085 self.args['p4passwd'] = p4passwd | |
| 1086 self.args['p4base'] = p4base | |
| 1087 self.args['p4extra_views'] = p4extra_views | |
| 1088 self.p4client = p4client | |
| 1089 | |
| 1090 def setBuild(self, build): | |
| 1091 Source.setBuild(self, build) | |
| 1092 self.args['p4client'] = self.p4client % { | |
| 1093 'slave': build.slavename, | |
| 1094 'builder': build.builder.name, | |
| 1095 } | |
| 1096 | |
| 1097 def computeSourceRevision(self, changes): | |
| 1098 if not changes: | |
| 1099 return None | |
| 1100 lastChange = max([int(c.revision) for c in changes]) | |
| 1101 return lastChange | |
| 1102 | |
| 1103 def startVC(self, branch, revision, patch): | |
| 1104 slavever = self.slaveVersion("p4") | |
| 1105 assert slavever, "slave is too old, does not know about p4" | |
| 1106 args = dict(self.args) | |
| 1107 args['branch'] = branch or self.branch | |
| 1108 args['revision'] = revision | |
| 1109 args['patch'] = patch | |
| 1110 cmd = LoggedRemoteCommand("p4", args) | |
| 1111 self.startCommand(cmd) | |
| 1112 | |
| 1113 class P4Sync(Source): | |
| 1114 """This is a partial solution for using a P4 source repository. You are | |
| 1115 required to manually set up each build slave with a useful P4 | |
| 1116 environment, which means setting various per-slave environment variables, | |
| 1117 and creating a P4 client specification which maps the right files into | |
| 1118 the slave's working directory. Once you have done that, this step merely | |
| 1119 performs a 'p4 sync' to update that workspace with the newest files. | |
| 1120 | |
| 1121 Each slave needs the following environment: | |
| 1122 | |
| 1123 - PATH: the 'p4' binary must be on the slave's PATH | |
| 1124 - P4USER: each slave needs a distinct user account | |
| 1125 - P4CLIENT: each slave needs a distinct client specification | |
| 1126 | |
| 1127 You should use 'p4 client' (?) to set up a client view spec which maps | |
| 1128 the desired files into $SLAVEBASE/$BUILDERBASE/source . | |
| 1129 """ | |
| 1130 | |
| 1131 name = "p4sync" | |
| 1132 | |
| 1133 def __init__(self, p4port, p4user, p4passwd, p4client, **kwargs): | |
| 1134 assert kwargs['mode'] == "copy", "P4Sync can only be used in mode=copy" | |
| 1135 self.branch = None | |
| 1136 Source.__init__(self, **kwargs) | |
| 1137 self.addFactoryArguments(p4port=p4port, | |
| 1138 p4user=p4user, | |
| 1139 p4passwd=p4passwd, | |
| 1140 p4client=p4client, | |
| 1141 ) | |
| 1142 self.args['p4port'] = p4port | |
| 1143 self.args['p4user'] = p4user | |
| 1144 self.args['p4passwd'] = p4passwd | |
| 1145 self.args['p4client'] = p4client | |
| 1146 | |
| 1147 def computeSourceRevision(self, changes): | |
| 1148 if not changes: | |
| 1149 return None | |
| 1150 lastChange = max([int(c.revision) for c in changes]) | |
| 1151 return lastChange | |
| 1152 | |
| 1153 def startVC(self, branch, revision, patch): | |
| 1154 slavever = self.slaveVersion("p4sync") | |
| 1155 assert slavever, "slave is too old, does not know about p4" | |
| 1156 cmd = LoggedRemoteCommand("p4sync", self.args) | |
| 1157 self.startCommand(cmd) | |
| 1158 | |
| 1159 class Monotone(Source): | |
| 1160 """Check out a revision from a monotone server at 'server_addr', | |
| 1161 branch 'branch'. 'revision' specifies which revision id to check | |
| 1162 out. | |
| 1163 | |
| 1164 This step will first create a local database, if necessary, and then pull | |
| 1165 the contents of the server into the database. Then it will do the | |
| 1166 checkout/update from this database.""" | |
| 1167 | |
| 1168 name = "monotone" | |
| 1169 | |
| 1170 def __init__(self, server_addr, branch, db_path="monotone.db", | |
| 1171 monotone="monotone", | |
| 1172 **kwargs): | |
| 1173 Source.__init__(self, **kwargs) | |
| 1174 self.addFactoryArguments(server_addr=server_addr, | |
| 1175 branch=branch, | |
| 1176 db_path=db_path, | |
| 1177 monotone=monotone, | |
| 1178 ) | |
| 1179 self.args.update({"server_addr": server_addr, | |
| 1180 "branch": branch, | |
| 1181 "db_path": db_path, | |
| 1182 "monotone": monotone}) | |
| 1183 | |
| 1184 def computeSourceRevision(self, changes): | |
| 1185 if not changes: | |
| 1186 return None | |
| 1187 return changes[-1].revision | |
| 1188 | |
| 1189 def startVC(self): | |
| 1190 slavever = self.slaveVersion("monotone") | |
| 1191 assert slavever, "slave is too old, does not know about monotone" | |
| 1192 cmd = LoggedRemoteCommand("monotone", self.args) | |
| 1193 self.startCommand(cmd) | |
| OLD | NEW |