130
130
'periodicBuildTime': job.frequency})
133
def productJobsDict(jobs):
134
"""Mapping of products to jobs.
136
:type jobs: iterable of importd.Job.Job
137
:return: mapping from product ids to jobs.
138
:rtype: dict of int to buildbot.importd.Job.Job
142
product_jobs = by_product.get(job.product_id, [])
143
product_jobs.append(job)
144
by_product[job.product_id] = product_jobs
147
def anonymousInterlock(jobs):
148
"""Create anonymous interlock between for sources and packages.
150
:type jobs: iterable of importd.Job.Job
151
:return: anonymous interlock ([sources], [packages]), or ``None`` if there
152
are no sources or no packages.
153
:rtype: 2-tuple of lists of importd.Job.Job or ``None``
155
sources, packages = [], []
157
if job.RCS == 'package':
158
packages.append(job.name)
159
elif job.RCS in ('cvs', 'svn'):
160
sources.append(job.name)
162
raise RuntimeError('RCS of job %r is %r'
163
% (job.name, job.RCS))
164
if len(sources) == 0 or len(packages) == 0:
167
return sources, packages
169
def nameInterlock(product_id, interlock):
170
"""Put the product name into an anonymous interlock.
172
:param product_id: id of the product associated to the interlock
173
:param interlock: ([feeders], [watchers]) pair
174
:precondition: all jobs in the interlock have product_id.
175
:return: named interlock, ("name", [feeders], [watchers])
177
feeders, watchers = interlock
178
name = Product.get(product_id).name
179
return name, feeders, watchers
181
def jobsInterlocks(jobs):
182
"""Create interlocks for the given jobs.
184
:param jobs: jobs to create interlocks for.
185
:type jobs: iterable of importd.Job.Job
186
:return: interlocks for BuildmasterConfig.
187
:rtype: list of (name, [feeders], [watchers]) tuples
190
by_product = productJobsDict(jobs)
191
for product_id, product_jobs in by_product.items():
192
anon = anonymousInterlock(product_jobs)
194
inter = nameInterlock(product_id, anon)
195
interlocks.append(inter)
198
def processDir(builders, log, basedir, tree, slavename, slave_home,
200
log.msg("checking dir " + basedir)
201
builder = JobBuilder()
202
for name in tree.iter_inventory(source=True, files=True):
203
jobs = builder.jobFromFile(os.path.join(basedir,name))
204
for (job, jobname) in jobs:
205
job.jobname = jobname
207
job.slave_home=slave_home
208
job.archive_mirror_dir = archive_mirror_dir
209
f1 = ImportDBuildFactory(job, jobname)
210
builders.append((jobname, slavename, "buildbot-jobs", f1,
211
{'periodicBuildTime':job.frequency}))
214
135
from twisted.python.failure import Failure
216
class ImportDBuild(ConfigurableBuild):
217
"""I am a build that informs the database of success and failure."""
137
class NotifyingBuild(ConfigurableBuild):
138
"""Build that notifies of starts and finishes and can refresh itself.
141
def getObserver(self):
142
raise NotImplementedError
219
144
def startBuild(self, remote, progress):
220
145
self.__finished = False
222
ImportDBImplementor(self).startBuild()
147
self.getObserver().startBuild()
225
150
tryToAbortTransaction()
239
164
self.buildException(f, "buildFinished")
240
165
ConfigurableBuild.buildFinished(self, event, successful)
167
def refreshBuilder(self, rerun, periodic):
168
self.builder.stopPeriodicBuildTimer()
169
# change the builder and run it again after the buildFinished
171
# XXX: This should not be needed. It is needed because we are
172
# in a deep call stack which will call into
173
# self.build.builder.expectations which is currently coupled
174
# to the value of self.build.builder.steps.
175
# If this is fixed, we can simply call self.refreshBuilder().
176
reactor.callLater(1, self.refreshBuilderDelayed, rerun, periodic)
178
def refreshBuilderDelayed(self, rerun, periodic):
179
"""refresh the builder and then force a build"""
180
# This might be better as a helper function in the module, but that
181
# feels more unclean than duplicating these lines as they come from
182
# several not-well-connected places
183
self.builder.buildFactory.steps = []
184
self.builder.buildFactory.addSteps()
185
self.builder.waiting = self.builder.newBuild()
186
self.builder.expectations = None
187
p = self.builder.waiting.setupProgress()
189
self.builder.expectations = Expectations(p)
190
self.builder.periodicBuildTime = periodic
191
self.builder.startPeriodicBuildTimer()
193
self.builder.forceBuild("botmaster", "import completed",
197
class ImportDBuild(NotifyingBuild):
198
"""Build that updates the database with RCS import status."""
200
def getObserver(self):
201
return ImportDBImplementor(self)
243
204
class ImportDBImplementor(object):
244
205
"""Implementation of the ImportDBuild behaviour, separates namespaces.
310
271
self.refreshBuilder(rerun = True)
312
273
def refreshBuilder(self, rerun):
313
self.build.builder.stopPeriodicBuildTimer()
314
# change the builder and run it again after the buildFinished
316
# XXX: This should not be needed. It is needed because we are
317
# in a deep call stack which will call into
318
# self.build.builder.expectations which is currently coupled
319
# to the value of self.build.builder.steps.
320
# If this is fixed, we can simply call self.refreshBuilder().
321
reactor.callLater(1, self.refreshBuilderDelayed, rerun)
323
def refreshBuilderDelayed(self, rerun):
324
"""refresh the builder and then force a build"""
325
job=self.build.importDJob
326
# This might be better as a helper function in the module, but that
327
# feels more unclean than duplicating these lines as they come from
328
# several not-well-connected places
329
self.build.builder.buildFactory.steps = []
330
self.build.builder.buildFactory.addSteps()
331
self.build.builder.waiting = self.build.builder.newBuild()
332
self.build.builder.expectations = None
333
p = self.build.builder.waiting.setupProgress()
335
self.build.builder.expectations = Expectations(p)
336
self.build.builder.periodicBuildTime = self.build.importDJob.frequency
337
self.build.builder.startPeriodicBuildTimer()
339
self.build.builder.forceBuild("botmaster", "import completed",
274
periodic = self.build.importDJob.frequency
275
self.build.refreshBuilder(rerun=rerun, periodic=periodic)
343
278
class ImportDBuildFactory(ConfigurableBuildFactory):
363
296
self.addImportDStep('mirrorTarget')
365
298
def addImportDStep(self, method):
366
steps.append((RunJobStep, {
368
'method': 'mirrorTarget',
369
'workdir': self.jobfile}))
299
raise NotImplementedError
371
301
def newBuild(self):
372
result=ConfigurableBuildFactory.newBuild(self)
373
result.importDJob=self.job
302
# Save the job inside the build, so the startBuild and buildFinished
303
# handlers can use it
304
result = ConfigurableBuildFactory.newBuild(self)
305
result.importDJob = self.job
527
459
# might raise UnknownCommand if it isn't implemented
528
460
d.addErrback(self.stepFailed)
531
class RunJobStep(JobBuildStep):
532
""" I 'run' a job as a single operation"""
534
def __init__(self, job, method,workdir=".", **kwargs):
535
JobBuildStep.__init__(self, **kwargs)
540
self.name="%s" % self.words()
543
return ["runJobStep %s" % self.method]
546
assert(self.job != None)
547
args = {'job': self.job,
548
'method': self.method,
552
#here, these are all passed to the child's copy of <class>. There is
553
# a case based constructor on the child, which is used to determine
554
# the class to instantiate.
556
#args = {'command': self.command,
557
# 'dir': self.workdir,
559
# # env, want_stdout, want_stderr
560
# 'timeout': self.timeout
562
d = self.remote.callRemote("startCommand", self, self.stepId,
564
# might raise UnknownCommand if it isn't implemented
565
d.addErrback(self.stepFailed)
569
known_keys=["TYPE","RCS", "repository", "module", "category",
570
"archivename", "branchfrom", "branchto", "frequency"]
572
_re_key = re.compile(r'^[ \t]*([a-zA-z]*)[ \t]*=[ \t]*([^ \t].*)[ \t]*$')
573
_re_job = re.compile(r'^.*\.info$')
576
"""setup needed tools"""
578
from importd import LoggingLogAdaptor
579
self.logger=logging.Logger("JobBuilder")
580
self.logger.addHandler(LoggingLogAdaptor(log))
582
def jobFromFile(self, filename):
583
"""I parse .job files and return Job objects"""
584
if not self._re_job.match(filename): return []
587
log.msg("Building from " + filename)
588
info = info2job.read_info(filename, self.logger)
589
return [ (x, info2job.jobfile_name(info, x))
590
for x in info2job.iter_jobs(info, self.logger)]