Refactor variable substitution for more scalable expansion of , etc.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 20 Jan 2004 16:48:03 +0000 (16:48 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 20 Jan 2004 16:48:03 +0000 (16:48 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@883 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/RELEASE.txt
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py

index 4bb0a8fa8204be53a32342d22b4797cba06b8799..b52d3adba3f1c96bb2ad89e0b17d175831e1d32c 100644 (file)
@@ -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
index 719e2134d6a4f5aa765d2be2988dc39f0ddd42ec..e2ceb2ac831110e9573cb61dba257ee447bcb775 100644 (file)
@@ -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),
                            "")
index 739611887e2395d2f0657f19070272474fe4af78..480b299f061155c252d2905d0dc2cdca9e80605c 100644 (file)
@@ -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__":
index 02f2f33f8786e2f93386b438cc56244464f25f44..3819c27e969769176a20ea56128703a12b6c1d4e 100644 (file)
@@ -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:
index 4e0b3809aca05829fd92ef48fc4cbb74bffa0263..f81a10270fb2e733785faed67c5d2a13bd6d113e 100644 (file)
@@ -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
         """
index 00a9bedd93296ebcd1ab6a0412a6bd232946d151..0af98fd3919c5acd49a8cb2866e84395ac253ee1 100644 (file)
@@ -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
 
index 3dbeebc46636a5c2b822c25e8a3074e006f4924f..ca5f649c5da380bea64eb8f32bf9162e5c32148b 100644 (file)
@@ -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']