Support using construction variables as re-usable, callable command generators. ...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 13 Mar 2003 14:18:35 +0000 (14:18 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 13 Mar 2003 14:18:35 +0000 (14:18 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@611 fdb21ef1-2011-0410-befe-b5e4ea1792b1

17 files changed:
doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Defaults.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Platform/win32.py
src/engine/SCons/Tool/gnulink.py
src/engine/SCons/Tool/linkloc.py
src/engine/SCons/Tool/mslib.py
src/engine/SCons/Tool/mslink.py
src/engine/SCons/Tool/sgilink.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
test/scan-once.py

index 7fc4f8e49280387f430e041c6e96599290a15064..bc28d3325f504c0ac4175bb7e04002b78d5dee33 100644 (file)
@@ -3924,6 +3924,55 @@ ${TARGET.suffix}     => .x
 ${TARGET.abspath}    => /top/dir/sub/dir/file.x
 .EE
 
+Lastly, a variable name
+may be a callable Python function
+associated with a
+construction variable in the environment.
+The function should
+take three arguments:
+.I target
+- a list of target nodes,
+.I source 
+- a list of source nodes, 
+.I env
+- the construction environment.
+SCons will insert whatever
+the called function returns
+into the expanded string:
+
+.ES
+def foo(target, source, env):
+    return "bar"
+
+# Will expand to $BAR to "bar baz"
+env=Environment(FOO=foo, BAR="$FOO baz")
+.EE
+
+You can use this feature to pass arguments to a
+Python function by creating a callable class
+that stores one or more arguments in an object,
+and then uses them when the
+.B __call__()
+method is called.
+Note that in this case,
+the entire variable expansion must
+be enclosed by curly braces
+so that the arguments will
+be associated with the
+instantiation of the class:
+
+.ES
+class foo:
+    def __init__(self, arg):
+        self.arg = arg
+
+    def __call__(self, target, source, env):
+        return arg + " bar"
+
+# Will expand $BAR to "my argument bar baz"
+env=Environment(FOO=foo, BAR="${FOO('my argument')} baz")
+.EE
+
 .LP
 The special pseudo-variables
 .R $(
index 3db1264d8de16317ee041be084758d17fe109c07..3f9c1b49e51877915de3650e8f483421e0d0f489 100644 (file)
@@ -21,6 +21,9 @@ RELEASE 0.12 - XXX
     that allows explicit specification of where the source files
     for an SConscript file can be found.
 
+  - Support more easily re-usable flavors of command generators by
+    calling callable variables when strings are expanded.
+
   From Steven Knight:
 
   - Added an INSTALL construction variable that can be set to a function
index ecad9b0b7e906cd00a45d7cd7c411883ba6c0b0c..45f4c980782f35c3e79321489be43273c3a80566 100644 (file)
@@ -126,8 +126,7 @@ def _do_create_action(act, strfunction=_null, varlist=[]):
             # like a function or a CommandGenerator in that variable
             # instead of a string.
             return CommandGeneratorAction(LazyCmdGenerator(var))
-        listCmds = map(lambda x: CommandAction(string.split(x)),
-                       string.split(act, '\n'))
+        listCmds = map(lambda x: CommandAction(x), string.split(act, '\n'))
         if len(listCmds) == 1:
             return listCmds[0]
         else:
@@ -161,49 +160,6 @@ class ActionBase:
     def get_actions(self):
         return [self]
 
-    def subst_dict(self, target, source, env):
-        """Create a dictionary for substitution of construction
-        variables.
-
-        This translates the following special arguments:
-
-            env    - the construction environment itself,
-                     the values of which (CC, CCFLAGS, etc.)
-                     are copied straight into the dictionary
-
-            target - the target (object or array of objects),
-                     used to generate the TARGET and TARGETS
-                     construction variables
-
-            source - the source (object or array of objects),
-                     used to generate the SOURCES and SOURCE
-                     construction variables
-        """
-
-        dict = {}
-
-        for k,v in env.items(): dict[k] = v
-
-        if not SCons.Util.is_List(target):
-            target = [target]
-
-        dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, target)))
-        if dict['TARGETS']:
-            dict['TARGET'] = dict['TARGETS'][0]
-
-        def rstr(x):
-            try:
-                return x.rstr()
-            except AttributeError:
-                return str(x)
-        if not SCons.Util.is_List(source):
-            source = [source]
-        dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(rstr, source)))
-        if dict['SOURCES']:
-            dict['SOURCE'] = dict['SOURCES'][0]
-
-        return dict
-
     def __add__(self, other):
         return _actionAppend(self, other)
 
@@ -226,11 +182,14 @@ _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
 class CommandAction(ActionBase):
     """Class for command-execution actions."""
     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().
         self.cmd_list = cmd
 
     def strfunction(self, target, source, env):
-        dict = self.subst_dict(target, source, env)
-        cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm)
+        cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env, _rm,
+                                               target, source)
         return map(_string_from_cmd_list, cmd_list)
 
     def __call__(self, target, source, env):
@@ -256,15 +215,15 @@ class CommandAction(ActionBase):
         else:
             raise SCons.Errors.UserError('Missing SPAWN construction variable.')
 
-        dict = self.subst_dict(target, source, env)
-        cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm)
+        cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env, _rm,
+                                               target, source)
         for cmd_line in cmd_list:
             if len(cmd_line):
                 if print_actions:
                     self.show(_string_from_cmd_list(cmd_line))
                 if execute_actions:
                     try:
-                        ENV = dict['ENV']
+                        ENV = env['ENV']
                     except KeyError:
                         global default_ENV
                         if not default_ENV:
@@ -280,16 +239,6 @@ class CommandAction(ActionBase):
                         return ret
         return 0
 
-    def _sig_dict(self, target, source, env):
-        """Supply a dictionary for use in computing signatures.
-
-        For signature purposes, it doesn't matter what targets or
-        sources we use, so long as we use the same ones every time
-        so the signature stays the same.  We supply an array of two
-        of each to allow for distinction between TARGET and TARGETS.
-        """
-        return self.subst_dict(['__t1__', '__t2__'], ['__s1__', '__s2__'], env)
-
     def get_raw_contents(self, target, source, env):
         """Return the complete contents of this action's command line.
         """
@@ -303,9 +252,11 @@ class CommandAction(ActionBase):
         #return SCons.Util.scons_subst(string.join(self.cmd_list),
         #                              self.subst_dict(target, source, env),
         #                              {})
