self.includes = None
self.build_args = {}
self.attributes = self.Attrs() # Generic place to stick information about the Node.
+ self.side_effect = 0 # true iff this node is a side effect
+ self.side_effects = [] # the side effects of building this target
def generate_build_args(self):
dict = copy.copy(self.env.Dictionary())
class CleanTask(SCons.Taskmaster.Task):
"""An SCons clean task."""
def show(self):
- if self.targets[0].builder:
+ if self.targets[0].builder or self.targets[0].side_effect:
print "Removed " + self.targets[0].path
def remove(self):
- if self.targets[0].builder:
+ if self.targets[0].builder or self.targets[0].side_effect:
try:
os.unlink(self.targets[0].path)
except OSError:
class Task:
"""Default SCons build engine task.
-
+
This controls the interaction of the actual building of node
and the rest of the engine.
needs to customze something by sub-classing Taskmaster (or
some other build engine class), we should first try to migrate
that functionality into this class.
-
+
Note that it's generally a good idea for sub-classes to call
these methods explicitly to update state, etc., rather than
roll their own interaction with Taskmaster from scratch."""
"""
return self.node
- def set_tstates(self, state):
- """Set all of the target nodes's states."""
- for t in self.targets:
- t.set_state(state)
-
def executed(self):
"""Called when the task has been successfully executed.
back on the pending list."""
if self.targets[0].get_state() == SCons.Node.executing:
- self.set_tstates(SCons.Node.executed)
for t in self.targets:
+ for side_effect in t.side_effects:
+ side_effect.set_state(None)
+ t.set_state(SCons.Node.executed)
t.built()
self.tm.executed(self.node)
def fail_stop(self):
"""Explicit stop-the-build failure."""
- self.set_tstates(SCons.Node.failed)
+ for t in self.targets:
+ t.set_state(SCons.Node.failed)
self.tm.stop()
def fail_continue(self):
n = walker.next()
while n:
n = walker.next()
-
+
self.tm.executed(self.node)
def make_ready(self):
bsig = self.tm.calc.bsig(t)
if not self.tm.calc.current(t, bsig):
state = SCons.Node.executing
- self.set_tstates(state)
+ for t in self.targets:
+ if state == SCons.Node.executing:
+ for side_effect in t.side_effects:
+ side_effect.set_state(state)
+ t.set_state(state)
class Calc:
def bsig(self, node):
def current(self, node, sig):
"""Default SCons build engine is-it-current function.
-
+
This returns "always out of date," so every node is always
built/visited.
"""
"""A generic Taskmaster for handling a bunch of targets.
Classes that override methods of this class should call
- the base class method, so this class can do its thing.
+ the base class method, so this class can do its thing.
"""
def __init__(self, targets=[], tasker=Task, calc=Calc()):
if self.ready:
return
-
+
while self.candidates:
node = self.candidates[-1]
state = node.get_state()
-
+
# Skip nodes that have already been executed:
if state != None and state != SCons.Node.stack:
self.candidates.pop()
# Add non-derived files that have not been built
# to the candidates list:
def derived(node):
- return node.builder and node.get_state() == None
+ return (node.builder or node.side_effect) and node.get_state() == None
derived = filter(derived, children)
if derived:
derived.reverse()
self.candidates.extend(derived)
continue
+ # Skip nodes whose side-effects are currently being built:
+ cont = 0
+ for side_effect in node.side_effects:
+ if side_effect.get_state() == SCons.Node.executing:
+ self.pending.append(node)
+ node.set_state(SCons.Node.pending)
+ self.candidates.pop()
+ cont = 1
+ break
+ if cont: continue
+
# Skip nodes that are pending on a currently executing node:
if node.depends_on(self.executing) or node.depends_on(self.pending):
self.pending.append(node)
def next_task(self):
"""Return the next task to be executed."""
-
+
self._find_next_ready_node()
node = self.ready
-
+
if node is None:
return None
-
+
self.executing.append(node)
+ self.executing.extend(node.side_effects)
try:
tlist = node.builder.targets(node)
except AttributeError:
tlist = [node]
- task = self.tasker(self, tlist, node in self.targets, node)
+ task = self.tasker(self, tlist, node in self.targets, node)
task.make_ready()
self.ready = None
-
+
return task
-
+
def is_blocked(self):
self._find_next_ready_node()
def executed(self, node):
self.executing.remove(node)
-
+ for side_effect in node.side_effects:
+ self.executing.remove(side_effect)
+
# move the current pending nodes to the candidates list:
# (they may not all be ready to build, but _find_next_ready_node()
# will figure out which ones are really ready)
self.csig = None
self.state = None
self.parents = []
+ self.side_effect = 0
+ self.side_effects = []
for kid in kids:
kid.parents.append(self)
t.executed()
assert tm.next_task() == None
assert scan_called == 5, scan_called
-
+
+ n1 = Node("n1")
+ n2 = Node("n2")
+ n3 = Node("n3")
+ n4 = Node("n4", [n1,n2,n3])
+ n5 = Node("n5", [n4])
+ n3.side_effect = 1
+ n1.side_effects = n2.side_effects = n3.side_effects = [n4]
+ tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5])
+ t = tm.next_task()
+ assert t.get_target() == n1
+ assert n4.state == SCons.Node.executing
+ assert tm.is_blocked()
+ t.executed()
+ assert not tm.is_blocked()
+ t = tm.next_task()
+ assert t.get_target() == n2
+ assert tm.is_blocked()
+ t.executed()
+ t = tm.next_task()
+ assert t.get_target() == n3
+ assert tm.is_blocked()
+ t.executed()
+ t = tm.next_task()
+ assert t.get_target() == n4
+ assert tm.is_blocked()
+ t.executed()
+ t = tm.next_task()
+ assert t.get_target() == n5
+ assert not tm.is_blocked()
+ assert not tm.next_task()
+ t.executed()
+
+
def test_cycle_detection(self):
n1 = Node("n1")
n2 = Node("n2", [n1])
--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (c) 2001, 2002 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+import os.path
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct',
+"""
+def copy(source, target):
+ print 'copy() < %s > %s' % (source, target)
+ open(target, "wb").write(open(source, "rb").read())
+
+def build(env, source, target):
+ copy(str(source[0]), str(target[0]))
+ if target[0].side_effects:
+ side_effect = open(str(target[0].side_effects[0]), "ab")
+ side_effect.write('%s -> %s\\n'%(str(source[0]), str(target[0])))
+
+Build = Builder(action=build)
+env = Environment(BUILDERS={'Build':Build})
+env.Build('foo.out', 'foo.in')
+env.Build('bar.out', 'bar.in')
+env.Build('blat.out', 'blat.in')
+env.SideEffect('log.txt', ['foo.out', 'bar.out', 'blat.out'])
+env.Build('log.out', 'log.txt')
+""")
+
+test.write('foo.in', 'foo.in\n')
+test.write('bar.in', 'bar.in\n')
+test.write('blat.in', 'blat.in\n')
+
+test.run(arguments = 'foo.out bar.out', stdout="""\
+copy() < foo.in > foo.out
+copy() < bar.in > bar.out
+""")
+
+expect = """\
+foo.in -> foo.out
+bar.in -> bar.out
+"""
+assert test.read('log.txt') == expect
+
+test.write('bar.in', 'bar.in 2 \n')
+
+test.run(arguments = 'log.txt', stdout="""\
+copy() < bar.in > bar.out
+copy() < blat.in > blat.out
+""")
+
+expect = """\
+foo.in -> foo.out
+bar.in -> bar.out
+bar.in -> bar.out
+blat.in -> blat.out
+"""
+assert test.read('log.txt') == expect
+
+test.write('foo.in', 'foo.in 2 \n')
+
+test.run(arguments = ".", stdout="""\
+copy() < foo.in > foo.out
+copy() < log.txt > log.out
+""")
+
+expect = """\
+foo.in -> foo.out
+bar.in -> bar.out
+bar.in -> bar.out
+blat.in -> blat.out
+foo.in -> foo.out
+"""
+assert test.read('log.txt') == expect
+
+test.run(arguments = "-c .")
+
+test.fail_test(os.path.exists(test.workpath('foo.out')))
+test.fail_test(os.path.exists(test.workpath('bar.out')))
+test.fail_test(os.path.exists(test.workpath('blat.out')))
+test.fail_test(os.path.exists(test.workpath('log.txt')))
+
+test.run(arguments = "-j 4 .", stdout="""\
+copy() < bar.in > bar.out
+copy() < blat.in > blat.out
+copy() < foo.in > foo.out
+copy() < log.txt > log.out
+""")
+
+expect = """\
+bar.in -> bar.out
+blat.in -> blat.out
+foo.in -> foo.out
+"""
+assert test.read('log.txt') == expect
+
+
+test.pass_test()