Handle recursive substitution in overrides.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 24 Feb 2004 06:19:49 +0000 (06:19 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 24 Feb 2004 06:19:49 +0000 (06:19 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@906 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/CHANGES.txt
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Executor.py
src/engine/SCons/ExecutorTests.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
test/CCFLAGS.py
test/LIBS.py
test/overrides.py

index 42c5de1d4b7e1e93f3cd1bb00ed2f2f6f8283d87..be8dfa04cf38ee02a5c23b6e0c6c099eb2517917 100644 (file)
@@ -125,7 +125,8 @@ RELEASE 0.95 - XXX
 
   - Refactor construction variable expansion to support recursive
     expansion of variables (e.g. CCFLAGS = "$CCFLAGS -g") without going
-    into an infinite loop.
+    into an infinite loop.  Support this in all construction variable
+    overrides, as well as when copying Environments.
 
   - Fix calling Configure() from more than one subsidiary SConscript file.
 
index a6262046014c5be07b77ea1f510ce310364ac05e..c6bc2a995729ec64a4170dc164c9c5ed843c1868 100644 (file)
@@ -505,7 +505,10 @@ class Base:
         apply_tools(clone, tools, toolpath)
 
         # Apply passed-in variables after the new tools.
-        apply(clone.Replace, (), kw)
+        new = {}
+        for key, value in kw.items():
+            new[key] = SCons.Util.scons_subst_once(value, self, key)
+        apply(clone.Replace, (), new)
         return clone
 
     def Detect(self, progs):
@@ -561,7 +564,10 @@ class Base:
             env = copy.copy(self)
             env._dict = copy.copy(self._dict)
             env['__env__'] = env
-            env._dict.update(overrides)
+            new = {}
+            for key, value in overrides.items():
+                new[key] = SCons.Util.scons_subst_once(value, self, key)
+            env._dict.update(new)
             return env
         else:
             return self
index 330bb7c2cbdbeb8f888a850cdeee95923267fc16..a5cf171691d576ef5481be02c289c41ff5469845 100644 (file)
@@ -1019,6 +1019,16 @@ class EnvironmentTestCase(unittest.TestCase):
         assert env3.get('BAR') is 2
         assert env3.get('BAZ') is 3
 
+        # Ensure that recursive variable substitution when copying
+        # environments works properly.
+        env1 = Environment(CCFLAGS = '-DFOO', XYZ = '-DXYZ')
+        env2 = env1.Copy(CCFLAGS = '$CCFLAGS -DBAR',
+                         XYZ = ['-DABC', 'x $XYZ y', '-DDEF'])
+        x = env2.get('CCFLAGS')
+        assert x == '-DFOO -DBAR', x
+        x = env2.get('XYZ')
+        assert x == ['-DABC', 'x -DXYZ y', '-DDEF'], x
+
     def test_Detect(self):
         """Test Detect()ing tools"""
         test = TestCmd.TestCmd(workdir = '')
@@ -1122,16 +1132,28 @@ class EnvironmentTestCase(unittest.TestCase):
 
     def test_Override(self):
         "Test overriding construction variables"
-        env = Environment(ONE=1, TWO=2)
-        assert env['ONE'] == 1
-        assert env['TWO'] == 2
-        env2 = env.Override({'TWO':'10'})
-        assert env2['ONE'] == 1
-        assert env2['TWO'] == '10'
-        assert env['TWO'] == 2
+        env = Environment(ONE=1, TWO=2, THREE=3, FOUR=4)
+        assert env['ONE'] == 1, env['ONE']
+        assert env['TWO'] == 2, env['TWO']
+        assert env['THREE'] == 3, env['THREE']
+        assert env['FOUR'] == 4, env['FOUR']
+
+        env2 = env.Override({'TWO'   : '10',
+                             'THREE' :'x $THREE y',
+                             'FOUR'  : ['x', '$FOUR', 'y']})
+        assert env2['ONE'] == 1, env2['ONE']
+        assert env2['TWO'] == '10', env2['TWO']
+        assert env2['THREE'] == 'x 3 y', env2['THREE']
+        assert env2['FOUR'] == ['x', 4, 'y'], env2['FOUR']
+
+        assert env['ONE'] == 1, env['ONE']
+        assert env['TWO'] == 2, env['TWO']
+        assert env['THREE'] == 3, env['THREE']
+        assert env['FOUR'] == 4, env['FOUR']
+
         env2.Replace(ONE = "won")
-        assert env2['ONE'] == "won"
-        assert env['ONE'] == 1
+        assert env2['ONE'] == "won", env2['ONE']
+        assert env['ONE'] == 1, env['ONE']
 
         assert env['__env__'] is env, env['__env__']
         assert env2['__env__'] is env2, env2['__env__']
index 8151f87d1c0af50230c4b192305463fe40cd2ba7..b636f60658dd56cdea8dfd45554a40ece7281124 100644 (file)
@@ -69,6 +69,12 @@ class Executor:
                 # The normal case:  use the Environment that was
                 # used to specify how these targets will be built.
                 env = self.env
+
+            # Create the build environment instance with appropriate
+            # overrides.  These get evaluated against the current
+            # environment's construction variables so that users can
+            # add to existing values by referencing the variable in
+            # the expansion.
             overrides = {}
             overrides.update(self.builder.overrides)
             overrides.update(self.overrides)
@@ -78,8 +84,13 @@ class Executor:
                 pass
             else:
                 overrides.update(generate_build_dict())
-            overrides.update(SCons.Util.subst_dict(self.targets, self.sources))
             self.build_env = env.Override(overrides)
+
+            # Now update the build environment with the things that we
+            # don't want expanded against the current construction
+            # variables.
+            self.build_env._update(SCons.Util.subst_dict(self.targets,
+                                                         self.sources))
             return self.build_env
 
     def get_action_list(self, target):
index d43f7e20f9355c451738447110e838de5a0019b3..9c640121ffdb8d01658440c1bcec4119bac8e63b 100644 (file)
@@ -34,10 +34,12 @@ class MyEnvironment:
     def __init__(self, **kw):
         self._dict = {}
         self._dict.update(kw)
+    def __getitem__(self, key):
+        return self._dict[key]
     def Override(self, overrides):
         d = self._dict.copy()
         d.update(overrides)
-        return d
+        return apply(MyEnvironment, (), d)
     def _update(self, dict):
         self._dict.update(dict)
 
index 2e053bed90bcf89c56401fe0a3bba41d80c1fb7a..63b945ff26b327fc1fbe5f91486b6cf0e3423291 100644 (file)
@@ -78,10 +78,19 @@ class MyNonGlobalAction:
         return [self]
 
 class Environment:
+    def __init__(self, **kw):
+        self._dict = {}
+        self._dict.update(kw)
+    def __getitem__(self, key):
+        return self._dict[key]
     def Dictionary(self, *args):
         return {}
     def Override(self, overrides):
-        return overrides
+        d = self._dict.copy()
+        d.update(overrides)
+        return apply(Environment, (), d)
+    def _update(self, dict):
+        self._dict.update(dict)
 
 class Builder:
     def __init__(self):
index 66433938e48e25e4284226767e7b091b538aaad6..4ca25b2ab02f8368f6757daeb06d406cfec7591a 100644 (file)
@@ -788,6 +788,55 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, di
 
     return ls.data
 
+def scons_subst_once(strSubst, env, key):
+    """Perform single (non-recursive) substitution of a single
+    construction variable keyword.
+
+    This is used when setting a variable when copying or overriding values
+    in an Environment.  We want to capture (expand) the old value before
+    we override it, so people can do things like:
+
+        env2 = env.Copy(CCFLAGS = '$CCFLAGS -g')
+
+    We do this with some straightforward, brute-force code here...
+    """
+    matchlist = ['$' + key, '${' + key + '}']
+    if is_List(strSubst):
+        result = []
+        for arg in strSubst:
+            if is_String(arg):
+                if arg in matchlist:
+                    arg = env[key]
+                    if is_List(arg):
+                        result.extend(arg)
+                    else:
+                        result.append(arg)
+                else:
+                    r = []
+                    for a in _separate_args.findall(arg):
+                        if a in matchlist:
+                            a = env[key]
+                        if is_List(a):
+                            r.extend(string.join(map(str, a)))
+                        else:
+                            r.append(str(a))
+                    result.append(string.join(r, ''))
+            else:
+                result.append(arg)
+        return result
+    elif is_String(strSubst):
+        result = []
+        for a in _separate_args.findall(strSubst):
+            if a in matchlist:
+                a = env[key]
+            if is_List(a):
+                result.extend(string.join(map(str, a)))
+            else:
+                result.append(str(a))
+        return string.join(result, '')
+    else:
+        return strSubst
+
 def render_tree(root, child_func, prune=0, margin=[0], visited={}):
     """
     Render a tree of nodes into an ASCII tree view.
index ce82013d1ebe8eee71f33cd32913f84069a1458c..d981abc4ef2025a388e3c65bc3b63297d5220a73 100644 (file)
@@ -63,6 +63,9 @@ class DummyEnv:
             return self.dict
         return self.dict[key]
 
+    def __getitem__(self, key):
+        return self.dict[key]
+
     def sig_dict(self):
         dict = self.dict.copy()
         dict["TARGETS"] = 'tsig'
@@ -289,6 +292,9 @@ class UtilTestCase(unittest.TestCase):
             '$S',                   'x y',
             '$LS',                  'x y',
             '$L',                   'x y',
+            '$S z',                 'x y z',
+            '$LS z',                'x y z',
+            '$L z',                 'x y z',
             #cs,                     'cs',
             #cl,                     'cl',
             '$CS',                  'cs',
@@ -590,11 +596,20 @@ class UtilTestCase(unittest.TestCase):
             #'$R',                   [[]],
             #['$R'],                 [[]],
             '$S',                   [['x', 'y']],
+            '$S z',                 [['x', 'y', 'z']],
             ['$S'],                 [['x', 'y']],
+            ['$S z'],               [['x', 'y z']],     # XXX - IS THIS BEST?
+            ['$S', 'z'],            [['x', 'y', 'z']],
             '$LS',                  [['x y']],
+            '$LS z',                [['x y', 'z']],
             ['$LS'],                [['x y']],
+            ['$LS z'],              [['x y z']],
+            ['$LS', 'z'],           [['x y', 'z']],
             '$L',                   [['x', 'y']],
+            '$L z',                 [['x', 'y', 'z']],
             ['$L'],                 [['x', 'y']],
+            ['$L z'],               [['x', 'y z']],     # XXX - IS THIS BEST?
+            ['$L', 'z'],            [['x', 'y', 'z']],
             cs,                     [['cs']],
             [cs],                   [['cs']],
             cl,                     [['cl']],
@@ -756,6 +771,71 @@ class UtilTestCase(unittest.TestCase):
         else:
             raise AssertionError, "did not catch expected SyntaxError"
 
+    def test_subst_once(self):
+        """Testing the scons_subst_once() method"""
+
+        loc = {
+            'CCFLAGS'           : '-DFOO',
+            'ONE'               : 1,
+            'RECURSE'           : 'r $RECURSE r',
+            'LIST'              : ['a', 'b', 'c'],
+        }
+
+        env = DummyEnv(loc)
+
+        cases = [
+            '$CCFLAGS -DBAR',
+            'OTHER_KEY',
+            '$CCFLAGS -DBAR',
+
+            '$CCFLAGS -DBAR',
+            'CCFLAGS',
+            '-DFOO -DBAR',
+
+            'x $ONE y',
+            'ONE',
+            'x 1 y',
+
+            'x $RECURSE y',
+            'RECURSE',
+            'x r $RECURSE r y',
+
+            '$LIST',
+            'LIST',
+            'a b c',
+
+            ['$LIST'],
+            'LIST',
+            ['a', 'b', 'c'],
+
+            ['x', '$LIST', 'y'],
+            'LIST',
+            ['x', 'a', 'b', 'c', 'y'],
+
+            ['x', 'x $LIST y', 'y'],
+            'LIST',
+            ['x', 'x a b c y', 'y'],
+
+            ['x', 'x $CCFLAGS y', 'y'],
+            'LIST',
+            ['x', 'x $CCFLAGS y', 'y'],
+
+            ['x', 'x $RECURSE y', 'y'],
+            'LIST',
+            ['x', 'x $RECURSE y', 'y'],
+        ]
+
+        failed = 0
+        while cases:
+            input, key, expect = cases[:3]
+            result = scons_subst_once(input, env, key)
+            if result != expect:
+                if failed == 0: print
+                print "    input %s (%s) => %s did not match %s" % (repr(input), repr(key), repr(result), repr(expect))
+                failed = failed + 1
+            del cases[:3]
+        assert failed == 0, "%d subst() cases failed" % failed
+
     def test_splitext(self):
         assert splitext('foo') == ('foo','')
         assert splitext('foo.bar') == ('foo','.bar')
index 60fb74ea93b2e50ac9f00749ae0341be5df2dd05..66640104ccd93daf9026bcdd0edbab1a619fe1f8 100644 (file)
@@ -45,6 +45,8 @@ foo.Object(target = 'foo%s', source = 'prog.c')
 bar.Object(target = 'bar%s', source = 'prog.c')
 foo.Program(target = 'foo', source = 'foo%s')
 bar.Program(target = 'bar', source = 'bar%s')
+foo.Program(target = 'prog', source = 'prog.c',
+            CCFLAGS = '$CCFLAGS -DBAR $BAZ', BAZ = '-DBAZ')
 """ % (fooflags, barflags, _obj, _obj, _obj, _obj))
 
 test.write('prog.c', r"""
@@ -57,6 +59,9 @@ main(int argc, char *argv[])
 #endif
 #ifdef BAR
        printf("prog.c:  BAR\n");
+#endif
+#ifdef BAZ
+       printf("prog.c:  BAZ\n");
 #endif
        exit (0);
 }
@@ -67,6 +72,11 @@ test.run(arguments = '.')
 
 test.run(program = test.workpath('foo'), stdout = "prog.c:  FOO\n")
 test.run(program = test.workpath('bar'), stdout = "prog.c:  BAR\n")
+test.run(program = test.workpath('prog'), stdout = """\
+prog.c:  FOO
+prog.c:  BAR
+prog.c:  BAZ
+""")
 
 test.write('SConstruct', """
 bar = Environment(CCFLAGS = '%s')
index e66899e71d86a286cb01496f4dd1b41d805f2b04..645c625e7e87f918e67a668e2b25507103a70217 100644 (file)
@@ -121,8 +121,8 @@ test.run(program=foo4_exe, stdout='sub1/bar.c\nsub1/baz.c\n')
 
 #
 test.write('SConstruct', """
-env = Environment()
-env.Program(target='foo1', source='foo1.c', LIBS=['baz', 'bar'], LIBPATH = '.')
+env = Environment(LIBS=['baz'])
+env.Program(target='foo1', source='foo1.c', LIBS=['$LIBS', 'bar'], LIBPATH = '.')
 SConscript('sub1/SConscript', 'env')
 SConscript('sub2/SConscript', 'env')
 """)
@@ -187,7 +187,18 @@ libraries = (['libtest_component2',
               'libtest_component1'])
 
 # To remove the dependency problem, you should rename blender to mlender.
-Program (source='', target='blender', LIBS=libraries, LIBPREFIX='lib', LIBPATH=libpath)
+Program (source='main.c', target='blender', LIBS=libraries, LIBPREFIX='lib', LIBPATH=libpath, CPPPATH=['src/component2'])
+""")
+
+test.write('main.c', """\
+#include <stdio.h>
+#include "message2.h"
+
+int main (void)
+{
+    DisplayMessage2();
+    exit (0);
+}
 """)
 
 test.write(['src', 'SConscript'], """\
@@ -196,38 +207,41 @@ SConscript(['component1/SConscript',
 """)
 
 test.write(['src', 'component1', 'SConscript'], """\
-source_files = ['message.c']
-Library (target='../../lib/libtest_component1', source=source_files)
+source_files = ['message1.c']
+Library (target='../../lib/libtest_component1', source=source_files, LINKFLAGS='')
 """)
 
-test.write(['src', 'component1', 'message.c'], """\
+test.write(['src', 'component1', 'message1.c'], """\
 #include <stdio.h>
 
-void DisplayMessage (void)
+void DisplayMessage1 (void)
 {
     printf ("src/component1/message.c\\n");
 }
 """)
 
-test.write(['src', 'component1', 'message.h'], """\
-void DisplayMessage (void);
+test.write(['src', 'component1', 'message1.h'], """\
+void DisplayMessage1 (void);
 """)
 
 test.write(['src', 'component2', 'SConscript'], """\
-source_files = ['hello.c']
+source_files = ['message2.c']
 include_paths = ['../component1']
 Library (target='../../lib/libtest_component2', source=source_files, CPPPATH=include_paths)
 """)
 
-test.write(['src', 'component2', 'hello.c'], """\
+test.write(['src', 'component2', 'message2.h'], """\
+void DisplayMessage2 (void);
+""")
+
+test.write(['src', 'component2', 'message2.c'], """\
 #include <stdio.h>
-#include "message.h"
+#include "message1.h"
 
-int main (void)
+int DisplayMessage2 (void)
 {
-    DisplayMessage();
+    DisplayMessage1();
     printf ("src/component2/hello.c\\n");
-    exit (0);
 }
 """)
 
index d9b6fdea74dc63dad4e3998e2123d18aa4e8d6d2..a247f7d3d3d1cf417920e54fab30de87f88a8768 100644 (file)
@@ -34,14 +34,18 @@ test = TestSCons.TestSCons()
 python = TestSCons.python
 
 test.write('SConstruct', """
-env = Environment(LIBS=['a'])
+env = Environment(CCFLAGS='-DFOO', LIBS=['a'])
 def build(target, source, env):
     print "env['CC'] =", env['CC']
+    print "env['CCFLAGS'] =", env['CCFLAGS']
     print "env['LIBS'] =", env['LIBS']
 builder = Builder(action=build, CC='buildcc', LIBS='buildlibs')
 env['BUILDERS']['Build'] = builder
 
-foo = env.Build('foo.out', 'foo.in', CC='mycc', LIBS = env['LIBS']+['b'])
+foo = env.Build('foo.out', 'foo.in',
+                CC='mycc',
+                CCFLAGS='$CCFLAGS -DBAR',
+                LIBS = env['LIBS']+['b'])
 bar = env.Build('bar.out', 'bar.in')
 Default([foo, bar])
 """)
@@ -52,9 +56,11 @@ test.write('bar.in', "bar.in\n")
 test.run(arguments = "-Q", stdout = """\
 build("foo.out", "foo.in")
 env['CC'] = mycc
+env['CCFLAGS'] = -DFOO -DBAR
 env['LIBS'] = ['a', 'b']
 build("bar.out", "bar.in")
 env['CC'] = buildcc
+env['CCFLAGS'] = -DFOO
 env['LIBS'] = buildlibs
 """)