-        return SCons.Util.scons_subst(string.join(self.cmd_list),
-                                      env.sig_dict(),
-                                      {})
+        cmd = self.cmd_list
+        if not SCons.Util.is_List(cmd):
+            cmd = [ cmd ]
+        return SCons.Util.scons_subst(string.join(map(str, cmd)),
+                                      env)
 
     def get_contents(self, target, source, env):
         """Return the signature contents of this action's command line.
@@ -324,9 +275,11 @@ class CommandAction(ActionBase):
         #                              self.subst_dict(target, source, env),
         #                              {},
         #                              _remove)
-        return SCons.Util.scons_subst(string.join(map(str, self.cmd_list)),
-                                      env.sig_dict(),
-                                      {},
+        cmd = self.cmd_list
+        if not SCons.Util.is_List(cmd):
+            cmd = [ cmd ]
+        return SCons.Util.scons_subst(string.join(map(str, cmd)),
+                                      env,
                                       _remove)
 
 class CommandGeneratorAction(ActionBase):
index de157ae7bf7beba4df7bbc7dd94c600cd5d5c224..eedc8655310b7538a6a69aa77213d4a0f2d99f9e 100644 (file)
@@ -100,6 +100,8 @@ class Environment:
         return self.d.get(key, value)
     def items(self):
         return self.d.items()
+    def Dictionary(self):
+        return self.d
     def sig_dict(self):
         d = {}
         for k,v in self.items(): d[k] = v
@@ -129,7 +131,7 @@ class ActionTestCase(unittest.TestCase):
 
         a2 = SCons.Action.Action("string")
         assert isinstance(a2, SCons.Action.CommandAction), a2
-        assert a2.cmd_list == ["string"], a2.cmd_list
+        assert a2.cmd_list == "string", a2.cmd_list
 
         if hasattr(types, 'UnicodeType'):
             exec "a3 = SCons.Action.Action(u'string')"
@@ -138,11 +140,11 @@ class ActionTestCase(unittest.TestCase):
         a4 = SCons.Action.Action(["x", "y", "z", [ "a", "b", "c"]])
         assert isinstance(a4, SCons.Action.ListAction), a4
         assert isinstance(a4.list[0], SCons.Action.CommandAction), a4.list[0]
-        assert a4.list[0].cmd_list == ["x"], a4.list[0].cmd_list
+        assert a4.list[0].cmd_list == "x", a4.list[0].cmd_list
         assert isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1]
-        assert a4.list[1].cmd_list == ["y"], a4.list[1].cmd_list
+        assert a4.list[1].cmd_list == "y", a4.list[1].cmd_list
         assert isinstance(a4.list[2], SCons.Action.CommandAction), a4.list[2]
-        assert a4.list[2].cmd_list == ["z"], a4.list[2].cmd_list
+        assert a4.list[2].cmd_list == "z", a4.list[2].cmd_list
         assert isinstance(a4.list[3], SCons.Action.CommandAction), a4.list[3]
         assert a4.list[3].cmd_list == [ "a", "b", "c" ], a4.list[3].cmd_list
 
@@ -158,25 +160,25 @@ class ActionTestCase(unittest.TestCase):
 
         a8 = SCons.Action.Action(["a8"])
         assert isinstance(a8, SCons.Action.CommandAction), a8
-        assert a8.cmd_list == [ "a8" ], a8.cmd_list
+        assert a8.cmd_list == "a8", a8.cmd_list
 
         a9 = SCons.Action.Action("x\ny\nz")
         assert isinstance(a9, SCons.Action.ListAction), a9
         assert isinstance(a9.list[0], SCons.Action.CommandAction), a9.list[0]
-        assert a9.list[0].cmd_list == ["x"], a9.list[0].cmd_list
+        assert a9.list[0].cmd_list == "x", a9.list[0].cmd_list
         assert isinstance(a9.list[1], SCons.Action.CommandAction), a9.list[1]
-        assert a9.list[1].cmd_list == ["y"], a9.list[1].cmd_list
+        assert a9.list[1].cmd_list == "y", a9.list[1].cmd_list
         assert isinstance(a9.list[2], SCons.Action.CommandAction), a9.list[2]
-        assert a9.list[2].cmd_list == ["z"], a9.list[2].cmd_list
+        assert a9.list[2].cmd_list == "z", a9.list[2].cmd_list
 
         a10 = SCons.Action.Action(["x", foo, "z"])
         assert isinstance(a10, SCons.Action.ListAction), a10
         assert isinstance(a10.list[0], SCons.Action.CommandAction), a10.list[0]
-        assert a10.list[0].cmd_list == ["x"], a10.list[0].cmd_list
+        assert a10.list[0].cmd_list == "x", a10.list[0].cmd_list
         assert isinstance(a10.list[1], SCons.Action.FunctionAction), a10.list[1]
         assert a10.list[1].execfunction == foo, a10.list[1].execfunction
         assert isinstance(a10.list[2], SCons.Action.CommandAction), a10.list[2]
-        assert a10.list[2].cmd_list == ["z"], a10.list[2].cmd_list
+        assert a10.list[2].cmd_list == "z", a10.list[2].cmd_list
 
         a11 = SCons.Action.Action(foo, strfunction=bar)
         assert isinstance(a11, SCons.Action.FunctionAction), a11
@@ -228,51 +230,6 @@ class ActionBaseTestCase(unittest.TestCase):
         l = a.get_actions()
         assert l == [a], l
 
-    def test_subst_dict(self):
-        """Test substituting dictionary values in an Action
-        """
-        a = SCons.Action.Action("x")
-
-        d = a.subst_dict([], [], Environment(a = 'A', b = 'B'))
-        assert d['a'] == 'A', d
-        assert d['b'] == 'B', d
-
-        d = a.subst_dict(target = 't', source = 's', env = Environment())
-        assert str(d['TARGETS']) == 't', d['TARGETS']
-        assert str(d['TARGET']) == 't', d['TARGET']
-        assert str(d['SOURCES']) == 's', d['SOURCES']
-        assert str(d['SOURCE']) == 's', d['SOURCE']
-
-        d = a.subst_dict(target = ['t1', 't2'],
-                         source = ['s1', 's2'],
-                         env = Environment())
-        TARGETS = map(lambda x: str(x), d['TARGETS'])
-        TARGETS.sort()
-        assert TARGETS == ['t1', 't2'], d['TARGETS']
-        assert str(d['TARGET']) == 't1', d['TARGET']
-        SOURCES = map(lambda x: str(x), d['SOURCES'])
-        SOURCES.sort()
-        assert SOURCES == ['s1', 's2'], d['SOURCES']
-        assert str(d['SOURCE']) == 's1', d['SOURCE']
-
-        class N:
-            def __init__(self, name):
-                self.name = name
-            def __str__(self):
-                return self.name
-            def rstr(self):
-                return 'rstr-' + self.name
-
-        d = a.subst_dict(target = [N('t3'), 't4'],
-                         source = ['s3', N('s4')],
-                         env = Environment())
-        TARGETS = map(lambda x: str(x), d['TARGETS'])
-        TARGETS.sort()
-        assert TARGETS == ['t3', 't4'], d['TARGETS']
-        SOURCES = map(lambda x: str(x), d['SOURCES'])
-        SOURCES.sort()
-        assert SOURCES == ['rstr-s4', 's3'], d['SOURCES']
-
     def test_add(self):
         """Test adding Actions to stuff."""
         # Adding actions to other Actions or to stuff that can
@@ -547,11 +504,18 @@ class CommandActionTestCase(unittest.TestCase):
     def test_get_raw_contents(self):
         """Test fetching the contents of a command Action
         """
+        def CmdGen(target, source, env):
+            assert target is None, target
+            return "%s %s" % \
+                   (env["foo"], env["bar"])
+
+        # The number 1 is there to make sure all args get converted to strings.
         a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar",
-                                        "$)", "|"])
+                                        "$)", "|", "$baz", 1])
         c = a.get_raw_contents(target=[], source=[],
-                               env=Environment(foo = 'FFF', bar = 'BBB'))
-        assert c == "| $( FFF | BBB $) |", c
+                               env=Environment(foo = 'FFF', bar = 'BBB',
+                                               baz = CmdGen))
+        assert c == "| $( FFF | BBB $) | FFF BBB 1", c
 
         # We've discusssed using the real target and source names in a
         # CommandAction's signature contents.  This would have have the
@@ -601,11 +565,18 @@ class CommandActionTestCase(unittest.TestCase):
     def test_get_contents(self):
         """Test fetching the contents of a command Action
         """
+        def CmdGen(target, source, env):
+            assert target is None, target
+            return "%s %s" % \
+                   (env["foo"], env["bar"])
+
+        # The number 1 is there to make sure all args get converted to strings.
         a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar",
-                                        "$)", "|"])
+                                        "$)", "|", "$baz", 1])
         c = a.get_contents(target=[], source=[],
-                           env=Environment(foo = 'FFF', bar = 'BBB'))
-        assert c == "| |", c
+                           env=Environment(foo = 'FFF', bar = 'BBB',
+                                           baz = CmdGen))
+        assert c == "| | FFF BBB 1", c
 
         # We've discusssed using the real target and source names in a
         # CommandAction's signature contents.  This would have have the
@@ -848,7 +819,7 @@ class ListActionTestCase(unittest.TestCase):
         assert isinstance(a.list[0], SCons.Action.CommandAction)
         assert isinstance(a.list[1], SCons.Action.FunctionAction)
         assert isinstance(a.list[2], SCons.Action.ListAction)
-        assert a.list[2].list[0].cmd_list == [ 'y' ]
+        assert a.list[2].list[0].cmd_list == 'y'
 
     def test_get_actions(self):
         """Test the get_actions() method for ListActions
