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
= upstream
323 self
.builderNames
= builderNames
325 def listBuilderNames(self
):
326 return self
.builderNames
328 def getPendingBuildTimes(self
):
329 # report the upstream's value
330 return self
.upstream
.getPendingBuildTimes()
332 def startService(self
):
333 service
.MultiService
.startService(self
)
334 self
.upstream
.subscribeToSuccessfulBuilds(self
.upstreamBuilt
)
336 def stopService(self
):
337 d
= service
.MultiService
.stopService(self
)
338 self
.upstream
.unsubscribeToSuccessfulBuilds(self
.upstreamBuilt
)
341 def upstreamBuilt(self
, ss
):
342 bs
= buildset
.BuildSet(self
.builderNames
, ss
,
343 properties
=self
.properties
)
344 self
.submitBuildSet(bs
)
346 def checkUpstreamScheduler(self
):
347 # find our *active* upstream scheduler (which may not be self.upstream!) by name
348 up_name
= self
.upstream
.name
350 for s
in self
.parent
.allSchedulers():
351 if s
.name
== up_name
and interfaces
.IUpstreamScheduler
.providedBy(s
):
354 log
.msg("ERROR: Couldn't find upstream scheduler of name <%s>" %
357 # if it's already correct, we're good to go
358 if upstream
is self
.upstream
:
361 # otherwise, associate with the new upstream. We also keep listening
362 # to the old upstream, in case it's in the middle of a build
363 upstream
.subscribeToSuccessfulBuilds(self
.upstreamBuilt
)
364 self
.upstream
= upstream
365 log
.msg("Dependent <%s> connected to new Upstream <%s>" %
366 (self
.name
, up_name
))
368 class Periodic(BaseUpstreamScheduler
):
369 """Instead of watching for Changes, this Scheduler can just start a build
370 at fixed intervals. The C{periodicBuildTimer} parameter sets the number
371 of seconds to wait between such periodic builds. The first build will be
374 # TODO: consider having this watch another (changed-based) scheduler and
375 # merely enforce a minimum time between builds.
377 compare_attrs
= ('name', 'builderNames', 'periodicBuildTimer', 'branch', 'properties')
379 def __init__(self
, name
, builderNames
, periodicBuildTimer
,
380 branch
=None, properties
={}):
381 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
382 self
.builderNames
= builderNames
383 self
.periodicBuildTimer
= periodicBuildTimer
385 self
.reason
= ("The Periodic scheduler named '%s' triggered this build"
387 self
.timer
= internet
.TimerService(self
.periodicBuildTimer
,
388 self
.doPeriodicBuild
)
389 self
.timer
.setServiceParent(self
)
391 def listBuilderNames(self
):
392 return self
.builderNames
394 def getPendingBuildTimes(self
):
395 # TODO: figure out when self.timer is going to fire next and report
399 def doPeriodicBuild(self
):
400 bs
= buildset
.BuildSet(self
.builderNames
,
401 SourceStamp(branch
=self
.branch
),
403 properties
=self
.properties
)
404 self
.submitBuildSet(bs
)
408 class Nightly(BaseUpstreamScheduler
):
409 """Imitate 'cron' scheduling. This can be used to schedule a nightly
410 build, or one which runs are certain times of the day, week, or month.
412 Pass some subset of minute, hour, dayOfMonth, month, and dayOfWeek; each
413 may be a single number or a list of valid values. The builds will be
414 triggered whenever the current time matches these values. Wildcards are
415 represented by a '*' string. All fields default to a wildcard except
416 'minute', so with no fields this defaults to a build every hour, on the
419 For example, the following master.cfg clause will cause a build to be
420 started every night at 3:00am::
422 s = Nightly('nightly', ['builder1', 'builder2'], hour=3, minute=0)
423 c['schedules'].append(s)
425 This scheduler will perform a build each monday morning at 6:23am and
428 s = Nightly('BeforeWork', ['builder1'],
429 dayOfWeek=0, hour=[6,8], minute=23)
431 The following runs a build every two hours::
433 s = Nightly('every2hours', ['builder1'], hour=range(0, 24, 2))
435 And this one will run only on December 24th::
437 s = Nightly('SleighPreflightCheck', ['flying_circuits', 'radar'],
438 month=12, dayOfMonth=24, hour=12, minute=0)
440 For dayOfWeek and dayOfMonth, builds are triggered if the date matches
441 either of them. All time values are compared against the tuple returned
442 by time.localtime(), so month and dayOfMonth numbers start at 1, not
443 zero. dayOfWeek=0 is Monday, dayOfWeek=6 is Sunday.
445 onlyIfChanged functionality
446 s = Nightly('nightly', ['builder1', 'builder2'],
447 hour=3, minute=0, onlyIfChanged=True)
448 When the flag is True (False by default), the build is trigged if
449 the date matches and if the branch has changed
451 fileIsImportant parameter is implemented as defined in class Scheduler
454 compare_attrs
= ('name', 'builderNames',
455 'minute', 'hour', 'dayOfMonth', 'month',
456 'dayOfWeek', 'branch', 'onlyIfChanged',
457 'fileIsImportant', 'properties')
459 def __init__(self
, name
, builderNames
, minute
=0, hour
='*',
460 dayOfMonth
='*', month
='*', dayOfWeek
='*',
461 branch
=None, fileIsImportant
=None, onlyIfChanged
=False, properties
={}):
462 # Setting minute=0 really makes this an 'Hourly' scheduler. This
463 # seemed like a better default than minute='*', which would result in
464 # a build every 60 seconds.
465 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
466 self
.builderNames
= builderNames
469 self
.dayOfMonth
= dayOfMonth
471 self
.dayOfWeek
= dayOfWeek
473 self
.onlyIfChanged
= onlyIfChanged
474 self
.delayedRun
= None
475 self
.nextRunTime
= None
476 self
.reason
= ("The Nightly scheduler named '%s' triggered this build"
479 self
.importantChanges
= []
480 self
.unimportantChanges
= []
481 self
.fileIsImportant
= None
483 assert callable(fileIsImportant
)
484 self
.fileIsImportant
= fileIsImportant
486 def addTime(self
, timetuple
, secs
):
487 return time
.localtime(time
.mktime(timetuple
)+secs
)
488 def findFirstValueAtLeast(self
, values
, value
, default
=None):
490 if v
>= value
: return v
494 self
.nextRunTime
= self
.calculateNextRunTime()
495 self
.delayedRun
= reactor
.callLater(self
.nextRunTime
- time
.time(),
496 self
.doPeriodicBuild
)
498 def startService(self
):
499 BaseUpstreamScheduler
.startService(self
)
502 def stopService(self
):
503 BaseUpstreamScheduler
.stopService(self
)
504 self
.delayedRun
.cancel()
506 def isRunTime(self
, timetuple
):
507 def check(ourvalue
, value
):
508 if ourvalue
== '*': return True
509 if isinstance(ourvalue
, int): return value
== ourvalue
510 return (value
in ourvalue
)
512 if not check(self
.minute
, timetuple
[4]):
513 #print 'bad minute', timetuple[4], self.minute
516 if not check(self
.hour
, timetuple
[3]):
517 #print 'bad hour', timetuple[3], self.hour
520 if not check(self
.month
, timetuple
[1]):
521 #print 'bad month', timetuple[1], self.month
524 if self
.dayOfMonth
!= '*' and self
.dayOfWeek
!= '*':
525 # They specified both day(s) of month AND day(s) of week.
526 # This means that we only have to match one of the two. If
527 # neither one matches, this time is not the right time.
528 if not (check(self
.dayOfMonth
, timetuple
[2]) or
529 check(self
.dayOfWeek
, timetuple
[6])):
533 if not check(self
.dayOfMonth
, timetuple
[2]):
534 #print 'bad day of month'
537 if not check(self
.dayOfWeek
, timetuple
[6]):
538 #print 'bad day of week'
543 def calculateNextRunTime(self
):
544 return self
.calculateNextRunTimeFrom(time
.time())
546 def calculateNextRunTimeFrom(self
, now
):
547 dateTime
= time
.localtime(now
)
549 # Remove seconds by advancing to at least the next minue
550 dateTime
= self
.addTime(dateTime
, 60-dateTime
[5])
552 # Now we just keep adding minutes until we find something that matches
554 # It not an efficient algorithm, but it'll *work* for now
555 yearLimit
= dateTime
[0]+2
556 while not self
.isRunTime(dateTime
):
557 dateTime
= self
.addTime(dateTime
, 60)
558 #print 'Trying', time.asctime(dateTime)
559 assert dateTime
[0] < yearLimit
, 'Something is wrong with this code'
560 return time
.mktime(dateTime
)
562 def listBuilderNames(self
):
563 return self
.builderNames
565 def getPendingBuildTimes(self
):
566 # TODO: figure out when self.timer is going to fire next and report
568 if self
.nextRunTime
is None: return []
569 return [self
.nextRunTime
]
571 def doPeriodicBuild(self
):
572 # Schedule the next run
575 if self
.onlyIfChanged
:
576 if len(self
.importantChanges
) > 0:
577 changes
= self
.importantChanges
+ self
.unimportantChanges
578 # And trigger a build
579 log
.msg("Nightly Scheduler <%s>: triggering build" % self
.name
)
580 bs
= buildset
.BuildSet(self
.builderNames
,
581 SourceStamp(changes
=changes
),
583 properties
=self
.properties
)
584 self
.submitBuildSet(bs
)
585 # Reset the change lists
586 self
.importantChanges
= []
587 self
.unimportantChanges
= []
589 log
.msg("Nightly Scheduler <%s>: skipping build - No important change" % self
.name
)
591 # And trigger a build
592 bs
= buildset
.BuildSet(self
.builderNames
,
593 SourceStamp(branch
=self
.branch
),
595 properties
=self
.properties
)
596 self
.submitBuildSet(bs
)
598 def addChange(self
, change
):
599 if self
.onlyIfChanged
:
600 if change
.branch
!= self
.branch
:
601 log
.msg("Nightly Scheduler <%s>: ignoring change %d on off-branch %s" % (self
.name
, change
.revision
, change
.branch
))
603 if not self
.fileIsImportant
:
604 self
.addImportantChange(change
)
605 elif self
.fileIsImportant(change
):
606 self
.addImportantChange(change
)
608 self
.addUnimportantChange(change
)
610 log
.msg("Nightly Scheduler <%s>: no add change" % self
.name
)
613 def addImportantChange(self
, change
):
614 log
.msg("Nightly Scheduler <%s>: change %s from %s is important, adding it" % (self
.name
, change
.revision
, change
.who
))
615 self
.importantChanges
.append(change
)
617 def addUnimportantChange(self
, change
):
618 log
.msg("Nightly Scheduler <%s>: change %s from %s is not important, adding it" % (self
.name
, change
.revision
, change
.who
))
619 self
.unimportantChanges
.append(change
)
622 class TryBase(BaseScheduler
):
623 def __init__(self
, name
, builderNames
, properties
={}):
624 BaseScheduler
.__init
__(self
, name
, properties
)
625 self
.builderNames
= builderNames
627 def listBuilderNames(self
):
628 return self
.builderNames
630 def getPendingBuildTimes(self
):
631 # we can't predict what the developers are going to do in the future
634 def addChange(self
, change
):
635 # Try schedulers ignore Changes
638 def processBuilderList(self
, builderNames
):
639 # self.builderNames is the configured list of builders
640 # available for try. If the user supplies a list of builders,
641 # it must be restricted to the configured list. If not, build
642 # on all of the configured builders.
644 for b
in builderNames
:
645 if not b
in self
.builderNames
:
646 log
.msg("%s got with builder %s" % (self
, b
))
647 log
.msg(" but that wasn't in our list: %s"
648 % (self
.builderNames
,))
651 builderNames
= self
.builderNames
654 class BadJobfile(Exception):
657 class JobFileScanner(basic
.NetstringReceiver
):
660 self
.transport
= self
# so transport.loseConnection works
663 def stringReceived(self
, s
):
664 self
.strings
.append(s
)
666 def loseConnection(self
):
669 class Try_Jobdir(TryBase
):
670 compare_attrs
= ( 'name', 'builderNames', 'jobdir', 'properties' )
672 def __init__(self
, name
, builderNames
, jobdir
, properties
={}):
673 TryBase
.__init
__(self
, name
, builderNames
, properties
)
675 self
.watcher
= MaildirService()
676 self
.watcher
.setServiceParent(self
)
678 def setServiceParent(self
, parent
):
679 self
.watcher
.setBasedir(os
.path
.join(parent
.basedir
, self
.jobdir
))
680 TryBase
.setServiceParent(self
, parent
)
682 def parseJob(self
, f
):
683 # jobfiles are serialized build requests. Each is a list of
684 # serialized netstrings, in the following order:
685 # "1", the version number of this format
686 # buildsetID, arbitrary string, used to find the buildSet later
687 # branch name, "" for default-branch
688 # base revision, "" for HEAD
689 # patchlevel, usually "1"
693 p
.dataReceived(f
.read())
695 raise BadJobfile("unable to parse netstrings")
699 raise BadJobfile("unknown version '%s'" % ver
)
700 buildsetID
, branch
, baserev
, patchlevel
, diff
= s
[:5]
706 patchlevel
= int(patchlevel
)
707 patch
= (patchlevel
, diff
)
708 ss
= SourceStamp(branch
, baserev
, patch
)
709 return builderNames
, ss
, buildsetID
711 def messageReceived(self
, filename
):
712 md
= os
.path
.join(self
.parent
.basedir
, self
.jobdir
)
713 if runtime
.platformType
== "posix":
714 # open the file before moving it, because I'm afraid that once
715 # it's in cur/, someone might delete it at any moment
716 path
= os
.path
.join(md
, "new", filename
)
718 os
.rename(os
.path
.join(md
, "new", filename
),
719 os
.path
.join(md
, "cur", filename
))
721 # do this backwards under windows, because you can't move a file
722 # that somebody is holding open. This was causing a Permission
723 # Denied error on bear's win32-twisted1.3 buildslave.
724 os
.rename(os
.path
.join(md
, "new", filename
),
725 os
.path
.join(md
, "cur", filename
))
726 path
= os
.path
.join(md
, "cur", filename
)
730 builderNames
, ss
, bsid
= self
.parseJob(f
)
732 log
.msg("%s reports a bad jobfile in %s" % (self
, filename
))
735 # Validate/fixup the builder names.
736 builderNames
= self
.processBuilderList(builderNames
)
740 bs
= buildset
.BuildSet(builderNames
, ss
, reason
=reason
,
741 bsid
=bsid
, properties
=self
.properties
)
742 self
.submitBuildSet(bs
)
744 class Try_Userpass(TryBase
):
745 compare_attrs
= ( 'name', 'builderNames', 'port', 'userpass', 'properties' )
746 implements(portal
.IRealm
)
748 def __init__(self
, name
, builderNames
, port
, userpass
, properties
={}):
749 TryBase
.__init
__(self
, name
, builderNames
, properties
)
750 if type(port
) is int:
751 port
= "tcp:%d" % port
753 self
.userpass
= userpass
754 c
= checkers
.InMemoryUsernamePasswordDatabaseDontUse()
755 for user
,passwd
in self
.userpass
:
756 c
.addUser(user
, passwd
)
758 p
= portal
.Portal(self
)
760 f
= pb
.PBServerFactory(p
)
761 s
= strports
.service(port
, f
)
762 s
.setServiceParent(self
)
765 # utility method for tests: figure out which TCP port we just opened.
766 return self
.services
[0]._port
.getHost().port
768 def requestAvatar(self
, avatarID
, mind
, interface
):
769 log
.msg("%s got connection from user %s" % (self
, avatarID
))
770 assert interface
== pb
.IPerspective
771 p
= Try_Userpass_Perspective(self
, avatarID
)
772 return (pb
.IPerspective
, p
, lambda: None)
774 class Try_Userpass_Perspective(pbutil
.NewCredPerspective
):
775 def __init__(self
, parent
, username
):
777 self
.username
= username
779 def perspective_try(self
, branch
, revision
, patch
, builderNames
, properties
={}):
780 log
.msg("user %s requesting build on builders %s" % (self
.username
,
782 # Validate/fixup the builder names.
783 builderNames
= self
.parent
.processBuilderList(builderNames
)
786 ss
= SourceStamp(branch
, revision
, patch
)
787 reason
= "'try' job from user %s" % self
.username
789 # roll the specified props in with our inherited props
790 combined_props
= Properties()
791 combined_props
.updateFromProperties(self
.parent
.properties
)
792 combined_props
.update(properties
, "try build")
794 bs
= buildset
.BuildSet(builderNames
,
797 properties
=combined_props
)
799 self
.parent
.submitBuildSet(bs
)
801 # return a remotely-usable BuildSetStatus object
802 from buildbot
.status
.client
import makeRemote
803 return makeRemote(bs
.status
)
805 class Triggerable(BaseUpstreamScheduler
):
806 """This scheduler doesn't do anything until it is triggered by a Trigger
807 step in a factory. In general, that step will not complete until all of
808 the builds that I fire have finished.
811 compare_attrs
= ('name', 'builderNames', 'properties')
813 def __init__(self
, name
, builderNames
, properties
={}):
814 BaseUpstreamScheduler
.__init
__(self
, name
, properties
)
815 self
.builderNames
= builderNames
817 def listBuilderNames(self
):
818 return self
.builderNames
820 def getPendingBuildTimes(self
):
823 def trigger(self
, ss
, set_props
=None):
824 """Trigger this scheduler. Returns a deferred that will fire when the
825 buildset is finished.
828 # properties for this buildset are composed of our own properties,
829 # potentially overridden by anything from the triggering build
831 props
.updateFromProperties(self
.properties
)
832 if set_props
: props
.updateFromProperties(set_props
)
834 bs
= buildset
.BuildSet(self
.builderNames
, ss
, properties
=props
)
835 d
= bs
.waitUntilFinished()
836 self
.submitBuildSet(bs
)