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 |