index 433fadf7ade7895004e6aace8adf5559c89ce703..2f1722780782247242d3fe2f6d4dd8ebd23b536e 100644 (file)
@@ -199,7 +199,7 @@ class BuilderTestCase(unittest.TestCase):
         Verify that we can retrieve the supplied action attribute.
         """
         builder = SCons.Builder.Builder(action="foo")
-        assert builder.action.cmd_list == ["foo"]
+        assert builder.action.cmd_list == "foo"
 
         def func():
             pass
index f0c2163fa0b88a496892abe70dc3d3208fcac294..f4bd637c77d59f9fea13e062bd0cf85e8f97f00c 100644 (file)
@@ -193,23 +193,23 @@ def copyFunc(dest, source, env):
     os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
     return 0
 
-def _concat(prefix, list, suffix, locals, globals, f=lambda x: x):
+def _concat(prefix, list, suffix, env, f=lambda x: x):
     """Creates a new list from 'list' by first interpolating each
     element in the list using 'locals' and 'globals' and then calling f
     on the list, and finally concatenating 'prefix' and 'suffix' onto
     each element of the list. A trailing space on 'prefix' or leading
     space on 'suffix' will cause them to be put into seperate list
     elements rather than being concatenated."""
-
+    
     if not list:
         return list
 
     if not SCons.Util.is_List(list):
         list = [list]
 
-    def subst(x, locals=locals, globals=globals):
+    def subst(x, env = env):
         if SCons.Util.is_String(x):
-            return SCons.Util.scons_subst(x, locals, globals)
+            return SCons.Util.scons_subst(x, env)
         else:
             return x
 
@@ -239,11 +239,12 @@ def _concat(prefix, list, suffix, locals, globals, f=lambda x: x):
 
     return ret
 
-def _stripixes(prefix, list, suffix, locals, globals, stripprefix, stripsuffix, c=_concat):
+def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=_concat):
     """This is a wrapper around _concat() that checks for the existence
     of prefixes or suffixes on list elements and strips them where it
     finds them.  This is used by tools (like the GNU linker) that need
     to turn something like 'libfoo.a' into '-lfoo'."""
+    
     def f(list, sp=stripprefix, ss=stripsuffix):
         ret = []
         for l in list:
@@ -253,7 +254,25 @@ def _stripixes(prefix, list, suffix, locals, globals, stripprefix, stripsuffix,
                 l = l[:-len(ss)]
             ret.append(l)
         return ret
-    return c(prefix, list, suffix, locals, globals, f)
+    return c(prefix, list, suffix, env, f)
+
+class NullCmdGenerator:
+    """This is a callable class that can be used in place of other
+    command generators if you don't want them to do anything.
+
+    The __call__ method for this class simply returns the thing
+    you instantiated it with.
+
+    Example usage:
+    env["DO_NOTHING"] = NullCmdGenerator
+    env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}"
+    """
+
+    def __init__(self, cmd):
+        self.cmd = cmd
+
+    def __call__(self, target, source, env):
+        return self.cmd
 
 ConstructionEnvironment = {
     'BUILDERS'   : { 'SharedLibrary'  : SharedLibrary,
@@ -270,8 +289,9 @@ ConstructionEnvironment = {
     'INSTALL'    : copyFunc,
     '_concat'     : _concat,
     '_stripixes'  : _stripixes,
-    '_LIBFLAGS'    : '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, locals(), globals())}',
-    '_LIBDIRFLAGS' : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, locals(), globals(), RDirs)} $)',
-    '_CPPINCFLAGS' : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, locals(), globals(), RDirs)} $)',
-    '_F77INCFLAGS' : '$( ${_concat(INCPREFIX, F77PATH, INCSUFFIX, locals(), globals(), RDirs)} $)'
+    '_LIBFLAGS'    : '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
+    '_LIBDIRFLAGS' : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs)} $)',
+    '_CPPINCFLAGS' : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs)} $)',
+    '_F77INCFLAGS' : '$( ${_concat(INCPREFIX, F77PATH, INCSUFFIX, __env__, RDirs)} $)',
+    'TEMPFILE'     : NullCmdGenerator
     }
index 8336f7451b279bbfd7f87952c8d4719c5cfcd6c0..37182c46b0e0a58b717b3ce5236d898f9a02d15e 100644 (file)
@@ -143,6 +143,32 @@ class BuilderDict(UserDict):
 
 _rm = re.compile(r'\$[()]')
 
+class _lister:
+    """This class is used to implement dummy targets and sources
+    for signature calculation."""
+    def __init__(self, fmt):
+        self.fmt = fmt
+    def _format(self, index):
+        # For some reason, I originally made the fake names of
+        # the targets and sources 1-based (['__t1__, '__t2__']),
+        # not 0-based.  We preserve this behavior by adding one
+        # to the returned item names, so everyone's targets
+        # won't get recompiled if they were using an old
+        # version.
+        return self.fmt % (index + 1)
+    def __getitem__(self, index):
+        return SCons.Util.PathList([self._format(index)])[0]
+    def __getslice__(self, i, j):
+        slice = []
+        for x in range(i, j):
+            slice.append(self._format(x))
+        return SCons.Util.PathList(slice)
+    def __getattr__(self, name):
+        # If we don't find an attribute in this class, let's
+        # look in PathList.  self[0:2] returns a PathList instance
+        # via __getslice__
+        return getattr(self[0:2], name)
+
 class Environment:
     """Base class for construction Environments.  These are
     the primary objects used to communicate dependency and
@@ -404,7 +430,7 @@ class Environment:
         else:
             return side_effects
 
-    def subst(self, string, raw=0):
+    def subst(self, string, raw=0, target=None, source=None):
        """Recursively interpolates construction variables from the
        Environment into the specified string, returning the expanded
        result.  Construction variables are specified by a $ prefix
@@ -418,16 +444,18 @@ class Environment:
             regex_remove = None
         else:
             regex_remove = _rm
-        return SCons.Util.scons_subst(string, self._dict, {}, regex_remove)
+        return SCons.Util.scons_subst(string, self, regex_remove,
+                                      target, source)
     
-    def subst_list(self, string, raw=0):
+    def subst_list(self, string, raw=0, target=None, source=None):
         """Calls through to SCons.Util.scons_subst_list().  See
         the documentation for that function."""
         if raw:
             regex_remove = None
         else:
             regex_remove = _rm
-        return SCons.Util.scons_subst_list(string, self._dict, {}, regex_remove)
+        return SCons.Util.scons_subst_list(string, self, regex_remove,
+                                           target, source)
 
     def get_scanner(self, skey):
         """Find the appropriate scanner given a key (usually a file suffix).
@@ -549,10 +577,9 @@ class Environment:
         This fills in static TARGET, TARGETS, SOURCE and SOURCES
         variables so that signatures stay the same every time.
         """
-        dict = {}
-        for k,v in self.items(): dict[k] = v
-        dict['TARGETS'] = SCons.Util.Lister('__t%d__')
+        dict = self._dict.copy()
+        dict['TARGETS'] = _lister('__t%d__')
         dict['TARGET'] = dict['TARGETS'][0]
-        dict['SOURCES'] = SCons.Util.Lister('__s%d__')
+        dict['SOURCES'] = _lister('__s%d__')
         dict['SOURCE'] = dict['SOURCES'][0]
         return dict
index e590d7d21e3f634da935e069593dc13dcbf07a59..9a6eb1cb51ac54160a8d373d01e685eb23d3567a 100644 (file)
@@ -477,7 +477,7 @@ class EnvironmentTestCase(unittest.TestCase):
                         action='buildfoo $target $source')
         assert not t.builder is None
         assert t.builder.action.__class__.__name__ == 'CommandAction'
-        assert t.builder.action.cmd_list == ['buildfoo', '$target', '$source']
+        assert t.builder.action.cmd_list == 'buildfoo $target $source'
         assert 'foo1.in' in map(lambda x: x.path, t.sources)
         assert 'foo2.in' in map(lambda x: x.path, t.sources)
 
@@ -540,6 +540,21 @@ class EnvironmentTestCase(unittest.TestCase):
         lst = env.subst_list([ "$AAA", "B $CCC" ])
         assert lst == [ [ "a", "b" ], [ "c", "B a", "b" ], [ "c" ] ], lst
 
+        # Test callables in the Environment
+        def foo(target, source, env):
+            assert target == 1, target
+            assert source == 2, source
+            return env["FOO"]
+
+        env = Environment(BAR=foo, FOO='baz')
+
+        subst = env.subst('test $BAR', target=1, source=2)
+        assert subst == 'test baz', subst
+
+        lst = env.subst_list('test $BAR', target=1, source=2)
+        assert lst[0][0] == 'test', lst[0][0]
+        assert lst[0][1] == 'baz', lst[0][1]
+
     def test_autogenerate(dict):
         """Test autogenerating variables in a dictionary."""
 
@@ -816,6 +831,9 @@ class EnvironmentTestCase(unittest.TestCase):
         assert s == '', s
         s = map(str, d['TARGETS'][3:5])
         assert s == ['__t4__', '__t5__'], s
+        s = map(lambda x: os.path.normcase(str(x)), d['TARGETS'].abspath)
+        assert s == map(os.path.normcase, [ os.path.join(os.getcwd(), '__t1__'),
+                                            os.path.join(os.getcwd(), '__t2__') ])
 
         s = str(d['SOURCE'])
         assert s == '__s1__', s
@@ -833,6 +851,9 @@ class EnvironmentTestCase(unittest.TestCase):
         assert s == '', s
         s = map(str, d['SOURCES'][3:5])
         assert s == ['__s4__', '__s5__'], s
+        s = map(lambda x: os.path.normcase(str(x)), d['SOURCES'].abspath)
+        assert s == map(os.path.normcase, [ os.path.join(os.getcwd(), '__s1__'),
+                                            os.path.join(os.getcwd(), '__s2__') ])
 
         
 if __name__ == "__main__":
index 6a14a7a904c4465d2f79b471b108e521c1ecfee4..870dd99686de9dec3ef04db6a20fbd6ebc7f4e5d 100644 (file)
@@ -38,29 +38,34 @@ import os.path
 import string
 import sys
 
-#
+class TempFileMunge:
+    """A callable class.  You can set an Environment variable to this,
+    then call it with a string argument, then it will perform temporary
+    file substitution on it.  This is used to circumvent the win32 long command
+    line limitation.
 
