1 # -*- test-case-name: buildbot.test.test_dependencies -*-
5 from zope
.interface
import implements
6 from twisted
.internet
import reactor
7 from twisted
.application
import service
, internet
, strports
8 from twisted
.python
import log
, runtime
9 from twisted
.protocols
import basic
10 from twisted
.cred
import portal
, checkers
11 from twisted
.spread
import pb
13 from buildbot
import interfaces
, buildset
, util
, pbutil
14 from buildbot
.status
import builder
15 from buildbot
.sourcestamp
import SourceStamp
16 from buildbot
.changes
.maildir
import MaildirService
17 from buildbot
.process
.properties
import Properties
20 class BaseScheduler(service
.MultiService
, util
.ComparableMixin
):
22 A Schduler creates BuildSets and submits them to the BuildMaster.
24 @ivar name: name of the scheduler
26 @ivar properties: additional properties specified in this
27 scheduler's configuration
28 @type properties: Properties object
30 implements(interfaces
.IScheduler
)
32 def __init__(self
, name
, properties
={}):
34 @param name: name for this scheduler
36 @param properties: properties to be propagated from this scheduler
37 @type properties: dict
39 service
.MultiService
.__init
__(self
)
41 self
.properties
= Properties()
42 self
.properties
.update(properties
, "Scheduler")
43 self
.properties
.setProperty("scheduler", name
, "Scheduler")
46 # TODO: why can't id() return a positive number? %d is ugly.
47 return "<Scheduler '%s' at %d>" % (self
.name
, id(self
))
49 def submitBuildSet(self
, bs
):
50 self
.parent
.submitBuildSet(bs
)
52 def addChange(self
, change
):
55 class BaseUpstreamScheduler(BaseScheduler
):
56 implements(interfaces
.IUpstreamScheduler
)
58 def __init__(self
, name
, properties
={}):
59 BaseScheduler
.__init
__(self
, name
, properties
)
60 self
.successWatchers
= []
62 def subscribeToSuccessfulBuilds(self
, watcher
):
63 self
.successWatchers
.append(watcher
)
64 def unsubscribeToSuccessfulBuilds(self
, watcher
):
65 self
.successWatchers
.remove(watcher
)
67 def submitBuildSet(self
, bs
):
68 d
= bs
.waitUntilFinished()
69 d
.addCallback(self
.buildSetFinished
)
70 BaseScheduler
.submitBuildSet(self
, bs
)
72 def buildSetFinished(self
, bss
):
75 if bss
.getResults() == builder
.SUCCESS
:
76 ss
= bss
.getSourceStamp()
77 for w
in self
.successWatchers
:
81 class Scheduler(BaseUpstreamScheduler
):
82 """The default Scheduler class will run a build after some period of time
83 called the C{treeStableTimer}, on a given set of Builders. It only pays
84 attention to a single branch. You you can provide a C{fileIsImportant}
85 function which will evaluate each Change to decide whether or not it
86 should trigger a new build.
89 fileIsImportant
= None
90 compare_attrs
= ('name', 'treeStableTimer', 'builderNames', 'branch',
91 'fileIsImportant', 'properties', 'categories')
93 def __init__(self
, name
, branch
, treeStableTimer
, builderNames
,
94 fileIsImportant
=None, properties
={}, categories
=None):
96 @param name: the name of this Scheduler
97 @param branch: The branch name that the Scheduler should pay
98 attention to. Any Change that is not on this branch
99 will be ignored. It can be set to None to only pay
100 attention to the default branch.
101 @param treeStableTimer: the duration, in seconds, for which the tree
102 must remain unchanged before a build will be
103 triggered. This is intended to avoid builds
104 of partially-committed fixes.
105 @param builderNames: a list of Builder names. When this Scheduler
106 decides to start a set of builds, they will be
107 run on the Builders named by this list.
109 @param fileIsImportant: A callable which takes one argument (a Change
110 instance) and returns True if the change is
111 worth building, and False if it is not.
112 Unimportant Changes are accumulated until the
113 build is triggered by an important change.
114 The default value of None means that all
115 Changes are important.
117 @param properties: properties to apply to all builds started from this
119 @param categories: A list of categories of changes to accept
122 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
123 self
.treeStableTimer
= treeStableTimer
124 errmsg
= ("The builderNames= argument to Scheduler must be a list "
125 "of Builder description names (i.e. the 'name' key of the "
126 "Builder specification dictionary)")
127 assert isinstance(builderNames
, (list, tuple)), errmsg
128 for b
in builderNames
:
129 assert isinstance(b
, str), errmsg
130 self
.builderNames
= builderNames
133 assert callable(fileIsImportant
)
134 self
.fileIsImportant
= fileIsImportant
136 self
.importantChanges
= []
137 self
.unimportantChanges
= []
138 self
.nextBuildTime
= None
140 self
.categories
= categories
142 def listBuilderNames(self
):
143 return self
.builderNames
145 def getPendingBuildTimes(self
):
146 if self
.nextBuildTime
is not None:
147 return [self
.nextBuildTime
]
150 def addChange(self
, change
):
151 if change
.branch
!= self
.branch
:
152 log
.msg("%s ignoring off-branch %s" % (self
, change
))
154 if self
.categories
is not None and change
.category
not in self
.categories
:
155 log
.msg("%s ignoring non-matching categories %s" % (self
, change
))
157 if not self
.fileIsImportant
:
158 self
.addImportantChange(change
)
159 elif self
.fileIsImportant(change
):
160 self
.addImportantChange(change
)
162 self
.addUnimportantChange(change
)
164 def addImportantChange(self
, change
):
165 log
.msg("%s: change is important, adding %s" % (self
, change
))
166 self
.importantChanges
.append(change
)
167 self
.nextBuildTime
= max(self
.nextBuildTime
,
168 change
.when
+ self
.treeStableTimer
)
169 self
.setTimer(self
.nextBuildTime
)
171 def addUnimportantChange(self
, change
):
172 log
.msg("%s: change is not important, adding %s" % (self
, change
))
173 self
.unimportantChanges
.append(change
)
175 def setTimer(self
, when
):
176 log
.msg("%s: setting timer to %s" %
177 (self
, time
.strftime("%H:%M:%S", time
.localtime(when
))))
183 self
.timer
= reactor
.callLater(when
- now
, self
.fireTimer
)
191 # clear out our state
193 self
.nextBuildTime
= None
194 changes
= self
.importantChanges
+ self
.unimportantChanges
195 self
.importantChanges
= []
196 self
.unimportantChanges
= []
198 # create a BuildSet, submit it to the BuildMaster
199 bs
= buildset
.BuildSet(self
.builderNames
,
200 SourceStamp(changes
=changes
),
201 properties
=self
.properties
)
202 self
.submitBuildSet(bs
)
204 def stopService(self
):
206 return service
.MultiService
.stopService(self
)
209 class AnyBranchScheduler(BaseUpstreamScheduler
):
210 """This Scheduler will handle changes on a variety of branches. It will
211 accumulate Changes for each branch separately. It works by creating a
212 separate Scheduler for each new branch it sees."""
214 schedulerFactory
= Scheduler
215 fileIsImportant
= None
217 compare_attrs
= ('name', 'branches', 'treeStableTimer', 'builderNames',
218 'fileIsImportant', 'properties')
220 def __init__(self
, name
, branches
, treeStableTimer
, builderNames
,
221 fileIsImportant
=None, properties
={}):
223 @param name: the name of this Scheduler
224 @param branches: The branch names that the Scheduler should pay
225 attention to. Any Change that is not on one of these
226 branches will be ignored. It can be set to None to
227 accept changes from any branch. Don't use [] (an
228 empty list), because that means we don't pay
229 attention to *any* branches, so we'll never build
231 @param treeStableTimer: the duration, in seconds, for which the tree
232 must remain unchanged before a build will be
233 triggered. This is intended to avoid builds
234 of partially-committed fixes.
235 @param builderNames: a list of Builder names. When this Scheduler
236 decides to start a set of builds, they will be
237 run on the Builders named by this list.
239 @param fileIsImportant: A callable which takes one argument (a Change
240 instance) and returns True if the change is
241 worth building, and False if it is not.
242 Unimportant Changes are accumulated until the
243 build is triggered by an important change.
244 The default value of None means that all
245 Changes are important.
247 @param properties: properties to apply to all builds started from this
251 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
252 self
.treeStableTimer
= treeStableTimer
253 for b
in builderNames
:
254 assert isinstance(b
, str)
255 self
.builderNames
= builderNames
256 self
.branches
= branches
257 if self
.branches
== []:
258 log
.msg("AnyBranchScheduler %s: branches=[], so we will ignore "
259 "all branches, and never trigger any builds. Please set "
260 "branches=None to mean 'all branches'" % self
)
261 # consider raising an exception here, to make this warning more
262 # prominent, but I can vaguely imagine situations where you might
263 # want to comment out branches temporarily and wouldn't
264 # appreciate it being treated as an error.
266 assert callable(fileIsImportant
)
267 self
.fileIsImportant
= fileIsImportant
268 self
.schedulers
= {} # one per branch
271 return "<AnyBranchScheduler '%s'>" % self
.name
273 def listBuilderNames(self
):
274 return self
.builderNames
276 def getPendingBuildTimes(self
):
278 for s
in self
.schedulers
.values():
279 if s
.nextBuildTime
is not None:
280 bts
.append(s
.nextBuildTime
)
283 def buildSetFinished(self
, bss
):
284 # we don't care if a build has finished; one of the per-branch builders
285 # will take care of it, instead.
288 def addChange(self
, change
):
289 branch
= change
.branch
290 if self
.branches
is not None and branch
not in self
.branches
:
291 log
.msg("%s ignoring off-branch %s" % (self
, change
))
293 s
= self
.schedulers
.get(branch
)
296 name
= self
.name
+ "." + branch
298 name
= self
.name
+ ".<default>"
299 s
= self
.schedulerFactory(name
, branch
,
300 self
.treeStableTimer
,
302 self
.fileIsImportant
)
303 s
.successWatchers
= self
.successWatchers
304 s
.setServiceParent(self
)
305 s
.properties
= self
.properties
306 # TODO: does this result in schedulers that stack up forever?
307 # When I make the persistify-pass, think about this some more.
308 self
.schedulers
[branch
] = s
312 class Dependent(BaseUpstreamScheduler
):
313 """This scheduler runs some set of 'downstream' builds when the
314 'upstream' scheduler has completed successfully."""
315 implements(interfaces
.IDownstreamScheduler
)
317 compare_attrs
= ('name', 'upstream', 'builderNames', 'properties')
319 def __init__(self
, name
, upstream
, builderNames
, properties
={}):
320 assert interfaces
.IUpstreamScheduler
.providedBy(upstream
)
321 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
322 self
.upstream_name
= upstream
.name
324 self
.builderNames
= builderNames
326 def listBuilderNames(self
):
327 return self
.builderNames
329 def getPendingBuildTimes(self
):
330 # report the upstream's value
331 return self
.getUpstreamScheduler().getPendingBuildTimes()
333 def startService(self
):
334 service
.MultiService
.startService(self
)
335 self
.upstream
= self
.findUpstreamScheduler()
336 self
.upstream
.subscribeToSuccessfulBuilds(self
.upstreamBuilt
)
338 def stopService(self
):
339 d
= service
.MultiService
.stopService(self
)
340 self
.upstream
.unsubscribeToSuccessfulBuilds(self
.upstreamBuilt
)
344 def upstreamBuilt(self
, ss
):
345 bs
= buildset
.BuildSet(self
.builderNames
, ss
,
346 properties
=self
.properties
)
347 self
.submitBuildSet(bs
)
349 def findUpstreamScheduler(self
):
350 # find our *active* upstream scheduler (which may not be self.upstream!) by name
352 for s
in self
.parent
.allSchedulers():
353 if s
.name
== self
.upstream_name
and interfaces
.IUpstreamScheduler
.providedBy(s
):
356 log
.msg("ERROR: Couldn't find upstream scheduler of name <%s>" %
360 def checkUpstreamScheduler(self
):
361 # if we don't already have an upstream, then there's nothing to worry about
362 if not self
.upstream
:
365 upstream
= self
.findUpstreamScheduler()
367 # if it's already correct, we're good to go
368 if upstream
is self
.upstream
:
371 # otherwise, associate with the new upstream. We also keep listening
372 # to the old upstream, in case it's in the middle of a build
373 upstream
.subscribeToSuccessfulBuilds(self
.upstreamBuilt
)
374 self
.upstream
= upstream
375 log
.msg("Dependent <%s> connected to new Upstream <%s>" %
376 (self
.name
, up_name
))
378 class Periodic(BaseUpstreamScheduler
):
379 """Instead of watching for Changes, this Scheduler can just start a build
380 at fixed intervals. The C{periodicBuildTimer} parameter sets the number
381 of seconds to wait between such periodic builds. The first build will be
384 # TODO: consider having this watch another (changed-based) scheduler and
385 # merely enforce a minimum time between builds.
387 compare_attrs
= ('name', 'builderNames', 'periodicBuildTimer', 'branch', 'properties')
389 def __init__(self
, name
, builderNames
, periodicBuildTimer
,
390 branch
=None, properties
={}):
391 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
392 self
.builderNames
= builderNames
393 self
.periodicBuildTimer
= periodicBuildTimer
395 self
.reason
= ("The Periodic scheduler named '%s' triggered this build"
397 self
.timer
= internet
.TimerService(self
.periodicBuildTimer
,
398 self
.doPeriodicBuild
)
399 self
.timer
.setServiceParent(self
)
401 def listBuilderNames(self
):
402 return self
.builderNames
404 def getPendingBuildTimes(self
):
405 # TODO: figure out when self.timer is going to fire next and report
409 def doPeriodicBuild(self
):
410 bs
= buildset
.BuildSet(self
.builderNames
,
411 SourceStamp(branch
=self
.branch
),
413 properties
=self
.properties
)
414 self
.submitBuildSet(bs
)
418 class Nightly(BaseUpstreamScheduler
):
419 """Imitate 'cron' scheduling. This can be used to schedule a nightly
420 build, or one which runs are certain times of the day, week, or month.
422 Pass some subset of minute, hour, dayOfMonth, month, and dayOfWeek; each
423 may be a single number or a list of valid values. The builds will be
424 triggered whenever the current time matches these values. Wildcards are
425 represented by a '*' string. All fields default to a wildcard except
426 'minute', so with no fields this defaults to a build every hour, on the
429 For example, the following master.cfg clause will cause a build to be
430 started every night at 3:00am::
432 s = Nightly('nightly', ['builder1', 'builder2'], hour=3, minute=0)
433 c['schedules'].append(s)
435 This scheduler will perform a build each monday morning at 6:23am and
438 s = Nightly('BeforeWork', ['builder1'],
439 dayOfWeek=0, hour=[6,8], minute=23)
441 The following runs a build every two hours::
443 s = Nightly('every2hours', ['builder1'], hour=range(0, 24, 2))
445 And this one will run only on December 24th::
447 s = Nightly('SleighPreflightCheck', ['flying_circuits', 'radar'],
448 month=12, dayOfMonth=24, hour=12, minute=0)
450 For dayOfWeek and dayOfMonth, builds are triggered if the date matches
451 either of them. All time values are compared against the tuple returned
452 by time.localtime(), so month and dayOfMonth numbers start at 1, not
453 zero. dayOfWeek=0 is Monday, dayOfWeek=6 is Sunday.
455 onlyIfChanged functionality
456 s = Nightly('nightly', ['builder1', 'builder2'],
457 hour=3, minute=0, onlyIfChanged=True)
458 When the flag is True (False by default), the build is trigged if
459 the date matches and if the branch has changed
461 fileIsImportant parameter is implemented as defined in class Scheduler
464 compare_attrs
= ('name', 'builderNames',
465 'minute', 'hour', 'dayOfMonth', 'month',
466 'dayOfWeek', 'branch', 'onlyIfChanged',
467 'fileIsImportant', 'properties')
469 def __init__(self
, name
, builderNames
, minute
=0, hour
='*',
470 dayOfMonth
='*', month
='*', dayOfWeek
='*',
471 branch
=None, fileIsImportant
=None, onlyIfChanged
=False, properties
={}):
472 # Setting minute=0 really makes this an 'Hourly' scheduler. This
473 # seemed like a better default than minute='*', which would result in
474 # a build every 60 seconds.
475 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
476 self
.builderNames
= builderNames
479 self
.dayOfMonth
= dayOfMonth
481 self
.dayOfWeek
= dayOfWeek
483 self
.onlyIfChanged
= onlyIfChanged
484 self
.delayedRun
= None
485 self
.nextRunTime
= None
486 self
.reason
= ("The Nightly scheduler named '%s' triggered this build"
489 self
.importantChanges
= []
490 self
.unimportantChanges
= []
491 self
.fileIsImportant
= None
493 assert callable(fileIsImportant
)
494 self
.fileIsImportant
= fileIsImportant
496 def addTime(self
, timetuple
, secs
):
497 return time
.localtime(time
.mktime(timetuple
)+secs
)
498 def findFirstValueAtLeast(self
, values
, value
, default
=None):
500 if v
>= value
: return v
504 self
.nextRunTime
= self
.calculateNextRunTime()
505 self
.delayedRun
= reactor
.callLater(self
.nextRunTime
- time
.time(),
506 self
.doPeriodicBuild
)
508 def startService(self
):
509 BaseUpstreamScheduler
.startService(self
)
512 def stopService(self
):
513 BaseUpstreamScheduler
.stopService(self
)
514 self
.delayedRun
.cancel()
516 def isRunTime(self
, timetuple
):
517 def check(ourvalue
, value
):
518 if ourvalue
== '*': return True
519 if isinstance(ourvalue
, int): return value
== ourvalue
520 return (value
in ourvalue
)
522 if not check(self
.minute
, timetuple
[4]):
523 #print 'bad minute', timetuple[4], self.minute
526 if not check(self
.hour
, timetuple
[3]):
527 #print 'bad hour', timetuple[3], self.hour
530 if not check(self
.month
, timetuple
[1]):
531 #print 'bad month', timetuple[1], self.month
534 if self
.dayOfMonth
!= '*' and self
.dayOfWeek
!= '*':
535 # They specified both day(s) of month AND day(s) of week.
536 # This means that we only have to match one of the two. If
537 # neither one matches, this time is not the right time.
538 if not (check(self
.dayOfMonth
, timetuple
[2]) or
539 check(self
.dayOfWeek
, timetuple
[6])):
543 if not check(self
.dayOfMonth
, timetuple
[2]):
544 #print 'bad day of month'
547 if not check(self
.dayOfWeek
, timetuple
[6]):
548 #print 'bad day of week'
553 def calculateNextRunTime(self
):
554 return self
.calculateNextRunTimeFrom(time
.time())
556 def calculateNextRunTimeFrom(self
, now
):
557 dateTime
= time
.localtime(now
)
559 # Remove seconds by advancing to at least the next minue
560 dateTime
= self
.addTime(dateTime
, 60-dateTime
[5])
562 # Now we just keep adding minutes until we find something that matches
564 # It not an efficient algorithm, but it'll *work* for now
565 yearLimit
= dateTime
[0]+2
566 while not self
.isRunTime(dateTime
):
567 dateTime
= self
.addTime(dateTime
, 60)
568 #print 'Trying', time.asctime(dateTime)
569 assert dateTime
[0] < yearLimit
, 'Something is wrong with this code'
570 return time
.mktime(dateTime
)
572 def listBuilderNames(self
):
573 return self
.builderNames
575 def getPendingBuildTimes(self
):
576 # TODO: figure out when self.timer is going to fire next and report
578 if self
.nextRunTime
is None: return []
579 return [self
.nextRunTime
]
581 def doPeriodicBuild(self
):
582 # Schedule the next run
585 if self
.onlyIfChanged
:
586 if len(self
.importantChanges
) > 0:
587 changes
= self
.importantChanges
+ self
.unimportantChanges
588 # And trigger a build
589 log
.msg("Nightly Scheduler <%s>: triggering build" % self
.name
)
590 bs
= buildset
.BuildSet(self
.builderNames
,
591 SourceStamp(changes
=changes
),
593 properties
=self
.properties
)
594 self
.submitBuildSet(bs
)
595 # Reset the change lists
596 self
.importantChanges
= []
597 self
.unimportantChanges
= []
599 log
.msg("Nightly Scheduler <%s>: skipping build - No important change" % self
.name
)
601 # And trigger a build
602 bs
= buildset
.BuildSet(self
.builderNames
,
603 SourceStamp(branch
=self
.branch
),
605 properties
=self
.properties
)
606 self
.submitBuildSet(bs
)
608 def addChange(self
, change
):
609 if self
.onlyIfChanged
:
610 if change
.branch
!= self
.branch
:
611 log
.msg("Nightly Scheduler <%s>: ignoring change %s on off-branch %s" % (self
.name
, change
.revision
, change
.branch
))
613 if not self
.fileIsImportant
:
614 self
.addImportantChange(change
)
615 elif self
.fileIsImportant(change
):
616 self
.addImportantChange(change
)
618 self
.addUnimportantChange(change
)
620 log
.msg("Nightly Scheduler <%s>: no add change" % self
.name
)
623 def addImportantChange(self
, change
):
624 log
.msg("Nightly Scheduler <%s>: change %s from %s is important, adding it" % (self
.name
, change
.revision
, change
.who
))
625 self
.importantChanges
.append(change
)
627 def addUnimportantChange(self
, change
):
628 log
.msg("Nightly Scheduler <%s>: change %s from %s is not important, adding it" % (self
.name
, change
.revision
, change
.who
))
629 self
.unimportantChanges
.append(change
)
632 class TryBase(BaseScheduler
):
633 def __init__(self
, name
, builderNames
, properties
={}):
634 BaseScheduler
.__init
__(self
, name
, properties
)
635 self
.builderNames
= builderNames
637 def listBuilderNames(self
):
638 return self
.builderNames
640 def getPendingBuildTimes(self
):
641 # we can't predict what the developers are going to do in the future
644 def addChange(self
, change
):
645 # Try schedulers ignore Changes
648 def processBuilderList(self
, builderNames
):
649 # self.builderNames is the configured list of builders
650 # available for try. If the user supplies a list of builders,
651 # it must be restricted to the configured list. If not, build
652 # on all of the configured builders.
654 for b
in builderNames
:
655 if not b
in self
.builderNames
:
656 log
.msg("%s got with builder %s" % (self
, b
))
657 log
.msg(" but that wasn't in our list: %s"
658 % (self
.builderNames
,))
661 builderNames
= self
.builderNames
664 class BadJobfile(Exception):
667 class JobFileScanner(basic
.NetstringReceiver
):
670 self
.transport
= self
# so transport.loseConnection works
673 def stringReceived(self
, s
):
674 self
.strings
.append(s
)
676 def loseConnection(self
):
679 class Try_Jobdir(TryBase
):
680 compare_attrs
= ( 'name', 'builderNames', 'jobdir', 'properties' )
682 def __init__(self
, name
, builderNames
, jobdir
, properties
={}):
683 TryBase
.__init
__(self
, name
, builderNames
, properties
)
685 self
.watcher
= MaildirService()
686 self
.watcher
.setServiceParent(self
)
688 def setServiceParent(self
, parent
):
689 self
.watcher
.setBasedir(os
.path
.join(parent
.basedir
, self
.jobdir
))
690 TryBase
.setServiceParent(self
, parent
)
692 def parseJob(self
, f
):
693 # jobfiles are serialized build requests. Each is a list of
694 # serialized netstrings, in the following order:
695 # "1", the version number of this format
696 # buildsetID, arbitrary string, used to find the buildSet later
697 # branch name, "" for default-branch
698 # base revision, "" for HEAD
699 # patchlevel, usually "1"
703 p
.dataReceived(f
.read())
705 raise BadJobfile("unable to parse netstrings")
709 raise BadJobfile("unknown version '%s'" % ver
)
710 buildsetID
, branch
, baserev
, patchlevel
, diff
= s
[:5]
716 patchlevel
= int(patchlevel
)
717 patch
= (patchlevel
, diff
)
718 ss
= SourceStamp(branch
, baserev
, patch
)
719 return builderNames
, ss
, buildsetID
721 def messageReceived(self
, filename
):
722 md
= os
.path
.join(self
.parent
.basedir
, self
.jobdir
)
723 if runtime
.platformType
== "posix":
724 # open the file before moving it, because I'm afraid that once
725 # it's in cur/, someone might delete it at any moment
726 path
= os
.path
.join(md
, "new", filename
)
728 os
.rename(os
.path
.join(md
, "new", filename
),
729 os
.path
.join(md
, "cur", filename
))
731 # do this backwards under windows, because you can't move a file
732 # that somebody is holding open. This was causing a Permission
733 # Denied error on bear's win32-twisted1.3 buildslave.
734 os
.rename(os
.path
.join(md
, "new", filename
),
735 os
.path
.join(md
, "cur", filename
))
736 path
= os
.path
.join(md
, "cur", filename
)
740 builderNames
, ss
, bsid
= self
.parseJob(f
)
742 log
.msg("%s reports a bad jobfile in %s" % (self
, filename
))
745 # Validate/fixup the builder names.
746 builderNames
= self
.processBuilderList(builderNames
)
750 bs
= buildset
.BuildSet(builderNames
, ss
, reason
=reason
,
751 bsid
=bsid
, properties
=self
.properties
)
752 self
.submitBuildSet(bs
)
754 class Try_Userpass(TryBase
):
755 compare_attrs
= ( 'name', 'builderNames', 'port', 'userpass', 'properties' )
756 implements(portal
.IRealm
)
758 def __init__(self
, name
, builderNames
, port
, userpass
, properties
={}):
759 TryBase
.__init
__(self
, name
, builderNames
, properties
)
760 if type(port
) is int:
761 port
= "tcp:%d" % port
763 self
.userpass
= userpass
764 c
= checkers
.InMemoryUsernamePasswordDatabaseDontUse()
765 for user
,passwd
in self
.userpass
:
766 c
.addUser(user
, passwd
)
768 p
= portal
.Portal(self
)
770 f
= pb
.PBServerFactory(p
)
771 s
= strports
.service(port
, f
)
772 s
.setServiceParent(self
)
775 # utility method for tests: figure out which TCP port we just opened.
776 return self
.services
[0]._port
.getHost().port
778 def requestAvatar(self
, avatarID
, mind
, interface
):
779 log
.msg("%s got connection from user %s" % (self
, avatarID
))
780 assert interface
== pb
.IPerspective
781 p
= Try_Userpass_Perspective(self
, avatarID
)
782 return (pb
.IPerspective
, p
, lambda: None)
784 class Try_Userpass_Perspective(pbutil
.NewCredPerspective
):
785 def __init__(self
, parent
, username
):
787 self
.username
= username
789 def perspective_try(self
, branch
, revision
, patch
, builderNames
, properties
={}):
790 log
.msg("user %s requesting build on builders %s" % (self
.username
,
792 # Validate/fixup the builder names.
793 builderNames
= self
.parent
.processBuilderList(builderNames
)
796 ss
= SourceStamp(branch
, revision
, patch
)
797 reason
= "'try' job from user %s" % self
.username
799 # roll the specified props in with our inherited props
800 combined_props
= Properties()
801 combined_props
.updateFromProperties(self
.parent
.properties
)
802 combined_props
.update(properties
, "try build")
804 bs
= buildset
.BuildSet(builderNames
,
807 properties
=combined_props
)
809 self
.parent
.submitBuildSet(bs
)
811 # return a remotely-usable BuildSetStatus object
812 from buildbot
.status
.client
import makeRemote
813 return makeRemote(bs
.status
)
815 class Triggerable(BaseUpstreamScheduler
):
816 """This scheduler doesn't do anything until it is triggered by a Trigger
817 step in a factory. In general, that step will not complete until all of
818 the builds that I fire have finished.
821 compare_attrs
= ('name', 'builderNames', 'properties')
823 def __init__(self
, name
, builderNames
, properties
={}):
824 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
825 self
.builderNames
= builderNames
827 def listBuilderNames(self
):
828 return self
.builderNames
830 def getPendingBuildTimes(self
):
833 def trigger(self
, ss
, set_props
=None):
834 """Trigger this scheduler. Returns a deferred that will fire when the
835 buildset is finished.
838 # properties for this buildset are composed of our own properties,
839 # potentially overridden by anything from the triggering build
841 props
.updateFromProperties(self
.properties
)
842 if set_props
: props
.updateFromProperties(set_props
)
844 bs
= buildset
.BuildSet(self
.builderNames
, ss
, properties
=props
)
845 d
= bs
.waitUntilFinished()
846 self
.submitBuildSet(bs
)