From c31d933609202d40181640364bebfa8662b3df85 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Tue, 20 Jan 2004 16:48:03 +0000 Subject: Refactor variable substitution for more scalable expansion of , etc. --- src/RELEASE.txt | 9 +++ src/engine/SCons/Action.py | 47 ++++++------ src/engine/SCons/ActionTests.py | 130 +++++++++++--------------------- src/engine/SCons/Environment.py | 30 +++----- src/engine/SCons/EnvironmentTests.py | 105 ++++++++++++++++++++++---- src/engine/SCons/Util.py | 141 ++++++++++++++++++++++++++++------- src/engine/SCons/UtilTests.py | 119 +++++++++++++++++++++++++---- 7 files changed, 392 insertions(+), 189 deletions(-) diff --git a/src/RELEASE.txt b/src/RELEASE.txt index 4bb0a8f..b52d3ad 100644 --- a/src/RELEASE.txt +++ b/src/RELEASE.txt @@ -34,6 +34,15 @@ RELEASE 0.95 - XXX variable to 0 (or zipfile.ZIP_STORED, if you import the underlying zipfile module). + - The meaning of some of the values of the "mode" argument to + the SCons.Util.scons_subst() and SCons.Util.scons_subst_list() + functions have been swapped. The wrapper Environment.subst() + and Environment.subst_list() methods remain completely unchanged, + this so this should have no external effect. (We've listed the + change here because it might cause a problem if you're reaching + into the SCons internals and calling either of the SCons.Util + functions directly.) + SCons is developed with an extensive regression test suite, and a rigorous development methodology for continually improving that suite. Because of this, SCons is of sufficient quality that you can use it diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 719e213..e2ceb2a 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -172,7 +172,7 @@ class CommandAction(ActionBase): def __init__(self, cmd): # Cmd list can actually be a list or a single item...basically # anything that we could pass in as the first arg to - # scons_subst_list(). + # Environment.subst_list(). self.cmd_list = cmd def strfunction(self, target, source, env): @@ -263,32 +263,28 @@ class CommandAction(ActionBase): return ret return 0 - def get_raw_contents(self, target, source, env): + def get_raw_contents(self, target, source, env, dict=None): """Return the complete contents of this action's command line. """ cmd = self.cmd_list - if not SCons.Util.is_List(cmd): - cmd = [ cmd ] - return SCons.Util.scons_subst(string.join(map(str, cmd)), - env, - SCons.Util.SUBST_RAW, - SCons.Util.target_prep(target), - SCons.Util.source_prep(source)) - - def get_contents(self, target, source, env): + if SCons.Util.is_List(cmd): + cmd = string.join(map(str, cmd)) + else: + cmd = str(cmd) + return env.subst(cmd, SCons.Util.SUBST_RAW, target, source, dict) + + def get_contents(self, target, source, env, dict=None): """Return the signature contents of this action's command line. This strips $(-$) and everything in between the string, since those parts don't affect signatures. """ cmd = self.cmd_list - if not SCons.Util.is_List(cmd): - cmd = [ cmd ] - return SCons.Util.scons_subst(string.join(map(str, cmd)), - env, - SCons.Util.SUBST_SIG, - SCons.Util.target_prep(target), - SCons.Util.source_prep(source)) + if SCons.Util.is_List(cmd): + cmd = string.join(map(str, cmd)) + else: + cmd = str(cmd) + return env.subst(cmd, SCons.Util.SUBST_SIG, target, source, dict) class CommandGeneratorAction(ActionBase): """Class for command-generator actions.""" @@ -321,13 +317,13 @@ class CommandGeneratorAction(ActionBase): act = self.__generate(target, source, env, 0) return act(target, rsources, env) - def get_contents(self, target, source, env): + def get_contents(self, target, source, env, dict=None): """Return the signature contents of this action's command line. This strips $(-$) and everything in between the string, since those parts don't affect signatures. """ - return self.__generate(target, source, env, 1).get_contents(target, source, env) + return self.__generate(target, source, env, 1).get_contents(target, source, env, dict=None) class LazyCmdGenerator: """This is a simple callable class that acts as a command generator. @@ -393,7 +389,7 @@ class FunctionAction(ActionBase): r = self.execfunction(target=target, source=rsources, env=env) return r - def get_contents(self, target, source, env): + def get_contents(self, target, source, env, dict=None): """Return the signature contents of this callable action. By providing direct access to the code object of the @@ -433,14 +429,13 @@ class ListAction(ActionBase): return r return 0 - def get_contents(self, target, source, env): + def get_contents(self, target, source, env, dict=None): """Return the signature contents of this action list. Simple concatenation of the signatures of the elements. """ - target = SCons.Util.target_prep(target) - source = SCons.Util.source_prep(source) - return string.join(map(lambda x, t=target, s=source, e=env: - x.get_contents(t, s, e), + dict = SCons.Util.subst_dict(target, source, env) + return string.join(map(lambda x, t=target, s=source, e=env, d=dict: + x.get_contents(t, s, e, d), self.list), "") diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 7396118..480b299 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -125,70 +125,11 @@ class Environment: self.d['ESCAPE'] = scons_env['ESCAPE'] for k, v in kw.items(): self.d[k] = v - def subst_dict(self, target, source): - dict = self.d.copy() - if not SCons.Util.is_List(target): - target = [target] - if not SCons.Util.is_List(source): - source = [source] - dict['TARGETS'] = target - dict['SOURCES'] = source - try: - dict['TARGET'] = target[0] - except IndexError: - dict['TARGET'] = '' - try: - dict['SOURCE'] = source[0] - except IndexError: - dict['SOURCE'] = '' - return dict - def subst(self, strSubst): - if not SCons.Util.is_String(strSubst): - return strSubst - try: - s0, s1 = strSubst[:2] - except (IndexError, ValueError): - return strSubst - if s0 == '$': - if s1 == '{': - return self.d.get(strSubst[2:-1], '') - else: - return self.d.get(strSubst[1:], '') - return strSubst - def subst_list(self, strSubst, raw=0, target=[], source=[]): - dict = self.subst_dict(target, source) - if SCons.Util.is_String(strSubst): - strSubst = string.split(strSubst) - elif not SCons.Util.is_List(strSubst): - return strSubst - result = [] - for s in strSubst: - if SCons.Util.is_String(s): - try: - s0, s1 = s[:2] - except (IndexError, ValueError): - result.append(s) - else: - if s0 == '$': - if s1 == '{': - s = eval(s[2:-1], {}, dict) - else: - s = dict.get(s[1:], '') - if s: - if not SCons.Util.is_List(s): - s = [s] - result.extend(s) - else: - result.append(s) - def l(obj): - try: - l = obj.is_literal - except AttributeError: - literal = None - else: - literal = l() - return CmdStringHolder(str(obj), literal) - return [map(l, result)] + # Just use the underlying scons_subst*() utility methods. + def subst(self, strSubst, raw=0, target=[], source=[], dict=None): + return SCons.Util.scons_subst(strSubst, self, raw, target, source, dict) + def subst_list(self, strSubst, raw=0, target=[], source=[], dict=None): + return SCons.Util.scons_subst_list(strSubst, self, raw, target, source, dict) def __getitem__(self, item): return self.d[item] def __setitem__(self, item, value): @@ -434,33 +375,35 @@ class CommandActionTestCase(unittest.TestCase): """Test fetching the string representation of command Actions """ + env = Environment() + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE') - s = act.strfunction([], [], Environment()) + s = act.strfunction([], [], env) assert s == ['xyzzy'], s - s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment()) - assert s == ['xyzzy target source'], s - s = act.strfunction([DummyNode('t1'), DummyNode('t2')], - [DummyNode('s1'), DummyNode('s2')], Environment()) + s = act.strfunction([t1], [s1], env) + assert s == ['xyzzy t1 s1'], s + s = act.strfunction([t1, t2], [s1, s2], env) assert s == ['xyzzy t1 s1'], s act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES') - s = act.strfunction([], [], Environment()) + s = act.strfunction([], [], env) assert s == ['xyzzy'], s - s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment()) - assert s == ['xyzzy target source'], s - s = act.strfunction([DummyNode('t1'), DummyNode('t2')], - [DummyNode('s1'), DummyNode('s2')], Environment()) + s = act.strfunction([t1], [s1], env) + assert s == ['xyzzy t1 s1'], s + s = act.strfunction([t1, t2], [s1, s2], env) assert s == ['xyzzy t1 t2 s1 s2'], s act = SCons.Action.CommandAction(['xyzzy', '$TARGET', '$SOURCE', '$TARGETS', '$SOURCES']) - s = act.strfunction([], [], Environment()) + s = act.strfunction([], [], env) assert s == ['xyzzy'], s - s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment()) - assert s == ['xyzzy target source target source'], s - s = act.strfunction([DummyNode('t1'), DummyNode('t2')], - [DummyNode('s1'), DummyNode('s2')], Environment()) + s = act.strfunction([t1], [s1], env) + assert s == ['xyzzy t1 s1 t1 s1'], s + s = act.strfunction([t1, t2], [s1, s2], env) assert s == ['xyzzy t1 s1 t1 t2 s1 s2'], s def test_execute(self): @@ -787,10 +730,14 @@ class CommandActionTestCase(unittest.TestCase): a = SCons.Action.CommandAction(["$TARGET"]) c = a.get_contents(target=t, source=s, env=env) assert c == "t1", c + c = a.get_contents(target=t, source=s, env=env, dict={}) + assert c == "", c a = SCons.Action.CommandAction(["$TARGETS"]) c = a.get_contents(target=t, source=s, env=env) assert c == "t1 t2 t3 t4 t5 t6", c + c = a.get_contents(target=t, source=s, env=env, dict={}) + assert c == "", c a = SCons.Action.CommandAction(["${TARGETS[2]}"]) c = a.get_contents(target=t, source=s, env=env) @@ -803,10 +750,14 @@ class CommandActionTestCase(unittest.TestCase): a = SCons.Action.CommandAction(["$SOURCE"]) c = a.get_contents(target=t, source=s, env=env) assert c == "s1", c + c = a.get_contents(target=t, source=s, env=env, dict={}) + assert c == "", c a = SCons.Action.CommandAction(["$SOURCES"]) c = a.get_contents(target=t, source=s, env=env) assert c == "s1 s2 s3 s4 s5 s6", c + c = a.get_contents(target=t, source=s, env=env, dict={}) + assert c == "", c a = SCons.Action.CommandAction(["${SOURCES[2]}"]) c = a.get_contents(target=t, source=s, env=env) @@ -906,10 +857,12 @@ class CommandGeneratorActionTestCase(unittest.TestCase): assert mystr == "$( foo $bar $)", mystr return "test" + env = Environment(foo = 'FFF', bar = 'BBB', + ignore = 'foo', test=test) a = SCons.Action.CommandGeneratorAction(f) - c = a.get_contents(target=[], source=[], - env=Environment(foo = 'FFF', bar = 'BBB', - ignore = 'foo', test=test)) + c = a.get_contents(target=[], source=[], env=env) + assert c == "guux FFF BBB test", c + c = a.get_contents(target=[], source=[], env=env, dict={}) assert c == "guux FFF BBB test", c @@ -1022,6 +975,8 @@ class FunctionActionTestCase(unittest.TestCase): a = SCons.Action.FunctionAction(Func) c = a.get_contents(target=[], source=[], env=Environment()) assert c == "\177\036\000\177\037\000d\000\000S", repr(c) + c = a.get_contents(target=[], source=[], env=Environment(), dict={}) + assert c == "\177\036\000\177\037\000d\000\000S", repr(c) a = SCons.Action.FunctionAction(Func, varlist=['XYZ']) c = a.get_contents(target=[], source=[], env=Environment()) @@ -1111,6 +1066,9 @@ class ListActionTestCase(unittest.TestCase): c = a.get_contents(target=[], source=[], env=Environment(s = self)) assert self.foo==1, self.foo assert c == "xyz", c + c = a.get_contents(target=[], source=[], env=Environment(s = self), dict={}) + assert self.foo==1, self.foo + assert c == "xyz", c class LazyActionTestCase(unittest.TestCase): def test_init(self): @@ -1151,9 +1109,11 @@ class LazyActionTestCase(unittest.TestCase): """Test fetching the contents of a lazy-evaluation Action """ a = SCons.Action.Action("${FOO}") - c = a.get_contents(target=[], source=[], - env = Environment(FOO = [["This", "is", "$(", "$a", "$)", "test"]])) - assert c == "This is test", c + env = Environment(FOO = [["This", "is", "a", "test"]]) + c = a.get_contents(target=[], source=[], env=env) + assert c == "This is a test", c + c = a.get_contents(target=[], source=[], env=env, dict={}) + assert c == "This is a test", c if __name__ == "__main__": diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 02f2f33..3819c27 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -363,7 +363,7 @@ class Base: return scanner return None - def subst(self, string, raw=0, target=None, source=None): + def subst(self, string, raw=0, target=None, source=None, dict=None): """Recursively interpolates construction variables from the Environment into the specified string, returning the expanded result. Construction variables are specified by a $ prefix @@ -373,33 +373,21 @@ class Base: may be surrounded by curly braces to separate the name from trailing characters. """ - if raw: - mode = SCons.Util.SUBST_RAW - else: - mode = SCons.Util.SUBST_CMD - return SCons.Util.scons_subst(string, self, mode, target, source) - - def subst_kw(self, kw, raw=0, target=None, source=None): - if raw: - mode = SCons.Util.SUBST_RAW - else: - mode = SCons.Util.SUBST_CMD + return SCons.Util.scons_subst(string, self, raw, target, source, dict) + + def subst_kw(self, kw, raw=0, target=None, source=None, dict=None): nkw = {} for k, v in kw.items(): - k = SCons.Util.scons_subst(k, self, mode, target, source) + k = self.subst(k, raw, target, source, dict) if SCons.Util.is_String(v): - v = SCons.Util.scons_subst(v, self, mode, target, source) + v = self.subst(v, raw, target, source, dict) nkw[k] = v return nkw - - def subst_list(self, string, raw=0, target=None, source=None): + + def subst_list(self, string, raw=0, target=None, source=None, dict=None): """Calls through to SCons.Util.scons_subst_list(). See the documentation for that function.""" - if raw: - mode = SCons.Util.SUBST_RAW - else: - mode = SCons.Util.SUBST_CMD - return SCons.Util.scons_subst_list(string, self, mode, target, source) + return SCons.Util.scons_subst_list(string, self, raw, target, source, dict) def use_build_signature(self): try: diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 4e0b380..f81a102 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -275,14 +275,11 @@ class EnvironmentTestCase(unittest.TestCase): env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo') mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB") assert mystr == "b bA bB b", mystr + env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c') mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB") assert mystr == "c cA cB c", mystr - env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ]) - lst = env.subst_list([ "$AAA", "B $CCC" ]) - assert lst == [ [ "a", "b" ], [ "c", "B a", "b" ], [ "c" ] ], lst - class DummyNode: def __init__(self, name): self.name = name @@ -293,6 +290,19 @@ class EnvironmentTestCase(unittest.TestCase): def get_subst_proxy(self): return self + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + + env = Environment(AAA = 'aaa') + s = env.subst('$AAA $TARGET $SOURCES', target=[t1, t2], source=[s1, s2]) + assert s == "aaa t1 s1 s2", s + s = env.subst('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2]) + assert s == "aaa t1 t2 s1", s + s = env.subst('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2], dict={}) + assert s == "aaa", s + # Test callables in the Environment def foo(target, source, env, for_signature): assert str(target) == 't', target @@ -300,14 +310,12 @@ class EnvironmentTestCase(unittest.TestCase): return env["FOO"] env = Environment(BAR=foo, FOO='baz') + t = DummyNode('t') + s = DummyNode('s') - subst = env.subst('test $BAR', target=DummyNode('t'), source=DummyNode('s')) + subst = env.subst('test $BAR', target=t, source=s) assert subst == 'test baz', subst - lst = env.subst_list('test $BAR', target=DummyNode('t'), source=DummyNode('s')) - assert lst[0][0] == 'test', lst[0][0] - assert lst[0][1] == 'baz', lst[0][1] - # Test not calling callables in the Environment if 0: # This will take some serious surgery to subst() and @@ -324,12 +332,6 @@ class EnvironmentTestCase(unittest.TestCase): subst = env.subst('$FOO', call=None) assert subst is bar, subst - subst = env.subst_list('$BAR', call=None) - assert subst is bar, subst - - subst = env.subst_list('$FOO', call=None) - assert subst is bar, subst - def test_subst_kw(self): """Test substituting construction variables within dictionaries""" env = Environment(AAA = 'a', BBB = 'b') @@ -338,6 +340,79 @@ class EnvironmentTestCase(unittest.TestCase): assert kw['a'] == 'aaa', kw['a'] assert kw['bbb'] == 'b', kw['bbb'] + def test_subst_list(self): + """Test substituting construction variables in command lists + """ + env = Environment(AAA = 'a', BBB = 'b') + l = env.subst_list("$AAA ${AAA}A $BBBB $BBB") + assert l == [["a", "aA", "b"]], l + + # Changed the tests below to reflect a bug fix in + # subst() + env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo') + l = env.subst_list("$AAA ${AAA}A ${AAA}B $BBB") + assert l == [["b", "bA", "bB", "b"]], l + + env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c') + l = env.subst_list("$AAA ${AAA}A ${AAA}B $BBB") + assert l == [["c", "cA", "cB", "c"]], mystr + + env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ]) + lst = env.subst_list([ "$AAA", "B $CCC" ]) + assert lst == [[ "a", "b"], ["c", "B a", "b"], ["c"]], lst + + 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 + + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + + env = Environment(AAA = 'aaa') + s = env.subst_list('$AAA $TARGET $SOURCES', target=[t1, t2], source=[s1, s2]) + assert s == [["aaa", "t1", "s1", "s2"]], s + s = env.subst_list('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2]) + assert s == [["aaa", "t1", "t2", "s1"]], s + s = env.subst_list('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2], dict={}) + assert s == [["aaa"]], s + + # Test callables in the Environment + def foo(target, source, env, for_signature): + assert str(target) == 't', target + assert str(source) == 's', source + return env["FOO"] + + env = Environment(BAR=foo, FOO='baz') + t = DummyNode('t') + s = DummyNode('s') + + lst = env.subst_list('test $BAR', target=t, source=s) + assert lst == [['test', 'baz']], lst + + # Test not calling callables in the Environment + if 0: + # This will take some serious surgery to subst() and + # subst_list(), so just leave these tests out until we can + # do that. + def bar(arg): + pass + + env = Environment(BAR=bar, FOO='$BAR') + + subst = env.subst_list('$BAR', call=None) + assert subst is bar, subst + + subst = env.subst_list('$FOO', call=None) + assert subst is bar, subst + def test_Builder_calls(self): """Test Builder calls through different environments """ diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 00a9bed..0af98fd 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -348,19 +348,96 @@ def escape_list(list, escape_func): return e(escape_func) return map(escape, list) -def target_prep(target): - if target and not isinstance(target, NodeList): - if not is_List(target): - target = [target] - target = NodeList(map(lambda x: x.get_subst_proxy(), target)) - return target - -def source_prep(source): - if source and not isinstance(source, NodeList): - if not is_List(source): - source = [source] - source = NodeList(map(lambda x: x.rfile().get_subst_proxy(), source)) - return source +class NLWrapper: + """A wrapper class that delays turning a list of sources or targets + into a NodeList until it's needed. The specified function supplied + when the object is initialized is responsible for turning raw nodes + into proxies that implement the special attributes like .abspath, + .source, etc. This way, we avoid creating those proxies just + "in case" someone is going to use $TARGET or the like, and only + go through the trouble if we really have to. + + In practice, this might be a wash performance-wise, but it's a little + cleaner conceptually... + """ + + def __init__(self, list, func): + self.list = list + self.func = func + def _create_nodelist(self): + try: + return self.nodelist + except AttributeError: + list = self.list + if list is None: + list = [] + elif not is_List(list): + list = [list] + # The map(self.func) call is what actually turns + # a list into appropriate proxies. + self.nodelist = NodeList(map(self.func, list)) + return self.nodelist + +class Targets_or_Sources(UserList.UserList): + """A class that implements $TARGETS or $SOURCES expansions by in turn + wrapping a NLWrapper. This class handles the different methods used + to access the list, calling the NLWrapper to create proxies on demand. + + Note that we subclass UserList.UserList purely so that the is_List() + function will identify an object of this class as a list during + variable expansion. We're not really using any UserList.UserList + methods in practice. + """ + def __init__(self, nl): + self.nl = nl + def __getattr__(self, attr): + nl = self.nl._create_nodelist() + return getattr(nl, attr) + def __getitem__(self, i): + nl = self.nl._create_nodelist() + return nl[i] + def __getslice__(self, i, j): + nl = self.nl._create_nodelist() + i = max(i, 0); j = max(j, 0) + return nl[i:j] + def __str__(self): + nl = self.nl._create_nodelist() + return str(nl) + def __repr__(self): + nl = self.nl._create_nodelist() + return repr(nl) + +class Target_or_Source: + """A class that implements $TARGET or $SOURCE expansions by in turn + wrapping a NLWrapper. This class handles the different methods used + to access an individual proxy Node, calling the NLWrapper to create + a proxy on demand. + """ + def __init__(self, nl): + self.nl = nl + def __getattr__(self, attr): + nl = self.nl._create_nodelist() + try: + nl0 = nl[0] + except IndexError: + # If there is nothing in the list, then we have no attributes to + # pass through, so raise AttributeError for everything. + raise AttributeError, "NodeList has no attribute: %s" % attr + return getattr(nl0, attr) + def __str__(self): + nl = self.nl._create_nodelist() + try: + nl0 = nl[0] + except IndexError: + return '' + return str(nl0) + def __repr__(self): + nl = self.nl._create_nodelist() + try: + nl0 = nl[0] + except IndexError: + return '' + return repr(nl0) def subst_dict(target, source, env): """Create a dictionary for substitution of special @@ -380,17 +457,17 @@ def subst_dict(target, source, env): build, which is made available as the __env__ construction variable """ - dict = { '__env__' : env } + dict = { '__env__' : env, } - target = target_prep(target) - dict['TARGETS'] = target - if dict['TARGETS']: - dict['TARGET'] = dict['TARGETS'][0] + if target: + tnl = NLWrapper(target, lambda x: x.get_subst_proxy()) + dict['TARGETS'] = Targets_or_Sources(tnl) + dict['TARGET'] = Target_or_Source(tnl) - source = source_prep(source) - dict['SOURCES'] = source - if dict['SOURCES']: - dict['SOURCE'] = dict['SOURCES'][0] + if source: + snl = NLWrapper(source, lambda x: x.rfile().get_subst_proxy()) + dict['SOURCES'] = Targets_or_Sources(snl) + dict['SOURCE'] = Target_or_Source(snl) return dict @@ -399,15 +476,15 @@ def subst_dict(target, source, env): # gives a command line suitable for passing to a shell. SUBST_SIG # gives a command line appropriate for calculating the signature # of a command line...if this changes, we should rebuild. -SUBST_RAW = 0 -SUBST_CMD = 1 +SUBST_CMD = 0 +SUBST_RAW = 1 SUBST_SIG = 2 _rm = re.compile(r'\$[()]') _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)') # Indexed by the SUBST_* constants above. -_regex_remove = [ None, _rm, _remove ] +_regex_remove = [ _rm, None, _remove ] # This regular expression splits a string into the following types of # arguments for use by the scons_subst() and scons_subst_list() functions: @@ -427,7 +504,7 @@ _separate_args = re.compile(r'(\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}|\s+|[^\s\ # space characters in the string result from the scons_subst() function. _space_sep = re.compile(r'[\t ]+(?![^{]*})') -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None): +def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=None): """Expand a string containing construction variable substitutions. This is the work-horse function for substitutions in file names @@ -522,8 +599,11 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None): else: return self.expand(args, lvars) + if dict is None: + dict = subst_dict(target, source, env) + ss = StringSubber(env, mode, target, source) - result = ss.substitute(strSubst, subst_dict(target, source, env)) + result = ss.substitute(strSubst, dict) if is_String(result): # Remove $(-$) pairs and any stuff in between, @@ -538,7 +618,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None): return result -def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None): +def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=None): """Substitute construction variables in a string (or list or other object) and separate the arguments into a command list. @@ -694,8 +774,11 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None): self.add_strip(x) self.in_strip = None + if dict is None: + dict = subst_dict(target, source, env) + ls = ListSubber(env, mode, target, source) - ls.substitute(strSubst, subst_dict(target, source, env), 0) + ls.substitute(strSubst, dict, 0) return ls.data diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 3dbeebc..ca5f649 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -180,6 +180,9 @@ class UtilTestCase(unittest.TestCase): 'FUNC1' : lambda x: x, 'FUNC2' : lambda target, source, env, for_signature: ['x$CCC'], + # Various tests refactored from ActionTests.py. + 'LIST' : [["This", "is", "$(", "$a", "$)", "test"]], + # Test recursion. 'RECURSE' : 'foo $RECURSE bar', 'RRR' : 'foo $SSS bar', @@ -338,6 +341,28 @@ class UtilTestCase(unittest.TestCase): "foo bar", "foo bar", "foo bar", + + # Verify what happens with no target or source nodes. + "$TARGET $SOURCES", + " ", + "", + "", + + "$TARGETS $SOURCE", + " ", + "", + "", + + # Various tests refactored from ActionTests.py. + "${LIST}", + "This is $( $) test", + "This is test", + "This is test", + + ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1], + "| $( a | b $) | c 1", + "| a | b | c 1", + "| | c 1", ] failed = 0 @@ -361,6 +386,25 @@ class UtilTestCase(unittest.TestCase): del subst_cases[:4] assert failed == 0, "%d subst() mode cases failed" % failed + t1 = MyNode('t1') + t2 = MyNode('t2') + s1 = MyNode('s1') + s2 = MyNode('s2') + result = scons_subst("$TARGET $SOURCES", env, + target=[t1, t2], + source=[s1, s2]) + assert result == "t1 s1 s2", result + result = scons_subst("$TARGET $SOURCES", env, + target=[t1, t2], + source=[s1, s2], + dict={}) + assert result == " ", result + + result = scons_subst("$TARGET $SOURCES", env, target=[], source=[]) + assert result == " ", result + result = scons_subst("$TARGETS $SOURCE", env, target=[], source=[]) + assert result == " ", result + # Test interpolating a callable. newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS", env, target=MyNode('t'), source=MyNode('s')) @@ -435,6 +479,9 @@ class UtilTestCase(unittest.TestCase): 'FUNC1' : lambda x: x, 'FUNC2' : lambda target, source, env, for_signature: ['x$CCC'], + # Various tests refactored from ActionTests.py. + 'LIST' : [["This", "is", "$(", "$a", "$)", "test"]], + # Test recursion. 'RECURSE' : 'foo $RECURSE bar', 'RRR' : 'foo $SSS bar', @@ -568,6 +615,20 @@ class UtilTestCase(unittest.TestCase): del cases[:2] assert failed == 0, "%d subst_list() cases failed" % failed + t1 = MyNode('t1') + t2 = MyNode('t2') + s1 = MyNode('s1') + s2 = MyNode('s2') + result = scons_subst_list("$TARGET $SOURCES", env, + target=[t1, t2], + source=[s1, s2]) + assert result == [['t1', 's1', 's2']], result + result = scons_subst_list("$TARGET $SOURCES", env, + target=[t1, t2], + source=[s1, s2], + dict={}) + assert result == [[]], result + # Test interpolating a callable. _t = DummyNode('t') _s = DummyNode('s') @@ -615,16 +676,41 @@ class UtilTestCase(unittest.TestCase): [["a", "aA", "b"]], "$RECURSE", - [["foo", "bar"]], - [["foo", "bar"]], - [["foo", "bar"]], + [["foo", "bar"]], + [["foo", "bar"]], + [["foo", "bar"]], "$RRR", - [["foo", "bar"]], - [["foo", "bar"]], - [["foo", "bar"]], + [["foo", "bar"]], + [["foo", "bar"]], + [["foo", "bar"]], + + # Verify what happens with no target or source nodes. + "$TARGET $SOURCES", + [[]], + [[]], + [[]], + + "$TARGETS $SOURCE", + [[]], + [[]], + [[]], + + # Various test refactored from ActionTests.py + "${LIST}", + [['This', 'is', '$(', '$)', 'test']], + [['This', 'is', 'test']], + [['This', 'is', 'test']], + + ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1], + [["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]], + [["|", "a", "|", "b", "|", "c", "1"]], + [["|", "|", "c", "1"]], ] + r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW) + assert r == [[]], r + failed = 0 while subst_list_cases: input, eraw, ecmd, esig = subst_list_cases[:4] @@ -1027,15 +1113,20 @@ class UtilTestCase(unittest.TestCase): d = subst_dict([], [], env) assert d['__env__'] is env, d['__env__'] - d = subst_dict(target = DummyNode('t'), source = DummyNode('s'), env=DummyEnv()) + t = DummyNode('t') + s = DummyNode('s') + env = DummyEnv() + d = subst_dict(target=t, source=s, env=env) assert str(d['TARGETS'][0]) == 't', d['TARGETS'] assert str(d['TARGET']) == 't', d['TARGET'] assert str(d['SOURCES'][0]) == 's', d['SOURCES'] assert str(d['SOURCE']) == 's', d['SOURCE'] - d = subst_dict(target = [DummyNode('t1'), DummyNode('t2')], - source = [DummyNode('s1'), DummyNode('s2')], - env = DummyEnv()) + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + d = subst_dict(target=[t1, t2], source=[s1, s2], env=env) TARGETS = map(lambda x: str(x), d['TARGETS']) TARGETS.sort() assert TARGETS == ['t1', 't2'], d['TARGETS'] @@ -1055,9 +1146,11 @@ class UtilTestCase(unittest.TestCase): def get_subst_proxy(self): return self - d = subst_dict(target = [N('t3'), DummyNode('t4')], - source = [DummyNode('s3'), N('s4')], - env = DummyEnv()) + t3 = N('t3') + t4 = DummyNode('t4') + s3 = DummyNode('s3') + s4 = N('s4') + d = subst_dict(target=[t3, t4], source=[s3, s4], env=env) TARGETS = map(lambda x: str(x), d['TARGETS']) TARGETS.sort() assert TARGETS == ['t3', 't4'], d['TARGETS'] -- cgit v0.12