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
62 def display(self, message):
63 """Allow the calling interface to display a message
68 """Called just before the task is executed.
70 This unlinks all targets and makes all directories before
73 # Now that it's the appropriate time, give the TaskMaster a
74 # chance to raise any exceptions it encountered while preparing
76 self.tm.exception_raise()
79 self.display(self.tm.message)
80 self.tm.message = None
82 for t in self.targets:
86 """Called to execute the task.
88 This method is called from multiple threads in a parallel build,
89 so only do thread safe stuff here. Do thread unsafe stuff in
90 prepare(), executed() or failed()."""
93 self.targets[0].build()
94 except KeyboardInterrupt:
97 raise SCons.Errors.ExplicitExit(self.targets[0], sys.exc_value.code)
98 except SCons.Errors.UserError:
100 except SCons.Errors.BuildError:
103 raise SCons.Errors.BuildError(self.targets[0],
109 def get_target(self):
110 """Fetch the target being built or updated by this task.
115 """Called when the task has been successfully executed.
117 This may have been a do-nothing operation (to preserve
118 build order), so check the node's state before updating
119 things. Most importantly, this calls back to the
120 Taskmaster to put any node tasks waiting on this one
121 back on the pending list."""
123 if self.targets[0].get_state() == SCons.Node.executing:
124 for t in self.targets:
125 for side_effect in t.side_effects:
126 side_effect.set_state(None)
127 t.set_state(SCons.Node.executed)
130 for t in self.targets:
133 self.tm.executed(self.node)
136 """Default action when a task fails: stop the build."""
140 """Explicit stop-the-build failure."""
141 for t in self.targets:
142 t.set_state(SCons.Node.failed)
145 def fail_continue(self):
146 """Explicit continue-the-build failure.
148 This sets failure status on the target nodes and all of
149 their dependent parent nodes.
151 for t in self.targets:
152 def get_parents(node, parent): return node.get_parents()
153 def set_state(node, parent): node.set_state(SCons.Node.failed)
154 walker = SCons.Node.Walker(t, get_parents, eval_func=set_state)
159 self.tm.executed(self.node)
161 def make_ready(self):
162 """Make a task ready for execution."""
163 state = SCons.Node.up_to_date
165 for t in self.targets:
166 c = calc or t.calculator()
168 state = SCons.Node.executing
169 for t in self.targets:
170 if state == SCons.Node.executing:
171 for side_effect in t.side_effects:
172 side_effect.set_state(state)
175 def order(dependencies):
176 """Re-order a list of dependencies (if we need to)."""
180 def bsig(self, node):
185 def current(self, node, sig):
186 """Default SCons build engine is-it-current function.
188 This returns "always out of date," so every node is always
194 """A generic Taskmaster for handling a bunch of targets.
196 Classes that override methods of this class should call
197 the base class method, so this class can do its thing.
200 def __init__(self, targets=[], tasker=Task, calc=Calc(), order=order):
201 self.targets = targets # top level targets
202 self.candidates = targets[:] # nodes that might be ready to be executed
203 self.candidates.reverse()
204 self.executing = [] # nodes that are currently executing
205 self.pending = [] # nodes that depend on a currently executing node
207 self.ready = None # the next task that is ready to be executed
210 self.exception_set(None, None)
213 def _find_next_ready_node(self):
214 """Find the next node that is ready to be built"""
219 while self.candidates:
220 node = self.candidates[-1]
221 state = node.get_state()
223 # Skip this node if it has already been executed:
224 if state != None and state != SCons.Node.stack:
225 self.candidates.pop()
228 # Mark this node as being on the execution stack:
229 node.set_state(SCons.Node.stack)
232 children = node.children()
234 e = SCons.Errors.ExplicitExit(node, sys.exc_value.code)
235 self.exception_set(SCons.Errors.ExplicitExit, e)
236 self.candidates.pop()
240 # We had a problem just trying to figure out the
241 # children (like a child couldn't be linked in to a
242 # BuildDir, or a Scanner threw something). Arrange to
243 # raise the exception when the Task is "executed."
244 x = SCons.Errors.TaskmasterException(sys.exc_type,
247 self.exception_set(x)
248 self.candidates.pop()
252 # Detect dependency cycles:
253 def in_stack(node): return node.get_state() == SCons.Node.stack
254 cycle = filter(in_stack, children)
256 nodes = filter(in_stack, self.candidates) + cycle
258 desc = "Dependency cycle: " + string.join(map(str, nodes), " -> ")
259 raise SCons.Errors.UserError, desc
261 # Find all of the derived dependencies (that is,
262 # children who have builders or are side effects):
264 def derived_nodes(node): return node.is_derived() or node.is_pseudo_derived()
265 derived = filter(derived_nodes, children)
267 # We had a problem just trying to figure out if any of
268 # the kids are derived (like a child couldn't be linked
269 # from a repository). Arrange to raise the exception
270 # when the Task is "executed."
271 x = SCons.Errors.TaskmasterException(sys.exc_type,
274 self.exception_set(x)
275 self.candidates.pop()
279 # If there aren't any children with builders and this
280 # was a top-level argument, then see if we can find any
281 # corresponding targets in linked build directories:
282 if not derived and node in self.targets:
283 alt, message = node.alter_targets()
285 self.message = message
286 self.candidates.pop()
287 self.candidates.extend(alt)
290 # Add derived files that have not been built
291 # to the candidates list:
292 def unbuilt_nodes(node): return node.get_state() == None
293 not_built = filter(unbuilt_nodes, derived)
296 self.candidates.extend(self.order(not_built))
299 # Skip this node if it has side-effects that are
300 # currently being built:
302 for side_effect in node.side_effects:
303 if side_effect.get_state() == SCons.Node.executing:
304 self.pending.append(node)
305 node.set_state(SCons.Node.pending)
306 self.candidates.pop()
311 # Skip this node if it is pending on a currently
313 if node.depends_on(self.executing) or node.depends_on(self.pending):
314 self.pending.append(node)
315 node.set_state(SCons.Node.pending)
316 self.candidates.pop()
319 # The default when we've gotten through all of the checks above:
320 # this node is ready to be built.
321 self.candidates.pop()
326 """Return the next task to be executed."""
328 self._find_next_ready_node()
336 tlist = node.builder.targets(node)
337 except AttributeError:
339 self.executing.extend(tlist)
340 self.executing.extend(node.side_effects)
342 task = self.tasker(self, tlist, node in self.targets, node)
346 # We had a problem just trying to get this task ready (like
347 # a child couldn't be linked in to a BuildDir when deciding
348 # whether this node is current). Arrange to raise the
349 # exception when the Task is "executed."
350 x = SCons.Errors.TaskmasterException(sys.exc_type,
353 self.exception_set(x)
358 def is_blocked(self):
359 self._find_next_ready_node()
361 return not self.ready and self.pending
364 """Stop the current build completely."""
369 def executed(self, node):
371 tlist = node.builder.targets(node)
372 except AttributeError:
375 self.executing.remove(t)
376 for side_effect in node.side_effects:
377 self.executing.remove(side_effect)
379 # move the current pending nodes to the candidates list:
380 # (they may not all be ready to build, but _find_next_ready_node()
381 # will figure out which ones are really ready)
382 for node in self.pending:
384 self.pending.reverse()
385 self.candidates.extend(self.pending)
388 def exception_set(self, type, value=None):
389 """Record an exception type and value to raise later, at an
392 self.exc_value = value
393 self.exc_traceback = traceback
395 def exception_raise(self):
396 """Raise any pending exception that was recorded while
397 getting a Task ready for execution."""
401 raise self.exc_type, self.exc_value
403 # exc_type was probably an instance,
404 # so raise it by itself.
407 self.exception_set(None, None)