-def TempFileMunge(env, cmd_list, for_signature): 
-    """Given a list of command line arguments, see if it is too
-    long to pass to the win32 command line interpreter.  If so,
-    create a temp file, then pass "@tempfile" as the sole argument
-    to the supplied command (which is the first element of cmd_list).
-    Otherwise, just return [cmd_list]."""
-    cmd = env.subst_list(cmd_list)[0]
-    if for_signature or \
-       (reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= 2048:
-        return [cmd_list]
-    else:
-        import tempfile
-        # We do a normpath because mktemp() has what appears to be
-        # a bug in Win32 that will use a forward slash as a path
-        # delimiter.  Win32's link mistakes that for a command line
-        # switch and barfs.
-        tmp = os.path.normpath(tempfile.mktemp())
-        args = map(SCons.Util.quote_spaces, cmd[1:])
-        open(tmp, 'w').write(string.join(args, " ") + "\n")
-        return [ [cmd[0], '@' + tmp],
-                 ['del', tmp] ]
+    Example usage:
+    env["TEMPFILE"] = TempFileMunge
+    env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES')}"
+    """
+    def __init__(self, cmd):
+        self.cmd = cmd
+
+    def __call__(self, target, source, env):
+        cmd = env.subst_list(self.cmd, 0, target, source)[0]
+        if target is None or \
+           (reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= 2048:
+            return self.cmd
+        else:
+            import tempfile
+            # We do a normpath because mktemp() has what appears to be
+            # a bug in Win32 that will use a forward slash as a path
+            # delimiter.  Win32's link mistakes that for a command line
+            # switch and barfs.
+            tmp = os.path.normpath(tempfile.mktemp())
+            args = map(SCons.Util.quote_spaces, cmd[1:])
+            open(tmp, 'w').write(string.join(args, " ") + "\n")
+            return [ cmd[0], '@' + tmp + '\ndel', tmp ]
 
 # The upshot of all this is that, if you are using Python 1.5.2,
 # you had better have cmd or command.com in your PATH when you run
@@ -128,4 +133,5 @@ def generate(env):
     env['LIBSUFFIXES']    = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
     env['SPAWN']          = spawn
     env['SHELL']          = cmd_interp
+    env['TEMPFILE']       = TempFileMunge
     env['ESCAPE']         = escape
index db7ff4bbdd696f5e47405b15039ef3e051ccfd7f..9f782c1652f3b6768736421c7c709a2f154b5a36 100644 (file)
@@ -52,7 +52,7 @@ def generate(env, platform):
     env['LINKCOM']     = '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
     env['LIBDIRPREFIX']='-L'
     env['LIBDIRSUFFIX']=''
-    env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, locals(), globals(), LIBPREFIX, LIBSUFFIX)}'
+    env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIX, LIBSUFFIX, __env__)}'
     env['LIBLINKPREFIX']='-l'
     env['LIBLINKSUFFIX']=''
 
index 51ce60cc8efecaaaba8cd8081d243a59cbb4c80e..d55a4eeb52d2146159da628e52778a622ce81260 100644 (file)
@@ -43,7 +43,6 @@ import SCons.Defaults
 import SCons.Errors
 import SCons.Util
 
-from SCons.Platform.win32 import TempFileMunge
 from SCons.Tool.msvc import get_msdev_paths
 from SCons.Tool.PharLapCommon import addPharLapPaths
 
@@ -66,8 +65,8 @@ class LinklocGenerator:
     def __init__(self, cmdline):
         self.cmdline = cmdline
 
-    def __call__(self, env, target, source, for_signature):
-        if for_signature:
+    def __call__(self, env, target, source):
+        if target is None:
             # Expand the contents of any linker command files recursively
             subs = 1
             strsub = env.subst(self.cmdline)
@@ -75,23 +74,21 @@ class LinklocGenerator:
                 strsub, subs = _re_linker_command.subn(repl_linker_command, strsub)
             return strsub
         else:
-            return TempFileMunge(env, string.split(self.cmdline), 0)
-
-_linklocLinkAction = SCons.Action.Action(SCons.Action.CommandGenerator(LinklocGenerator("$LINK $LINKFLAGS $( $_LIBDIRFLAGS $) $_LIBFLAGS -exe $TARGET $SOURCES")))
-_linklocShLinkAction = SCons.Action.Action(SCons.Action.CommandGenerator(LinklocGenerator("$SHLINK $SHLINKFLAGS $( $_LIBDIRFLAGS $) $_LIBFLAGS -dll $TARGET $SOURCES")))
+            return "${TEMPFILE('" + self.cmdline + "')}"
 
 def generate(env, platform):
     """Add Builders and construction variables for ar to an Environment."""
     env['BUILDERS']['SharedLibrary'] = SCons.Defaults.SharedLibrary
     env['BUILDERS']['Program'] = SCons.Defaults.Program
 
+    env['SUBST_CMD_FILE'] = LinklocGenerator
     env['SHLINK']      = '$LINK'
     env['SHLINKFLAGS'] = '$LINKFLAGS'
-    env['SHLINKCOM']   = _linklocShLinkAction
+    env['SHLINKCOM']   = '${SUBST_CMD_FILE("$SHLINK $SHLINKFLAGS $( $_LIBDIRFLAGS $) $_LIBFLAGS -dll $TARGET $SOURCES")}'
     env['SHLIBEMITTER']= None
     env['LINK']        = "linkloc"
     env['LINKFLAGS']   = ''
-    env['LINKCOM']     = _linklocLinkAction
+    env['LINKCOM']     = '${SUBST_CMD_FILE("$LINK $LINKFLAGS $( $_LIBDIRFLAGS $) $_LIBFLAGS -exe $TARGET $SOURCES")}'
     env['LIBDIRPREFIX']='-libpath '
     env['LIBDIRSUFFIX']=''
     env['LIBLINKPREFIX']='-lib '
index e8667eefe778dbaea7f8f2fd352fd0ee5768850b..958218fd933d2cebbe048608d76b6896186eb886 100644 (file)
@@ -35,15 +35,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import SCons.Defaults
 
-from SCons.Platform.win32 import TempFileMunge
-
-def win32ArGenerator(env, target, source, for_signature, **kw):
-    args = [ '$AR', '$ARFLAGS', '/OUT:%s' % target[0]]
-    args.extend(map(SCons.Util.to_String, source))
-    return TempFileMunge(env, args, for_signature)
-
-ArAction = SCons.Action.CommandGenerator(win32ArGenerator)
-
 def generate(env, platform):
     """Add Builders and construction variables for lib to an Environment."""
     env['BUILDERS']['Library'] = SCons.Defaults.StaticLibrary
@@ -51,7 +42,7 @@ def generate(env, platform):
     
     env['AR']          = 'lib'
     env['ARFLAGS']     = '/nologo'
-    env['ARCOM']       = ArAction
+    env['ARCOM']       = "${TEMPFILE('$AR $ARFLAGS /OUT:$TARGET $SOURCES')}"
 
 def exists(env):
     return env.Detect('lib')
index 6ef154234675e1f90b8907e3092c7bc308f4c03d..fa174e40cb017b0d4f694ca7e0a0c6cebbe7b87f 100644 (file)
@@ -42,44 +42,42 @@ import SCons.Errors
 import SCons.Util
 import msvc
 
-from SCons.Platform.win32 import TempFileMunge
 from SCons.Tool.msvc import get_msdev_paths
-    
-def win32LinkGenerator(env, target, source, for_signature):
-    args = [ '$LINK', '$LINKFLAGS', '/OUT:%s' % target[0],
-             '$(', '$_LIBDIRFLAGS', '$)', '$_LIBFLAGS' ]
-    
-    if env.has_key('PDB') and env['PDB']:
-        args.extend(['/PDB:%s'%target[0].File(env['PDB']), '/DEBUG'])
 
-    args.extend(map(SCons.Util.to_String, source))
-    return TempFileMunge(env, args, for_signature)
+def pdbGenerator(env, target, source):
+    if target and env.has_key('PDB') and env['PDB']:
+        return ['/PDB:%s'%target[0].File(env['PDB']), '/DEBUG']
 
-def win32LibGenerator(target, source, env, for_signature):
-    listCmd = [ "$SHLINK", "$SHLINKFLAGS" ]
+def win32ShlinkTargets(target, source, env):
+    if target:
+        listCmd = []
+        dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
+        if dll: listCmd.append("/out:%s"%dll)
 
-    if env.has_key('PDB') and env['PDB']:
-        listCmd.extend(['/PDB:%s'%target[0].File(env['PDB']), '/DEBUG'])
-
-    dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
-    if dll: listCmd.append("/out:%s"%dll)
-
-    implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
-    if implib: listCmd.append("/implib:%s"%implib)
-    
-    listCmd.extend([ '$_LIBDIRFLAGS', '$_LIBFLAGS' ])
+        implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
+        if implib: listCmd.append("/implib:%s"%implib)
 
-    deffile = env.FindIxes(source, "WIN32DEFPREFIX", "WIN32DEFSUFFIX")
-    
-    for src in source:
-        if src == deffile:
-            # Treat this source as a .def file.
-            listCmd.append("/def:%s" % src)
-        else:
-            # Just treat it as a generic source file.
-            listCmd.append(str(src))
-
-    return TempFileMunge(env, listCmd, for_signature)
+        return listCmd
+    else:
+        # For signature calculation
+        return '/out:$TARGET'
+
+def win32ShlinkSources(target, source, env):
+    if target:
+        listCmd = []
+
+        deffile = env.FindIxes(source, "WIN32DEFPREFIX", "WIN32DEFSUFFIX")
+        for src in source:
+            if src == deffile:
+                # Treat this source as a .def file.
+                listCmd.append("/def:%s" % src)
+            else:
+                # Just treat it as a generic source file.
+                listCmd.append(str(src))
+        return listCmd
+    else:
+        # For signature calculation
+        return "$SOURCES"
 
 def win32LibEmitter(target, source, env):
     msvc.validate_vars(env)
@@ -120,9 +118,6 @@ def prog_emitter(target, source, env):
         
     return (target,source)
 
-ShLibAction = SCons.Action.CommandGenerator(win32LibGenerator)
-LinkAction = SCons.Action.CommandGenerator(win32LinkGenerator)
-
 def generate(env, platform):
     """Add Builders and construction variables for ar to an Environment."""
     env['BUILDERS']['SharedLibrary'] = SCons.Defaults.SharedLibrary
@@ -130,14 +125,14 @@ def generate(env, platform):
     
     env['SHLINK']      = '$LINK'
     env['SHLINKFLAGS'] = '$LINKFLAGS /dll'
-    env['SHLINKCOM']   = ShLibAction
+    env['_SHLINK_TARGETS'] = win32ShlinkTargets
+    env['_SHLINK_SOURCES'] = win32ShlinkSources
+    env['SHLINKCOM']   = '${TEMPFILE("$SHLINK $SHLINKFLAGS $_SHLINK_TARGETS $( $_LIBDIRFLAGS $) $_LIBFLAGS $_PDB $_SHLINK_SOURCES")}'
     env['SHLIBEMITTER']= win32LibEmitter
     env['LINK']        = 'link'
     env['LINKFLAGS']   = '/nologo'
-    if str(platform) == 'cygwin':
-        env['LINKCOM'] = '$LINK $LINKFLAGS /OUT:$TARGET $( $_LIBDIRFLAGS $) $_LIBFLAGS $SOURCES'
-    else:
-        env['LINKCOM'] = LinkAction
+    env['_PDB'] = pdbGenerator
+    env['LINKCOM'] = '${TEMPFILE("$LINK $LINKFLAGS /OUT:$TARGET $( $_LIBDIRFLAGS $) $_LIBFLAGS $_PDB $SOURCES")}'
     env['PROGEMITTER'] = prog_emitter
     env['LIBDIRPREFIX']='/LIBPATH:'
     env['LIBDIRSUFFIX']=''
index 6d04ec2e16415f7e6673b8360bd89d7f67721699..8304bb6cff3f10af6194ca3c4a27a7757b0901c4 100644 (file)
@@ -52,7 +52,7 @@ def generate(env, platform):
     env['LINKCOM']     = '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
     env['LIBDIRPREFIX']='-L'
     env['LIBDIRSUFFIX']=''
-    env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, locals(), globals(), LIBPREFIX, LIBSUFFIX)}'
+    env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIX, LIBSUFFIX, __env__)}'
     env['LIBLINKPREFIX']='-l'
     env['LIBLINKSUFFIX']=''
 
index b03ff0f9090df43c3cf4f0408933c8154d58a367..16ff38867d2b3de7b0bf6c1b28e0f4efd7d3d950 100644 (file)
@@ -194,50 +194,7 @@ class PathList(UserList.UserList):
         # suffix and basepath.
         return self.__class__([ UserList.UserList.__getitem__(self, item), ])
 
-class Lister(PathList):
-    """A special breed of fake list that not only supports the inherited
-    "path dissection" attributes of PathList, but also uses a supplied
-    format string to generate arbitrary (slices of) individually-named
-    elements on the fly.
-    """
-    def __init__(self, fmt):
-        self.fmt = fmt
-        PathList.__init__(self, [ self._element(0), self._element(1) ])
-    def __getitem__(self, index):
-        return PathList([self._element(index)])
-    def _element(self, index):
-        """Generate the index'th element in this list."""
-        # For some reason, I originally made the fake names of
-        # the targets and sources 1-based (['__t1__, '__t2__']),
-        # not 0-based.  We preserve this behavior by adding one
-        # to the returned item names, so everyone's targets
-        # won't get recompiled if they were using an old version.
-        return self.fmt % (index + 1)
-    def __iter__(self):
-        """Return an iterator object for Python 2.2."""
-        class Lister_iter:
-            def __init__(self, data):
-                self.data = data
-                self.index = 0
-            def __iter__(self):
-                return self
-            def next(self):
-                try:
-                    element = self.data[self.index]
-                except IndexError:
-                    raise StopIteration
-                self.index = self.index + 1
-                return element
-        return Lister_iter(self.data)
-    def __getslice__(self, i, j):
-        slice = []
-        if j == sys.maxint:
-            j = i + 2
-        for x in range(i, j):
-            slice.append(self._element(x))
-        return PathList(slice)
-
-_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[^}]*})$')
+_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$')
 
 def get_environment_var(varstr):
     """Given a string, first determine if it looks like a reference
@@ -272,7 +229,7 @@ def quote_spaces(arg):
 #               a command line.
 #
 # \0\2          signifies a division between multiple distinct
-#               commands
+#               commands, i.e., a newline
 #
 # \0\3          This string should be interpreted literally.
 #               This code occurring anywhere in the string means
@@ -282,7 +239,7 @@ def quote_spaces(arg):
 # \0\4          A literal dollar sign '$'
 #
 # \0\5          Placed before and after interpolated variables
-#               so that we do not accidentally smush to variables
+#               so that we do not accidentally smush two variables
 #               together during the recursive interpolation process.
 
 _cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})')
@@ -397,7 +354,49 @@ class DisplayEngine:
             self.__call__ = self.dont_print
 
 
-def scons_subst_list(strSubst, globals, locals, remove=None):
+def subst_dict(target, source, env):
+    """Create a dictionary for substitution of construction
+    variables.
+
+    This translates the following special arguments:
+
+    env    - the construction environment itself,
+             the values of which (CC, CCFLAGS, etc.)
+             are copied straight into the dictionary
+
+    target - the target (object or array of objects),
+             used to generate the TARGET and TARGETS
+             construction variables
+
+    source - the source (object or array of objects),
+             used to generate the SOURCES and SOURCE
+             construction variables
+    """
+
+    dict = env.Dictionary().copy()
+
+    if not is_List(target):
+        target = [target]
+
+    dict['TARGETS'] = PathList(map(os.path.normpath, map(str, target)))
+    if dict['TARGETS']:
+        dict['TARGET'] = dict['TARGETS'][0]
+
+    def rstr(x):
+        try:
+            return x.rstr()
+        except AttributeError:
+            return str(x)
+    if not is_List(source):
+        source = [source]
+    dict['SOURCES'] = PathList(map(os.path.normpath, map(rstr, source)))
+    if dict['SOURCES']:
+        dict['SOURCE'] = dict['SOURCES'][0]
+
+    return dict
+
+def scons_subst_list(strSubst, env, remove=None, target=None,
+                     source=None):
     """
     This function serves the same purpose as scons_subst(), except
     this function returns the interpolated list as a list of lines, where
