From: stevenknight Date: Thu, 15 May 2003 14:09:42 +0000 (+0000) Subject: Refactor how actions get executed to eliminate a lot of redundant signature calcualat... X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=02418deab71cb4aa61b39fbc0d21a98c9382cea0;p=scons.git Refactor how actions get executed to eliminate a lot of redundant signature calcualations. git-svn-id: http://scons.tigris.org/svn/scons/trunk@681 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/bin/files b/bin/files index dec67505..86f297c5 100644 --- a/bin/files +++ b/bin/files @@ -3,6 +3,7 @@ ./SCons/Defaults.py ./SCons/Environment.py ./SCons/Errors.py +./SCons/Executor.py ./SCons/Job.py ./SCons/Node/Alias.py ./SCons/Node/FS.py diff --git a/src/CHANGES.txt b/src/CHANGES.txt index e1d842cb..509ed4c8 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -80,8 +80,11 @@ RELEASE 0.14 - XXX - Interpolate the null string if an out-of-range subscript is used for a construction variable. - - Fix the internal Link function so that it creates properly links or - copies of files in subsidiary BuildDir directories. + - Fix the internal Link function so that it properly links or copies + files in subsidiary BuildDir directories. + + - Refactor the internal representation of a single execution instance + of an action to eliminate redundant signature calculations. From Damyan Pepper: diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 24277b41..65d6886e 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -4,6 +4,7 @@ SCons/Builder.py SCons/Defaults.py SCons/Environment.py SCons/Errors.py +SCons/Executor.py SCons/Job.py SCons/exitfuncs.py SCons/Node/__init__.py diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 8b0ac85c..414c5de6 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -49,6 +49,7 @@ import os.path from SCons.Errors import InternalError, UserError import SCons.Action +import SCons.Executor import SCons.Node import SCons.Node.FS import SCons.Util @@ -135,12 +136,8 @@ def _init_nodes(builder, env, overrides, tlist, slist): the proper Builder information. """ - for s in slist: - src_key = s.scanner_key() # the file suffix - scanner = env.get_scanner(src_key) - if scanner: - s.source_scanner = scanner - + # First, figure out if there are any errors in the way the targets + # were specified. for t in tlist: if t.side_effect: raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t) @@ -161,14 +158,43 @@ def _init_nodes(builder, env, overrides, tlist, slist): elif t.sources != slist: raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t) + # The targets are fine, so find or make the appropriate Executor to + # build this particular list of targets from this particular list of + # sources. + executor = None + if builder.multi: + try: + executor = tlist[0].get_executor(create = 0) + except AttributeError: + pass + else: + executor.add_sources(slist) + if executor is None: + executor = SCons.Executor.Executor(builder, + tlist[0].generate_build_env(env), + overrides, + tlist, + slist) + + # Now set up the relevant information in the target Nodes themselves. + for t in tlist: t.overrides = overrides t.cwd = SCons.Node.FS.default_fs.getcwd() t.builder_set(builder) t.env_set(env) t.add_source(slist) + t.set_executor(executor) if builder.scanner: t.target_scanner = builder.scanner + # Last, add scanners from the Environment to the source Nodes. + for s in slist: + src_key = s.scanner_key() # the file suffix + scanner = env.get_scanner(src_key) + if scanner: + s.source_scanner = scanner + + def _adjust_suffix(suff): if suff and not suff[0] in [ '.', '$' ]: return '.' + suff @@ -336,20 +362,6 @@ class BuilderBase: return tlist - def get_actions(self): - return self.action.get_actions() - - def get_raw_contents(self, target, source, env): - """Fetch the "contents" of the builder's action. - """ - return self.action.get_raw_contents(target, source, env) - - def get_contents(self, target, source, env): - """Fetch the "contents" of the builder's action - (for signature calculation). - """ - return self.action.get_contents(target, source, env) - def src_suffixes(self, env): return map(lambda x, e=env: e.subst(_adjust_suffix(x)), self.src_suffix) diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 6def6d57..f7b329de 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -39,8 +39,8 @@ import TestCmd import SCons.Action import SCons.Builder -import SCons.Errors import SCons.Environment +import SCons.Errors # Initial setup of the common environment for all tests, # a temporary working directory containing a @@ -82,6 +82,8 @@ class Environment: return {} def autogenerate(self, dir=''): return {} + def __setitem__(self, item, var): + self.d[item] = var def __getitem__(self, item): return self.d[item] def has_key(self, item): @@ -127,6 +129,14 @@ class MyNode: return self.name def is_derived(self): return self.has_builder() + def generate_build_env(self, env): + return env + def get_build_env(self): + return self.executor.get_build_env() + def set_executor(self, executor): + self.executor = executor + def get_executor(self, create=1): + return self.executor class BuilderTestCase(unittest.TestCase): @@ -162,9 +172,10 @@ class BuilderTestCase(unittest.TestCase): n1 = MyNode("n1"); n2 = MyNode("n2"); builder(env, target = n1, source = n2) - assert n1.env == env - assert n1.builder == builder - assert n1.sources == [n2] + assert n1.env == env, n1.env + assert n1.builder == builder, n1.builder + assert n1.sources == [n2], n1.sources + assert n1.executor, "no executor found" assert not hasattr(n2, 'env') target = builder(env, target = 'n3', source = 'n4') @@ -179,6 +190,14 @@ class BuilderTestCase(unittest.TestCase): assert target.name == 'n8 n9' assert target.sources[0].name == 'n10 n11' + # A test to be uncommented when we freeze the environment + # as part of calling the builder. + #env1 = Environment(VAR='foo') + #target = builder(env1, target = 'n12', source = 'n13') + #env1['VAR'] = 'bar' + #be = target.get_build_env() + #assert be['VAR'] == 'foo', be['VAR'] + if not hasattr(types, 'UnicodeType'): uni = str else: @@ -230,52 +249,6 @@ class BuilderTestCase(unittest.TestCase): assert b1 != b3 assert b2 != b3 - def test_get_actions(self): - """Test fetching the Builder's Action list - """ - def func(): - pass - builder = SCons.Builder.Builder(action=SCons.Action.ListAction(["x", - func, - "z"])) - a = builder.get_actions() - assert len(a) == 3, a - assert isinstance(a[0], SCons.Action.CommandAction), a[0] - assert isinstance(a[1], SCons.Action.FunctionAction), a[1] - assert isinstance(a[2], SCons.Action.CommandAction), a[2] - - def test_get_contents(self): - """Test returning the signature contents of a Builder - """ - - class DummyNode: - def __init__(self, name): - self.name = name - def __str__(self): - return self.name - def rfile(self): - return self - def get_subst_proxy(self): - return self - - target = map(DummyNode, map(lambda x: "__t%d__" % x, range(1, 7))) - source = map(DummyNode, map(lambda x: "__s%d__" % x, range(1, 7))) - b1 = SCons.Builder.Builder(action = "foo ${TARGETS[5]}") - contents = b1.get_contents(target,source,Environment()) - assert contents == "foo __t6__", contents - - b1 = SCons.Builder.Builder(action = "bar ${SOURCES[3:5]}") - contents = b1.get_contents(target,source,Environment()) - assert contents == "bar __s4__ __s5__", contents - - b2 = SCons.Builder.Builder(action = Func) - contents = b2.get_contents(target,source,Environment()) - assert contents == "\177\036\000\177\037\000d\000\000S", repr(contents) - - b3 = SCons.Builder.Builder(action = SCons.Action.ListAction(["foo", Func, "bar"])) - contents = b3.get_contents(target,source,Environment()) - assert contents == "foo\177\036\000\177\037\000d\000\000Sbar", repr(contents) - def test_node_factory(self): """Test a Builder that creates nodes of a specified class """ diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py new file mode 100644 index 00000000..2b4e634f --- /dev/null +++ b/src/engine/SCons/Executor.py @@ -0,0 +1,141 @@ +"""SCons.Executor + +A module for executing actions with specific lists of target and source +Nodes. + +""" + +# +# __COPYRIGHT__ +# +# 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__" + + +class Executor: + """A class for controlling instances of executing an action. + + This largely exists to hold a single association of a builder, + environment, environment overrides, targets and sources for later + processing as needed. + """ + + def __init__(self, builder, env, overrides, targets, sources): + self.builder = builder + self.env = env + self.overrides = overrides + self.targets = targets + self.sources = sources[:] + + def get_build_env(self): + """Fetch or create the appropriate build Environment + for this Executor. + """ + try: + return self.build_env + except AttributeError: + if self.env is None: + # There was no Environment specifically associated with + # this set of targets (which kind of implies that it + # is--or they are--source files, but who knows...). + # So use the environment associated with the Builder + # itself. + env = self.builder.env + overrides = self.builder.overrides + else: + # The normal case: use the Environment that was + # used to specify how these targets will be built. + env = self.env + overrides = self.overrides + self.build_env = env.Override(overrides) + return self.build_env + + def get_action_list(self, target): + """Fetch or create the appropriate action list (for this target). + + There is an architectural mistake here: we cache the action list + for the Executor and re-use it regardless of which target is + being asked for. In practice, this doesn't seem to be a problem + because executing the action list will update all of the targets + involved, so only one target's pre- and post-actions will win, + anyway. This is probably a bug we should fix... + """ + try: + al = self.action_list + except AttributeError: + al = self.builder.action.get_actions() + self.action_list = al + # XXX shouldn't reach into node attributes like this + return target.pre_actions + al + target.post_actions + + def __call__(self, target, func): + """Actually execute the action list.""" + action_list = self.get_action_list(target) + if not action_list: + return + env = self.get_build_env() + for action in action_list: + func(action, self.targets, self.sources, env) + + def add_sources(self, sources): + """Add source files to this Executor's list. This is necessary + for "multi" Builders that can be called repeatedly to build up + a source file list for a given target.""" + slist = filter(lambda x, s=self.sources: x not in s, sources) + self.sources.extend(slist) + + def get_raw_contents(self): + """Fetch the raw signature contents. This, along with + get_contents(), is the real reason this class exists, so we can + compute this once and cache it regardless of how many target or + source Nodes there are. + """ + try: + return self.raw_contents + except AttributeError: + action = self.builder.action + self.raw_contents = action.get_raw_contents(self.targets, + self.sources, + self.get_build_env()) + return self.raw_contents + + def get_contents(self): + """Fetch the signature contents. This, along with + get_raw_contents(), is the real reason this class exists, so we + can compute this once and cache it regardless of how many target + or source Nodes there are. + """ + try: + return self.contents + except AttributeError: + action = self.builder.action + self.contents = action.get_contents(self.targets, + self.sources, + self.get_build_env()) + return self.contents + + def get_timestamp(self): + """Fetch a time stamp for this Executor. We don't have one, of + course (only files do), but this is the interface used by the + timestamp module. + """ + return None diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py new file mode 100644 index 00000000..6af5794f --- /dev/null +++ b/src/engine/SCons/ExecutorTests.py @@ -0,0 +1,173 @@ +# +# __COPYRIGHT__ +# +# 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 string +import sys +import unittest + +import SCons.Executor + + +class MyEnvironment: + def __init__(self, **kw): + self._dict = {} + self._dict.update(kw) + def Override(self, overrides): + d = self._dict.copy() + d.update(overrides) + return d + +class MyAction: + actions = ['action1', 'action2'] + def get_actions(self): + return self.actions + def get_raw_contents(self, target, source, env): + return string.join(['RAW'] + self.actions + target + source) + def get_contents(self, target, source, env): + return string.join(self.actions + target + source) + +class MyBuilder: + def __init__(self, env, overrides): + self.env = env + self.overrides = overrides + self.action = MyAction() + +class MyNode: + def __init__(self, pre, post): + self.pre_actions = pre + self.post_actions = post + + +class ExecutorTestCase(unittest.TestCase): + + def test__init__(self): + """Test creating an Executor""" + source_list = ['s1', 's2'] + x = SCons.Executor.Executor('b', 'e', 'o', 't', source_list) + assert x.builder == 'b', x.builder + assert x.env == 'e', x.env + assert x.overrides == 'o', x.overrides + assert x.targets == 't', x.targets + source_list.append('s3') + assert x.sources == ['s1', 's2'], x.sources + + def test_get_build_env(self): + """Test fetching and generating a build environment""" + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + x.build_env = 'eee' + be = x.get_build_env() + assert be == 'eee', be + + x = SCons.Executor.Executor('b', + MyEnvironment(X='xxx'), + {'O':'ooo'}, + 't', + ['s1', 's2']) + be = x.get_build_env() + assert be == {'O':'ooo', 'X':'xxx'}, be + + env = MyEnvironment(Y='yyy') + over = {'O':'ooo'} + x = SCons.Executor.Executor(MyBuilder(env, over), None, {}, 't', 's') + be = x.get_build_env() + assert be == {'O':'ooo', 'Y':'yyy'}, be + + def test_get_action_list(self): + """Test fetching and generating an action list""" + x = SCons.Executor.Executor('b', 'e', 'o', 't', 's') + x.action_list = ['aaa'] + al = x.get_action_list(MyNode([], [])) + assert al == ['aaa'], al + al = x.get_action_list(MyNode(['PRE'], ['POST'])) + assert al == ['PRE', 'aaa', 'POST'], al + + x = SCons.Executor.Executor(MyBuilder('e', 'o'), None, {}, 't', 's') + al = x.get_action_list(MyNode(['pre'], ['post'])) + assert al == ['pre', 'action1', 'action2', 'post'], al + + def test__call__(self): + """Test calling an Executor""" + actions = [] + env = MyEnvironment(CALL='call') + b = MyBuilder(env, {}) + x = SCons.Executor.Executor(b, None, {}, ['t1', 't2'], ['s1', 's2']) + def func(action, target, source, env, a=actions): + a.append(action) + assert target == ['t1', 't2'], target + assert source == ['s1', 's2'], source + assert env == {'CALL':'call'}, env + x(MyNode(['pre'], ['post']), func) + assert actions == ['pre', 'action1', 'action2', 'post'], actions + + def test_add_sources(self): + """Test adding sources to an Executor""" + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + assert x.sources == ['s1', 's2'], x.sources + x.add_sources(['s1', 's2']) + assert x.sources == ['s1', 's2'], x.sources + x.add_sources(['s3', 's1', 's4']) + assert x.sources == ['s1', 's2', 's3', 's4'], x.sources + + def test_get_raw_contents(self): + """Test fetching the raw signatures contents""" + env = MyEnvironment(RC='raw contents') + + x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s']) + x.raw_contents = 'raw raw raw' + rc = x.get_raw_contents() + assert rc == 'raw raw raw', rc + + x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s']) + rc = x.get_raw_contents() + assert rc == 'RAW action1 action2 t s', rc + + def test_get_contents(self): + """Test fetching the signatures contents""" + env = MyEnvironment(C='contents') + + x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s']) + x.contents = 'contents' + c = x.get_contents() + assert c == 'contents', c + + x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s']) + c = x.get_contents() + assert c == 'action1 action2 t s', c + + def test_get_timetstamp(self): + """Test fetching the "timestamp" """ + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + ts = x.get_timestamp() + assert ts is None, ts + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ ExecutorTestCase ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index b4641a7e..c715adeb 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -1073,9 +1073,8 @@ class File(Entry): return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0, cwd=self.cwd) - def generate_build_env(self): - env = SCons.Node.Node.generate_build_env(self) - + def generate_build_env(self, env): + """Generate an appropriate Environment to build this File.""" return env.Override({'Dir' : self.Dir, 'File' : self.File, 'RDirs' : self.RDirs}) diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 1f582641..13f171e9 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -46,28 +46,6 @@ except NameError : True = 1 ; False = 0 -class Builder: - def __init__(self, factory): - self.factory = factory - - def get_actions(self): - class Action: - def __call__(self, targets, sources, env): - global built_it - built_it = 1 - return 0 - def show(self, string): - pass - def strfunction(self, targets, sources, env): - return "" - return [Action()] - - def targets(self, t): - return [t] - - def source_factory(self, name): - return self.factory(name) - scanner_count = 0 class Scanner: @@ -95,6 +73,34 @@ class Environment: def Override(self, overrides): return self +class Action: + def __call__(self, targets, sources, env): + global built_it + built_it = 1 + return 0 + def show(self, string): + pass + def strfunction(self, targets, sources, env): + return "" + def get_actions(self): + return [self] + +class Builder: + def __init__(self, factory): + self.factory = factory + self.env = Environment() + self.overrides = {} + self.action = Action() + + def get_actions(self): + return [self] + + def targets(self, t): + return [t] + + def source_factory(self, name): + return self.factory(name) + class BuildDirTestCase(unittest.TestCase): def runTest(self): """Test build dir functionality""" @@ -916,7 +922,7 @@ class FSTestCase(unittest.TestCase): test.write("remove_me", "\n") assert os.path.exists(test.workpath("remove_me")) f1 = fs.File(test.workpath("remove_me")) - f1.builder = 1 + f1.builder = Builder(fs.File) f1.env_set(Environment()) f1.prepare() assert not os.path.exists(test.workpath("remove_me")) diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 2cf6d4b7..ff578719 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -53,6 +53,9 @@ class MyAction: self.order = built_order return 0 + def get_actions(self): + return [self] + class MyNonGlobalAction: def __init__(self): self.order = 0 @@ -71,11 +74,24 @@ class MyNonGlobalAction: self.order = built_order return 0 + def get_actions(self): + return [self] + +class Environment: + def Dictionary(self, *args): + return {} + def Override(self, overrides): + return overrides + class Builder: + def __init__(self): + self.env = Environment() + self.overrides = {} + self.action = MyAction() def targets(self, t): return [t] def get_actions(self): - return [MyAction()] + return [self.action] def get_contents(self, target, source, env): return 7 @@ -86,6 +102,7 @@ class NoneBuilder(Builder): class ListBuilder(Builder): def __init__(self, *nodes): + Builder.__init__(self) self.nodes = nodes def execute(self, target, source, env): if hasattr(self, 'status'): @@ -107,12 +124,6 @@ class ExceptBuilder2: def execute(self, target, source, env): raise "foo" -class Environment: - def Dictionary(self, *args): - return {} - def Override(self, overrides): - return overrides - class Scanner: called = None def __call__(self, node): @@ -154,7 +165,7 @@ class NodeTestCase(unittest.TestCase): node.sources = ["yyy", "zzz"] node.build() assert built_it - assert built_target[0] == node, built_target[0] + assert built_target == [node], built_target assert built_source == ["yyy", "zzz"], built_source built_it = None @@ -163,10 +174,10 @@ class NodeTestCase(unittest.TestCase): node.env_set(Environment()) node.path = "qqq" node.sources = ["rrr", "sss"] - node.overrides = { "foo" : 1, "bar" : 2 } + node.builder.overrides = { "foo" : 1, "bar" : 2 } node.build() assert built_it - assert built_target[0] == node, built_target[0] + assert built_target == [node], built_target assert built_source == ["rrr", "sss"], built_source assert built_args["foo"] == 1, built_args assert built_args["bar"] == 2, built_args @@ -184,6 +195,17 @@ class NodeTestCase(unittest.TestCase): fff.sources = ["hhh", "iii"] ggg.sources = ["hhh", "iii"] # [Charles C. 1/7/2002] Uhhh, why are there no asserts here? + # [SK, 15 May 2003] I dunno, let's add some... + built_it = None + fff.build() + assert built_it + assert built_target == [fff], built_target + assert built_source == ["hhh", "iii"], built_source + built_it = None + ggg.build() + assert built_it + assert built_target == [ggg], built_target + assert built_source == ["hhh", "iii"], built_source built_it = None jjj = MyNode("jjj") @@ -285,31 +307,6 @@ class NodeTestCase(unittest.TestCase): assert t == [], t assert m == None, m - def test_builder_sig_adapter(self): - """Test the node's adapter for builder signatures - """ - node = SCons.Node.Node() - node.builder_set(Builder()) - node.env_set(Environment()) - c = node.builder_sig_adapter().get_contents() - assert c == 7, c - - class ListBuilder: - def __init__(self, targets): - self.tgt = targets - def targets(self, t): - return self.tgt - def get_contents(self, target, source, env): - assert target == self.tgt - return 8 - - node1 = SCons.Node.Node() - node2 = SCons.Node.Node() - node.builder_set(ListBuilder([node1, node2])) - node.env_set(Environment()) - c = node.builder_sig_adapter().get_contents() - assert c == 8, c - def test_current(self): """Test the default current() method """ @@ -605,7 +602,7 @@ class NodeTestCase(unittest.TestCase): """Test Scanner functionality """ node = MyNode("nnn") - node.builder = 1 + node.builder = Builder() node.env_set(Environment()) s = Scanner() diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 5231f902..09c930b6 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -122,21 +122,36 @@ class Node: # what line in what file created the node, for example). Annotate(self) - def generate_build_env(self): + def generate_build_env(self, env): """Generate the appropriate Environment to build this node.""" - if self.env is None: - # The node itself doesn't have an associated Environment - # (which kind of implies it's a source code file, but who - # knows...). Regardless of why, use the environment (if - # any) associated with the Builder itself. - env = self.builder.env - overrides = self.builder.overrides - else: - # The normal case: use the Environment used to specify how - # this Node is to be built. - env = self.env - overrides = self.overrides - return env.Override(overrides) + return env + + def get_build_env(self): + """Fetch the appropriate Environment to build this node.""" + executor = self.get_executor() + return executor.get_build_env() + + def set_executor(self, executor): + """Set the action executor for this node.""" + self.executor = executor + + def get_executor(self, create=1): + """Fetch the action executor for this node. Create one if + there isn't already one, and requested to do so.""" + try: + executor = self.executor + except AttributeError: + if not create: + raise + import SCons.Builder + env = self.generate_build_env(self.builder.env) + executor = SCons.Executor.Executor(self.builder, + env, + self.builder.overrides, + [self], + self.sources) + self.executor = executor + return executor def _for_each_action(self, func): """Call a function for each action required to build a node. @@ -147,15 +162,8 @@ class Node: to do different things.""" if not self.has_builder(): return - action_list = self.pre_actions + \ - self.builder.get_actions() + \ - self.post_actions - if not action_list: - return - targets = self.builder.targets(self) - env = self.generate_build_env() - for action in action_list: - func(action, targets, self.sources, env) + executor = self.get_executor() + executor(self, func) def build(self): """Actually build the node. @@ -249,24 +257,6 @@ class Node: """ return [], None - def builder_sig_adapter(self): - """Create an adapter for calculating a builder's signature. - - The underlying signature class will call get_contents() - to fetch the signature of a builder, but the actual - content of that signature depends on the node and the - environment (for construction variable substitution), - so this adapter provides the right glue between the two. - """ - class Adapter: - def __init__(self, node): - self.node = node - def get_contents(self): - return self.node.builder.get_contents(self.node.builder.targets(self.node), self.node.sources, self.node.generate_build_env()) - def get_timestamp(self): - return None - return Adapter(self) - def get_found_includes(self, env, scanner, target): """Return the scanned include lines (implicit dependencies) found in this node. @@ -351,7 +341,7 @@ class Node: self.implicit = [] self.del_bsig() - build_env = self.generate_build_env() + build_env = self.get_build_env() for child in self.children(scan=0): self._add_child(self.implicit, @@ -589,7 +579,7 @@ class Node: user, of the include tree for the sources of this node. """ if self.has_builder() and self.env: - env = self.generate_build_env() + env = self.get_build_env() for s in self.sources: def f(node, env=env, scanner=s.source_scanner, target=self): return node.get_found_includes(env, scanner, target) diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index 64cd5f9f..830b94a7 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -166,7 +166,6 @@ class SConf: except: pass - for n in nodes: state = n.get_state() if (state != SCons.Node.executed and @@ -193,6 +192,7 @@ class SConf: #target = self.confdir.File("conftest_" + str(_ac_build_counter)) f = "conftest_" + str(_ac_build_counter) target = os.path.join(str(self.confdir), f) + self.env['SCONF_TEXT'] = text if text != None: source = self.confdir.File(f + extension) sourceNode = self.env.SConfSourceBuilder(target=source, @@ -200,7 +200,6 @@ class SConf: nodesToBeBuilt.append(sourceNode) else: source = None - self.env['SCONF_TEXT'] = text node = builder(target = target, source = source) nodesToBeBuilt.append(node) @@ -301,7 +300,7 @@ class SConf: # We record errors in the cache. Only non-exisiting targets may # have recorded errors needs_rebuild = target[0].exists() - buildSig = target[0].builder.get_contents(target, source, env) + buildSig = target[0].builder.action.get_contents(target, source, env) for node in source: if node.get_state() != SCons.Node.up_to_date: # if any of the sources has changed, we cannot use our cache diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py index 7e1cc400..bb3efb8f 100644 --- a/src/engine/SCons/Sig/SigTests.py +++ b/src/engine/SCons/Sig/SigTests.py @@ -138,7 +138,7 @@ class DummyNode: def store_timestamp(self): pass - def builder_sig_adapter(self): + def get_executor(self): class Adapter: def get_contents(self): return 111 diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index cd6fe7e8..cf7a86f0 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -282,7 +282,7 @@ class Calculator: sigs = map(lambda n, c=self: n.calc_signature(c), children) if node.has_builder(): - sigs.append(self.module.signature(node.builder_sig_adapter())) + sigs.append(self.module.signature(node.get_executor())) bsig = self.module.collect(filter(lambda x: not x is None, sigs))