10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
40 """Default SCons build engine task.
42 This controls the interaction of the actual building of node
43 and the rest of the engine.
45 This is expected to handle all of the normally-customizable
46 aspects of controlling a build, so any given application
47 *should* be able to do what it wants by sub-classing this
48 class and overriding methods as appropriate. If an application
49 needs to customze something by sub-classing Taskmaster (or
50 some other build engine class), we should first try to migrate
51 that functionality into this class.
53 Note that it's generally a good idea for sub-classes to call
54 these methods explicitly to update state, etc., rather than
55 roll their own interaction with Taskmaster from scratch."""
56 def __init__(self, tm, targets, top, node):
58 self.targets = targets
63 def display(self, message):
64 """Allow the calling interface to display a message
69 """Called just before the task is executed.
71 This unlinks all targets and makes all directories before
74 # Now that it's the appropriate time, give the TaskMaster a
75 # chance to raise any exceptions it encountered while preparing
77 self.exception_raise()
80 self.display(self.tm.message)
81 self.tm.message = None
83 for t in self.targets:
85 for s in t.side_effects:
89 """Called to execute the task.
91 This method is called from multiple threads in a parallel build,
92 so only do thread safe stuff here. Do thread unsafe stuff in
93 prepare(), executed() or failed()."""
96 everything_was_cached = 1
97 for t in self.targets:
98 if not t.retrieve_from_cache():
99 everything_was_cached = 0
101 if not everything_was_cached:
102 self.targets[0].build()
103 except KeyboardInterrupt:
106 exc_value = sys.exc_info()[1]
107 raise SCons.Errors.ExplicitExit(self.targets[0], exc_value.code)
108 except SCons.Errors.UserError:
110 except SCons.Errors.BuildError:
113 exc_type, exc_value, exc_traceback = sys.exc_info()
114 raise SCons.Errors.BuildError(self.targets[0],
120 def get_target(self):
121 """Fetch the target being built or updated by this task.
126 """Called when the task has been successfully executed.
128 This may have been a do-nothing operation (to preserve
129 build order), so check the node's state before updating
130 things. Most importantly, this calls back to the
131 Taskmaster to put any node tasks waiting on this one
132 back on the pending list."""
134 if self.targets[0].get_state() == SCons.Node.executing:
135 for t in self.targets:
136 for side_effect in t.side_effects:
137 side_effect.set_state(None)
138 t.set_state(SCons.Node.executed)
141 for t in self.targets:
144 self.tm.executed(self.node)
147 """Default action when a task fails: stop the build."""
151 """Explicit stop-the-build failure."""
152 for t in self.targets:
153 t.set_state(SCons.Node.failed)
154 self.tm.failed(self.node)
155 next_top = self.tm.next_top_level_candidate()
159 # We're stopping because of a build failure, but give the
160 # calling Task class a chance to postprocess() the top-level
161 # target under which the build failure occurred.
162 self.targets = [next_top]
165 def fail_continue(self):
166 """Explicit continue-the-build failure.
168 This sets failure status on the target nodes and all of
169 their dependent parent nodes.
171 for t in self.targets:
172 # Set failure state on all of the parents that were dependent
173 # on this failed build.
174 def set_state(node): node.set_state(SCons.Node.failed)
175 t.call_for_all_waiting_parents(set_state)
177 self.tm.executed(self.node)
179 def mark_targets(self, state):
180 for t in self.targets:
183 def mark_targets_and_side_effects(self, state):
184 for t in self.targets:
185 for side_effect in t.side_effects:
186 side_effect.set_state(state)
189 def make_ready_all(self):
190 """Mark all targets in a task ready for execution.
192 This is used when the interface needs every target Node to be
193 visited--the canonical example being the "scons -c" option.
195 self.out_of_date = self.targets[:]
196 self.mark_targets_and_side_effects(SCons.Node.executing)
198 def make_ready_current(self):
199 """Mark all targets in a task ready for execution if any target
202 This is the default behavior for building only what's necessary.
204 self.out_of_date = []
205 for t in self.targets:
207 self.out_of_date.append(t)
209 self.mark_targets_and_side_effects(SCons.Node.executing)
211 self.mark_targets(SCons.Node.up_to_date)
213 make_ready = make_ready_current
215 def postprocess(self):
216 """Post process a task after it's been executed."""
217 for t in self.targets:
221 return self.exception
224 self.exception = (None, None, None)
225 self.exception_raise = self._no_exception_to_raise
227 def exception_set(self, exception=None):
229 exception = sys.exc_info()
230 self.exception = exception
231 self.exception_raise = self._exception_raise
233 def _no_exception_to_raise(self):
236 def _exception_raise(self):
237 """Raise a pending exception that was recorded while
238 getting a Task ready for execution."""
239 self.tm.exception_raise(self.exc_info())
242 def order(dependencies):
243 """Re-order a list of dependencies (if we need to)."""
248 """A generic Taskmaster for handling a bunch of targets.
250 Classes that override methods of this class should call
251 the base class method, so this class can do its thing.
254 def __init__(self, targets=[], tasker=Task, order=order):
255 self.targets = targets # top level targets
256 self.candidates = targets[:] # nodes that might be ready to be executed
257 self.candidates.reverse()
258 self.executing = [] # nodes that are currently executing
259 self.pending = [] # nodes that depend on a currently executing node
261 self.ready = None # the next task that is ready to be executed
265 # See if we can alter the target list to find any
266 # corresponding targets in linked build directories
267 for node in self.targets:
268 alt, message = node.alter_targets()
270 self.message = message
271 self.candidates.extend(self.order(alt))
274 def _find_next_ready_node(self):
275 """Find the next node that is ready to be built"""
280 self.ready_exc = None
282 while self.candidates:
283 node = self.candidates[-1]
284 state = node.get_state()
286 # Skip this node if it has already been executed:
287 if state != None and state != SCons.Node.stack:
288 self.candidates.pop()
291 # Mark this node as being on the execution stack:
292 node.set_state(SCons.Node.stack)
295 childinfo = map(lambda N: (N.get_state(),
296 N.is_derived() or N.is_pseudo_derived(),
299 exc_value = sys.exc_info()[1]
300 e = SCons.Errors.ExplicitExit(node, exc_value.code)
301 self.ready_exc = (SCons.Errors.ExplicitExit, e)
302 self.candidates.pop()
305 except KeyboardInterrupt:
308 # We had a problem just trying to figure out the
309 # children (like a child couldn't be linked in to a
310 # BuildDir, or a Scanner threw something). Arrange to
311 # raise the exception when the Task is "executed."
312 self.ready_exc = sys.exc_info()
313 self.candidates.pop()
318 # Skip this node if any of its children have failed. This
319 # catches the case where we're descending a top-level target
320 # and one of our children failed while trying to be built
321 # by a *previous* descent of an earlier top-level target.
322 if filter(lambda I: I[0] == SCons.Node.failed, childinfo):
323 node.set_state(SCons.Node.failed)
324 self.candidates.pop()
327 # Detect dependency cycles:
328 cycle = filter(lambda I: I[0] == SCons.Node.stack, childinfo)
330 nodes = filter(lambda N: N.get_state() == SCons.Node.stack,
332 map(lambda I: I[2], cycle)
334 desc = "Dependency cycle: " + string.join(map(str, nodes), " -> ")
335 raise SCons.Errors.UserError, desc
337 # Find all of the derived dependencies (that is,
338 # children who have builders or are side effects):
339 # Add derived files that have not been built
340 # to the candidates list:
341 not_built = filter(lambda I: I[1] and not I[0], childinfo)
343 # We're waiting on one more derived files that have not
344 # yet been built. Add this node to the waiting_parents
345 # list of each of those derived files.
346 map(lambda I, P=node: I[2].add_to_waiting_parents(P), not_built)
348 self.candidates.extend(self.order(map(lambda I: I[2],
352 # Skip this node if it has side-effects that are
353 # currently being built:
354 if reduce(lambda E,N:
355 E or N.get_state() == SCons.Node.executing,
358 self.pending.append(node)
359 node.set_state(SCons.Node.pending)
360 self.candidates.pop()
363 # Skip this node if it is pending on a currently
365 if node.depends_on(self.executing) or node.depends_on(self.pending):
366 self.pending.append(node)
367 node.set_state(SCons.Node.pending)
368 self.candidates.pop()
371 # The default when we've gotten through all of the checks above:
372 # this node is ready to be built.
373 self.candidates.pop()
378 """Return the next task to be executed."""
380 self._find_next_ready_node()
388 tlist = node.builder.targets(node)
389 except AttributeError:
391 self.executing.extend(tlist)
392 self.executing.extend(node.side_effects)
394 task = self.tasker(self, tlist, node in self.targets, node)
397 except KeyboardInterrupt:
400 # We had a problem just trying to get this task ready (like
401 # a child couldn't be linked in to a BuildDir when deciding
402 # whether this node is current). Arrange to raise the
403 # exception when the Task is "executed."
404 self.ready_exc = sys.exc_info()
407 task.exception_set(self.ready_exc)
410 self.ready_exc = None
414 def is_blocked(self):
415 self._find_next_ready_node()
417 return not self.ready and (self.pending or self.executing)
419 def next_top_level_candidate(self):
420 candidates = self.candidates[:]
423 if c in self.targets:
428 """Stop the current build completely."""
433 def failed(self, node):
435 tlist = node.builder.targets(node)
436 except AttributeError:
439 self.executing.remove(t)
440 for side_effect in node.side_effects:
441 self.executing.remove(side_effect)
443 def executed(self, node):
445 tlist = node.builder.targets(node)
446 except AttributeError:
449 self.executing.remove(t)
450 for side_effect in node.side_effects:
451 self.executing.remove(side_effect)
453 # move the current pending nodes to the candidates list:
454 # (they may not all be ready to build, but _find_next_ready_node()
455 # will figure out which ones are really ready)
456 for node in self.pending:
458 self.pending.reverse()
459 self.candidates.extend(self.pending)
462 def exception_raise(self, exception):
465 exc_type, exc_value, exc_traceback = exc
467 exc_type, exc_value = exc
469 raise exc_type, exc_value, exc_traceback