@@ -409,7 +408,7 @@ def scons_subst_list(strSubst, globals, locals, remove=None):
     function.
 
     There are a few simple rules this function follows in order to
-    determine how to parse strSubst and consruction variables into lines
+    determine how to parse strSubst and construction variables into lines
     and arguments:
 
     1) A string is interpreted as a space delimited list of arguments.
@@ -422,33 +421,34 @@ def scons_subst_list(strSubst, globals, locals, remove=None):
        (e.g. file names) to contain embedded newline characters.
     """
 
-    def convert(x):
-        """This function is used to convert construction variable
-        values or the value of strSubst to a string for interpolation.
-        This function follows the rules outlined in the documentaion
-        for scons_subst_list()"""
-        if x is None:
-            return ''
-        elif is_String(x):
-            return _space_sep.sub('\0', x)
-        elif is_List(x):
-            try:
-                return x.to_String()
-            except AttributeError:
-                return string.join(map(to_String, x), '\0')
-        else:
-            return to_String(x)
-
-    def repl(m, globals=globals, locals=locals):
+    if target != None:
+        dict = subst_dict(target, source, env)
+    else:
+        dict = env.sig_dict()
+
+    def repl(m,
+             target=target,
+             source=source,
+             env=env,
+             local_vars = dict,
+             global_vars = { "__env__" : env }):
         key = m.group(1)
         if key[0] == '{':
             key = key[1:-1]
         try:
-            e = eval(key, globals, locals)
-            # The \0\5 escape code keeps us from smushing two or more
-            # variables together during recusrive substitution, i.e.
-            # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad)
-            return "\0\5" + _convert(e) + "\0\5"
+            e = eval(key, global_vars, local_vars)
+            if callable(e):
+                # We wait to evaluate callables until the end of everything
+                # else.  For now, we instert a special escape sequence
+                # that we will look for later.
+                return '\0\5' + _convert(e(target=target,
+                                           source=source,
+                                           env=env)) + '\0\5'
+            else:
+                # The \0\5 escape code keeps us from smushing two or more
+                # variables together during recusrive substitution, i.e.
+                # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad)
+                return "\0\5" + _convert(e) + "\0\5"
         except NameError:
             return '\0\5'
 
@@ -474,7 +474,8 @@ def scons_subst_list(strSubst, globals, locals, remove=None):
     return map(lambda x: map(CmdStringHolder, filter(lambda y:y, string.split(x, '\0\1'))),
                listLines)
 
-def scons_subst(strSubst, globals, locals, remove=None):
+def scons_subst(strSubst, env, remove=None, target=None,
+                source=None):
     """Recursively interpolates dictionary variables into
     the specified string, returning the expanded result.
     Variables are specified by a $ prefix in the string and
