diff options
author | Steven Knight <knight@baldmt.com> | 2002-01-26 13:40:32 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2002-01-26 13:40:32 (GMT) |
commit | e9a2f2b02f67bf88917d74c9725f8d72b86b7cfc (patch) | |
tree | a49960ab7d5b30717957d2e3f2be184e65eecc8a | |
parent | ca75f94e83c37ca03de9740bb141e442c53db235 (diff) | |
download | SCons-e9a2f2b02f67bf88917d74c9725f8d72b86b7cfc.zip SCons-e9a2f2b02f67bf88917d74c9725f8d72b86b7cfc.tar.gz SCons-e9a2f2b02f67bf88917d74c9725f8d72b86b7cfc.tar.bz2 |
Only execute an Action once for a List of targets.
-rw-r--r-- | src/CHANGES.txt | 6 | ||||
-rw-r--r-- | src/engine/SCons/Builder.py | 98 | ||||
-rw-r--r-- | src/engine/SCons/BuilderTests.py | 45 | ||||
-rw-r--r-- | src/engine/SCons/Taskmaster.py | 147 | ||||
-rw-r--r-- | src/engine/SCons/TaskmasterTests.py | 31 |
5 files changed, 232 insertions, 95 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt index cd3ebd4..f381241 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -57,6 +57,12 @@ RELEASE 0.04 - - 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. diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 6fffeee..846b286 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -52,6 +52,26 @@ def Builder(**kw): +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). @@ -113,32 +133,17 @@ class BuilderBase: 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. @@ -161,6 +166,53 @@ class BuilderBase: 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 diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 65aa934..e05cdb7 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -61,10 +61,12 @@ sys.exit(0) 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): @@ -245,14 +247,24 @@ class BuilderTestCase(unittest.TestCase): 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: @@ -446,6 +458,31 @@ class BuilderTestCase(unittest.TestCase): 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", diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 828fecd..aa6fe84 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -54,30 +54,33 @@ class Task: 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. @@ -87,10 +90,20 @@ class Task: 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.""" @@ -98,23 +111,41 @@ class Task: 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: @@ -196,13 +227,18 @@ class Taskmaster: # 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): @@ -214,30 +250,23 @@ class Taskmaster: 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) diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index 5e661ee..799f8cf 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -132,13 +132,13 @@ class TaskmasterTestCase(unittest.TestCase): 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) @@ -303,11 +303,24 @@ class TaskmasterTestCase(unittest.TestCase): 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 |