Support construction variable expansion anywhere in a file or path name.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 5 Sep 2003 19:26:05 +0000 (19:26 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 5 Sep 2003 19:26:05 +0000 (19:26 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@788 fdb21ef1-2011-0410-befe-b5e4ea1792b1

18 files changed:
src/CHANGES.txt
src/RELEASE.txt
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
test/AlwaysBuild.py
test/Depends.py
test/Ignore.py
test/Install.py
test/InstallAs.py [new file with mode: 0644]
test/Precious.py
test/SideEffect.py
test/SourceCode.py [new file with mode: 0644]
test/expansion.py [new file with mode: 0644]
test/special-filenames.py

index 1eeb7a973b3ce482dc8389602810288e75d9e273..8f1f75466e3b04be54901acc3e6a5ad10c1fcd24 100644 (file)
@@ -36,6 +36,9 @@ RELEASE X.XX - XXX
 
   - Accomodate alphanumeric version strings in EnsurePythonVersion().
 
+  - Support arbitrary expansion of construction variables within
+    file and directory arguments to Builder calls and Environment methods.
+
   From Bram Moolenaar:
 
   - Split the non-SCons-specific functionality from SConf.py to a new,
index 36b9f00d22d0ad33d2fa8ced1c91e78f41037d28..ae6938733e01d19ee284cc2c2f7dfbf8f25d7f33 100644 (file)
@@ -27,6 +27,16 @@ RELEASE X.XX - XXX
 
   Please note the following important changes since release 0.92:
 
+  - Construction variables are now expanded anywhere within a
+    target or source name, as well as in the arguments to the following
+    Environment methods:  AlwaysBuild(), Depends(), Ignore(), Install(),
+    InstallAs(), Precious(), SideEffect() and SourceCode().
+
+    If you have any files or directories that actually contain one or
+    more dollar signs ($), you must now precede the dollar sign with
+    another dollar sign ($$) when referring to the file or directory
+    as part of calling a Builder, or any of the above methods.
+
   Please note the following important changes since release 0.91:
 
   - The Debian package available from the SCons web site now
index af03e82360708c758f567aea04dc6879de3bd30b..fe017aeb79e88c2fa578610a99bd977a5e8b2056 100644 (file)
@@ -49,7 +49,6 @@ import UserDict
 import SCons.Action
 from SCons.Errors import InternalError, UserError
 import SCons.Executor
-import SCons.Node
 import SCons.Node.FS
 import SCons.Util
 import SCons.Warnings
@@ -329,7 +328,7 @@ class BuilderBase:
         src_suf = self.get_src_suffix(env)
 
         source = adjustixes(source, None, src_suf)
-        slist = SCons.Node.arg2nodes(source, self.source_factory)
+        slist = env.arg2nodes(source, self.source_factory)
 
         pre = self.get_prefix(env, slist)
         suf = self.get_suffix(env, slist)
@@ -342,7 +341,7 @@ class BuilderBase:
             tlist = [ t_from_s(pre, suf, self.splitext) ]
         else:
             target = adjustixes(target, pre, suf)
-            tlist = SCons.Node.arg2nodes(target, self.target_factory)
+            tlist = env.arg2nodes(target, self.target_factory)
 
         if self.emitter:
             # The emitter is going to do str(node), but because we're
@@ -369,8 +368,8 @@ class BuilderBase:
 
             # Have to call arg2nodes yet again, since it is legal for
             # emitters to spit out strings as well as Node instances.
-            slist = SCons.Node.arg2nodes(source, self.source_factory)
-            tlist = SCons.Node.arg2nodes(target, self.target_factory)
+            slist = env.arg2nodes(source, self.source_factory)
+            tlist = env.arg2nodes(target, self.target_factory)
 
         return tlist, slist
 
@@ -503,7 +502,7 @@ class MultiStepBuilder(BuilderBase):
             source = target
             target = None
 
-        slist = SCons.Node.arg2nodes(source, self.source_factory)
+        slist = env.arg2nodes(source, self.source_factory)
         final_sources = []
 
         try:
index e0ba2febe8060229b92fd2f40ad80978b20661d8..b51eb9fa475a4b8347dd319fae89d3be02fe46a2 100644 (file)
@@ -59,6 +59,8 @@ env_scanner = None
 
 scons_env = SCons.Environment.Environment()
 
+env_arg2nodes_called = None
+
 class Environment:
     def __init__(self, **kw):
         self.d = {}
@@ -67,6 +69,8 @@ class Environment:
         self.d['ESCAPE'] = scons_env['ESCAPE']
         for k, v in kw.items():
             self.d[k] = v
+        global env_arg2nodes_called
+        env_arg2nodes_called = None
     def subst(self, s):
         if not SCons.Util.is_String(s):
             return s
@@ -78,6 +82,17 @@ class Environment:
         except IndexError:
             pass
         return self.d.get(s, s)
+    def arg2nodes(self, args, factory):
+        global env_arg2nodes_called
+        env_arg2nodes_called = 1
+        if not SCons.Util.is_List(args):
+            args = [args]
+        list = []
+        for a in args:
+            if SCons.Util.is_String(a):
+                a = factory(a)
+            list.append(a)
+        return list
     def get_scanner(self, ext):
         return env_scanner
     def Dictionary(self):
@@ -108,8 +123,6 @@ class Environment:
         d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
         d['SOURCE'] = d['SOURCES'][0]
         return d
-    
-env = Environment()
 
 class MyNode_without_target_from_source:
     def __init__(self, name):
@@ -173,11 +186,13 @@ class BuilderTestCase(unittest.TestCase):
     def test__call__(self):
         """Test calling a builder to establish source dependencies
         """
+        env = Environment()
         builder = SCons.Builder.Builder(action="foo", node_factory=MyNode)
 
         n1 = MyNode("n1");
         n2 = MyNode("n2");
         builder(env, target = n1, source = n2)
+        assert env_arg2nodes_called
         assert n1.env == env, n1.env
         assert n1.builder == builder, n1.builder
         assert n1.sources == [n2], n1.sources
@@ -342,6 +357,7 @@ class BuilderTestCase(unittest.TestCase):
 
         Make sure that there is no '.' separator appended.
         """
+        env = Environment()
         builder = SCons.Builder.Builder(prefix = 'lib.')
         assert builder.get_prefix(env) == 'lib.'
         builder = SCons.Builder.Builder(prefix = 'lib')
@@ -431,6 +447,7 @@ class BuilderTestCase(unittest.TestCase):
         Make sure that the '.' separator is appended to the
         beginning if it isn't already present.
         """
+        env = Environment()
         builder = SCons.Builder.Builder(suffix = '.o')
         assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
         builder = SCons.Builder.Builder(suffix = 'o')
@@ -484,6 +501,7 @@ class BuilderTestCase(unittest.TestCase):
                     open(t, 'w').write("function2\n")
             return 1
 
+        env = Environment()
         builder = SCons.Builder.Builder(action = function2)
         tgts = builder(env, target = [outfile, outfile2], source = 'foo')
         for t in tgts:
@@ -525,6 +543,7 @@ class BuilderTestCase(unittest.TestCase):
 
     def test_MultiStepBuilder(self):
         """Testing MultiStepBuilder class."""
+        env = Environment()
         builder1 = SCons.Builder.Builder(action='foo',
                                          src_suffix='.bar',
                                          suffix='.foo')
@@ -574,8 +593,7 @@ class BuilderTestCase(unittest.TestCase):
         def func_action(target, source, env):
             return 0
         
-        env['BAR_SUFFIX'] = '.BAR2'
-        env['FOO_SUFFIX'] = '.FOO2'
+        env = Environment(BAR_SUFFIX = '.BAR2', FOO_SUFFIX = '.FOO2')
         builder = SCons.Builder.Builder(action={ '.foo' : func_action,
                                                  '.bar' : func_action,
                                                  '$BAR_SUFFIX' : func_action,
@@ -709,6 +727,7 @@ class BuilderTestCase(unittest.TestCase):
         class TestScanner:
             pass
         scn = TestScanner()
+        env = Environment()
         builder = SCons.Builder.Builder(scanner=scn)
         tgt = builder(env, target='foo2', source='bar')
         assert tgt.target_scanner == scn, tgt.target_scanner
@@ -731,6 +750,7 @@ class BuilderTestCase(unittest.TestCase):
             def instance(self, env):
                  return self
         env_scanner = TestScanner()
+        env = Environment()
         builder = SCons.Builder.Builder(action='action')
         tgt = builder(env, target='foo.x', source='bar')
         src = tgt.sources[0]
@@ -768,6 +788,7 @@ class BuilderTestCase(unittest.TestCase):
                 source.append("baz")
             return ( target, source )
 
+        env = Environment()
         builder = SCons.Builder.Builder(action='foo',
                                         emitter=emit,
                                         node_factory=MyNode)
@@ -862,6 +883,7 @@ class BuilderTestCase(unittest.TestCase):
     def test_no_target(self):
         """Test deducing the target from the source."""
 
+        env = Environment()
         b = SCons.Builder.Builder(action='foo', suffix='.o')
 
         tgt = b(env, 'aaa')
index fae86e44c8e0ce4db12d06ac6ecb0c5ea45256b6..f72bc9537508d36254c2c0492d57ba3415c2cff3 100644 (file)
@@ -54,6 +54,11 @@ import SCons.Tool
 import SCons.Util
 import SCons.Warnings
 
+class _Null:
+    pass
+
+_null = _Null
+
 def installFunc(target, source, env):
     """Install a source file into a target using the function specified
     as the INSTALL construction variable."""
@@ -170,6 +175,7 @@ class Environment:
                  options=None,
                  **kw):
         self.fs = SCons.Node.FS.default_fs
+        self.lookup_list = SCons.Node.arg2nodes_lookups
         self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
 
         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
@@ -211,6 +217,40 @@ class Environment:
     def __cmp__(self, other):
        return cmp(self._dict, other._dict)
 
+    def arg2nodes(self, args, node_factory=_null, lookup_list=_null):
+        if node_factory is _null:
+            node_factory = self.fs.File
+        if lookup_list is _null:
+            lookup_list = self.lookup_list
+
+        if not args:
+            return []
+
+        if not SCons.Util.is_List(args):
+            args = [args]
+
+        nodes = []
+        for v in args:
+            if SCons.Util.is_String(v):
+                n = None
+                for l in lookup_list:
+                    n = l(v)
+                    if not n is None:
+                        break
+                if not n is None:
+                    if SCons.Util.is_String(n):
+                        n = self.subst(n, raw=1)
+                        if node_factory:
+                            n = node_factory(n)
+                    nodes.append(n)
+                elif node_factory:
+                    v = self.subst(v, raw=1)
+                    nodes.append(node_factory(v))
+            else:
+                nodes.append(v)
+    
+        return nodes
+
     def Builders(self):
        pass    # XXX
 
@@ -330,21 +370,21 @@ class Environment:
         self._dict[envname][name] = nv
 
 
-    def        Depends(self, target, dependency):
-       """Explicity specify that 'target's depend on 'dependency'."""
-        tlist = SCons.Node.arg2nodes(target, self.fs.File)
-        dlist = SCons.Node.arg2nodes(dependency, self.fs.File)
-       for t in tlist:
-           t.add_dependency(dlist)
+    def Depends(self, target, dependency):
+        """Explicity specify that 'target's depend on 'dependency'."""
+        tlist = self.arg2nodes(target, self.fs.File)
+        dlist = self.arg2nodes(dependency, self.fs.File)
+        for t in tlist:
+            t.add_dependency(dlist)
 
-       if len(tlist) == 1:
-           tlist = tlist[0]
-       return tlist
+        if len(tlist) == 1:
+            tlist = tlist[0]
+        return tlist
 
     def Ignore(self, target, dependency):
         """Ignore a dependency."""
-        tlist = SCons.Node.arg2nodes(target, self.fs.File)
-        dlist = SCons.Node.arg2nodes(dependency, self.fs.File)
+        tlist = self.arg2nodes(target, self.fs.File)
+        dlist = self.arg2nodes(dependency, self.fs.File)
         for t in tlist:
             t.add_ignore(dlist)
 
@@ -355,7 +395,7 @@ class Environment:
     def AlwaysBuild(self, *targets):
         tlist = []
         for t in targets:
-            tlist.extend(SCons.Node.arg2nodes(t, self.fs.File))
+            tlist.extend(self.arg2nodes(t, self.fs.File))
 
         for t in tlist:
             t.set_always_build()
@@ -367,7 +407,7 @@ class Environment:
     def Precious(self, *targets):
         tlist = []
         for t in targets:
-            tlist.extend(SCons.Node.arg2nodes(t, self.fs.File))
+            tlist.extend(self.arg2nodes(t, self.fs.File))
 
         for t in tlist:
             t.set_precious()
@@ -422,11 +462,11 @@ class Environment:
     def Install(self, dir, source):
         """Install specified files in the given directory."""
         try:
-            dnodes = SCons.Node.arg2nodes(dir, self.fs.Dir)
+            dnodes = self.arg2nodes(dir, self.fs.Dir)
         except TypeError:
             raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?" % str(dir)
         try:
-            sources = SCons.Node.arg2nodes(source, self.fs.File)
+            sources = self.arg2nodes(source, self.fs.File)
         except TypeError:
             if SCons.Util.is_List(source):
                 raise SCons.Errors.UserError, "Source `%s' of Install() contains one or more non-files.  Install() source must be one or more files." % repr(map(str, source))
@@ -443,8 +483,8 @@ class Environment:
 
     def InstallAs(self, target, source):
         """Install sources as targets."""
-        sources = SCons.Node.arg2nodes(source, self.fs.File)
-        targets = SCons.Node.arg2nodes(target, self.fs.File)
+        sources = self.arg2nodes(source, self.fs.File)
+        targets = self.arg2nodes(target, self.fs.File)
         ret = []
         for src, tgt in map(lambda x, y: (x, y), sources, targets):
             ret.append(InstallBuilder(self, tgt, src))
@@ -454,7 +494,7 @@ class Environment:
 
     def SourceCode(self, entry, builder):
         """Arrange for a source code builder for (part of) a tree."""
-        entries = SCons.Node.arg2nodes(entry, self.fs.Entry)
+        entries = self.arg2nodes(entry, self.fs.Entry)
         for entry in entries:
             entry.set_src_builder(builder)
         if len(entries) == 1:
@@ -464,8 +504,8 @@ class Environment:
     def SideEffect(self, side_effect, target):
         """Tell scons that side_effects are built as side 
         effects of building targets."""
-        side_effects = SCons.Node.arg2nodes(side_effect, self.fs.File)
-        targets = SCons.Node.arg2nodes(target, self.fs.File)
+        side_effects = self.arg2nodes(side_effect, self.fs.File)
+        targets = self.arg2nodes(target, self.fs.File)
 
         for side_effect in side_effects:
             # A builder of 1 means the node is supposed to appear
index e79ce7c0fe2584684489e429bf19760789e76bc3..12766ecbbfcfbd8d12d92b556da4c18a696d3f7f 100644 (file)
@@ -128,7 +128,128 @@ class EnvironmentTestCase(unittest.TestCase):
         env2.Replace(ONE = "won")
         assert env2['ONE'] == "won"
         assert env['ONE'] == 1
-        
+
+    def test_arg2nodes(self):
+        """Test the arg2nodes method
+        """
+        env = Environment()
+        dict = {}
+        class X(SCons.Node.Node):
+            pass
+        def Factory(name, directory = None, create = 1, dict=dict, X=X):
+            if not dict.has_key(name):
+                dict[name] = X()
+                dict[name].name = name
+            return dict[name]
+
+        nodes = env.arg2nodes("Util.py UtilTests.py", Factory)
+        assert len(nodes) == 1, nodes
+        assert isinstance(nodes[0], X)
+        assert nodes[0].name == "Util.py UtilTests.py"
+
+        import types
+        if hasattr(types, 'UnicodeType'):
+            code = """if 1:
+                nodes = env.arg2nodes(u"Util.py UtilTests.py", Factory)
+                assert len(nodes) == 1, nodes
+                assert isinstance(nodes[0], X)
+                assert nodes[0].name == u"Util.py UtilTests.py"
+                \n"""
+            exec code in globals(), locals()
+
+        nodes = env.arg2nodes(["Util.py", "UtilTests.py"], Factory)
+        assert len(nodes) == 2, nodes
+        assert isinstance(nodes[0], X)
+        assert isinstance(nodes[1], X)
+        assert nodes[0].name == "Util.py"
+        assert nodes[1].name == "UtilTests.py"
+
+        n1 = Factory("Util.py")
+        nodes = env.arg2nodes([n1, "UtilTests.py"], Factory)
+        assert len(nodes) == 2, nodes
+        assert isinstance(nodes[0], X)
+        assert isinstance(nodes[1], X)
+        assert nodes[0].name == "Util.py"
+        assert nodes[1].name == "UtilTests.py"
+
+        class SConsNode(SCons.Node.Node):
+            pass
+        nodes = env.arg2nodes(SConsNode())
+        assert len(nodes) == 1, nodes
+        assert isinstance(nodes[0], SConsNode), node
+
+        class OtherNode:
+            pass
+        nodes = env.arg2nodes(OtherNode())
+        assert len(nodes) == 1, nodes
+        assert isinstance(nodes[0], OtherNode), node
+
+        def lookup_a(str, F=Factory):
+            if str[0] == 'a':
+                n = F(str)
+                n.a = 1
+                return n
+            else:
+                return None
+
+        def lookup_b(str, F=Factory):
+            if str[0] == 'b':
+                n = F(str)
+                n.b = 1
+                return n
+            else:
+                return None
+
+        env_ll = env.Copy()
+        env_ll.lookup_list = [lookup_a, lookup_b]
+
+        nodes = env_ll.arg2nodes(['aaa', 'bbb', 'ccc'], Factory)
+        assert len(nodes) == 3, nodes
+
+        assert nodes[0].name == 'aaa', nodes[0]
+        assert nodes[0].a == 1, nodes[0]
+        assert not hasattr(nodes[0], 'b'), nodes[0]
+
+        assert nodes[1].name == 'bbb'
+        assert not hasattr(nodes[1], 'a'), nodes[1]
+        assert nodes[1].b == 1, nodes[1]
+
+        assert nodes[2].name == 'ccc'
+        assert not hasattr(nodes[2], 'a'), nodes[1]
+        assert not hasattr(nodes[2], 'b'), nodes[1]
+
+        def lookup_bbbb(str, F=Factory):
+            if str == 'bbbb':
+                n = F(str)
+                n.bbbb = 1
+                return n
+            else:
+                return None
+
+        def lookup_c(str, F=Factory):
+            if str[0] == 'c':
+                n = F(str)
+                n.c = 1
+                return n
+            else:
+                return None
+
+        nodes = env.arg2nodes(['bbbb', 'ccc'], Factory,
+                                     [lookup_c, lookup_bbbb, lookup_b])
+        assert len(nodes) == 2, nodes
+
+        assert nodes[0].name == 'bbbb'
+        assert not hasattr(nodes[0], 'a'), nodes[1]
+        assert not hasattr(nodes[0], 'b'), nodes[1]
+        assert nodes[0].bbbb == 1, nodes[1]
+        assert not hasattr(nodes[0], 'c'), nodes[0]
+
+        assert nodes[1].name == 'ccc'
+        assert not hasattr(nodes[1], 'a'), nodes[1]
+        assert not hasattr(nodes[1], 'b'), nodes[1]
+        assert not hasattr(nodes[1], 'bbbb'), nodes[0]
+        assert nodes[1].c == 1, nodes[1]
+
     def test_Builder_calls(self):
         """Test Builder calls through different environments
         """
@@ -354,7 +475,8 @@ class EnvironmentTestCase(unittest.TestCase):
 
     def test_Install(self):
        """Test Install and InstallAs methods"""
-        env=Environment()
+        env = Environment(FOO='iii', BAR='jjj')
+
         tgt = env.Install('export', [ 'build/foo1', 'build/foo2' ])
         paths = map(str, tgt)
         paths.sort()
@@ -363,6 +485,14 @@ class EnvironmentTestCase(unittest.TestCase):
         for tnode in tgt:
             assert tnode.builder == InstallBuilder
 
+        tgt = env.Install('$FOO', [ 'build/${BAR}1', 'build/${BAR}2' ])
+        paths = map(str, tgt)
+        paths.sort()
+        expect = map(os.path.normpath, [ 'iii/jjj1', 'iii/jjj2' ])
+        assert paths == expect, paths
+        for tnode in tgt:
+            assert tnode.builder == InstallBuilder
+
         exc_caught = None
         try:
             tgt = env.Install('export', 'export')
@@ -400,6 +530,11 @@ class EnvironmentTestCase(unittest.TestCase):
         for tnode in tgt:
             assert tnode.builder == InstallBuilder
 
+        tgt = env.InstallAs(target='${FOO}.t', source='${BAR}.s')
+        assert tgt.path == 'iii.t'
+        assert tgt.sources[0].path == 'jjj.s'
+        assert tgt.builder == InstallBuilder
+
     def test_ReservedVariables(self):
         """Test generation of warnings when reserved variable names
         are set in an environment."""
@@ -570,7 +705,7 @@ class EnvironmentTestCase(unittest.TestCase):
 
     def test_Depends(self):
        """Test the explicit Depends method."""
-       env = Environment()
+       env = Environment(FOO = 'xxx', BAR='yyy')
        t = env.Depends(target='EnvironmentTest.py', dependency='Environment.py')
        assert t.__class__.__name__ == 'File'
        assert t.path == 'EnvironmentTest.py'
@@ -579,9 +714,17 @@ class EnvironmentTestCase(unittest.TestCase):
        assert d.__class__.__name__ == 'File'
        assert d.path == 'Environment.py'
 
+       t = env.Depends(target='${FOO}.py', dependency='${BAR}.py')
+       assert t.__class__.__name__ == 'File'
+       assert t.path == 'xxx.py'
+       assert len(t.depends) == 1
+       d = t.depends[0]
+       assert d.__class__.__name__ == 'File'
+       assert d.path == 'yyy.py'
+
     def test_Ignore(self):
         """Test the explicit Ignore method."""
-        env = Environment()
+        env = Environment(FOO='yyy', BAR='zzz')
         t = env.Ignore(target='targ.py', dependency='dep.py')
         assert t.__class__.__name__ == 'File'
         assert t.path == 'targ.py'
@@ -589,16 +732,23 @@ class EnvironmentTestCase(unittest.TestCase):
         i = t.ignore[0]
         assert i.__class__.__name__ == 'File'
         assert i.path == 'dep.py'
+        t = env.Ignore(target='$FOO$BAR', dependency='$BAR$FOO')
+        assert t.__class__.__name__ == 'File'
+        assert t.path == 'yyyzzz'
+        assert len(t.ignore) == 1
+        i = t.ignore[0]
+        assert i.__class__.__name__ == 'File'
+        assert i.path == 'zzzyyy'
 
     def test_AlwaysBuild(self):
         """Test the AlwaysBuild() method"""
-        env = Environment()
-        t = env.AlwaysBuild('a', 'b', ['c', 'd'])
+        env = Environment(FOO='fff', BAR='bbb')
+        t = env.AlwaysBuild('a', 'b$FOO', ['c', 'd'], '$BAR')
         assert t[0].__class__.__name__ == 'File'
         assert t[0].path == 'a'
         assert t[0].always_build
         assert t[1].__class__.__name__ == 'File'
-        assert t[1].path == 'b'
+        assert t[1].path == 'bfff'
         assert t[1].always_build
         assert t[2].__class__.__name__ == 'File'
         assert t[2].path == 'c'
@@ -606,16 +756,19 @@ class EnvironmentTestCase(unittest.TestCase):
         assert t[3].__class__.__name__ == 'File'
         assert t[3].path == 'd'
         assert t[3].always_build
+        assert t[4].__class__.__name__ == 'File'
+        assert t[4].path == 'bbb'
+        assert t[4].always_build
 
     def test_Precious(self):
         """Test the Precious() method."""
-        env = Environment()
-        t = env.Precious('a', 'b', ['c', 'd'])
+        env = Environment(FOO='ggg', BAR='hhh')
+        t = env.Precious('a', '${BAR}b', ['c', 'd'], '$FOO')
         assert t[0].__class__.__name__ == 'File'
         assert t[0].path == 'a'
         assert t[0].precious
         assert t[1].__class__.__name__ == 'File'
-        assert t[1].path == 'b'
+        assert t[1].path == 'hhhb'
         assert t[1].precious
         assert t[2].__class__.__name__ == 'File'
         assert t[2].path == 'c'
@@ -623,6 +776,9 @@ class EnvironmentTestCase(unittest.TestCase):
         assert t[3].__class__.__name__ == 'File'
         assert t[3].path == 'd'
         assert t[3].precious
+        assert t[4].__class__.__name__ == 'File'
+        assert t[4].path == 'ggg'
+        assert t[4].precious
 
     def test_Command(self):
         """Test the Command() method."""
@@ -654,28 +810,47 @@ class EnvironmentTestCase(unittest.TestCase):
 
     def test_SourceCode(self):
         """Test the SourceCode() method."""
-        env = Environment()
+        env = Environment(FOO='mmm', BAR='nnn')
         e = env.SourceCode('foo', None)
+        assert e.path == 'foo'
         s = e.src_builder()
         assert s is None, s
 
         b = Builder()
-        env.SourceCode(e, b)
+        e = env.SourceCode(e, b)
+        assert e.path == 'foo'
         s = e.src_builder()
         assert s is b, s
 
+        e = env.SourceCode('$BAR$FOO', None)
+        assert e.path == 'nnnmmm'
+        s = e.src_builder()
+        assert s is None, s
+
     def test_SideEffect(self):
         """Test the SideEffect() method"""
-        env = Environment()
+        env = Environment(LIB='lll', FOO='fff', BAR='bbb')
+
         foo = env.Object('foo.obj', 'foo.cpp')
         bar = env.Object('bar.obj', 'bar.cpp')
         s = env.SideEffect('mylib.pdb', ['foo.obj', 'bar.obj'])
+        assert s.path == 'mylib.pdb'
         assert s.side_effect
         assert foo.side_effects == [s]
         assert bar.side_effects == [s]
         assert s.depends_on([bar])
         assert s.depends_on([foo])
 
+        fff = env.Object('fff.obj', 'fff.cpp')
+        bbb = env.Object('bbb.obj', 'bbb.cpp')
+        s = env.SideEffect('my${LIB}.pdb', ['${FOO}.obj', '${BAR}.obj'])
+        assert s.path == 'mylll.pdb'
+        assert s.side_effect
+        assert fff.side_effects == [s], fff.side_effects
+        assert bbb.side_effects == [s], bbb.side_effects
+        assert s.depends_on([bbb])
+        assert s.depends_on([fff])
+
     def test_subst(self):
        """Test substituting construction variables within strings
        
index 3d3f563bdde54b593954dce18aec99b0b527c9a1..38b25af78c63f6eb5ba21d3f1dfa37ff79d13f70 100644 (file)
@@ -575,7 +575,9 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None):
     strSubst = string.replace(string.replace(strSubst, '\0\4', '$'),
                               '\0\5', '')
     # strip out redundant white-space
-    return string.strip(_space_sep.sub(' ', strSubst))
+    if mode != SUBST_RAW:
+        strSubst = string.strip(_space_sep.sub(' ', strSubst))
+    return strSubst
 
 def render_tree(root, child_func, prune=0, margin=[0], visited={}):
     """
index 23120e6163c3a140210d469b2b61267a78bd0ce8..09b96d956d575fa7a926a97a46206c828bdb145a 100644 (file)
@@ -172,17 +172,23 @@ class UtilTestCase(unittest.TestCase):
         assert newcom == cvt("test foo/blah.cppblah /bar/ack.cppblah"), newcom
 
         newcom = scons_subst("test $xxx", env)
+        assert newcom == cvt("test "), newcom
+        newcom = scons_subst("test $xxx", env, mode=SUBST_CMD)
+        assert newcom == cvt("test"), newcom
+        newcom = scons_subst("test $xxx", env, mode=SUBST_SIG)
         assert newcom == cvt("test"), newcom
 
         newcom = scons_subst("test $($xxx$)", env)
         assert newcom == cvt("test $($)"), newcom
-
-        newcom = scons_subst("test $( $xxx $)", env)
-        assert newcom == cvt("test $( $)"), newcom
-
+        newcom = scons_subst("test $($xxx$)", env, mode=SUBST_CMD)
+        assert newcom == cvt("test"), newcom
         newcom = scons_subst("test $($xxx$)", env, mode=SUBST_SIG)
         assert newcom == cvt("test"), newcom
 
+        newcom = scons_subst("test $( $xxx $)", env)
+        assert newcom == cvt("test $(  $)"), newcom
+        newcom = scons_subst("test $( $xxx $)", env, mode=SUBST_CMD)
+        assert newcom == cvt("test"), newcom
         newcom = scons_subst("test $( $xxx $)", env, mode=SUBST_SIG)
         assert newcom == cvt("test"), newcom
 
index f4126c1560d2eaa4c7c03eb131e70317e6159f50..832120acb12b2d725a7d711b02c309a79bc00def 100644 (file)
@@ -31,25 +31,36 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
-test.write('SConstruct', """
+test.subdir('sub')
+
+test.write('SConstruct', """\
 def bfunc(target, source, env):
     import shutil
     shutil.copyfile('f2.in', str(target[0]))
 
 B = Builder(action=bfunc)
-env = Environment(BUILDERS = { 'B' : B })
+env = Environment(BUILDERS = { 'B' : B }, SUBDIR='sub')
 env.B('f1.out', source='f1.in')
 env.AlwaysBuild('f1.out')
-""")
+
+env.B(r'%s', source='f3.in')
+env.AlwaysBuild(r'%s')
+""" % (os.path.join('sub', 'f3.out'),
+       os.path.join('$SUBDIR', 'f3.out')
+      ))
 
 test.write('f1.in', "f1.in\n")
 test.write('f2.in', "1")
+test.write('f3.in', "f3.in\n")
 
 test.run(arguments = ".")
 test.fail_test(test.read('f1.out') != '1')
+test.fail_test(test.read(['sub', 'f3.out']) != '1')
 
 test.write('f2.in', "2")
+
 test.run(arguments = ".")
 test.fail_test(test.read('f1.out') != '2')
+test.fail_test(test.read(['sub', 'f3.out']) != '2')
 
 test.pass_test()
index dd3c2b57f22ae232b4b7aa81cdaef46deb20e035..d65f2b6edeebbdcfd9d71bba8d9e18dad02108d9 100644 (file)
@@ -24,7 +24,8 @@
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import sys
+import os.path
+
 import TestSCons
 
 python = TestSCons.python
@@ -44,14 +45,17 @@ file.close()
 test.write('SConstruct', """
 Foo = Builder(action = r"%s build.py $TARGET $SOURCES subdir/foo.dep")
 Bar = Builder(action = r"%s build.py $TARGET $SOURCES subdir/bar.dep")
-env = Environment(BUILDERS = { 'Foo' : Foo, 'Bar' : Bar })
-env.Depends(target = ['f1.out', 'f2.out'], dependency = 'subdir/foo.dep')
-env.Depends(target = 'f3.out', dependency = 'subdir/bar.dep')
+env = Environment(BUILDERS = { 'Foo' : Foo, 'Bar' : Bar }, SUBDIR='subdir')
+env.Depends(target = ['f1.out', 'f2.out'], dependency = r'%s')
+env.Depends(target = r'%s', dependency = 'subdir/bar.dep')
 env.Foo(target = 'f1.out', source = 'f1.in')
 env.Foo(target = 'f2.out', source = 'f2.in')
-env.Bar(target = 'f3.out', source = 'f3.in')
+env.Bar(target = 'subdir/f3.out', source = 'f3.in')
 SConscript('subdir/SConscript', "env")
-""" % (python, python))
+""" % (python,
+       python,
+       os.path.join('$SUBDIR', 'foo.dep'),
+       os.path.join('$SUBDIR', 'f3.out')))
 
 test.write(['subdir', 'SConscript'], """
 Import("env")
@@ -71,11 +75,11 @@ test.write(['subdir', 'foo.dep'], "subdir/foo.dep 1\n")
 
 test.write(['subdir', 'bar.dep'], "subdir/bar.dep 1\n")
 
-test.run(arguments = '.')
+test.run(arguments = '--debug=dtree .')
 
 test.fail_test(test.read('f1.out') != "f1.in\nsubdir/foo.dep 1\n")
 test.fail_test(test.read('f2.out') != "f2.in\nsubdir/foo.dep 1\n")
-test.fail_test(test.read('f3.out') != "f3.in\nsubdir/bar.dep 1\n")
+test.fail_test(test.read(['subdir', 'f3.out']) != "f3.in\nsubdir/bar.dep 1\n")
 test.fail_test(test.read(['subdir', 'f4.out']) !=
                "subdir/f4.in\nsubdir/bar.dep 1\n")
 
@@ -87,7 +91,7 @@ test.run(arguments = '.')
 
 test.fail_test(test.read('f1.out') != "f1.in\nsubdir/foo.dep 2\n")
 test.fail_test(test.read('f2.out') != "f2.in\nsubdir/foo.dep 2\n")
-test.fail_test(test.read('f3.out') != "f3.in\nsubdir/bar.dep 2\n")
+test.fail_test(test.read(['subdir', 'f3.out']) != "f3.in\nsubdir/bar.dep 2\n")
 test.fail_test(test.read(['subdir', 'f4.out']) !=
                "subdir/f4.in\nsubdir/bar.dep 2\n")
 
@@ -97,7 +101,7 @@ test.run(arguments = '.')
 
 test.fail_test(test.read('f1.out') != "f1.in\nsubdir/foo.dep 2\n")
 test.fail_test(test.read('f2.out') != "f2.in\nsubdir/foo.dep 2\n")
-test.fail_test(test.read('f3.out') != "f3.in\nsubdir/bar.dep 3\n")
+test.fail_test(test.read(['subdir', 'f3.out']) != "f3.in\nsubdir/bar.dep 3\n")
 test.fail_test(test.read(['subdir', 'f4.out']) !=
                "subdir/f4.in\nsubdir/bar.dep 3\n")
 
index 5d87bbaf3092fedc761db6baa8848a8eb21e52f7..68094345d39539b88ee4eaad3436aecf487544ac 100644 (file)
@@ -24,7 +24,8 @@
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import sys
+import os.path
+
 import TestSCons
 
 python = TestSCons.python
@@ -42,14 +43,19 @@ for arg in sys.argv[2:]:
 file.close()
 """)
 
-test.write('SConstruct', """
+test.write('SConstruct', """\
 Foo = Builder(action = r"%s build.py $TARGET $SOURCES")
 Bar = Builder(action = r"%s build.py $TARGET $SOURCES")
-env = Environment(BUILDERS = { 'Foo' : Foo, 'Bar' : Bar })
+env = Environment(BUILDERS = { 'Foo' : Foo, 'Bar' : Bar }, SUBDIR='subdir')
 env.Foo(target = 'f1.out', source = ['f1a.in', 'f1b.in'])
 env.Ignore(target = 'f1.out', dependency = 'f1b.in')
 SConscript('subdir/SConscript', "env")
-""" % (python, python))
+env.Foo(target = 'subdir/f3.out', source = ['subdir/f3a.in', 'subdir/f3b.in'])
+env.Ignore(target = r'%s', dependency = r'%s')
+""" % (python,
+       python,
+       os.path.join('$SUBDIR', 'f3.out'),
+       os.path.join('$SUBDIR', 'f3b.in')))
 
 test.write(['subdir', 'SConscript'], """
 Import("env")
@@ -63,31 +69,42 @@ test.write('f1b.in', "f1b.in\n")
 test.write(['subdir', 'f2a.in'], "subdir/f2a.in\n")
 test.write(['subdir', 'f2b.in'], "subdir/f2b.in\n")
 
+test.write(['subdir', 'f3a.in'], "subdir/f3a.in\n")
+test.write(['subdir', 'f3b.in'], "subdir/f3b.in\n")
+
 test.run(arguments = '.')
 
 test.fail_test(test.read('f1.out') != "f1a.in\nf1b.in\n")
 test.fail_test(test.read(['subdir', 'f2.out']) !=
                "subdir/f2a.in\nsubdir/f2b.in\n")
+test.fail_test(test.read(['subdir', 'f3.out']) !=
+               "subdir/f3a.in\nsubdir/f3b.in\n")
 
 test.up_to_date(arguments = '.')
 
 test.write('f1b.in', "f1b.in 2\n")
 test.write(['subdir', 'f2a.in'], "subdir/f2a.in 2\n")
+test.write(['subdir', 'f3b.in'], "subdir/f3b.in 2\n")
 
 test.up_to_date(arguments = '.')
 
 test.fail_test(test.read('f1.out') != "f1a.in\nf1b.in\n")
 test.fail_test(test.read(['subdir', 'f2.out']) !=
                "subdir/f2a.in\nsubdir/f2b.in\n")
+test.fail_test(test.read(['subdir', 'f3.out']) !=
+               "subdir/f3a.in\nsubdir/f3b.in\n")
 
 test.write('f1a.in', "f1a.in 2\n")
 test.write(['subdir', 'f2b.in'], "subdir/f2b.in 2\n")
+test.write(['subdir', 'f3a.in'], "subdir/f3a.in 2\n")
 
 test.run(arguments = '.')
 
 test.fail_test(test.read('f1.out') != "f1a.in 2\nf1b.in 2\n")
 test.fail_test(test.read(['subdir', 'f2.out']) !=
                "subdir/f2a.in 2\nsubdir/f2b.in 2\n")
+test.fail_test(test.read(['subdir', 'f3.out']) !=
+               "subdir/f3a.in 2\nsubdir/f3b.in 2\n")
 
 test.up_to_date(arguments = '.')
 
index f69c954b195d998260e23ccfb838176b38e29c1e..76376625f2cd4e2b1ec9e900978c8a78959fda92 100644 (file)
@@ -35,9 +35,12 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
+test.subdir('sub')
+
 f1_out = test.workpath('export', 'f1.out')
 f2_out = test.workpath('export', 'f2.out')
 f3_out = test.workpath('export', 'f3.out')
+f4_out = test.workpath('export', 'f4.out')
 
 test.write('SConstruct', """\
 def cat(env, source, target):
@@ -64,17 +67,23 @@ env1.Install(dir='export', source=t)
 
 t = env3.Cat(target='f3.out', source='f3.in')
 env3.Install(dir='export', source=t)
-""")
+
+env4 = env1.Copy(EXPORT='export', SUBDIR='sub')
+t = env4.Cat(target='sub/f4.out', source='sub/f4.in')
+env4.Install(dir='$EXPORT', source=r'%s')
+""" % (os.path.join('$SUBDIR', 'f4.out')))
 
 test.write('f1.in', "f1.in\n")
 test.write('f2.in', "f2.in\n")
 test.write('f3.in', "f3.in\n")
+test.write(['sub', 'f4.in'], "sub/f4.in\n")
 
 test.run(arguments = '.')
 
 test.fail_test(test.read(f1_out) != "f1.in\n")
 test.fail_test(test.read(f2_out) != "f2.in\n")
 test.fail_test(test.read(f3_out) != "f3.in\n")
+test.fail_test(test.read(f4_out) != "sub/f4.in\n")
 
 test.fail_test(test.read('my_install.out') != os.path.join('export', 'f3.out'))
 
diff --git a/test/InstallAs.py b/test/InstallAs.py
new file mode 100644 (file)
index 0000000..643ac85
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Test the InstallAs() Environment method.
+"""
+
+import os.path
+import sys
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('install', 'subdir')
+
+install = test.workpath('install')
+install_file1_out = test.workpath('install', 'file1.out')
+install_file2_out = test.workpath('install', 'file2.out')
+install_file3_out = test.workpath('install', 'file3.out')
+
+#
+test.write('SConstruct', r"""
+env = Environment(INSTALLDIR=r'%s', SUBDIR='subdir')
+env.InstallAs(r'%s', 'file1.in')
+env.InstallAs([r'%s', r'%s'], ['file2.in', r'%s'])
+""" % (install,
+       install_file1_out,
+       os.path.join('$INSTALLDIR', 'file2.out'),
+       install_file3_out,
+       os.path.join('$SUBDIR', 'file3.in')))
+
+test.write('file1.in', "file1.in\n")
+test.write('file2.in', "file2.in\n")
+test.write(['subdir', 'file3.in'], "subdir/file3.in\n")
+
+test.run(arguments = '.')
+
+test.fail_test(test.read(install_file1_out) != "file1.in\n")
+test.fail_test(test.read(install_file2_out) != "file2.in\n")
+test.fail_test(test.read(install_file3_out) != "subdir/file3.in\n")
+
+test.up_to_date(arguments = '.')
+
+#
+test.pass_test()
index 8b2c7ff671ca40bee157847a16dc53801b1780b8..6aee9b05cb9873177b47155233694be62e10db69 100644 (file)
@@ -24,8 +24,8 @@
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import os
-import sys
+import os.path
+
 import TestSCons
 
 python = TestSCons.python
@@ -39,74 +39,81 @@ import sys
 sys.exit(0)
 """)
 
-test.write('SConstruct', """
+test.write('SConstruct', """\
 B = Builder(action = r"%s build.py $TARGET $SOURCES")
-env = Environment(BUILDERS = { 'B' : B })
+env = Environment(BUILDERS = { 'B' : B }, SUBDIR = 'subdir')
 f1 = env.B(target = 'f1.out', source = 'f1.in')
 env.B(target = 'f2.out', source = 'f2.in')
 env.B(target = 'f3.out', source = 'f3.in')
-env.Precious(f1, 'f2.out')
+env.B(target = 'subdir/f4.out', source = 'f4.in')
+env.Precious(f1, 'f2.out', r'%s')
 SConscript('subdir/SConscript', "env")
-""" % python)
+""" % (python,
+       os.path.join('$SUBDIR', 'f4.out')))
 
 test.write(['subdir', 'SConscript'], """
 Import("env")
-env.B(target = 'f4.out', source = 'f4.in')
-f5 = env.B(target = 'f5.out', source = 'f5.in')
-env.B(target = 'f6.out', source = 'f6.in')
-env.Precious(['f4.out', f5])
+env.B(target = 'f5.out', source = 'f5.in')
+f6 = env.B(target = 'f6.out', source = 'f6.in')
+env.B(target = 'f7.out', source = 'f7.in')
+env.Precious(['f5.out', f6])
 """)
 
 test.write('f1.in', "f1.in\n")
 test.write('f2.in', "f2.in\n")
 test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
 
-test.write(['subdir', 'f4.in'], "subdir/f4.in\n")
 test.write(['subdir', 'f5.in'], "subdir/f5.in\n")
 test.write(['subdir', 'f6.in'], "subdir/f6.in\n")
+test.write(['subdir', 'f7.in'], "subdir/f7.in\n")
 
 test.write('f1.out', "SHOULD NOT BE REMOVED\n")
 test.write('f2.out', "SHOULD NOT BE REMOVED\n")
 test.write('f3.out', "SHOULD BE REMOVED\n")
-
 test.write(['subdir', 'f4.out'], "SHOULD NOT BE REMOVED\n")
+
 test.write(['subdir', 'f5.out'], "SHOULD NOT BE REMOVED\n")
-test.write(['subdir', 'f6.out'], "SHOULD BE REMOVED\n")
+test.write(['subdir', 'f6.out'], "SHOULD NOT BE REMOVED\n")
+test.write(['subdir', 'f7.out'], "SHOULD BE REMOVED\n")
 
 test.run(arguments = '.')
 
 test.fail_test(not os.path.exists(test.workpath('f1.out')))
 test.fail_test(not os.path.exists(test.workpath('f2.out')))
 test.fail_test(os.path.exists(test.workpath('f3.out')))
-
 test.fail_test(not os.path.exists(test.workpath('subdir', 'f4.out')))
+
 test.fail_test(not os.path.exists(test.workpath('subdir', 'f5.out')))
-test.fail_test(os.path.exists(test.workpath('subdir', 'f6.out')))
+test.fail_test(not os.path.exists(test.workpath('subdir', 'f6.out')))
+test.fail_test(os.path.exists(test.workpath('subdir', 'f7.out')))
 
 test.write('f3.out', "SHOULD BE REMOVED\n")
-test.write(['subdir', 'f6.out'], "SHOULD BE REMOVED\n")
+test.write(['subdir', 'f7.out'], "SHOULD BE REMOVED\n")
 
 test.run(arguments = '.')
 
 test.fail_test(not os.path.exists(test.workpath('f1.out')))
 test.fail_test(not os.path.exists(test.workpath('f2.out')))
 test.fail_test(not os.path.exists(test.workpath('f3.out')))
-
 test.fail_test(not os.path.exists(test.workpath('subdir', 'f4.out')))
+
 test.fail_test(not os.path.exists(test.workpath('subdir', 'f5.out')))
 test.fail_test(not os.path.exists(test.workpath('subdir', 'f6.out')))
+test.fail_test(not os.path.exists(test.workpath('subdir', 'f7.out')))
 
 test.write('f3.in', "f3.in 2\n")
-test.write(['subdir', 'f6.in'], "subdir/f6.in 2\n")
+test.write(['subdir', 'f7.in'], "subdir/f7.in 2\n")
 
 test.run(arguments = '.')
 
 test.fail_test(not os.path.exists(test.workpath('f1.out')))
 test.fail_test(not os.path.exists(test.workpath('f2.out')))
 test.fail_test(os.path.exists(test.workpath('f3.out')))
-
 test.fail_test(not os.path.exists(test.workpath('subdir', 'f4.out')))
+
 test.fail_test(not os.path.exists(test.workpath('subdir', 'f5.out')))
-test.fail_test(os.path.exists(test.workpath('subdir', 'f6.out')))
+test.fail_test(not os.path.exists(test.workpath('subdir', 'f6.out')))
+test.fail_test(os.path.exists(test.workpath('subdir', 'f7.out')))
 
 test.pass_test()
index ca2dc4f7805199cf36b5be683015d9b1d2c73f63..61b0d58d1fec7c109ff3fdf249effeb17352dba6 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import TestSCons
 import os.path
 
+import TestSCons
+
 test = TestSCons.TestSCons()
 
-test.write('SConstruct', 
-"""
+test.write('SConstruct', """\
 def copy(source, target):
     open(target, "wb").write(open(source, "rb").read())
 
@@ -38,20 +38,25 @@ def build(env, source, target):
     copy(str(source[0]), str(target[0]))
     if target[0].side_effects:
         side_effect = open(str(target[0].side_effects[0]), "ab")
-        side_effect.write('%s -> %s\\n'%(str(source[0]), str(target[0])))
+        side_effect.write('%%s -> %%s\\n'%%(str(source[0]), str(target[0])))
 
 Build = Builder(action=build)
-env = Environment(BUILDERS={'Build':Build})
+env = Environment(BUILDERS={'Build':Build}, SUBDIR='subdir')
 env.Build('foo.out', 'foo.in')
 env.Build('bar.out', 'bar.in')
 env.Build('blat.out', 'blat.in')
 env.SideEffect('log.txt', ['foo.out', 'bar.out', 'blat.out'])
 env.Build('log.out', 'log.txt')
-""")
+env.Build('subdir/baz.out', 'baz.in')
+env.SideEffect(r'%s', ['blat.out', r'%s'])
+env.Build('subdir/out.out', 'subdir/out.txt')
+""" % (os.path.join('$SUBDIR', 'out.txt'),
+       os.path.join('$SUBDIR', 'baz.out')))
 
 test.write('foo.in', 'foo.in\n')
 test.write('bar.in', 'bar.in\n')
 test.write('blat.in', 'blat.in\n')
+test.write('baz.in', 'baz.in\n')
 
 test.run(arguments = 'foo.out bar.out', stdout=test.wrap_stdout("""\
 build("foo.out", "foo.in")
@@ -84,7 +89,11 @@ test.write('foo.in', 'foo.in 2 \n')
 test.run(arguments = ".", stdout=test.wrap_stdout("""\
 build("foo.out", "foo.in")
 build("log.out", "log.txt")
-"""))
+build("%s", "baz.in")
+build("%s", "%s")
+""" % (os.path.join('subdir', 'baz.out'),
+       os.path.join('subdir', 'out.out'),
+       os.path.join('subdir', 'out.txt'))))
 
 expect = """\
 foo.in -> foo.out
@@ -107,7 +116,11 @@ build("bar.out", "bar.in")
 build("blat.out", "blat.in")
 build("foo.out", "foo.in")
 build("log.out", "log.txt")
-"""))
+build("%s", "baz.in")
+build("%s", "%s")
+""" % (os.path.join('subdir', 'baz.out'),
+       os.path.join('subdir', 'out.out'),
+       os.path.join('subdir', 'out.txt'))))
 
 expect = """\
 bar.in -> bar.out
diff --git a/test/SourceCode.py b/test/SourceCode.py
new file mode 100644 (file)
index 0000000..cd8baf4
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Test fetching source files using the SourceCode() method.
+"""
+
+import os
+import stat
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('sub')
+
+test.write('SConstruct', """\
+import os.path
+
+def cat(env, source, target):
+    target = str(target[0])
+    source = map(str, source)
+    f = open(target, "wb")
+    for src in source:
+        f.write(open(src, "rb").read())
+    f.close()
+
+def sc_cat(env, source, target):
+    source = []
+    for t in target:
+        head, tail = os.path.split(str(t))
+        source.append(os.path.join(head, 'sc-' + tail))
+    cat(env, source, target)
+
+env = Environment(BUILDERS={'Cat':Builder(action=cat)}, SUBDIR='sub')
+env.Cat('aaa.out', 'sub/aaa.in')
+env.Cat('bbb.out', 'sub/bbb.in')
+env.Cat('ccc.out', 'sub/ccc.in')
+env.Cat('all', ['aaa.out', 'bbb.out', 'ccc.out'])
+env.SourceCode('$SUBDIR', Builder(action=sc_cat, env=env))
+SConscript('sub/SConscript', "env")
+""")
+
+test.write(['sub', 'sc-aaa.in'], "sub/sc-aaa.in\n")
+test.write(['sub', 'sc-bbb.in'], "sub/sc-bbb.in\n")
+test.write(['sub', 'sc-ccc.in'], "sub/sc-ccc.in\n")
+
+test.write(['sub', 'sc-SConscript'], "'sub/sc-SConscript'\n")
+
+test.run(arguments = '.',
+         stdout = test.wrap_stdout(read_str = """\
+sc_cat("%s", [])
+""" % (os.path.join('sub', 'SConscript')),
+                                   build_str = """\
+sc_cat("%s", [])
+cat("aaa.out", "%s")
+sc_cat("%s", [])
+cat("bbb.out", "%s")
+sc_cat("%s", [])
+cat("ccc.out", "%s")
+cat("all", ["aaa.out", "bbb.out", "ccc.out"])
+""" % (os.path.join('sub', 'aaa.in'),
+       os.path.join('sub', 'aaa.in'),
+       os.path.join('sub', 'bbb.in'),
+       os.path.join('sub', 'bbb.in'),
+       os.path.join('sub', 'ccc.in'),
+       os.path.join('sub', 'ccc.in'))))
+
+test.fail_test(test.read(['sub', 'SConscript']) != "'sub/sc-SConscript'\n")
+test.fail_test(test.read('all') != "sub/sc-aaa.in\nsub/sc-bbb.in\nsub/sc-ccc.in\n")
+
+test.pass_test()
diff --git a/test/expansion.py b/test/expansion.py
new file mode 100644 (file)
index 0000000..7b8676b
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Test construction variable expansion in Builder paths.
+"""
+
+import os.path
+import sys
+import time
+import TestSCons
+
+_exe = TestSCons._exe
+_obj = TestSCons._obj
+
+test = TestSCons.TestSCons()
+
+test.subdir('sub')
+
+test.write('SConstruct', """\
+env = Environment(SUBDIR = 'sub')
+env.Program(target = 'foo1', source = env.Object(source = r'%s'))
+env.Program(source = env.Object(target = r'%s', source = 'f2.c'))
+env.Program('foo3', r'%s')
+env.Program(r'%s')
+""" % (os.path.join('$SUBDIR', 'f1.c'),
+       os.path.join('$SUBDIR', 'foo2'),
+       os.path.join('$SUBDIR', 'f3.c'),
+       os.path.join('$SUBDIR', 'foo4.c')))
+
+test.write(['sub', 'f1.c'], r"""
+int
+main(int argc, char *argv[])
+{
+       argv[argc++] = "--";
+       printf("sub/f1.c\n");
+       exit (0);
+}
+""")
+
+test.write('f2.c', r"""
+int
+main(int argc, char *argv[])
+{
+       argv[argc++] = "--";
+       printf("f2.c\n");
+       exit (0);
+}
+""")
+
+test.write(['sub', 'f3.c'], r"""
+int
+main(int argc, char *argv[])
+{
+       argv[argc++] = "--";
+       printf("sub/f3.c\n");
+       exit (0);
+}
+""")
+
+test.write(['sub', 'foo4.c'], r"""
+int
+main(int argc, char *argv[])
+{
+       argv[argc++] = "--";
+       printf("sub/foo4.c\n");
+       exit (0);
+}
+""")
+
+test.run(arguments = '.')
+
+test.run(program = test.workpath('foo1' + _exe), stdout = "sub/f1.c\n")
+test.run(program = test.workpath('sub', 'foo2' + _exe), stdout = "f2.c\n")
+test.run(program = test.workpath('foo3' + _exe), stdout = "sub/f3.c\n")
+test.run(program = test.workpath('sub','foo4' + _exe), stdout = "sub/foo4.c\n")
+
+test.fail_test(not os.path.exists(test.workpath('sub', 'f1' + _obj)))
+test.fail_test(not os.path.exists(test.workpath('sub', 'foo2' + _obj)))
+test.fail_test(not os.path.exists(test.workpath('sub', 'f3' + _obj)))
+test.fail_test(not os.path.exists(test.workpath('sub', 'foo4' + _obj)))
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
index 2002b37bf0d5836ac8ad691e9a3d8cda787d76bc..98096b7ad1148c0e5477dfd08d442ec0d1ef7901 100644 (file)
@@ -45,7 +45,7 @@ attempt_file_names = [
     "File&with&ampersand",
     "File?with?question",
     "File\twith\ttab",
-    "File$with$dollar",
+    "File$$with$$dollar",
     "Combination '\"\n\\;<>?|*\t&"
     ]
 
@@ -57,7 +57,8 @@ open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read())
 file_names = []
 for fn in attempt_file_names:
     try:
-        test.write(fn + '.in', fn + '\n')
+        in_name = string.replace(fn, '$$', '$') + '.in'
+        test.write(in_name, fn + '\n')
         file_names.append(fn)
     except IOError:
         # if the Python interpreter can't handle it, don't bother
@@ -76,6 +77,7 @@ env=Environment(BUILDERS = {'Build' : Builder(action = '%s cat.py $TARGET $SOURC
 test.run(arguments='.')
 
 for fn in file_names:
-    test.fail_test(test.read(fn + '.out') != fn + '\n')
+    out_name = string.replace(fn, '$$', '$') + '.out'
+    test.fail_test(test.read(out_name) != fn + '\n')
 
 test.pass_test()