@@ -487,12 +488,24 @@ def scons_subst(strSubst, globals, locals, remove=None):
 
     # This function needs to be fast, so don't call scons_subst_list
 
-    def repl(m, globals=globals, locals=locals):
+    if target != None:
+        dict = subst_dict(target, source, env)
+    else:
+        dict = env.sig_dict()
+
+    def repl(m,
+             target=target,
+             source=source,
+             env=env,
+             local_vars = dict,
+             global_vars = { '__env__' : env }):
         key = m.group(1)
         if key[0] == '{':
             key = key[1:-1]
         try:
-            e = eval(key, globals, locals)
+            e = eval(key, global_vars, local_vars)
+            if callable(e):
+                e = e(target=target, source=source, env=env)
             if e is None:
                 s = ''
             elif is_List(e):
index f77265a71c1e7a84f051dc12d0f18a2ad6ba8ad2..603631cc5c1a08a35ebc6e13b01e9df9fb38910d 100644 (file)
@@ -43,22 +43,57 @@ class OutBuffer:
     def write(self, str):
         self.buffer = self.buffer + str
 
+class DummyEnv:
+    def __init__(self, dict={}):
+        self.dict = dict
+
+    def Dictionary(self, key = None):
+        if not key:
+            return self.dict
+        return self.dict[key]
+
+    def sig_dict(self):
+        dict = self.dict.copy()
+        dict["TARGETS"] = 'tsig'
+        dict["SOURCES"] = 'ssig'
+        return dict
+
+def CmdGen1(target, source, env):
+    # Nifty trick...since Environment references are interpolated,
+    # instantiate an instance of a callable class with this one,
+    # which will then get evaluated.
+    assert target == 't', target
+    assert source == 's', source
+    return "${CMDGEN2('foo')}"
+
+class CmdGen2:
+    def __init__(self, mystr):
+        self.mystr = mystr
+
+    def __call__(self, target, source, env):
+        assert target == 't', target
+        assert source == 's', source
+        return [ self.mystr, env.Dictionary('BAR') ]
 
 class UtilTestCase(unittest.TestCase):
-    def test_subst_PathList(self):
-        """Test the subst function with PathLists"""
+    def test_subst(self):
+        """Test the subst function"""
         loc = {}
-        loc['TARGETS'] = PathList(map(os.path.normpath, [ "./foo/bar.exe",
-                                                          "/bar/baz.obj",
-                                                          "../foo/baz.obj" ]))
-        loc['TARGET'] = loc['TARGETS'][0]
-        loc['SOURCES'] = PathList(map(os.path.normpath, [ "./foo/blah.cpp",
-                                                          "/bar/ack.cpp",
-                                                          "../foo/ack.c" ]))
-        loc['SOURCE'] = loc['SOURCES'][0]
+        target = [ "./foo/bar.exe",
+                   "/bar/baz.obj",
+                   "../foo/baz.obj" ]
+        source = [ "./foo/blah.cpp",
+                   "/bar/ack.cpp",
+                   "../foo/ack.c" ]
         loc['xxx'] = None
         loc['zero'] = 0
         loc['one'] = 1
+        loc['BAR'] = 'baz'
+
+        loc['CMDGEN1'] = CmdGen1
+        loc['CMDGEN2'] = CmdGen2
+
+        env = DummyEnv(loc)
 
         if os.sep == '/':
             def cvt(str):
@@ -67,151 +102,102 @@ class UtilTestCase(unittest.TestCase):
             def cvt(str):
                 return string.replace(str, '/', os.sep)
 
-        newcom = scons_subst("test $TARGETS $SOURCES", loc, {})
+        newcom = scons_subst("test $TARGETS $SOURCES", env,
+                             target=target, source=source)
         assert newcom == cvt("test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c")
 
-        newcom = scons_subst("test ${TARGETS[:]} ${SOURCES[0]}", loc, {})
+        newcom = scons_subst("test ${TARGETS[:]} ${SOURCES[0]}", env,
+                             target=target, source=source)
         assert newcom == cvt("test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp")
 
-        newcom = scons_subst("test ${TARGETS[1:]}v", loc, {})
+        newcom = scons_subst("test ${TARGETS[1:]}v", env,
+                             target=target, source=source)
         assert newcom == cvt("test /bar/baz.obj ../foo/baz.objv")
 
-        newcom = scons_subst("test $TARGET", loc, {})
+        newcom = scons_subst("test $TARGET", env,
+                             target=target, source=source)
         assert newcom == cvt("test foo/bar.exe")
 
-        newcom = scons_subst("test $TARGET$FOO[0]", loc, {})
+        newcom = scons_subst("test $TARGET$FOO[0]", env,
+                             target=target, source=source)
         assert newcom == cvt("test foo/bar.exe[0]")
 
-        newcom = scons_subst("test ${TARGET.file}", loc, {})
+        newcom = scons_subst("test ${TARGET.file}", env,
+                             target=target, source=source)
         assert newcom == cvt("test bar.exe")
 
-        newcom = scons_subst("test ${TARGET.filebase}", loc, {})
+        newcom = scons_subst("test ${TARGET.filebase}", env,
+                             target=target, source=source)
         assert newcom == cvt("test bar")
 
-        newcom = scons_subst("test ${TARGET.suffix}", loc, {})
+        newcom = scons_subst("test ${TARGET.suffix}", env,
+                             target=target, source=source)
         assert newcom == cvt("test .exe")
 
-        newcom = scons_subst("test ${TARGET.base}", loc, {})
+        newcom = scons_subst("test ${TARGET.base}", env,
+                             target=target, source=source)
         assert newcom == cvt("test foo/bar")
 
-        newcom = scons_subst("test ${TARGET.dir}", loc, {})
+        newcom = scons_subst("test ${TARGET.dir}", env,
+                             target=target, source=source)
         assert newcom == cvt("test foo")
 
-        cwd = SCons.Util.updrive(os.getcwd())
-
-        newcom = scons_subst("test ${TARGET.abspath}", loc, {})
-        assert newcom == cvt("test %s/foo/bar.exe" % (cwd)), newcom
+        newcom = scons_subst("test ${TARGET.abspath}", env,
+                             target=target, source=source)
+        assert newcom == cvt("test %s/foo/bar.exe"%SCons.Util.updrive(os.getcwd())), newcom
 
