Package buildbot :: Package steps :: Module source
[frames] | no frames]

Source Code for Module buildbot.steps.source

   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 'timeout': timeout, 126 'retry': retry, 127 'patch': None, # set during .start 128 } 129 # This will get added to args later, after properties are rendered 130 self.workdir = workdir 131 132 self.alwaysUseLatest = alwaysUseLatest 133 134 # Compute defaults for descriptions: 135 description = ["updating"] 136 descriptionDone = ["update"] 137 if mode == "clobber": 138 description = ["checkout"] 139 # because checkingouting takes too much space 140 descriptionDone = ["checkout"] 141 elif mode == "export": 142 description = ["exporting"] 143 descriptionDone = ["export"] 144 self.description = description 145 self.descriptionDone = descriptionDone
146
147 - def setStepStatus(self, step_status):
149
150 - def setDefaultWorkdir(self, workdir):
151 self.workdir = self.workdir or workdir
152
153 - def describe(self, done=False):
154 if done: 155 return self.descriptionDone 156 return self.description
157
158 - def computeSourceRevision(self, changes):
159 """Each subclass must implement this method to do something more 160 precise than -rHEAD every time. For version control systems that use 161 repository-wide change numbers (SVN, P4), this can simply take the 162 maximum such number from all the changes involved in this build. For 163 systems that do not (CVS), it needs to create a timestamp based upon 164 the latest Change, the Build's treeStableTimer, and an optional 165 self.checkoutDelay value.""" 166 return None
167
168 - def computeRepositoryURL(self, repository):
169 ''' 170 Helper function that the repository URL based on the parameter the 171 source step took and the Change 'repository' property 172 ''' 173 174 assert not repository or callable(repository) or isinstance(repository, dict) or \ 175 isinstance(repository, str) or isinstance(repository, unicode) 176 177 s = self.build.getSourceStamp() 178 props = self.build.getProperties() 179 180 if not repository: 181 assert s.repository 182 return str(s.repository) 183 else: 184 if callable(repository): 185 return str(props.render(repository(s.repository))) 186 elif isinstance(repository, dict): 187 return str(props.render(repository.get(s.repository))) 188 else: # string or unicode 189 try: 190 repourl = str(repository % s.repository) 191 except TypeError: 192 # that's the backward compatibility case 193 repourl = props.render(repository) 194 return repourl
195
196 - def start(self):
197 if self.notReally: 198 log.msg("faking %s checkout/update" % self.name) 199 self.step_status.setText(["fake", self.name, "successful"]) 200 self.addCompleteLog("log", 201 "Faked %s checkout/update 'successful'\n" \ 202 % self.name) 203 return SKIPPED 204 205 # Allow workdir to be WithProperties 206 properties = self.build.getProperties() 207 self.args['workdir'] = properties.render(self.workdir) 208 209 # what source stamp would this build like to use? 210 s = self.build.getSourceStamp() 211 # if branch is None, then use the Step's "default" branch 212 branch = s.branch or self.branch 213 # if revision is None, use the latest sources (-rHEAD) 214 revision = s.revision 215 if not revision and not self.alwaysUseLatest: 216 revision = self.computeSourceRevision(s.changes) 217 # the revision property is currently None, so set it to something 218 # more interesting 219 self.setProperty('revision', str(revision), "Source") 220 221 # if patch is None, then do not patch the tree after checkout 222 223 # 'patch' is None or a tuple of (patchlevel, diff, root) 224 # root is optional. 225 patch = s.patch 226 if patch: 227 self.addCompleteLog("patch", patch[1]) 228 229 if self.alwaysUseLatest: 230 revision = None 231 self.startVC(branch, revision, patch)
232
233 - def commandComplete(self, cmd):
234 if cmd.updates.has_key("got_revision"): 235 got_revision = cmd.updates["got_revision"][-1] 236 if got_revision is not None: 237 self.setProperty("got_revision", str(got_revision), "Source")
238 239 240
241 -class BK(Source):
242 """I perform BitKeeper checkout/update operations.""" 243 244 name = 'bk' 245
246 - def __init__(self, bkurl=None, baseURL=None, 247 directory=None, extra_args=None, **kwargs):
248 """ 249 @type bkurl: string 250 @param bkurl: the URL which points to the BitKeeper server. 251 252 @type baseURL: string 253 @param baseURL: if branches are enabled, this is the base URL to 254 which a branch name will be appended. It should 255 probably end in a slash. Use exactly one of 256 C{bkurl} and C{baseURL}. 257 """ 258 259 self.bkurl = bkurl 260 self.baseURL = baseURL 261 self.extra_args = extra_args 262 263 Source.__init__(self, **kwargs) 264 self.addFactoryArguments(bkurl=bkurl, 265 baseURL=baseURL, 266 directory=directory, 267 extra_args=extra_args, 268 ) 269 270 if bkurl and baseURL: 271 raise ValueError("you must use exactly one of bkurl and baseURL")
272 273
274 - def computeSourceRevision(self, changes):
275 return changes.revision
276 277
278 - def startVC(self, branch, revision, patch):
279 280 warnings = [] 281 slavever = self.slaveVersion("bk") 282 if not slavever: 283 m = "slave does not have the 'bk' command" 284 raise BuildSlaveTooOldError(m) 285 286 if self.bkurl: 287 assert not branch # we need baseURL= to use branches 288 self.args['bkurl'] = self.computeRepositoryURL(self.bkurl) 289 else: 290 self.args['bkurl'] = self.computeRepositoryURL(self.baseURL) + branch 291 self.args['revision'] = revision 292 self.args['patch'] = patch 293 self.args['branch'] = branch 294 if self.extra_args is not None: 295 self.args['extra_args'] = self.extra_args 296 297 revstuff = [] 298 revstuff.append("[branch]") 299 if revision is not None: 300 revstuff.append("r%s" % revision) 301 if patch is not None: 302 revstuff.append("[patch]") 303 self.description.extend(revstuff) 304 self.descriptionDone.extend(revstuff) 305 306 cmd = LoggedRemoteCommand("bk", self.args) 307 self.startCommand(cmd, warnings)
308 309 310
311 -class CVS(Source):
312 """I do CVS checkout/update operations. 313 314 Note: if you are doing anonymous/pserver CVS operations, you will need 315 to manually do a 'cvs login' on each buildslave before the slave has any 316 hope of success. XXX: fix then, take a cvs password as an argument and 317 figure out how to do a 'cvs login' on each build 318 """ 319 320 name = "cvs" 321 322 #progressMetrics = ('output',) 323 # 324 # additional things to track: update gives one stderr line per directory 325 # (starting with 'cvs server: Updating ') (and is fairly stable if files 326 # is empty), export gives one line per directory (starting with 'cvs 327 # export: Updating ') and another line per file (starting with U). Would 328 # be nice to track these, requires grepping LogFile data for lines, 329 # parsing each line. Might be handy to have a hook in LogFile that gets 330 # called with each complete line. 331
332 - def __init__(self, cvsroot=None, cvsmodule="", 333 global_options=[], branch=None, checkoutDelay=None, 334 checkout_options=[], export_options=[], extra_options=[], 335 login=None, 336 **kwargs):
337 338 """ 339 @type cvsroot: string 340 @param cvsroot: CVS Repository from which the source tree should 341 be obtained. '/home/warner/Repository' for local 342 or NFS-reachable repositories, 343 ':pserver:anon@foo.com:/cvs' for anonymous CVS, 344 'user@host.com:/cvs' for non-anonymous CVS or 345 CVS over ssh. Lots of possibilities, check the 346 CVS documentation for more. 347 348 @type cvsmodule: string 349 @param cvsmodule: subdirectory of CVS repository that should be 350 retrieved 351 352 @type login: string or None 353 @param login: if not None, a string which will be provided as a 354 password to the 'cvs login' command, used when a 355 :pserver: method is used to access the repository. 356 This login is only needed once, but must be run 357 each time (just before the CVS operation) because 358 there is no way for the buildslave to tell whether 359 it was previously performed or not. 360 361 @type branch: string 362 @param branch: the default branch name, will be used in a '-r' 363 argument to specify which branch of the source tree 364 should be used for this checkout. Defaults to None, 365 which means to use 'HEAD'. 366 367 @type checkoutDelay: int or None 368 @param checkoutDelay: if not None, the number of seconds to put 369 between the last known Change and the 370 timestamp given to the -D argument. This 371 defaults to exactly half of the parent 372 Build's .treeStableTimer, but it could be 373 set to something else if your CVS change 374 notification has particularly weird 375 latency characteristics. 376 377 @type global_options: list of strings 378 @param global_options: these arguments are inserted in the cvs 379 command line, before the 380 'checkout'/'update' command word. See 381 'cvs --help-options' for a list of what 382 may be accepted here. ['-r'] will make 383 the checked out files read only. ['-r', 384 '-R'] will also assume the repository is 385 read-only (I assume this means it won't 386 use locks to insure atomic access to the 387 ,v files). 388 389 @type checkout_options: list of strings 390 @param checkout_options: these arguments are inserted in the cvs 391 command line, after 'checkout' but before 392 branch or revision specifiers. 393 394 @type export_options: list of strings 395 @param export_options: these arguments are inserted in the cvs 396 command line, after 'export' but before 397 branch or revision specifiers. 398 399 @type extra_options: list of strings 400 @param extra_options: these arguments are inserted in the cvs 401 command line, after 'checkout' or 'export' but before 402 branch or revision specifiers. 403 """ 404 405 self.checkoutDelay = checkoutDelay 406 self.branch = branch 407 self.cvsroot = cvsroot 408 409 Source.__init__(self, **kwargs) 410 self.addFactoryArguments(cvsroot=cvsroot, 411 cvsmodule=cvsmodule, 412 global_options=global_options, 413 checkout_options=checkout_options, 414 export_options=export_options, 415 extra_options=extra_options, 416 branch=branch, 417 checkoutDelay=checkoutDelay, 418 login=login, 419 ) 420 421 self.args.update({'cvsmodule': cvsmodule, 422 'global_options': global_options, 423 'checkout_options':checkout_options, 424 'export_options':export_options, 425 'extra_options':extra_options, 426 'login': login, 427 })
428
429 - def computeSourceRevision(self, changes):
430 if not changes: 431 return None 432 lastChange = max([c.when for c in changes]) 433 if self.checkoutDelay is not None: 434 when = lastChange + self.checkoutDelay 435 else: 436 lastSubmit = max([r.submittedAt for r in self.build.requests]) 437 when = (lastChange + lastSubmit) / 2 438 return formatdate(when)
439
440 - def startVC(self, branch, revision, patch):
441 if self.slaveVersionIsOlderThan("cvs", "1.39"): 442 # the slave doesn't know to avoid re-using the same sourcedir 443 # when the branch changes. We have no way of knowing which branch 444 # the last build used, so if we're using a non-default branch and 445 # either 'update' or 'copy' modes, it is safer to refuse to 446 # build, and tell the user they need to upgrade the buildslave. 447 if (branch != self.branch 448 and self.args['mode'] in ("update", "copy")): 449 m = ("This buildslave (%s) does not know about multiple " 450 "branches, and using mode=%s would probably build the " 451 "wrong tree. " 452 "Refusing to build. Please upgrade the buildslave to " 453 "buildbot-0.7.0 or newer." % (self.build.slavename, 454 self.args['mode'])) 455 log.msg(m) 456 raise BuildSlaveTooOldError(m) 457 458 if self.slaveVersionIsOlderThan("cvs", "2.10"): 459 if self.args['extra_options'] or self.args['export_options']: 460 m = ("This buildslave (%s) does not support export_options " 461 "or extra_options arguments to the CVS step." 462 % (self.build.slavename)) 463 log.msg(m) 464 raise BuildSlaveTooOldError(m) 465 # the unwanted args are empty, and will probably be ignored by 466 # the slave, but delete them just to be safe 467 del self.args['export_options'] 468 del self.args['extra_options'] 469 470 if branch is None: 471 branch = "HEAD" 472 self.args['cvsroot'] = self.computeRepositoryURL(self.cvsroot) 473 self.args['branch'] = branch 474 self.args['revision'] = revision 475 self.args['patch'] = patch 476 477 if self.args['branch'] == "HEAD" and self.args['revision']: 478 # special case. 'cvs update -r HEAD -D today' gives no files 479 # TODO: figure out why, see if it applies to -r BRANCH 480 self.args['branch'] = None 481 482 # deal with old slaves 483 warnings = [] 484 slavever = self.slaveVersion("cvs", "old") 485 486 if slavever == "old": 487 # 0.5.0 488 if self.args['mode'] == "export": 489 self.args['export'] = 1 490 elif self.args['mode'] == "clobber": 491 self.args['clobber'] = 1 492 elif self.args['mode'] == "copy": 493 self.args['copydir'] = "source" 494 self.args['tag'] = self.args['branch'] 495 assert not self.args['patch'] # 0.5.0 slave can't do patch 496 497 cmd = LoggedRemoteCommand("cvs", self.args) 498 self.startCommand(cmd, warnings)
499 500
501 -class SVN(Source):
502 """I perform Subversion checkout/update operations.""" 503 504 name = 'svn' 505 branch_placeholder = '%%BRANCH%%' 506
507 - def __init__(self, svnurl=None, baseURL=None, defaultBranch=None, 508 directory=None, username=None, password=None, 509 extra_args=None, keep_on_purge=None, ignore_ignores=None, 510 always_purge=None, depth=None, **kwargs):
511 """ 512 @type svnurl: string 513 @param svnurl: the URL which points to the Subversion server, 514 combining the access method (HTTP, ssh, local file), 515 the repository host/port, the repository path, the 516 sub-tree within the repository, and the branch to 517 check out. Use exactly one of C{svnurl} and C{baseURL}. 518 519 @param baseURL: if branches are enabled, this is the base URL to 520 which a branch name will be appended. It should 521 probably end in a slash. Use exactly one of 522 C{svnurl} and C{baseURL}. 523 524 @param defaultBranch: if branches are enabled, this is the branch 525 to use if the Build does not specify one 526 explicitly. It will simply be appended 527 to C{baseURL} and the result handed to 528 the SVN command. 529 530 @type username: string 531 @param username: username to pass to svn's --username 532 533 @type password: string 534 @param password: password to pass to svn's --password 535 """ 536 537 if not 'workdir' in kwargs and directory is not None: 538 # deal with old configs 539 warn("Please use workdir=, not directory=", DeprecationWarning) 540 kwargs['workdir'] = directory 541 542 self.svnurl = svnurl 543 self.baseURL = baseURL 544 self.branch = defaultBranch 545 self.username = username 546 self.password = password 547 self.extra_args = extra_args 548 self.keep_on_purge = keep_on_purge 549 self.ignore_ignores = ignore_ignores 550 self.always_purge = always_purge 551 self.depth = depth 552 553 Source.__init__(self, **kwargs) 554 self.addFactoryArguments(svnurl=svnurl, 555 baseURL=baseURL, 556 defaultBranch=defaultBranch, 557 directory=directory, 558 username=username, 559 password=password, 560 extra_args=extra_args, 561 keep_on_purge=keep_on_purge, 562 ignore_ignores=ignore_ignores, 563 always_purge=always_purge, 564 depth=depth, 565 ) 566 567 if svnurl and baseURL: 568 raise ValueError("you must use either svnurl OR baseURL")
569
570 - def computeSourceRevision(self, changes):
571 if not changes or None in [c.revision for c in changes]: 572 return None 573 lastChange = max([int(c.revision) for c in changes]) 574 return lastChange
575
576 - def checkCompatibility(self):
577 ''' Handle compatibility between old slaves/svn clients ''' 578 579 slavever = self.slaveVersion("svn", "old") 580 581 if not slavever: 582 m = "slave does not have the 'svn' command" 583 raise BuildSlaveTooOldError(m) 584 585 if self.slaveVersionIsOlderThan("svn", "1.39"): 586 # the slave doesn't know to avoid re-using the same sourcedir 587 # when the branch changes. We have no way of knowing which branch 588 # the last build used, so if we're using a non-default branch and 589 # either 'update' or 'copy' modes, it is safer to refuse to 590 # build, and tell the user they need to upgrade the buildslave. 591 if (self.args['branch'] != self.branch 592 and self.args['mode'] in ("update", "copy")): 593 m = ("This buildslave (%s) does not know about multiple " 594 "branches, and using mode=%s would probably build the " 595 "wrong tree. " 596 "Refusing to build. Please upgrade the buildslave to " 597 "buildbot-0.7.0 or newer." % (self.build.slavename, 598 self.args['mode'])) 599 raise BuildSlaveTooOldError(m) 600 601 if (self.depth is not None) and self.slaveVersionIsOlderThan("svn","2.9"): 602 m = ("This buildslave (%s) does not support svn depth " 603 "arguments. Refusing to build. " 604 "Please upgrade the buildslave." % (self.build.slavename)) 605 raise BuildSlaveTooOldError(m) 606 607 if (self.username is not None or self.password is not None) \ 608 and self.slaveVersionIsOlderThan("svn", "2.8"): 609 m = ("This buildslave (%s) does not support svn usernames " 610 "and passwords. " 611 "Refusing to build. Please upgrade the buildslave to " 612 "buildbot-0.7.10 or newer." % (self.build.slavename,)) 613 raise BuildSlaveTooOldError(m)
614
615 - def getSvnUrl(self, branch, revision, patch):
616 ''' Compute the svn url that will be passed to the svn remote command ''' 617 if self.svnurl: 618 return self.computeRepositoryURL(self.svnurl) 619 else: 620 if branch is None: 621 m = ("The SVN source step belonging to builder '%s' does not know " 622 "which branch to work with. This means that the change source " 623 "did not specify a branch and that defaultBranch is None." \ 624 % self.build.builder.name) 625 raise RuntimeError(m) 626 627 computed = self.computeRepositoryURL(self.baseURL) 628 629 if self.branch_placeholder in self.baseURL: 630 return computed.replace(self.branch_placeholder, branch) 631 else: 632 return computed + branch
633
634 - def startVC(self, branch, revision, patch):
635 warnings = [] 636 637 self.checkCompatibility() 638 639 self.args['svnurl'] = self.getSvnUrl(branch, revision, patch) 640 self.args['revision'] = revision 641 self.args['patch'] = patch 642 self.args['always_purge'] = self.always_purge 643 644 #Set up depth if specified 645 if self.depth is not None: 646 self.args['depth'] = self.depth 647 648 if self.username is not None: 649 self.args['username'] = self.username 650 if self.password is not None: 651 self.args['password'] = self.password 652 653 if self.extra_args is not None: 654 self.args['extra_args'] = self.extra_args 655 656 revstuff = [] 657 #revstuff.append(self.args['svnurl']) 658 if self.args['svnurl'].find('trunk') == -1: 659 revstuff.append("[branch]") 660 if revision is not None: 661 revstuff.append("r%s" % revision) 662 if patch is not None: 663 revstuff.append("[patch]") 664 self.description.extend(revstuff) 665 self.descriptionDone.extend(revstuff) 666 667 cmd = LoggedRemoteCommand("svn", self.args) 668 self.startCommand(cmd, warnings)
669 670
671 -class Darcs(Source):
672 """Check out a source tree from a Darcs repository at 'repourl'. 673 674 Darcs has no concept of file modes. This means the eXecute-bit will be 675 cleared on all source files. As a result, you may need to invoke 676 configuration scripts with something like: 677 678 C{s(step.Configure, command=['/bin/sh', './configure'])} 679 """ 680 681 name = "darcs" 682
683 - def __init__(self, repourl=None, baseURL=None, defaultBranch=None, 684 **kwargs):
685 """ 686 @type repourl: string 687 @param repourl: the URL which points at the Darcs repository. This 688 is used as the default branch. Using C{repourl} does 689 not enable builds of alternate branches: use 690 C{baseURL} to enable this. Use either C{repourl} or 691 C{baseURL}, not both. 692 693 @param baseURL: if branches are enabled, this is the base URL to 694 which a branch name will be appended. It should 695 probably end in a slash. Use exactly one of 696 C{repourl} and C{baseURL}. 697 698 @param defaultBranch: if branches are enabled, this is the branch 699 to use if the Build does not specify one 700 explicitly. It will simply be appended to 701 C{baseURL} and the result handed to the 702 'darcs pull' command. 703 """ 704 self.repourl = repourl 705 self.baseURL = baseURL 706 self.branch = defaultBranch 707 Source.__init__(self, **kwargs) 708 self.addFactoryArguments(repourl=repourl, 709 baseURL=baseURL, 710 defaultBranch=defaultBranch, 711 ) 712 assert self.args['mode'] != "export", \ 713 "Darcs does not have an 'export' mode" 714 if repourl and baseURL: 715 raise ValueError("you must provide exactly one of repourl and" 716 " baseURL")
717
718 - def startVC(self, branch, revision, patch):
719 slavever = self.slaveVersion("darcs") 720 if not slavever: 721 m = "slave is too old, does not know about darcs" 722 raise BuildSlaveTooOldError(m) 723 724 if self.slaveVersionIsOlderThan("darcs", "1.39"): 725 if revision: 726 # TODO: revisit this once we implement computeSourceRevision 727 m = "0.6.6 slaves can't handle args['revision']" 728 raise BuildSlaveTooOldError(m) 729 730 # the slave doesn't know to avoid re-using the same sourcedir 731 # when the branch changes. We have no way of knowing which branch 732 # the last build used, so if we're using a non-default branch and 733 # either 'update' or 'copy' modes, it is safer to refuse to 734 # build, and tell the user they need to upgrade the buildslave. 735 if (branch != self.branch 736 and self.args['mode'] in ("update", "copy")): 737 m = ("This buildslave (%s) does not know about multiple " 738 "branches, and using mode=%s would probably build the " 739 "wrong tree. " 740 "Refusing to build. Please upgrade the buildslave to " 741 "buildbot-0.7.0 or newer." % (self.build.slavename, 742 self.args['mode'])) 743 raise BuildSlaveTooOldError(m) 744 745 if self.repourl: 746 assert not branch # we need baseURL= to use branches 747 self.args['repourl'] = self.computeRepositoryURL(self.repourl) 748 else: 749 self.args['repourl'] = self.computeRepositoryURL(self.baseURL) + branch 750 self.args['revision'] = revision 751 self.args['patch'] = patch 752 753 revstuff = [] 754 if branch is not None and branch != self.branch: 755 revstuff.append("[branch]") 756 self.description.extend(revstuff) 757 self.descriptionDone.extend(revstuff) 758 759 cmd = LoggedRemoteCommand("darcs", self.args) 760 self.startCommand(cmd)
761 762
763 -class Git(Source):
764 """Check out a source tree from a git repository 'repourl'.""" 765 766 name = "git" 767
768 - def __init__(self, repourl=None, 769 branch="master", 770 submodules=False, 771 ignore_ignores=None, 772 reference=None, 773 shallow=False, 774 progress=False, 775 **kwargs):
776 """ 777 @type repourl: string 778 @param repourl: the URL which points at the git repository 779 780 @type branch: string 781 @param branch: The branch or tag to check out by default. If 782 a build specifies a different branch, it will 783 be used instead of this. 784 785 @type submodules: boolean 786 @param submodules: Whether or not to update (and initialize) 787 git submodules. 788 789 @type reference: string 790 @param reference: The path to a reference repository to obtain 791 objects from, if any. 792 793 @type shallow: boolean 794 @param shallow: Use a shallow or clone, if possible 795 796 @type progress: boolean 797 @param progress: Pass the --progress option when fetching. This 798 can solve long fetches getting killed due to 799 lack of output, but requires Git 1.7.2+. 800 """ 801 Source.__init__(self, **kwargs) 802 self.repourl = repourl 803 self.addFactoryArguments(repourl=repourl, 804 branch=branch, 805 submodules=submodules, 806 ignore_ignores=ignore_ignores, 807 reference=reference, 808 shallow=shallow, 809 progress=progress, 810 ) 811 self.args.update({'branch': branch, 812 'submodules': submodules, 813 'ignore_ignores': ignore_ignores, 814 'reference': reference, 815 'shallow': shallow, 816 'progress': progress, 817 })
818
819 - def computeSourceRevision(self, changes):
820 if not changes: 821 return None 822 return changes[-1].revision
823
824 - def startVC(self, branch, revision, patch):
825 if branch is not None: 826 self.args['branch'] = branch 827 828 self.args['repourl'] = self.computeRepositoryURL(self.repourl) 829 self.args['revision'] = revision 830 self.args['patch'] = patch 831 slavever = self.slaveVersion("git") 832 if not slavever: 833 raise BuildSlaveTooOldError("slave is too old, does not know " 834 "about git") 835 cmd = LoggedRemoteCommand("git", self.args) 836 self.startCommand(cmd)
837 838
839 -class Bzr(Source):
840 """Check out a source tree from a bzr (Bazaar) repository at 'repourl'. 841 842 """ 843 844 name = "bzr" 845
846 - def __init__(self, repourl=None, baseURL=None, defaultBranch=None, 847 forceSharedRepo=None, 848 **kwargs):
849 """ 850 @type repourl: string 851 @param repourl: the URL which points at the bzr repository. This 852 is used as the default branch. Using C{repourl} does 853 not enable builds of alternate branches: use 854 C{baseURL} to enable this. Use either C{repourl} or 855 C{baseURL}, not both. 856 857 @param baseURL: if branches are enabled, this is the base URL to 858 which a branch name will be appended. It should 859 probably end in a slash. Use exactly one of 860 C{repourl} and C{baseURL}. 861 862 @param defaultBranch: if branches are enabled, this is the branch 863 to use if the Build does not specify one 864 explicitly. It will simply be appended to 865 C{baseURL} and the result handed to the 866 'bzr checkout pull' command. 867 868 869 @param forceSharedRepo: Boolean, defaults to False. If set to True, 870 the working directory will be made into a 871 bzr shared repository if it is not already. 872 Shared repository greatly reduces the amount 873 of history data that needs to be downloaded 874 if not using update/copy mode, or if using 875 update/copy mode with multiple branches. 876 """ 877 self.repourl = repourl 878 self.baseURL = baseURL 879 self.branch = defaultBranch 880 Source.__init__(self, **kwargs) 881 self.addFactoryArguments(repourl=repourl, 882 baseURL=baseURL, 883 defaultBranch=defaultBranch, 884 forceSharedRepo=forceSharedRepo 885 ) 886 self.args.update({'forceSharedRepo': forceSharedRepo}) 887 if repourl and baseURL: 888 raise ValueError("you must provide exactly one of repourl and" 889 " baseURL")
890
891 - def computeSourceRevision(self, changes):
892 if not changes: 893 return None 894 lastChange = max([int(c.revision) for c in changes]) 895 return lastChange
896
897 - def startVC(self, branch, revision, patch):
898 slavever = self.slaveVersion("bzr") 899 if not slavever: 900 m = "slave is too old, does not know about bzr" 901 raise BuildSlaveTooOldError(m) 902 903 if self.repourl: 904 assert not branch # we need baseURL= to use branches 905 self.args['repourl'] = self.computeRepositoryURL(self.repourl) 906 else: 907 self.args['repourl'] = self.computeRepositoryURL(self.baseURL) + branch 908 self.args['revision'] = revision 909 self.args['patch'] = patch 910 911 revstuff = [] 912 if branch is not None and branch != self.branch: 913 revstuff.append("[" + branch + "]") 914 if revision is not None: 915 revstuff.append("r%s" % revision) 916 self.description.extend(revstuff) 917 self.descriptionDone.extend(revstuff) 918 919 cmd = LoggedRemoteCommand("bzr", self.args) 920 self.startCommand(cmd)
921 922
923 -class Mercurial(Source):
924 """Check out a source tree from a mercurial repository 'repourl'.""" 925 926 name = "hg" 927
928 - def __init__(self, repourl=None, baseURL=None, defaultBranch=None, 929 branchType='dirname', clobberOnBranchChange=True, **kwargs):
930 """ 931 @type repourl: string 932 @param repourl: the URL which points at the Mercurial repository. 933 This uses the 'default' branch unless defaultBranch is 934 specified below and the C{branchType} is set to 935 'inrepo'. It is an error to specify a branch without 936 setting the C{branchType} to 'inrepo'. 937 938 @param baseURL: if 'dirname' branches are enabled, this is the base URL 939 to which a branch name will be appended. It should 940 probably end in a slash. Use exactly one of C{repourl} 941 and C{baseURL}. 942 943 @param defaultBranch: if branches are enabled, this is the branch 944 to use if the Build does not specify one 945 explicitly. 946 For 'dirname' branches, It will simply be 947 appended to C{baseURL} and the result handed to 948 the 'hg update' command. 949 For 'inrepo' branches, this specifies the named 950 revision to which the tree will update after a 951 clone. 952 953 @param branchType: either 'dirname' or 'inrepo' depending on whether 954 the branch name should be appended to the C{baseURL} 955 or the branch is a mercurial named branch and can be 956 found within the C{repourl} 957 958 @param clobberOnBranchChange: boolean, defaults to True. If set and 959 using inrepos branches, clobber the tree 960 at each branch change. Otherwise, just 961 update to the branch. 962 """ 963 self.repourl = repourl 964 self.baseURL = baseURL 965 self.branch = defaultBranch 966 self.branchType = branchType 967 self.clobberOnBranchChange = clobberOnBranchChange 968 Source.__init__(self, **kwargs) 969 self.addFactoryArguments(repourl=repourl, 970 baseURL=baseURL, 971 defaultBranch=defaultBranch, 972 branchType=branchType, 973 clobberOnBranchChange=clobberOnBranchChange, 974 ) 975 if repourl and baseURL: 976 raise ValueError("you must provide exactly one of repourl and" 977 " baseURL")
978
979 - def startVC(self, branch, revision, patch):
980 slavever = self.slaveVersion("hg") 981 if not slavever: 982 raise BuildSlaveTooOldError("slave is too old, does not know " 983 "about hg") 984 985 if self.repourl: 986 # we need baseURL= to use dirname branches 987 assert self.branchType == 'inrepo' or not branch 988 self.args['repourl'] = self.computeRepositoryURL(self.repourl) 989 if branch: 990 self.args['branch'] = branch 991 else: 992 self.args['repourl'] = self.computeRepositoryURL(self.baseURL) + (branch or '') 993 self.args['revision'] = revision 994 self.args['patch'] = patch 995 self.args['clobberOnBranchChange'] = self.clobberOnBranchChange 996 self.args['branchType'] = self.branchType 997 998 revstuff = [] 999 if branch is not None and branch != self.branch: 1000 revstuff.append("[branch]") 1001 self.description.extend(revstuff) 1002 self.descriptionDone.extend(revstuff) 1003 1004 cmd = LoggedRemoteCommand("hg", self.args) 1005 self.startCommand(cmd)
1006
1007 - def computeSourceRevision(self, changes):
1008 if not changes: 1009 return None 1010 # without knowing the revision ancestry graph, we can't sort the 1011 # changes at all. So for now, assume they were given to us in sorted 1012 # order, and just pay attention to the last one. See ticket #103 for 1013 # more details. 1014 if len(changes) > 1: 1015 log.msg("Mercurial.computeSourceRevision: warning: " 1016 "there are %d changes here, assuming the last one is " 1017 "the most recent" % len(changes)) 1018 return changes[-1].revision
1019 1020
1021 -class P4(Source):
1022 """ P4 is a class for accessing perforce revision control""" 1023 name = "p4" 1024
1025 - def __init__(self, p4base=None, defaultBranch=None, p4port=None, p4user=None, 1026 p4passwd=None, p4extra_views=[], p4line_end='local', 1027 p4client='buildbot_%(slave)s_%(builder)s', **kwargs):
1028 """ 1029 @type p4base: string 1030 @param p4base: A view into a perforce depot, typically 1031 "//depot/proj/" 1032 1033 @type defaultBranch: string 1034 @param defaultBranch: Identify a branch to build by default. Perforce 1035 is a view based branching system. So, the branch 1036 is normally the name after the base. For example, 1037 branch=1.0 is view=//depot/proj/1.0/... 1038 branch=1.1 is view=//depot/proj/1.1/... 1039 1040 @type p4port: string 1041 @param p4port: Specify the perforce server to connection in the format 1042 <host>:<port>. Example "perforce.example.com:1666" 1043 1044 @type p4user: string 1045 @param p4user: The perforce user to run the command as. 1046 1047 @type p4passwd: string 1048 @param p4passwd: The password for the perforce user. 1049 1050 @type p4extra_views: list of tuples 1051 @param p4extra_views: Extra views to be added to 1052 the client that is being used. 1053 1054 @type p4line_end: string 1055 @param p4line_end: value of the LineEnd client specification property 1056 1057 @type p4client: string 1058 @param p4client: The perforce client to use for this buildslave. 1059 """ 1060 1061 self.p4base = p4base 1062 self.branch = defaultBranch 1063 Source.__init__(self, **kwargs) 1064 self.addFactoryArguments(p4base=p4base, 1065 defaultBranch=defaultBranch, 1066 p4port=p4port, 1067 p4user=p4user, 1068 p4passwd=p4passwd, 1069 p4extra_views=p4extra_views, 1070 p4line_end=p4line_end, 1071 p4client=p4client, 1072 ) 1073 self.args['p4port'] = p4port 1074 self.args['p4user'] = p4user 1075 self.args['p4passwd'] = p4passwd 1076 self.args['p4extra_views'] = p4extra_views 1077 self.args['p4line_end'] = p4line_end 1078 self.p4client = p4client
1079
1080 - def setBuild(self, build):
1081 Source.setBuild(self, build) 1082 self.args['p4base'] = self.computeRepositoryURL(self.p4base) 1083 self.args['p4client'] = self.p4client % { 1084 'slave': build.slavename, 1085 'builder': build.builder.name, 1086 }
1087
1088 - def computeSourceRevision(self, changes):
1089 if not changes: 1090 return None 1091 lastChange = max([int(c.revision) for c in changes]) 1092 return lastChange
1093
1094 - def startVC(self, branch, revision, patch):
1095 slavever = self.slaveVersion("p4") 1096 assert slavever, "slave is too old, does not know about p4" 1097 args = dict(self.args) 1098 args['branch'] = branch or self.branch 1099 args['revision'] = revision 1100 args['patch'] = patch 1101 cmd = LoggedRemoteCommand("p4", args) 1102 self.startCommand(cmd)
1103
1104 -class P4Sync(Source):
1105 """This is a partial solution for using a P4 source repository. You are 1106 required to manually set up each build slave with a useful P4 1107 environment, which means setting various per-slave environment variables, 1108 and creating a P4 client specification which maps the right files into 1109 the slave's working directory. Once you have done that, this step merely 1110 performs a 'p4 sync' to update that workspace with the newest files. 1111 1112 Each slave needs the following environment: 1113 1114 - PATH: the 'p4' binary must be on the slave's PATH 1115 - P4USER: each slave needs a distinct user account 1116 - P4CLIENT: each slave needs a distinct client specification 1117 1118 You should use 'p4 client' (?) to set up a client view spec which maps 1119 the desired files into $SLAVEBASE/$BUILDERBASE/source . 1120 """ 1121 1122 name = "p4sync" 1123
1124 - def __init__(self, p4port, p4user, p4passwd, p4client, **kwargs):
1125 assert kwargs['mode'] == "copy", "P4Sync can only be used in mode=copy" 1126 self.branch = None 1127 Source.__init__(self, **kwargs) 1128 self.addFactoryArguments(p4port=p4port, 1129 p4user=p4user, 1130 p4passwd=p4passwd, 1131 p4client=p4client, 1132 ) 1133 self.args['p4port'] = p4port 1134 self.args['p4user'] = p4user 1135 self.args['p4passwd'] = p4passwd 1136 self.args['p4client'] = p4client
1137
1138 - def computeSourceRevision(self, changes):
1139 if not changes: 1140 return None 1141 lastChange = max([int(c.revision) for c in changes]) 1142 return lastChange
1143
1144 - def startVC(self, branch, revision, patch):
1145 slavever = self.slaveVersion("p4sync") 1146 assert slavever, "slave is too old, does not know about p4" 1147 cmd = LoggedRemoteCommand("p4sync", self.args) 1148 self.startCommand(cmd)
1149