8 # Copyright (c) 2001, 2002 Steven Knight
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__"
41 """Default SCons build engine task.
43 This controls the interaction of the actual building of node
44 and the rest of the engine.
46 This is expected to handle all of the normally-customizable
47 aspects of controlling a build, so any given application
48 *should* be able to do what it wants by sub-classing this
49 class and overriding methods as appropriate. If an application
50 needs to customze something by sub-classing Taskmaster (or
51 some other build engine class), we should first try to migrate
52 that functionality into this class.
54 Note that it's generally a good idea for sub-classes to call
55 these methods explicitly to update state, etc., rather than
56 roll their own interaction with Taskmaster from scratch."""
57 def __init__(self, tm, targets, top, node):
59 self.targets = targets
65 """Called just before the task is executed."""
66 if self.targets[0].get_state() != SCons.Node.up_to_date:
67 self.targets[0].prepare()
70 """Called to execute the task.
72 This methods is called from multiple threads in
73 a parallel build, so only do thread safe stuff here.
74 Do thread unsafe stuff in prepare(), executed() or failed()."""
75 if self.targets[0].get_state() != SCons.Node.up_to_date:
76 self.targets[0].build()
79 """Fetch the target being built or updated by this task.
84 """Called when the task has been successfully executed.
86 This may have been a do-nothing operation (to preserve
87 build order), so check the node's state before updating
88 things. Most importantly, this calls back to the
89 Taskmaster to put any node tasks waiting on this one
90 back on the pending list."""
92 if self.targets[0].get_state() == SCons.Node.executing:
93 for t in self.targets:
94 for side_effect in t.side_effects:
95 side_effect.set_state(None)
96 t.set_state(SCons.Node.executed)
99 self.tm.executed(self.node)
102 """Default action when a task fails: stop the build."""
106 """Explicit stop-the-build failure."""
107 for t in self.targets:
108 t.set_state(SCons.Node.failed)
111 def fail_continue(self):
112 """Explicit continue-the-build failure.
114 This sets failure status on the target nodes and all of
115 their dependent parent nodes.
117 for t in self.targets:
118 def get_parents(node, parent): return node.get_parents()
119 def set_state(node, parent): node.set_state(SCons.Node.failed)
120 walker = SCons.Node.Walker(t, get_parents, eval_func=set_state)
125 self.tm.executed(self.node)
127 def make_ready(self):
128 """Make a task ready for execution."""
129 state = SCons.Node.up_to_date
130 for t in self.targets:
131 if not t.current(self.tm.calc):
132 state = SCons.Node.executing
133 for t in self.targets:
134 if state == SCons.Node.executing:
135 for side_effect in t.side_effects:
136 side_effect.set_state(state)
140 def bsig(self, node):
145 def current(self, node, sig):
146 """Default SCons build engine is-it-current function.
148 This returns "always out of date," so every node is always
154 """A generic Taskmaster for handling a bunch of targets.
156 Classes that override methods of this class should call
157 the base class method, so this class can do its thing.
160 def __init__(self, targets=[], tasker=Task, calc=Calc()):
161 self.targets = targets # top level targets
162 self.candidates = targets[:] # nodes that might be ready to be executed
163 self.candidates.reverse()
164 self.executing = [] # nodes that are currently executing
165 self.pending = [] # nodes that depend on a currently executing node
167 self.ready = None # the next task that is ready to be executed
170 def _find_next_ready_node(self):
171 """Find the next node that is ready to be built"""
176 while self.candidates:
177 node = self.candidates[-1]
178 state = node.get_state()
180 # Skip nodes that have already been executed:
181 if state != None and state != SCons.Node.stack:
182 self.candidates.pop()
185 # keep track of which nodes are in the execution stack:
186 node.set_state(SCons.Node.stack)
188 children = node.children()
190 # detect dependency cycles:
191 def in_stack(node): return node.get_state() == SCons.Node.stack
192 cycle = filter(in_stack, children)
194 nodes = filter(in_stack, self.candidates) + cycle
196 desc = "Dependency cycle: " + string.join(map(str, nodes), " -> ")
197 raise SCons.Errors.UserError, desc
199 # Add non-derived files that have not been built
200 # to the candidates list:
202 return (node.builder or node.side_effect) and node.get_state() == None
203 derived = filter(derived, children)
206 self.candidates.extend(derived)
209 # Skip nodes whose side-effects are currently being built:
211 for side_effect in node.side_effects:
212 if side_effect.get_state() == SCons.Node.executing:
213 self.pending.append(node)
214 node.set_state(SCons.Node.pending)
215 self.candidates.pop()
220 # Skip nodes that are pending on a currently executing node:
221 if node.depends_on(self.executing) or node.depends_on(self.pending):
222 self.pending.append(node)
223 node.set_state(SCons.Node.pending)
224 self.candidates.pop()
227 self.candidates.pop()
232 """Return the next task to be executed."""
234 self._find_next_ready_node()
242 tlist = node.builder.targets(node)
243 except AttributeError:
245 self.executing.extend(tlist)
246 self.executing.extend(node.side_effects)
248 task = self.tasker(self, tlist, node in self.targets, node)
254 def is_blocked(self):
255 self._find_next_ready_node()
257 return not self.ready and self.pending
260 """Stop the current build completely."""
265 def executed(self, node):
267 tlist = node.builder.targets(node)
268 except AttributeError:
271 self.executing.remove(t)
272 for side_effect in node.side_effects:
273 self.executing.remove(side_effect)
275 # move the current pending nodes to the candidates list:
276 # (they may not all be ready to build, but _find_next_ready_node()
277 # will figure out which ones are really ready)
278 for node in self.pending:
280 self.pending.reverse()
281 self.candidates.extend(self.pending)