-        newcom = scons_subst("test ${SOURCES.abspath}", loc, {})
-        assert newcom == cvt("test %s/foo/blah.cpp %s %s/foo/ack.c"%(cwd,
+        newcom = scons_subst("test ${SOURCES.abspath}", env,
+                             target=target, source=source)
+        assert newcom == cvt("test %s/foo/blah.cpp %s %s/foo/ack.c"%(SCons.Util.updrive(os.getcwd()),
                                                                      SCons.Util.updrive(os.path.abspath(os.path.normpath("/bar/ack.cpp"))),
                                                                      SCons.Util.updrive(os.path.normpath(os.getcwd()+"/..")))), newcom
 
-        newcom = scons_subst("test ${SOURCE.abspath}", loc, {})
-        assert newcom == cvt("test %s/foo/blah.cpp" % (cwd)), newcom
+        newcom = scons_subst("test ${SOURCE.abspath}", env,
+                             target=target, source=source)
+        assert newcom == cvt("test %s/foo/blah.cpp"%SCons.Util.updrive(os.getcwd())), newcom
 
-        newcom = scons_subst("test $xxx", loc, {})
+        newcom = scons_subst("test $xxx", env)
         assert newcom == cvt("test"), newcom
 
-        newcom = scons_subst("test $($xxx$)", loc, {})
+        newcom = scons_subst("test $($xxx$)", env)
         assert newcom == cvt("test $($)"), newcom
 
-        newcom = scons_subst("test $( $xxx $)", loc, {})
+        newcom = scons_subst("test $( $xxx $)", env)
         assert newcom == cvt("test $( $)"), newcom
 
-        newcom = scons_subst("test $($xxx$)", loc, {}, re.compile('\$[()]'))
+        newcom = scons_subst("test $($xxx$)", env, re.compile('\$[()]'))
         assert newcom == cvt("test"), newcom
 
-        newcom = scons_subst("test $( $xxx $)", loc, {}, re.compile('\$[()]'))
+        newcom = scons_subst("test $( $xxx $)", env, re.compile('\$[()]'))
         assert newcom == cvt("test"), newcom
 
-        newcom = scons_subst("test $zero", loc, {})
+        newcom = scons_subst("test $zero", env)
         assert newcom == cvt("test 0"), newcom
 
-        newcom = scons_subst("test $one", loc, {})
+        newcom = scons_subst("test $one", env)
         assert newcom == cvt("test 1"), newcom
 
-        newcom = scons_subst("test aXbXcXd", loc, {}, re.compile('X'))
+        newcom = scons_subst("test aXbXcXd", env, re.compile('X'))
         assert newcom == cvt("test abcd"), newcom
 
-        glob = { 'a' : 1, 'b' : 2 }
-        loc = {'a' : 3, 'c' : 4 }
-        newcom = scons_subst("test $a $b $c $d test", glob, loc)
-        assert newcom == "test 3 2 4 test", newcom
+        newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS",
+                             env, target='t', source='s')
+        assert newcom == cvt("test foo baz s t"), newcom
 
         # Test against a former bug in scons_subst_list()
         glob = { "FOO" : "$BAR",
                  "BAR" : "BAZ",
                  "BLAT" : "XYX",
                  "BARXYX" : "BADNEWS" }
-        newcom = scons_subst("$FOO$BLAT", glob, {})
+        newcom = scons_subst("$FOO$BLAT", DummyEnv(glob))
         assert newcom == "BAZXYX", newcom
 
         # Test for double-dollar-sign behavior
         glob = { "FOO" : "BAR",
                  "BAZ" : "BLAT" }
-        newcom = scons_subst("$$FOO$BAZ", glob, {})
+        newcom = scons_subst("$$FOO$BAZ", DummyEnv(glob))
         assert newcom == "$FOOBLAT", newcom
 
-    def test_subst_Lister(self):
-        """Test the subst function with Listers"""
-        loc = {}
-        loc['TARGETS'] = Lister('t%d')
-        loc['TARGET'] = loc['TARGETS'][0]
-        loc['SOURCES'] = Lister('s%d')
-        loc['SOURCE'] = loc['SOURCES'][0]
-        loc['xxx'] = None
-        loc['zero'] = 0
-        loc['one'] = 1
-
-        if os.sep == '/':
-            def cvt(str):
-                return str
-        else:
-            def cvt(str):
-                return string.replace(str, '/', os.sep)
-
-        newcom = scons_subst("test $TARGETS $SOURCES", loc, {})
-        assert newcom == cvt("test t1 t2 s1 s2"), newcom
-
-        newcom = scons_subst("test ${TARGETS[:]} ${SOURCES[0]}", loc, {})
-        assert newcom == cvt("test t1 t2 s1"), newcom
-
-        newcom = scons_subst("test ${TARGETS[1:]}v", loc, {})
-        assert newcom == cvt("test t2 t3v"), newcom
-
-        newcom = scons_subst("test $TARGET", loc, {})
-        assert newcom == cvt("test t1"), newcom
-
-        newcom = scons_subst("test $TARGET$FOO[0]", loc, {})
-        assert newcom == cvt("test t1[0]"), newcom
-
-        newcom = scons_subst("test ${TARGET.file}", loc, {})
-        assert newcom == cvt("test t1"), newcom
-
-        newcom = scons_subst("test ${TARGET.filebase}", loc, {})
-        assert newcom == cvt("test t1"), newcom
-
-        newcom = scons_subst("test ${TARGET.suffix}", loc, {})
-        assert newcom == cvt("test"), newcom
-
-        newcom = scons_subst("test ${TARGET.base}", loc, {})
-        assert newcom == cvt("test t1"), newcom
-
-        newcom = scons_subst("test ${TARGET.dir}", loc, {})
-        assert newcom == cvt("test"), newcom
-
-        cwd = SCons.Util.updrive(os.getcwd())
-
-        newcom = scons_subst("test ${TARGET.abspath}", loc, {})
-        assert newcom == cvt("test %s/t1" % (cwd)), newcom
-
-        newcom = scons_subst("test ${SOURCES.abspath}", loc, {})
-        assert newcom == cvt("test %s/s1 %s/s2" % (cwd, cwd)), newcom
-
-        newcom = scons_subst("test ${SOURCE.abspath}", loc, {})
-        assert newcom == cvt("test %s/s1" % cwd), newcom
-
     def test_splitext(self):
         assert splitext('foo') == ('foo','')
         assert splitext('foo.bar') == ('foo','.bar')
@@ -229,13 +215,12 @@ class UtilTestCase(unittest.TestCase):
                 return 1
         
         loc = {}
-        loc['TARGETS'] = PathList(map(os.path.normpath, [ "./foo/bar.exe",
-                                                          "/bar/baz with spaces.obj",
-                                                          "../foo/baz.obj" ]))
-        loc['TARGET'] = loc['TARGETS'][0]
-        loc['SOURCES'] = PathList(map(os.path.normpath, [ "./foo/blah with spaces.cpp",
-                                                          "/bar/ack.cpp",
-                                                          "../foo/ack.c" ]))
+        target = [ "./foo/bar.exe",
+                   "/bar/baz with spaces.obj",
+                   "../foo/baz.obj" ]
+        source = [ "./foo/blah with spaces.cpp",
+                   "/bar/ack.cpp",
+                   "../foo/ack.c" ]
         loc['xxx'] = None
         loc['NEWLINE'] = 'before\nafter'
 
@@ -244,6 +229,11 @@ class UtilTestCase(unittest.TestCase):
         loc['BAR'] = Node('bar with spaces.out')
         loc['CRAZY'] = Node('crazy\nfile.in')
 
+        loc['CMDGEN1'] = CmdGen1
+        loc['CMDGEN2'] = CmdGen2
+
+        env = DummyEnv(loc)
+
         if os.sep == '/':
             def cvt(str):
                 return str
@@ -251,20 +241,26 @@ class UtilTestCase(unittest.TestCase):
             def cvt(str):
                 return string.replace(str, '/', os.sep)
 
-        cmd_list = scons_subst_list("$TARGETS", loc, {})
+        cmd_list = scons_subst_list("$TARGETS", env,
+                                    target=target,
+                                    source=source)
         assert cmd_list[0][1] == cvt("/bar/baz with spaces.obj"), cmd_list[0][1]
 
-        cmd_list = scons_subst_list("$SOURCES $NEWLINE $TARGETS", loc, {})
+        cmd_list = scons_subst_list("$SOURCES $NEWLINE $TARGETS", env,
+                                    target=target,
+                                    source=source)
         assert len(cmd_list) == 2, cmd_list
         assert cmd_list[0][0] == cvt('foo/blah with spaces.cpp'), cmd_list[0][0]
         assert cmd_list[1][2] == cvt("/bar/baz with spaces.obj"), cmd_list[1]
 
-        cmd_list = scons_subst_list("$SOURCES$NEWLINE", loc, {})
+        cmd_list = scons_subst_list("$SOURCES$NEWLINE", env,
+                                    target=target,
+                                    source=source)
         assert len(cmd_list) == 2, cmd_list
         assert cmd_list[1][0] == 'after', cmd_list[1][0]
         assert cmd_list[0][2] == cvt('../foo/ack.cbefore'), cmd_list[0][2]
 
-        cmd_list = scons_subst_list("$DO --in=$FOO --out=$BAR", loc, {})
+        cmd_list = scons_subst_list("$DO --in=$FOO --out=$BAR", env)
         assert len(cmd_list) == 1, cmd_list
         assert len(cmd_list[0]) == 3, cmd_list
         assert cmd_list[0][0] == 'do something', cmd_list[0][0]
@@ -272,7 +268,7 @@ class UtilTestCase(unittest.TestCase):
         assert cmd_list[0][2] == '--out=bar with spaces.out', cmd_list[0][2]
 
         # This test is now fixed, and works like it should.
-        cmd_list = scons_subst_list("$DO --in=$CRAZY --out=$BAR", loc, {})
+        cmd_list = scons_subst_list("$DO --in=$CRAZY --out=$BAR", env)
         assert len(cmd_list) == 1, map(str, cmd_list[0])
         assert len(cmd_list[0]) == 3, cmd_list
         assert cmd_list[0][0] == 'do something', cmd_list[0][0]
@@ -282,30 +278,37 @@ class UtilTestCase(unittest.TestCase):
         # Test inputting a list to scons_subst_list()
         cmd_list = scons_subst_list([ "$SOURCES$NEWLINE", "$TARGETS",
                                         "This is a test" ],
-                                    loc, {})
+                                    env,
+                                    target=target,
+                                    source=source)
         assert len(cmd_list) == 2, len(cmd_list)
         assert cmd_list[0][0] == cvt('foo/blah with spaces.cpp'), cmd_list[0][0]
         assert cmd_list[1][0] == cvt("after"), cmd_list[1]
         assert cmd_list[1][4] == "This is a test", cmd_list[1]
 
