1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from twisted.python import log, failure
17 from twisted.internet import reactor
18 from buildbot.process.buildstep import BuildStep
19 from buildbot.status import builder, buildstep
20 from buildbot import config
21
23 """Raised by Blocker when it is passed an upstream step that cannot
24 be found or is in a bad state."""
25 pass
26
28 """
29 Build step that blocks until at least one other step finishes.
30
31 @ivar upstreamSteps: a non-empty list of (builderName, stepName) tuples
32 identifying the other build steps that must
33 complete in order to unblock this Blocker.
34 @ivar idlePolicy: string: what to do if one of the upstream builders is
35 idle when this Blocker starts; one of:
36 \"error\": just blow up (the Blocker will fail with
37 status EXCEPTION)
38 \"ignore\": carry on as if the referenced build step
39 was not mentioned (or is already complete)
40 \"block\": block until the referenced builder starts
41 a build, and then block until the referenced build
42 step in that build finishes
43 @ivar timeout: int: how long to block, in seconds, before giving up and
44 failing (default: None, meaning block forever)
45 """
46 parms = (BuildStep.parms +
47 ['upstreamSteps',
48 'idlePolicy',
49 'timeout',
50 ])
51
52 flunkOnFailure = True
53 upstreamSteps = None
54 idlePolicy = "block"
55 timeout = None
56
57 VALID_IDLE_POLICIES = ("error", "ignore", "block")
58
86
89
91 return "<%s %x: %s>" % (self.__class__.__name__, id(self), self.name)
92
93 - def _log(self, message, *args):
94 log.msg(repr(self) + ": " + (message % args))
95
97 """
98 Return true if buildStatus1 and buildStatus2 are from related
99 builds, i.e. a Blocker step running in buildStatus2 should be
100 blocked by an upstream step in buildStatus1. Return false if
101 they are unrelated.
102
103 Default implementation simply raises NotImplementedError: you
104 *must* subclass Blocker and implement this method, because
105 BuildBot currently provides no way to relate different builders.
106 This might change if ticket #875 (\"build flocks\") is
107 implemented.
108 """
109 raise NotImplementedError(
110 "abstract method: you must subclass Blocker "
111 "and implement buildsMatch()")
112
114 try:
115
116
117
118
119 builder = botmaster.builders[builderName]
120 except KeyError:
121 raise BadStepError(
122 "no builder named %r" % builderName)
123
124
125
126
127 myBuildStatus = self.build.getStatus()
128 builderStatus = builder.builder_status
129 matchingBuild = None
130
131
132
133
134
135
136
137
138
139
140
141 all_builds = (builderStatus.buildCache.values() +
142 builderStatus.getCurrentBuilds())
143
144 for buildStatus in all_builds:
145 if self.buildsMatch(myBuildStatus, buildStatus):
146 matchingBuild = buildStatus
147 break
148
149 if matchingBuild is None:
150 msg = "no matching builds found in builder %r" % builderName
151 if self.idlePolicy == "error":
152 raise BadStepError(msg + " (is it idle?)")
153 elif self.idlePolicy == "ignore":
154
155 self._log(msg + ": skipping it")
156 return None
157 elif self.idlePolicy == "block":
158 self._log(msg + ": will block until it starts a build")
159 self._blocking_builders.add(builderStatus)
160 return None
161
162 self._log("found builder %r: %r", builderName, builder)
163 return matchingBuild
164
174
184
186 return [self.name+":", "blocking on"] + self._getFullnames()
187
188 - def _getFinishStatusText(self, code, elapsed):
189 meaning = builder.Results[code]
190 text = [self.name+":",
191 "upstream %s" % meaning,
192 "after %.1f sec" % elapsed]
193 if code != builder.SUCCESS:
194 text += self._getFullnames()
195 return text
196
198 return [self.name+":", "timed out", "(%.1f sec)" % self.timeout]
199
201 self.step_status.setText(self._getBlockingStatusText())
202
203 if self.timeout is not None:
204 self._timer = reactor.callLater(self.timeout, self._timeoutExpired)
205
206 self._log("searching for upstream build steps")
207 botmaster = self.build.slavebuilder.slave.parent
208 errors = []
209 for (builderName, stepName) in self.upstreamSteps:
210 buildStatus = stepStatus = None
211 try:
212 buildStatus = self._getBuildStatus(botmaster, builderName)
213 if buildStatus is not None:
214 stepStatus = self._getStepStatus(buildStatus, stepName)
215 except BadStepError, err:
216 errors.append(err.message)
217 if stepStatus is not None:
218
219
220
221 self._blocking_steps.append(stepStatus)
222
223 if len(errors) == 1:
224 raise BadStepError(errors[0])
225 elif len(errors) > 1:
226 raise BadStepError("multiple errors:\n" + "\n".join(errors))
227
228 self._log("will block on %d upstream build steps: %r",
229 len(self._blocking_steps), self._blocking_steps)
230 if self._blocking_builders:
231 self._log("will also block on %d builders starting a build: %r",
232 len(self._blocking_builders), self._blocking_builders)
233
234
235
236
237
238
239
240 for stepStatus in self._blocking_steps[:]:
241 self._awaitStepFinished(stepStatus)
242 self._log("after registering for each upstream build step, "
243 "_blocking_steps = %r",
244 self._blocking_steps)
245
246
247 for bs in self._blocking_builders:
248 bs.subscribe(BuilderStatusReceiver(self, bs))
249
251
252
253
254 d = stepStatus.waitUntilFinished()
255 d.addCallback(self._upstreamStepFinished)
256
270
272 assert isinstance(stepStatus, buildstep.BuildStepStatus)
273 self._log("upstream build step %s:%s finished; results=%r",
274 stepStatus.getBuild().builder.getName(),
275 stepStatus.getName(),
276 stepStatus.getResults())
277
278 if self._timed_out:
279
280 self._blocking_steps.remove(stepStatus)
281 return
282
283 (code, text) = stepStatus.getResults()
284 if code != builder.SUCCESS and self._overall_code == builder.SUCCESS:
285
286 self._overall_code = code
287 self._overall_text.extend(text)
288 self._log("now _overall_code=%r, _overall_text=%r",
289 self._overall_code, self._overall_text)
290
291 self._blocking_steps.remove(stepStatus)
292 self._checkFinished()
293
295 assert isinstance(builderStatus, builder.BuilderStatus)
296 self._log("builder %r (%r) started a build; buildStatus=%r",
297 builderStatus, builderStatus.getName(), buildStatus)
298
299 myBuildStatus = self.build.getStatus()
300 if not self.buildsMatch(myBuildStatus, buildStatus):
301 self._log("but the just-started build does not match: "
302 "ignoring it")
303 return
304
305 builderStatus.unsubscribe(receiver)
306
307
308
309
310 new_blocking_steps = []
311 for (builderName, stepName) in self.upstreamSteps:
312 if builderName == builderStatus.getName():
313 try:
314 stepStatus = self._getStepStatus(buildStatus, stepName)
315 except BadStepError:
316 self.failed(failure.Failure())
317
318
319
320 else:
321 new_blocking_steps.append(stepStatus)
322
323 self._blocking_steps.extend(new_blocking_steps)
324 for stepStatus in new_blocking_steps:
325 self._awaitStepFinished(stepStatus)
326
327 self._blocking_builders.remove(builderStatus)
328 self._checkFinished()
329
331 if self.step_status.isFinished():
332
333
334 self._log("_checkFinished: already finished, so nothing to do here")
335 return
336
337 self._log("_checkFinished: _blocking_steps=%r, _blocking_builders=%r",
338 self._blocking_steps, self._blocking_builders)
339
340 if not self._blocking_steps and not self._blocking_builders:
341 if self.timeout:
342 self._timer.cancel()
343
344 self.finished(self._overall_code)
345 self.step_status.setText2(self._overall_text)
346 (start, finish) = self.step_status.getTimes()
347 self.step_status.setText(
348 self._getFinishStatusText(self._overall_code, finish - start))
349
351 - def __init__(self, blocker, builderStatus):
352
353
354 self.blocker = blocker
355 self.builderStatus = builderStatus
356
359
361 log.msg("BuilderStatusReceiver: "
362 "apparently, builder %r has started build %r"
363 % (name, buildStatus))
364 self.blocker._upstreamBuildStarted(self.builderStatus, buildStatus, self)
365
368