50
49
needs to customze something by sub-classing Taskmaster (or
51
50
some other build engine class), we should first try to migrate
52
51
that functionality into this class.
54
53
Note that it's generally a good idea for sub-classes to call
55
54
these methods explicitly to update state, etc., rather than
56
55
roll their own interaction with Taskmaster from scratch."""
57
def __init__(self, tm, targets, top, scanner = None):
56
def __init__(self, tm, targets, top, node):
59
58
self.targets = targets
61
self.scanner = scanner
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:
84
for s in t.side_effects:
64
if self.targets[0].get_state() != SCons.Node.up_to_date:
65
self.targets[0].prepare()
66
self.targets[0].build()
88
"""Called to execute the task.
90
This method is called from multiple threads in a parallel build,
91
so only do thread safe stuff here. Do thread unsafe stuff in
92
prepare(), executed() or failed()."""
95
everything_was_cached = 1
96
for t in self.targets:
97
if not t.retrieve_from_cache():
98
everything_was_cached = 0
100
if not everything_was_cached:
101
self.targets[0].build()
102
except KeyboardInterrupt:
105
exc_value = sys.exc_info()[1]
106
raise SCons.Errors.ExplicitExit(self.targets[0], exc_value.code)
107
except SCons.Errors.UserError:
109
except SCons.Errors.BuildError:
112
exc_type, exc_value, exc_traceback = sys.exc_info()
113
raise SCons.Errors.BuildError(self.targets[0],
68
119
def get_target(self):
69
120
"""Fetch the target being built or updated by this task.
71
return self.targets[0]
73
def set_tstates(self, state):
74
"""Set all of the target nodes's states."""
75
for t in self.targets:
78
124
def executed(self):
79
125
"""Called when the task has been successfully executed.
114
159
This sets failure status on the target nodes and all of
115
160
their dependent parent nodes.
118
for t in self.targets:
119
def get_parents(node, parent): return node.get_parents()
120
def set_nodes(node, parent, nodes=nodes): nodes[node] = 1
121
walker = SCons.Node.Walker(t, get_parents, eval_func=set_nodes)
125
pending = filter(lambda x: x.get_state() == SCons.Node.pending,
128
for t in map(lambda r: r.task, pending):
130
self.tm.pending_remove(tasks.keys())
132
def make_ready(self):
133
"""Make a task ready for execution."""
134
state = SCons.Node.up_to_date
135
for t in self.targets:
136
bsig = self.tm.calc.bsig(t)
138
if not self.tm.calc.current(t, bsig):
139
state = SCons.Node.executing
140
self.set_tstates(state)
141
self.tm.add_ready(self)
146
def bsig(self, node):
151
def current(self, node, sig):
152
"""Default SCons build engine is-it-current function.
154
This returns "always out of date," so every node is always
162
for t in self.targets:
163
# Set failure state on all of the parents that were dependent
164
# on this failed build.
165
def set_state(node): node.set_state(SCons.Node.failed)
166
t.call_for_all_waiting_parents(set_state)
168
self.tm.executed(self.node)
170
def mark_targets(self, state):
171
for t in self.targets:
174
def mark_targets_and_side_effects(self, state):
175
for t in self.targets:
176
for side_effect in t.side_effects:
177
side_effect.set_state(state)
180
def make_ready_all(self):
181
"""Mark all targets in a task ready for execution.
183
This is used when the interface needs every target Node to be
184
visited--the canonical example being the "scons -c" option.
186
self.out_of_date = self.targets[:]
187
self.mark_targets_and_side_effects(SCons.Node.executing)
189
def make_ready_current(self):
190
"""Mark all targets in a task ready for execution if any target
193
This is the default behavior for building only what's necessary.
195
self.out_of_date = []
196
for t in self.targets:
198
self.out_of_date.append(t)
200
self.mark_targets_and_side_effects(SCons.Node.executing)
202
self.mark_targets(SCons.Node.up_to_date)
204
make_ready = make_ready_current
206
def postprocess(self):
207
"""Post process a task after it's been executed."""
208
for t in self.targets:
212
return self.tm.exception
215
self.tm.exception_clear()
217
def exception_set(self):
218
self.tm.exception_set()
222
def order(dependencies):
223
"""Re-order a list of dependencies (if we need to)."""
161
227
class Taskmaster:
162
228
"""A generic Taskmaster for handling a bunch of targets.
164
230
Classes that override methods of this class should call
165
the base class method, so this class can do its thing.
231
the base class method, so this class can do its thing.
168
def __init__(self, targets=[], tasker=Task, calc=Calc()):
170
def out_of_date(node, parent):
172
# The state is set, so someone has already been here
173
# (finished or currently executing). Find another one.
175
# Scan the file before fetching its children().
177
scanner = parent.src_scanner_get(node.scanner_key())
180
return filter(lambda x: x.get_state() != SCons.Node.up_to_date,
181
node.children(scanner))
183
def cycle_error(node, stack):
185
nodes = stack + [node]
234
def __init__(self, targets=[], tasker=Task, order=order):
235
self.targets = targets # top level targets
236
self.candidates = targets[:] # nodes that might be ready to be executed
237
self.candidates.reverse()
238
self.executing = [] # nodes that are currently executing
239
self.pending = [] # nodes that depend on a currently executing node
241
self.ready = None # the next task that is ready to be executed
243
self.exception_clear()
246
def _find_next_ready_node(self):
247
"""Find the next node that is ready to be built"""
252
while self.candidates:
253
node = self.candidates[-1]
254
state = node.get_state()
256
# Skip this node if it has already been executed:
257
if state != None and state != SCons.Node.stack:
258
self.candidates.pop()
261
# Mark this node as being on the execution stack:
262
node.set_state(SCons.Node.stack)
265
children = node.children()
267
exc_value = sys.exc_info()[1]
268
e = SCons.Errors.ExplicitExit(node, exc_value.code)
269
self.exception_set((SCons.Errors.ExplicitExit, e))
270
self.candidates.pop()
273
except KeyboardInterrupt:
276
# We had a problem just trying to figure out the
277
# children (like a child couldn't be linked in to a
278
# BuildDir, or a Scanner threw something). Arrange to
279
# raise the exception when the Task is "executed."
281
self.candidates.pop()
285
# Detect dependency cycles:
286
def in_stack(node): return node.get_state() == SCons.Node.stack
287
cycle = filter(in_stack, children)
289
nodes = filter(in_stack, self.candidates) + cycle
187
291
desc = "Dependency cycle: " + string.join(map(str, nodes), " -> ")
188
292
raise SCons.Errors.UserError, desc
190
def eval_node(node, parent, self=self):
192
# The state is set, so someone has already been here
193
# (finished or currently executing). Find another one.
196
# It's a source file, we don't need to build it,
197
# but mark it as "up to date" so targets won't
199
node.set_state(SCons.Node.up_to_date)
200
# set the signature for non-derived files
201
# here so they don't get recalculated over
203
node.set_csig(self.calc.csig(node))
294
# Find all of the derived dependencies (that is,
295
# children who have builders or are side effects):
206
tlist = node.builder.targets(node)
207
except AttributeError:
210
scanner = parent.src_scanner_get(node.scanner_key())
213
task = self.tasker(self, tlist, self.walkers[0].is_done(), scanner)
214
if not tlist[0].children_are_executed(scanner):
216
t.set_state(SCons.Node.pending)
218
self.pending = self.pending + 1
222
#XXX In Python 2.2 we can get rid of f1, f2 and f3:
223
self.walkers = map(lambda x, f1=out_of_date,
226
SCons.Node.Walker(x, f1, f2, f3),
233
self._find_next_ready_node()
297
def derived_nodes(node): return node.is_derived() or node.is_pseudo_derived()
298
derived = filter(derived_nodes, children)
299
except KeyboardInterrupt:
302
# We had a problem just trying to figure out if any of
303
# the kids are derived (like a child couldn't be linked
304
# from a repository). Arrange to raise the exception
305
# when the Task is "executed."
307
self.candidates.pop()
311
# If there aren't any children with builders and this
312
# was a top-level argument, then see if we can find any
313
# corresponding targets in linked build directories:
314
if not derived and node in self.targets:
315
alt, message = node.alter_targets()
317
self.message = message
318
self.candidates.pop()
319
self.candidates.extend(alt)
322
# Add derived files that have not been built
323
# to the candidates list:
324
def unbuilt_nodes(node): return node.get_state() == None
325
not_built = filter(unbuilt_nodes, derived)
327
# We're waiting on one more derived files that have not
328
# yet been built. Add this node to the waiting_parents
329
# list of each of those derived files.
330
def add_to_waiting_parents(child, parent=node):
331
child.add_to_waiting_parents(parent)
332
map(add_to_waiting_parents, not_built)
334
self.candidates.extend(self.order(not_built))
337
# Skip this node if it has side-effects that are
338
# currently being built:
340
for side_effect in node.side_effects:
341
if side_effect.get_state() == SCons.Node.executing:
342
self.pending.append(node)
343
node.set_state(SCons.Node.pending)
344
self.candidates.pop()
349
# Skip this node if it is pending on a currently
351
if node.depends_on(self.executing) or node.depends_on(self.pending):
352
self.pending.append(node)
353
node.set_state(SCons.Node.pending)
354
self.candidates.pop()
357
# The default when we've gotten through all of the checks above:
358
# this node is ready to be built.
359
self.candidates.pop()
235
363
def next_task(self):
236
364
"""Return the next task to be executed."""
238
task = self.ready.pop()
240
self._find_next_ready_node()
366
self._find_next_ready_node()
245
def _find_next_ready_node(self):
246
"""Find the next node that is ready to be built"""
248
n = self.walkers[0].next()
374
tlist = node.builder.targets(node)
375
except AttributeError:
377
self.executing.extend(tlist)
378
self.executing.extend(node.side_effects)
380
task = self.tasker(self, tlist, node in self.targets, node)
383
except KeyboardInterrupt:
386
# We had a problem just trying to get this task ready (like
387
# a child couldn't be linked in to a BuildDir when deciding
388
# whether this node is current). Arrange to raise the
389
# exception when the Task is "executed."
255
395
def is_blocked(self):
256
return not self.ready and self.pending
396
self._find_next_ready_node()
398
return not self.ready and (self.pending or self.executing)
259
401
"""Stop the current build completely."""
264
def add_ready(self, task):
265
"""Add a task to the ready queue.
267
self.ready.append(task)
269
def pending_to_ready(self, tasks):
270
"""Move the specified tasks from the pending count
271
to the 'ready' queue.
273
self.pending_remove(tasks)
277
def pending_remove(self, tasks):
278
"""Remove tasks from the pending count.
280
We assume that the caller has already confirmed that
281
the nodes in this task are in pending state.
283
self.pending = self.pending - len(tasks)
406
def failed(self, node):
408
tlist = node.builder.targets(node)
409
except AttributeError:
412
self.executing.remove(t)
413
for side_effect in node.side_effects:
414
self.executing.remove(side_effect)
416
def executed(self, node):
418
tlist = node.builder.targets(node)
419
except AttributeError:
422
self.executing.remove(t)
423
for side_effect in node.side_effects:
424
self.executing.remove(side_effect)
426
# move the current pending nodes to the candidates list:
427
# (they may not all be ready to build, but _find_next_ready_node()
428
# will figure out which ones are really ready)
429
for node in self.pending:
431
self.pending.reverse()
432
self.candidates.extend(self.pending)
435
def exception_set(self, exception=None):
436
if exception is None:
437
exception = sys.exc_info()
438
self.exception = exception
439
self.exception_raise = self._exception_raise
441
def exception_clear(self):
442
self.exception = (None, None, None)
443
self.exception_raise = self._no_exception_to_raise
445
def _no_exception_to_raise(self):
448
def _exception_raise(self):
449
"""Raise a pending exception that was recorded while
450
getting a Task ready for execution."""
451
exc_type, exc_value = self.exception[:2]
452
raise exc_type, exc_value