-        glob = { 'a' : 1, 'b' : 2 }
-        loc = {'a' : 3, 'c' : 4 }
-        cmd_list = scons_subst_list("test $a $b $c $d test", glob, loc)
-        assert len(cmd_list) == 1, cmd_list
-        assert map(str, cmd_list[0]) == ['test', '3', '2', '4', 'test'], map(str, cmd_list[0])
+        # Test interpolating a callable.
+        cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES", env,
+                                    target='t', source='s')
+        assert len(cmd_list) == 1, len(cmd_list)
+        assert cmd_list[0][0] == 'testing', cmd_list[0][0]
+        assert cmd_list[0][1] == 'foo', cmd_list[0][1]
+        assert cmd_list[0][2] == 'bar with spaces.out', cmd_list[0][2]
+        assert cmd_list[0][3] == 't', cmd_list[0][3]
+        assert cmd_list[0][4] == 's', cmd_list[0][4]
+
 
         # Test against a former bug in scons_subst_list()
         glob = { "FOO" : "$BAR",
                  "BAR" : "BAZ",
                  "BLAT" : "XYX",
                  "BARXYX" : "BADNEWS" }
-        cmd_list = scons_subst_list("$FOO$BLAT", glob, {})
+        cmd_list = scons_subst_list("$FOO$BLAT", DummyEnv(glob))
         assert cmd_list[0][0] == "BAZXYX", cmd_list[0][0]
 
         # Test for double-dollar-sign behavior
         glob = { "FOO" : "BAR",
                  "BAZ" : "BLAT" }
-        cmd_list = scons_subst_list("$$FOO$BAZ", glob, {})
+        cmd_list = scons_subst_list("$$FOO$BAZ", DummyEnv(glob))
         assert cmd_list[0][0] == "$FOOBLAT", cmd_list[0][0]
 
         # Now test escape functionality
@@ -315,7 +318,7 @@ class UtilTestCase(unittest.TestCase):
             return foo
         glob = { "FOO" : PathList([ 'foo\nwith\nnewlines',
                                     'bar\nwith\nnewlines' ]) }
-        cmd_list = scons_subst_list("$FOO", glob, {})
+        cmd_list = scons_subst_list("$FOO", DummyEnv(glob))
         assert cmd_list[0][0] == 'foo\nwith\nnewlines', cmd_list[0][0]
         cmd_list[0][0].escape(escape_func)
         assert cmd_list[0][0] == '**foo\nwith\nnewlines**', cmd_list[0][0]
@@ -519,9 +522,13 @@ class UtilTestCase(unittest.TestCase):
         """Testing get_environment_var()."""
         assert get_environment_var("$FOO") == "FOO", get_environment_var("$FOO")
         assert get_environment_var("${BAR}") == "BAR", get_environment_var("${BAR}")
+        assert get_environment_var("$FOO_BAR1234") == "FOO_BAR1234", get_environment_var("$FOO_BAR1234")
+        assert get_environment_var("${BAR_FOO1234}") == "BAR_FOO1234", get_environment_var("${BAR_FOO1234}")
         assert get_environment_var("${BAR}FOO") == None, get_environment_var("${BAR}FOO")
         assert get_environment_var("$BAR ") == None, get_environment_var("$BAR ")
         assert get_environment_var("FOO$BAR") == None, get_environment_var("FOO$BAR")
+        assert get_environment_var("$FOO[0]") == None, get_environment_var("$FOO[0]")
+        assert get_environment_var("${some('complex expression')}") == None, get_environment_var("${some('complex expression')}")
 
     def test_Proxy(self):
         """Test generic Proxy class."""
@@ -553,8 +560,8 @@ class UtilTestCase(unittest.TestCase):
         """Test the Literal() function."""
         cmd_list = [ '$FOO', Literal('$BAR') ]
         cmd_list = scons_subst_list(cmd_list,
-                                    { 'FOO' : 'BAZ',
-                                      'BAR' : 'BLAT' }, {})
+                                    DummyEnv({ 'FOO' : 'BAZ',
+                                               'BAR' : 'BLAT' }))
         def escape_func(cmd):
             return '**' + cmd + '**'
 
@@ -624,6 +631,49 @@ class UtilTestCase(unittest.TestCase):
         test._dirlist = None
         sys.stdout = old_stdout
 
+    def test_subst_dict(self):
+        """Test substituting dictionary values in an Action
+        """
+        d = subst_dict([], [], DummyEnv({'a' : 'A', 'b' : 'B'}))
+        assert d['a'] == 'A', d
+        assert d['b'] == 'B', d
+
+        d = subst_dict(target = 't', source = 's', env=DummyEnv())
+        assert str(d['TARGETS']) == 't', d['TARGETS']
+        assert str(d['TARGET']) == 't', d['TARGET']
+        assert str(d['SOURCES']) == 's', d['SOURCES']
+        assert str(d['SOURCE']) == 's', d['SOURCE']
+
+        d = subst_dict(target = ['t1', 't2'],
+                       source = ['s1', 's2'],
+                       env = DummyEnv())
+        TARGETS = map(lambda x: str(x), d['TARGETS'])
+        TARGETS.sort()
+        assert TARGETS == ['t1', 't2'], d['TARGETS']
+        assert str(d['TARGET']) == 't1', d['TARGET']
+        SOURCES = map(lambda x: str(x), d['SOURCES'])
+        SOURCES.sort()
+        assert SOURCES == ['s1', 's2'], d['SOURCES']
+        assert str(d['SOURCE']) == 's1', d['SOURCE']
+
+        class N:
+            def __init__(self, name):
+                self.name = name
+            def __str__(self):
+                return self.name
+            def rstr(self):
+                return 'rstr-' + self.name
+
+        d = subst_dict(target = [N('t3'), 't4'],
+                       source = ['s3', N('s4')],
+                       env = DummyEnv())
+        TARGETS = map(lambda x: str(x), d['TARGETS'])
+        TARGETS.sort()
+        assert TARGETS == ['t3', 't4'], d['TARGETS']
+        SOURCES = map(lambda x: str(x), d['SOURCES'])
+        SOURCES.sort()
+        assert SOURCES == ['rstr-s4', 's3'], d['SOURCES']
+
 
 if __name__ == "__main__":
     suite = unittest.makeSuite(UtilTestCase, 'test_')
index b9104384ba217b7ef43f3403905e205245fb016e..ffd379bec562100d291354457e648ec45870a293 100644 (file)
@@ -303,7 +303,7 @@ import re
 for k in fromdict.keys():
     if k != "ENV" and k != "SCANNERS" and k != "CFLAGS" and k != "CXXFLAGS" \
     and not SCons.Util.is_Dict(fromdict[k]):
-        todict[k] = SCons.Util.scons_subst(str(fromdict[k]), fromdict, {})
+        todict[k] = env.subst(str(fromdict[k]))
 todict["CFLAGS"] = fromdict["CPPFLAGS"] + " " + \
     string.join(map(lambda x: "-I" + x, env["CPPPATH"])) + " " + \
     string.join(map(lambda x: "-L" + x, env["LIBPATH"]))