- Remove targets before building them, and add an Environment
Precious() method to override that.
+ - Eliminate redundant calls to the same builder when the target is a
+ list of targets: Add a ListBuilder class that wraps Builders to
+ handle lists atomically. Extend the Task class to support building
+ and updating multiple targets in a single Task. Simplify the
+ interface between Task and Taskmaster.
+
From Steve Leblanc:
- Add var=value command-line arguments.
+def _init_nodes(builder, env, tlist, slist):
+ """Initialize lists of target and source nodes with all of
+ the proper Builder information.
+ """
+ for t in tlist:
+ t.cwd = SCons.Node.FS.default_fs.getcwd() # XXX
+ t.builder_set(builder)
+ t.env_set(env)
+ t.add_source(slist)
+ if builder.scanner:
+ t.scanner_set(builder.scanner.instance(env))
+
+ for s in slist:
+ s.env_set(env, 1)
+ scanner = env.get_scanner(os.path.splitext(s.name)[1])
+ if scanner:
+ s.scanner_set(scanner.instance(env))
+
+
+
class BuilderBase:
"""Base class for Builders, objects that create output
nodes (files) from input nodes (files).
self.node_factory)
return tlist, slist
- def _init_nodes(self, env, tlist, slist):
- """Initialize lists of target and source nodes with all of
- the proper Builder information.
- """
- for t in tlist:
- t.cwd = SCons.Node.FS.default_fs.getcwd() # XXX
- t.builder_set(self)
- t.env_set(env)
- t.add_source(slist)
- if self.scanner:
- t.scanner_set(self.scanner.instance(env))
-
- for s in slist:
- s.env_set(env, 1)
- scanner = env.get_scanner(os.path.splitext(s.name)[1])
- if scanner:
- s.scanner_set(scanner.instance(env))
-
- if len(tlist) == 1:
- tlist = tlist[0]
- return tlist
-
def __call__(self, env, target = None, source = None):
tlist, slist = self._create_nodes(env, target, source)
- return self._init_nodes(env, tlist, slist)
+ if len(tlist) == 1:
+ _init_nodes(self, env, tlist, slist)
+ tlist = tlist[0]
+ else:
+ _init_nodes(ListBuilder(self, env, tlist), env, tlist, slist)
+
+ return tlist
+
def execute(self, **kw):
"""Execute a builder's action to create an output object.
return [self.src_suffix]
return []
+ def targets(self, node):
+ """Return the list of targets for this builder instance.
+
+ For most normal builders, this is just the supplied node.
+ """
+ return [ node ]
+
+class ListBuilder:
+ """This is technically not a Builder object, but a wrapper
+ around another Builder object. This is designed to look
+ like a Builder object, though, for purposes of building an
+ array of targets from a single Action execution.
+ """
+
+ def __init__(self, builder, env, tlist):
+ self.builder = builder
+ self.scanner = builder.scanner
+ self.env = env
+ self.tlist = tlist
+
+ def execute(self, **kw):
+ if hasattr(self, 'status'):
+ return self.status
+ for t in self.tlist:
+ # unlink all targets before building any
+ t.remove()
+ kw['target'] = self.tlist[0]
+ self.status = apply(self.builder.execute, (), kw)
+ for t in self.tlist:
+ if not t is kw['target']:
+ t.build()
+ return self.status
+
+ def get_raw_contents(self, **kw):
+ return apply(self.builder.get_raw_contents, (), kw)
+
+ def get_contents(self, **kw):
+ return apply(self.builder.get_contents, (), kw)
+
+ def src_suffixes(self):
+ return self.builder.src_suffixes()
+
+ def targets(self, node):
+ """Return the list of targets for this builder instance.
+ """
+ return self.tlist
+
class MultiStepBuilder(BuilderBase):
"""This is a builder subclass that can build targets in
multiple steps. The src_builder parameter to the constructor
act_py = test.workpath('act.py')
outfile = test.workpath('outfile')
+outfile2 = test.workpath('outfile')
show_string = None
instanced = None
env_scanner = None
+count = 0
class Environment:
def subst(self, s):
assert r == 0
assert show_string == expect7, show_string
+ global count
+ count = 0
def function1(**kw):
- open(kw['target'], 'w').write("function1\n")
+ global count
+ count = count + 1
+ if not type(kw['target']) is type([]):
+ kw['target'] = [ kw['target'] ]
+ for t in kw['target']:
+ open(t, 'w').write("function1\n")
return 1
builder = MyBuilder(action = function1, name = "function1")
- r = builder.execute(target = outfile)
- assert r == 1
- c = test.read(outfile, 'r')
+ r = builder.execute(target = [outfile, outfile2])
+ assert r == 1
+ assert count == 1
+ c = test.read(outfile, 'r')
+ assert c == "function1\n", c
+ c = test.read(outfile2, 'r')
assert c == "function1\n", c
class class1a:
assert tgts[1].path == 'tgt4b.o', \
"Target has unexpected name: %s" % tgts[1].path
+ def test_ListBuilder(self):
+ """Testing ListBuilder class."""
+ global count
+ count = 0
+ def function2(**kw):
+ global count
+ count = count + 1
+ if not type(kw['target']) is type([]):
+ kw['target'] = [ kw['target'] ]
+ for t in kw['target']:
+ open(t, 'w').write("function2\n")
+ return 1
+
+ builder = SCons.Builder.Builder(action = function2, name = "function2")
+ tgts = builder(env, target = [outfile, outfile2], source = 'foo')
+ r = tgts[0].builder.execute(target = tgts[0])
+ assert r == 1, r
+ c = test.read(outfile, 'r')
+ assert c == "function2\n", c
+ c = test.read(outfile2, 'r')
+ assert c == "function2\n", c
+ r = tgts[1].builder.execute(target = tgts[1])
+ assert r == 1, r
+ assert count == 1, count
+
def test_MultiStepBuilder(self):
"""Testing MultiStepBuilder class."""
builder1 = SCons.Builder.Builder(name = "builder1",
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."""
- def __init__(self, tm, target, top):
+ def __init__(self, tm, targets, top):
self.tm = tm
- self.target = target
+ self.targets = targets
+ self.bsig = {}
self.top = top
def execute(self):
- if not self.target.get_state() == SCons.Node.up_to_date:
- self.target.build()
+ if not self.targets[0].get_state() == SCons.Node.up_to_date:
+ self.targets[0].build()
def get_target(self):
"""Fetch the target being built or updated by this task.
"""
- return self.target
+ return self.targets[0]
- def set_bsig(self, bsig):
- """Set the task's (*not* the target's) build signature.
+ def set_bsig(self, target, bsig):
+ """Set the task's (*not* the target's) build signature
+ for this target.
- This will be used later to update the target's build
- signature if the build succeeds."""
- self.bsig = bsig
+ This will be used later to update the target's actual
+ build signature *if* the build succeeds."""
+ self.bsig[target] = bsig
- def set_tstate(self, state):
- """Set the target node's state."""
- self.target.set_state(state)
+ 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.
things. Most importantly, this calls back to the
Taskmaster to put any node tasks waiting on this one
back on the pending list."""
- if self.target.get_state() == SCons.Node.executing:
- self.set_tstate(SCons.Node.executed)
- self.target.set_bsig(self.bsig)
- self.tm.add_pending(self.target)
+ if self.targets[0].get_state() == SCons.Node.executing:
+ self.set_tstates(SCons.Node.executed)
+ for t in self.targets:
+ t.set_bsig(self.bsig[t])
+ parents = {}
+ for p in reduce(lambda x, y: x + y.get_parents(), self.targets, []):
+ parents[p] = 1
+ ready = filter(lambda x: (x.get_state() == SCons.Node.pending
+ and x.children_are_executed()),
+ parents.keys())
+ tasks = {}
+ for t in map(lambda r: r.task, ready):
+ tasks[t] = 1
+ self.tm.pending_to_ready(tasks.keys())
def failed(self):
"""Default action when a task fails: stop the build."""
def fail_stop(self):
"""Explicit stop-the-build failure."""
- self.set_tstate(SCons.Node.failed)
+ self.set_tstates(SCons.Node.failed)
self.tm.stop()
def fail_continue(self):
"""Explicit continue-the-build failure.
- This sets failure status on the target node and all of
- its dependent parent nodes.
+ This sets failure status on the target nodes and all of
+ their dependent parent nodes.
"""
- def get_parents(node): return node.get_parents()
- walker = SCons.Node.Walker(self.target, get_parents)
- while 1:
- node = walker.next()
- if node == None: break
- self.tm.remove_pending(node)
- node.set_state(SCons.Node.failed)
-
+ nodes = {}
+ for t in self.targets:
+ def get_parents(node): return node.get_parents()
+ walker = SCons.Node.Walker(t, get_parents)
+ while 1:
+ n = walker.next()
+ if n == None: break
+ nodes[n] = 1
+ pending = filter(lambda x: x.get_state() == SCons.Node.pending,
+ nodes.keys())
+ tasks = {}
+ for t in map(lambda r: r.task, ready):
+ tasks[t] = 1
+ self.tm.pending_remove(tasks.keys())
+
+ def make_ready(self):
+ """Make a task ready for execution."""
+ state = SCons.Node.up_to_date
+ for t in self.targets:
+ bsig = self.tm.calc.bsig(t)
+ self.set_bsig(t, bsig)
+ if not self.tm.calc.current(t, bsig):
+ state = SCons.Node.executing
+ self.set_tstates(state)
+ self.tm.add_ready(self)
+
class Calc:
# and over again:
n.set_csig(self.calc.csig(n))
continue
- task = self.tasker(self, n, self.walkers[0].is_done())
- if not n.children_are_executed():
- n.set_state(SCons.Node.pending)
- n.task = task
+ try:
+ tlist = n.builder.targets(n)
+ except AttributeError:
+ tlist = [ n ]
+ task = self.tasker(self, tlist, self.walkers[0].is_done())
+ if not tlist[0].children_are_executed():
+ for t in tlist:
+ t.set_state(SCons.Node.pending)
+ t.task = task
self.pending = self.pending + 1
continue
- self.make_ready(task, n)
+ task.make_ready()
return
def is_blocked(self):
self.pending = 0
self.ready = []
- def add_pending(self, node):
- """Add all the pending parents that are now executable
- to the 'ready' queue."""
- ready = filter(lambda x: (x.get_state() == SCons.Node.pending
- and x.children_are_executed()),
- node.get_parents())
- for n in ready:
- task = n.task
- delattr(n, "task")
- self.make_ready(task, n)
- self.pending = self.pending - len(ready)
-
- def remove_pending(self, node):
- """Remove a node from the 'ready' queue."""
- if node.get_state() == SCons.Node.pending:
- self.pending = self.pending - 1
-
- def make_ready(self, task, node):
- """Common routine that takes a single task+node and makes
- them available on the 'ready' queue."""
- bsig = self.calc.bsig(node)
- task.set_bsig(bsig)
- if self.calc.current(node, bsig):
- task.set_tstate(SCons.Node.up_to_date)
- else:
- task.set_tstate(SCons.Node.executing)
+ def add_ready(self, task):
+ """Add a task to the ready queue.
+ """
self.ready.append(task)
+
+ def pending_to_ready(self, tasks):
+ """Move the specified tasks from the pending count
+ to the 'ready' queue.
+ """
+ self.pending_remove(tasks)
+ for t in tasks:
+ t.make_ready()
+
+ def pending_remove(self, tasks):
+ """Remove tasks from the pending count.
+
+ We assume that the caller has already confirmed that
+ the nodes in this task are in pending state.
+ """
+ self.pending = self.pending - len(tasks)
class MyTask(SCons.Taskmaster.Task):
def execute(self):
global built
- if self.target.get_state() == SCons.Node.up_to_date:
+ if self.targets[0].get_state() == SCons.Node.up_to_date:
if self.top:
- built = self.target.name + " up-to-date top"
+ built = self.targets[0].name + " up-to-date top"
else:
- built = self.target.name + " up-to-date"
+ built = self.targets[0].name + " up-to-date"
else:
- self.target.build()
+ self.targets[0].build()
n1.set_state(None)
n2.set_state(None)
assert built == "MyTM.stop()"
assert tm.next_task() is None
- #def test_add_pending(self):
- # pass
- #
- #def test_remove_pending(self):
- # pass
+ def test_add_ready(self):
+ """Test adding a task to the ready queue"""
+ class MyTask:
+ def __init__(self, tm, tlist, top):
+ pass
+ def make_ready(self):
+ pass
+ n1 = Node("n1")
+ tm = SCons.Taskmaster.Taskmaster([n1], tasker = MyTask)
+ task = MyTask(tm, [], 0)
+ tm.add_ready(task)
+ assert tm.ready == [ task ], tm.ready
+
+ def test_pending_to_ready(self):
+ pass
+
+ def test_pending_remove(self):
+ pass