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
19 from buildbot.process.buildstep import BuildStep
20 from buildbot.status import builder, buildstep
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
60 BuildStep.__init__(self, **kwargs)
61 if self.upstreamSteps is None:
62 raise ValueError("you must supply upstreamSteps")
63 if len(self.upstreamSteps) < 1:
64 raise ValueError("upstreamSteps must be a non-empty list")
65 if self.idlePolicy not in self.VALID_IDLE_POLICIES:
66 raise ValueError(
67 "invalid value for idlePolicy: %r (must be one of %s)"
68 % (self.idlePolicy,
69 ", ".join(map(repr, self.VALID_IDLE_POLICIES))))
70
71
72
73 self._blocking_steps = []
74
75
76
77 self._blocking_builders = set()
78
79 self._overall_code = builder.SUCCESS
80 self._overall_text = []
81
82 self._timer = None
83 self._timed_out = False
84
87
89 return "<%s %x: %s>" % (self.__class__.__name__, id(self), self.name)
90
91 - def _log(self, message, *args):
92 log.msg(repr(self) + ": " + (message % args))
93
95 """
96 Return true if buildStatus1 and buildStatus2 are from related
97 builds, i.e. a Blocker step running in buildStatus2 should be
98 blocked by an upstream step in buildStatus1. Return false if
99 they are unrelated.
100
101 Default implementation simply raises NotImplementedError: you
102 *must* subclass Blocker and implement this method, because
103 BuildBot currently provides no way to relate different builders.
104 This might change if ticket #875 (\"build flocks\") is
105 implemented.
106 """
107 raise NotImplementedError(
108 "abstract method: you must subclass Blocker "
109 "and implement buildsMatch()")
110
112 try:
113
114
115
116
117 builder = botmaster.builders[builderName]
118 except KeyError:
119 raise BadStepError(
120 "no builder named %r" % builderName)
121
122
123
124
125 myBuildStatus = self.build.getStatus()
126 builderStatus = builder.builder_status
127 matchingBuild = None
128
129
130
131
132
133
134
135
136
137
138
139 all_builds = (builderStatus.buildCache.values() +
140 builderStatus.getCurrentBuilds())
141
142 for buildStatus in all_builds:
143 if self.buildsMatch(myBuildStatus, buildStatus):
144 matchingBuild = buildStatus
145 break
146
147 if matchingBuild is None:
148 msg = "no matching builds found in builder %r" % builderName
149 if self.idlePolicy == "error":
150 raise BadStepError(msg + " (is it idle?)")
151 elif self.idlePolicy == "ignore":
152
153 self._log(msg + ": skipping it")
154 return None
155 elif self.idlePolicy == "block":
156 self._log(msg + ": will block until it starts a build")
157 self._blocking_builders.add(builderStatus)
158 return None
159
160 self._log("found builder %r: %r", builderName, builder)
161 return matchingBuild
162
172
182
184 return [self.name+":", "blocking on"] + self._getFullnames()
185
186 - def _getFinishStatusText(self, code, elapsed):
187 meaning = builder.Results[code]
188 text = [self.name+":",
189 "upstream %s" % meaning,
190 "after %.1f sec" % elapsed]
191 if code != builder.SUCCESS:
192 text += self._getFullnames()
193 return text
194
196 return [self.name+":", "timed out", "(%.1f sec)" % self.timeout]
197
199 self.step_status.setText(self._getBlockingStatusText())
200
201 if self.timeout is not None:
202 self._timer = reactor.callLater(self.timeout, self._timeoutExpired)
203
204 self._log("searching for upstream build steps")
205 botmaster = self.build.slavebuilder.slave.parent
206 errors = []
207 for (builderName, stepName) in self.upstreamSteps:
208 buildStatus = stepStatus = None
209 try:
210 buildStatus = self._getBuildStatus(botmaster, builderName)
211 if buildStatus is not None:
212 stepStatus = self._getStepStatus(buildStatus, stepName)
213 except BadStepError, err:
214 errors.append(err.message)
215 if stepStatus is not None:
216
217
218
219 self._blocking_steps.append(stepStatus)
220
221 if len(errors) == 1:
222 raise BadStepError(errors[0])
223 elif len(errors) > 1:
224 raise BadStepError("multiple errors:\n" + "\n".join(errors))
225
226 self._log("will block on %d upstream build steps: %r",
227 len(self._blocking_steps), self._blocking_steps)
228 if self._blocking_builders:
229 self._log("will also block on %d builders starting a build: %r",
230 len(self._blocking_builders), self._blocking_builders)
231
232
233
234
235
236
237
238 for stepStatus in self._blocking_steps[:]:
239 self._awaitStepFinished(stepStatus)
240 self._log("after registering for each upstream build step, "
241 "_blocking_steps = %r",
242 self._blocking_steps)
243
244
245 for bs in self._blocking_builders:
246 bs.subscribe(BuilderStatusReceiver(self, bs))
247
249
250
251
252 d = stepStatus.waitUntilFinished()
253 d.addCallback(self._upstreamStepFinished)
254
268
270 assert isinstance(stepStatus, buildstep.BuildStepStatus)
271 self._log("upstream build step %s:%s finished; results=%r",
272 stepStatus.getBuild().builder.getName(),
273 stepStatus.getName(),
274 stepStatus.getResults())
275
276 if self._timed_out:
277
278 self._blocking_steps.remove(stepStatus)
279 return
280
281 (code, text) = stepStatus.getResults()
282 if code != builder.SUCCESS and self._overall_code == builder.SUCCESS:
283
284 self._overall_code = code
285 self._overall_text.extend(text)
286 self._log("now _overall_code=%r, _overall_text=%r",
287 self._overall_code, self._overall_text)
288
289 self._blocking_steps.remove(stepStatus)
290 self._checkFinished()
291
293 assert isinstance(builderStatus, builder.BuilderStatus)
294 self._log("builder %r (%r) started a build; buildStatus=%r",
295 builderStatus, builderStatus.getName(), buildStatus)
296
297 myBuildStatus = self.build.getStatus()
298 if not self.buildsMatch(myBuildStatus, buildStatus):
299 self._log("but the just-started build does not match: "
300 "ignoring it")
301 return
302
303 builderStatus.unsubscribe(receiver)
304
305
306
307
308 new_blocking_steps = []
309 for (builderName, stepName) in self.upstreamSteps:
310 if builderName == builderStatus.getName():
311 try:
312 stepStatus = self._getStepStatus(buildStatus, stepName)
313 except BadStepError:
314 self.failed(failure.Failure())
315
316
317
318 else:
319 new_blocking_steps.append(stepStatus)
320
321 self._blocking_steps.extend(new_blocking_steps)
322 for stepStatus in new_blocking_steps:
323 self._awaitStepFinished(stepStatus)
324
325 self._blocking_builders.remove(builderStatus)
326 self._checkFinished()
327
329 if self.step_status.isFinished():
330
331
332 self._log("_checkFinished: already finished, so nothing to do here")
333 return
334
335 self._log("_checkFinished: _blocking_steps=%r, _blocking_builders=%r",
336 self._blocking_steps, self._blocking_builders)
337
338 if not self._blocking_steps and not self._blocking_builders:
339 if self.timeout:
340 self._timer.cancel()
341
342 self.finished(self._overall_code)
343 self.step_status.setText2(self._overall_text)
344 (start, finish) = self.step_status.getTimes()
345 self.step_status.setText(
346 self._getFinishStatusText(self._overall_code, finish - start))
347
349 - def __init__(self, blocker, builderStatus):
350
351
352 self.blocker = blocker
353 self.builderStatus = builderStatus
354
357
359 log.msg("BuilderStatusReceiver: "
360 "apparently, builder %r has started build %r"
361 % (name, buildStatus))
362 self.blocker._upstreamBuildStarted(self.builderStatus, buildStatus, self)
363
366