Commits

Anonymous committed 717c16b

Support using construction variables as re-usable, callable command generators. (Charles Crain)

  • Participants
  • Parent commits 75c7c96

Comments (0)

Files changed (17)

 ${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 $(
     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

src/engine/SCons/Action.py

             # 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:
     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)
 
 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):
         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:
                         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.
         """
         #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.
         #                              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):

src/engine/SCons/ActionTests.py

         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
 
         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')"
         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
 
 
         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
         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
     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
     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
         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

src/engine/SCons/BuilderTests.py

         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

src/engine/SCons/Defaults.py

     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
 
 
     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:
                 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,
     '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
     }

src/engine/SCons/Environment.py

 
 _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
         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
             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).
         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

src/engine/SCons/EnvironmentTests.py

                         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)
 
         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."""
 
         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
         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__":

src/engine/SCons/Platform/win32.py

 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
     env['LIBSUFFIXES']    = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
     env['SPAWN']          = spawn
     env['SHELL']          = cmd_interp
+    env['TEMPFILE']       = TempFileMunge
     env['ESCAPE']         = escape

src/engine/SCons/Tool/gnulink.py

     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']=''
 

src/engine/SCons/Tool/linkloc.py

 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
 
     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)
                 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 '

src/engine/SCons/Tool/mslib.py

 
 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
     
     env['AR']          = 'lib'
     env['ARFLAGS']     = '/nologo'
-    env['ARCOM']       = ArAction
+    env['ARCOM']       = "${TEMPFILE('$AR $ARFLAGS /OUT:$TARGET $SOURCES')}"
 
 def exists(env):
     return env.Detect('lib')

src/engine/SCons/Tool/mslink.py

 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'])
+        implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
+        if implib: listCmd.append("/implib:%s"%implib)
 
-    dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
-    if dll: listCmd.append("/out:%s"%dll)
+        return listCmd
+    else:
+        # For signature calculation
+        return '/out:$TARGET'
 
-    implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
-    if implib: listCmd.append("/implib:%s"%implib)
-    
-    listCmd.extend([ '$_LIBDIRFLAGS', '$_LIBFLAGS' ])
+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 TempFileMunge(env, listCmd, for_signature)
+        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)
         
     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
     
     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']=''

src/engine/SCons/Tool/sgilink.py

     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']=''
 

src/engine/SCons/Util.py

         # 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
 #               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
 # \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*|{[^}]*})')
             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
     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.
        (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)
+    if target != None:
+        dict = subst_dict(target, source, env)
+    else:
+        dict = env.sig_dict()
 
-    def repl(m, globals=globals, locals=locals):
+    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'
 
     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
 
     # 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):

src/engine/SCons/UtilTests.py

     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):
             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}", env,
+                             target=target, source=source)
+        assert newcom == cvt("test %s/foo/bar.exe"%SCons.Util.updrive(os.getcwd())), newcom
 
-        newcom = scons_subst("test ${TARGET.abspath}", loc, {})
-        assert newcom == cvt("test %s/foo/bar.exe" % (cwd)), 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')
                 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'
 
         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
             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]
         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]
         # 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
             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]
         """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."""
         """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 + '**'
 
         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_')

test/scan-once.py

 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"]))