Enhance OverrideEnvironment, and rename the base class to an enhanced and maybe-even...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 29 Dec 2004 16:26:58 +0000 (16:26 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 29 Dec 2004 16:26:58 +0000 (16:26 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1195 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Executor.py
src/engine/SCons/Options/OptionsTests.py
src/engine/SCons/Scanner/Prog.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py

index 6db1e6af8321cef4c041fb0e0881aa81270eb0f3..83e4208246dc5eed6dd06fa6c02ea3b0357bc871 100644 (file)
@@ -415,7 +415,7 @@ class CommandAction(_ActionAction):
                 return result
         return 0
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         """Return the signature contents of this action's command line.
 
         This strips $(-$) and everything in between the string,
@@ -426,7 +426,7 @@ class CommandAction(_ActionAction):
             cmd = string.join(map(str, cmd))
         else:
             cmd = str(cmd)
-        return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source, dict)
+        return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source)
 
 class CommandGeneratorAction(ActionBase):
     """Class for command-generator actions."""
@@ -464,13 +464,13 @@ class CommandGeneratorAction(ActionBase):
         return act(target, source, env, errfunc, presub,
                    show, execute, chdir)
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         """Return the signature contents of this action's command line.
 
         This strips $(-$) and everything in between the string,
         since those parts don't affect signatures.
         """
-        return self._generate(target, source, env, 1).get_contents(target, source, env, dict=None)
+        return self._generate(target, source, env, 1).get_contents(target, source, env)
 
 # Ooh, polymorphism -- pretty scary, eh, kids?
 #
@@ -516,9 +516,9 @@ class LazyAction(CommandGeneratorAction, CommandAction):
         c = self.get_parent_class(env)
         return apply(c.__call__, args, kw)
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         c = self.get_parent_class(env)
-        return c.get_contents(self, target, source, env, dict)
+        return c.get_contents(self, target, source, env)
 
 class FunctionAction(_ActionAction):
     """Class for Python function actions."""
@@ -569,7 +569,7 @@ class FunctionAction(_ActionAction):
             raise SCons.Errors.BuildError(node=target, errstr=e.strerror)
         return result
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         """Return the signature contents of this callable action.
 
         By providing direct access to the code object of the
@@ -590,7 +590,7 @@ class FunctionAction(_ActionAction):
                     # This is weird, just do the best we can.
                     contents = str(self.execfunction)
                 else:
-                    contents = gc(target, source, env, dict)
+                    contents = gc(target, source, env)
         return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
                                                      self.varlist)))
 
@@ -618,14 +618,13 @@ class ListAction(ActionBase):
                                       a.presub_lines(env),
                                       self.list))
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         """Return the signature contents of this action list.
 
         Simple concatenation of the signatures of the elements.
         """
-        dict = SCons.Util.subst_dict(target, source)
-        return string.join(map(lambda x, t=target, s=source, e=env, d=dict:
-                                      x.get_contents(t, s, e, d),
+        return string.join(map(lambda x, t=target, s=source, e=env:
+                                      x.get_contents(t, s, e),
                                self.list),
                            "")
 
@@ -651,7 +650,7 @@ class ActionCaller:
         self.parent = parent
         self.args = args
         self.kw = kw
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         actfunc = self.parent.actfunc
         try:
             # "self.actfunc" is a function.
index 705daff2746aa702d5421bd63cd8f9ef75122186..4c1d521b194d6a090f1c79c267f961600aac7e97 100644 (file)
@@ -128,11 +128,11 @@ class Environment:
         for k, v in kw.items():
             self.d[k] = v
     # Just use the underlying scons_subst*() utility methods.
-    def subst(self, strSubst, raw=0, target=[], source=[], dict=None):
-        return SCons.Util.scons_subst(strSubst, self, raw, target, source, dict)
+    def subst(self, strSubst, raw=0, target=[], source=[]):
+        return SCons.Util.scons_subst(strSubst, self, raw, target, source, self.d)
     subst_target_source = subst
-    def subst_list(self, strSubst, raw=0, target=[], source=[], dict=None):
-        return SCons.Util.scons_subst_list(strSubst, self, raw, target, source, dict)
+    def subst_list(self, strSubst, raw=0, target=[], source=[]):
+        return SCons.Util.scons_subst_list(strSubst, self, raw, target, source, self.d)
     def __getitem__(self, item):
         return self.d[item]
     def __setitem__(self, item, value):
@@ -1088,7 +1088,7 @@ class CommandActionTestCase(unittest.TestCase):
         # Make sure that CommandActions use an Environment's
         # subst_target_source() method for substitution.
         class SpecialEnvironment(Environment):
-            def subst_target_source(self, strSubst, raw=0, target=[], source=[], dict=None):
+            def subst_target_source(self, strSubst, raw=0, target=[], source=[]):
                 return 'subst_target_source: ' + strSubst
 
         c = a.get_contents(target=DummyNode('ttt'), source = DummyNode('sss'),
@@ -1112,14 +1112,10 @@ class CommandActionTestCase(unittest.TestCase):
         a = SCons.Action.CommandAction(["$TARGET"])
         c = a.get_contents(target=t, source=s, env=env)
         assert c == "t1", c
-        c = a.get_contents(target=t, source=s, env=env, dict={})
-        assert c == "", c
 
         a = SCons.Action.CommandAction(["$TARGETS"])
         c = a.get_contents(target=t, source=s, env=env)
         assert c == "t1 t2 t3 t4 t5 t6", c
-        c = a.get_contents(target=t, source=s, env=env, dict={})
-        assert c == "", c
 
         a = SCons.Action.CommandAction(["${TARGETS[2]}"])
         c = a.get_contents(target=t, source=s, env=env)
@@ -1132,14 +1128,10 @@ class CommandActionTestCase(unittest.TestCase):
         a = SCons.Action.CommandAction(["$SOURCE"])
         c = a.get_contents(target=t, source=s, env=env)
         assert c == "s1", c
-        c = a.get_contents(target=t, source=s, env=env, dict={})
-        assert c == "", c
 
         a = SCons.Action.CommandAction(["$SOURCES"])
         c = a.get_contents(target=t, source=s, env=env)
         assert c == "s1 s2 s3 s4 s5 s6", c
-        c = a.get_contents(target=t, source=s, env=env, dict={})
-        assert c == "", c
 
         a = SCons.Action.CommandAction(["${SOURCES[2]}"])
         c = a.get_contents(target=t, source=s, env=env)
@@ -1253,8 +1245,6 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
         a = SCons.Action.CommandGeneratorAction(f)
         c = a.get_contents(target=[], source=[], env=env)
         assert c == "guux FFF BBB test", c
-        c = a.get_contents(target=[], source=[], env=env, dict={})
-        assert c == "guux FFF BBB test", c
 
 
 class FunctionActionTestCase(unittest.TestCase):
@@ -1381,8 +1371,6 @@ class FunctionActionTestCase(unittest.TestCase):
 
         c = a.get_contents(target=[], source=[], env=Environment())
         assert c in matches, repr(c)
-        c = a.get_contents(target=[], source=[], env=Environment(), dict={})
-        assert c in matches, repr(c)
 
         a = SCons.Action.FunctionAction(GlobalFunc, varlist=['XYZ'])
 
@@ -1394,7 +1382,7 @@ class FunctionActionTestCase(unittest.TestCase):
         assert c in matches_foo, repr(c)
 
         class Foo:
-            def get_contents(self, target, source, env, dict=None):
+            def get_contents(self, target, source, env):
                 return 'xyzzy'
         a = SCons.Action.FunctionAction(Foo())
         c = a.get_contents(target=[], source=[], env=Environment())
@@ -1481,9 +1469,6 @@ class ListActionTestCase(unittest.TestCase):
         c = a.get_contents(target=[], source=[], env=Environment(s = self))
         assert self.foo==1, self.foo
         assert c == "xyz", c
-        c = a.get_contents(target=[], source=[], env=Environment(s = self), dict={})
-        assert self.foo==1, self.foo
-        assert c == "xyz", c
 
 class LazyActionTestCase(unittest.TestCase):
     def test___init__(self):
@@ -1533,8 +1518,6 @@ class LazyActionTestCase(unittest.TestCase):
         env = Environment(FOO = [["This", "is", "a", "test"]])
         c = a.get_contents(target=[], source=[], env=env)
         assert c == "This is a test", c
-        c = a.get_contents(target=[], source=[], env=env, dict={})
-        assert c == "This is a test", c
 
 class ActionCallerTestCase(unittest.TestCase):
     def test___init__(self):
index 75b1ecadaea0e17708543cf0f36e7b1b7fa222de..4af4ced92355adb7a9d699dd72c953a0ef72b30e 100644 (file)
@@ -214,18 +214,91 @@ class BuilderDict(UserDict):
         for i, v in dict.items():
             self.__setitem__(i, v)
 
-class _Environment:
-    """Abstract base class for different flavors of construction
-    environment objects.
-
-    This collects common methods that need to be used by all types of
-    construction environment (proxies as well as "real" environments)
-    so that construction variable substitution and translation from
-    strings into Nodes happen in accordance with the object's other
-    rules--in other words, for the case of proxies, wherever we need to
-    do something like env.subst(), env.arg2nodes() and fetch attributes or
-    values like the wrapper proxy, not the underlying wrapped environment.
+class SubstitutionEnvironment:
+    """Base class for different flavors of construction environments.
+
+    This class contains a minimal set of methods that handle contruction
+    variable expansion and conversion of strings to Nodes, which may or
+    may not be actually useful as a stand-alone class.  Which methods
+    ended up in this class is pretty arbitrary right now.  They're
+    basically the ones which we've empirically determined are common to
+    the different construction environment subclasses, and most of the
+    others that use or touch the underlying dictionary of construction
+    variables.
+
+    Eventually, this class should contain all the methods that we
+    determine are necessary for a "minimal" interface to the build engine.
+    A full "native Python" SCons environment has gotten pretty heavyweight
+    with all of the methods and Tools and construction variables we've
+    jammed in there, so it would be nice to have a lighter weight
+    alternative for interfaces that don't need all of the bells and
+    whistles.  (At some point, we'll also probably rename this class
+    "Base," since that more reflects what we want this class to become,
+    but because we've released comments that tell people to subclass
+    Environment.Base to create their own flavors of construction
+    environment, we'll save that for a future refactoring when this
+    class actually becomes useful.)
     """
+    def __init__(self, **kw):
+        """Initialization of an underlying SubstitutionEnvironment class.
+        """
+        if __debug__: logInstanceCreation(self)
+        self.fs = SCons.Node.FS.default_fs
+        self.ans = SCons.Node.Alias.default_ans
+        self.lookup_list = SCons.Node.arg2nodes_lookups
+        self._dict = kw.copy()
+        self._dict['__env__'] = self
+
+    def __cmp__(self, other):
+        # Since an Environment now has an '__env__' construction variable
+        # that refers to itself, delete that variable to avoid infinite
+        # loops when comparing the underlying dictionaries in some Python
+        # versions (*cough* 1.5.2 *cough*)...
+        sdict = self._dict.copy()
+        del sdict['__env__']
+        odict = other._dict.copy()
+        del odict['__env__']
+        return cmp(sdict, odict)
+
+    def __delitem__(self, key):
+        del self._dict[key]
+
+    def __getitem__(self, key):
+        return self._dict[key]
+
+    def __setitem__(self, key, value):
+        if key in reserved_construction_var_names:
+            SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
+                                "Ignoring attempt to set reserved variable `%s'" % key)
+        elif key == 'BUILDERS':
+            try:
+                bd = self._dict[key]
+                for k in bd.keys():
+                    del bd[k]
+            except KeyError:
+                self._dict[key] = BuilderDict(kwbd, self)
+            self._dict[key].update(value)
+        elif key == 'SCANNERS':
+            self._dict[key] = value
+            self.scanner_map_delete()
+        else:
+            if not SCons.Util.is_valid_construction_var(key):
+                raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
+            self._dict[key] = value
+
+    def get(self, key, default=None):
+        "Emulates the get() method of dictionaries."""
+        return self._dict.get(key, default)
+
+    def has_key(self, key):
+        return self._dict.has_key(key)
+
+    def items(self):
+        "Emulates the items() method of dictionaries."""
+        result = self._dict.items()
+        result = filter(lambda t: t[0] != '__env__', result)
+        return result
+
     def arg2nodes(self, args, node_factory=_null, lookup_list=_null):
         if node_factory is _null:
             node_factory = self.fs.File
@@ -268,7 +341,13 @@ class _Environment:
     
         return nodes
 
-    def subst(self, string, raw=0, target=None, source=None, dict=None, conv=None):
+    def gvars(self):
+        return self._dict
+
+    def lvars(self):
+        return {}
+
+    def subst(self, string, raw=0, target=None, source=None, conv=None):
         """Recursively interpolates construction variables from the
         Environment into the specified string, returning the expanded
         result.  Construction variables are specified by a $ prefix
@@ -278,21 +357,25 @@ class _Environment:
         may be surrounded by curly braces to separate the name from
         trailing characters.
         """
-        return SCons.Util.scons_subst(string, self, raw, target, source, dict, conv)
+        gvars = self.gvars()
+        lvars = self.lvars()
+        return SCons.Util.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
 
-    def subst_kw(self, kw, raw=0, target=None, source=None, dict=None):
+    def subst_kw(self, kw, raw=0, target=None, source=None):
         nkw = {}
         for k, v in kw.items():
-            k = self.subst(k, raw, target, source, dict)
+            k = self.subst(k, raw, target, source)
             if SCons.Util.is_String(v):
-                v = self.subst(v, raw, target, source, dict)
+                v = self.subst(v, raw, target, source)
             nkw[k] = v
         return nkw
 
-    def subst_list(self, string, raw=0, target=None, source=None, dict=None, conv=None):
+    def subst_list(self, string, raw=0, target=None, source=None, conv=None):
         """Calls through to SCons.Util.scons_subst_list().  See
         the documentation for that function."""
-        return SCons.Util.scons_subst_list(string, self, raw, target, source, dict, conv)
+        gvars = self.gvars()
+        lvars = self.lvars()
+        return SCons.Util.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
 
     def subst_path(self, path):
         """Substitute a path list, turning EntryProxies into Nodes
@@ -335,7 +418,30 @@ class _Environment:
 
     subst_target_source = subst
 
-class Base(_Environment):
+    def Override(self, overrides):
+        """
+        Produce a modified environment whose variables are overriden by
+        the overrides dictionaries.  "overrides" is a dictionary that
+        will override the variables of this environment.
+
+        This function is much more efficient than Copy() or creating
+        a new Environment because it doesn't copy the construction
+        environment dictionary, it just wraps the underlying construction
+        environment, and doesn't even create a wrapper object if there
+        are no overrides.
+        """
+        if overrides:
+            o = copy_non_reserved_keywords(overrides)
+            overrides = {}
+            for key, value in o.items():
+                overrides[key] = SCons.Util.scons_subst_once(value, self, key)
+        if overrides:
+            env = OverrideEnvironment(self, overrides)
+            return env
+        else:
+            return self
+
+class Base(SubstitutionEnvironment):
     """Base class for "real" construction Environments.  These are the
     primary objects used to communicate dependency and construction
     information to the build engine.
@@ -365,6 +471,16 @@ class Base(_Environment):
                  toolpath=[],
                  options=None,
                  **kw):
+        """
+        Initialization of a basic SCons construction environment,
+        including setting up special construction variables like BUILDER,
+        PLATFORM, etc., and searching for and applying available Tools.
+
+        Note that we do *not* call the underlying base class
+        (SubsitutionEnvironment) initialization, because we need to
+        initialize things in a very specific order that doesn't work
+        with the much simpler base class initialization.
+        """
         if __debug__: logInstanceCreation(self)
         self.fs = SCons.Node.FS.default_fs
         self.ans = SCons.Node.Alias.default_ans
@@ -408,58 +524,9 @@ class Base(_Environment):
         if options:
             options.Update(self)
 
-    def __cmp__(self, other):
-        # Since an Environment now has an '__env__' construction variable
-        # that refers to itself, delete that variable to avoid infinite
-        # loops when comparing the underlying dictionaries in some Python
-        # versions (*cough* 1.5.2 *cough*)...
-        sdict = self._dict.copy()
-        del sdict['__env__']
-        odict = other._dict.copy()
-        del odict['__env__']
-        return cmp(sdict, odict)
-
-    def __getitem__(self, key):
-        return self._dict[key]
-
-    def __setitem__(self, key, value):
-        if key in reserved_construction_var_names:
-            SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
-                                "Ignoring attempt to set reserved variable `%s'" % key)
-        elif key == 'BUILDERS':
-            try:
-                bd = self._dict[key]
-                for k in bd.keys():
-                    del bd[k]
-            except KeyError:
-                self._dict[key] = BuilderDict(kwbd, self)
-            self._dict[key].update(value)
-        elif key == 'SCANNERS':
-            self._dict[key] = value
-            self.scanner_map_delete()
-        else:
-            if not SCons.Util.is_valid_construction_var(key):
-                raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
-            self._dict[key] = value
-
-    def __delitem__(self, key):
-        del self._dict[key]
-
-    def items(self):
-        "Emulates the items() method of dictionaries."""
-        return self._dict.items()
-
-    def has_key(self, key):
-        return self._dict.has_key(key)
-
-    def get(self, key, default=None):
-        "Emulates the get() method of dictionaries."""
-        return self._dict.get(key, default)
-
     #######################################################################
     # Utility methods that are primarily for internal use by SCons.
-    # These begin with lower-case letters.  Note that the subst() method
-    # is actually already out of the closet and used by people.
+    # These begin with lower-case letters.
     #######################################################################
 
     def get_calculator(self):
@@ -722,31 +789,6 @@ class Base(_Environment):
             if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix: 
                 return path
 
-    def Override(self, overrides):
-        """
-        Produce a modified environment whose variables
-        are overriden by the overrides dictionaries.
-
-        overrides - a dictionary that will override
-        the variables of this environment.
-
-        This function is much more efficient than Copy()
-        or creating a new Environment because it doesn't do
-        a deep copy of the dictionary, and doesn't do a copy
-        at all if there are no overrides.
-        """
-
-        if overrides:
-            o = copy_non_reserved_keywords(overrides)
-            overrides = {}
-            for key, value in o.items():
-                overrides[key] = SCons.Util.scons_subst_once(value, self, key)
-        if overrides:
-            env = OverrideEnvironment(self, overrides)
-            return env
-        else:
-            return self
-
     def ParseConfig(self, command, function=None):
         """
         Use the specified function to parse the output of the command
@@ -1340,21 +1382,25 @@ class Base(_Environment):
         """
         return SCons.Node.Python.Value(value)
 
-class OverrideEnvironment(_Environment):
-    """A proxy that overrides variables in a wrapped "real"
-    (Environment.Base) construction environment by returning values from
-    an overrides dictionary in preference to values from the underlying
-    subject environment.
+class OverrideEnvironment(SubstitutionEnvironment):
+    """A proxy that overrides variables in a wrapped construction
+    environment by returning values from an overrides dictionary in
+    preference to values from the underlying subject environment.
 
     This is a lightweight (I hope) proxy that passes through most use of
     attributes to the underlying Environment.Base class, but has just
     enough additional methods defined to act like a real construction
-    environment with overridden values.
-
-    Note that because we subclass _Environment, this class also has
-    inherited arg2nodes() and subst*() methods.  Those methods can't
+    environment with overridden values.  It can wrap either a Base
+    construction environment, or another OverrideEnvironment, which
+    can in turn nest arbitrary OverrideEnvironments...
+
+    Note that we do *not* call the underlying base class
+    (SubsitutionEnvironment) initialization, because we get most of those
+    from proxying the attributes of the subject construction environment.
+    But because we subclass SubstitutionEnvironment, this class also
+    has inherited arg2nodes() and subst*() methods; those methods can't
     be proxied because they need *this* object's methods to fetch the
-    overridden values.
+    values from the overrides dictionary.
     """
     def __init__(self, subject, overrides={}):
         if __debug__: logInstanceCreation(self, 'OverrideEnvironment')
@@ -1385,7 +1431,7 @@ class OverrideEnvironment(_Environment):
             pass
         return self.__dict__['__subject'].__delitem__(key)
     def get(self, key, default=None):
-        "Emulates the get() method of dictionaries."""
+        """Emulates the get() method of dictionaries."""
         try:
             return self.__dict__['overrides'][key]
         except KeyError:
@@ -1397,7 +1443,7 @@ class OverrideEnvironment(_Environment):
         except KeyError:
             return self.__dict__['__subject'].has_key(key)
     def items(self):
-        "Emulates the items() method of dictionaries."""
+        """Emulates the items() method of dictionaries."""
         return self.Dictionary().items()
 
     # Overridden private construction environment methods.
@@ -1407,20 +1453,15 @@ class OverrideEnvironment(_Environment):
         """
         self.__dict__['overrides'].update(dict)
 
+    def gvars(self):
+        return self.__dict__['__subject'].gvars()
+
+    def lvars(self):
+        lvars = self.__dict__['__subject'].lvars()
+        lvars.update(self.__dict__['overrides'])
+        return lvars
+
     # Overridden public construction environment methods.
-    def Dictionary(self, *args):
-       if not args:
-            result = self.__dict__['__subject'].Dictionary().copy()
-            result.update(self.__dict__['overrides'])
-           return result
-       dlist = map(lambda k, s=self: s.__getitem__(k), args)
-       if len(dlist) == 1:
-           dlist = dlist[0]
-       return dlist
-    def Override(self, overrides):
-        kw = copy_non_reserved_keywords(overrides)
-        self.__dict__['overrides'].update(our_deepcopy(kw))
-        return self
     def Replace(self, **kw):
         kw = copy_non_reserved_keywords(kw)
         self.__dict__['overrides'].update(our_deepcopy(kw))
index 71383e7a7707964cbffcf767562bc2acacc5d16e..92b89c0eb038956caa0f08e1abc0bdffc42b666c 100644 (file)
@@ -146,38 +146,74 @@ class DummyNode:
 
 
 
-class EnvironmentTestCase(unittest.TestCase):
+class SubstitutionTestCase(unittest.TestCase):
 
     def test___init__(self):
-        """Test construction Environment creation
+        """Test initializing a SubstitutionEnvironment
+        """
+        env = SubstitutionEnvironment()
+        assert env['__env__'] is env, env['__env__']
 
-        Create two with identical arguments and check that
-        they compare the same.
+    def test___cmp__(self):
+        """Test comparing SubstitutionEnvironments
         """
-        env1 = Environment(XXX = 'x', YYY = 'y')
-        env2 = Environment(XXX = 'x', YYY = 'y')
-        assert env1 == env2, diff_env(env1, env2)
 
-        assert env1['__env__'] is env1, env1['__env__']
-        assert env2['__env__'] is env2, env2['__env__']
+        env1 = SubstitutionEnvironment(XXX = 'x')
+        env2 = SubstitutionEnvironment(XXX = 'x')
+        env3 = SubstitutionEnvironment(XXX = 'xxx')
+        env4 = SubstitutionEnvironment(XXX = 'x', YYY = 'x')
+
+        assert env1 == env2
+        assert env1 != env3
+        assert env1 != env4
+
+    def test___delitem__(self):
+        """Test deleting a variable from a SubstitutionEnvironment
+        """
+        env1 = SubstitutionEnvironment(XXX = 'x', YYY = 'y')
+        env2 = SubstitutionEnvironment(XXX = 'x')
+        del env1['YYY']
+        assert env1 == env2
+
+    def test___getitem__(self):
+        """Test deleting a variable from a SubstitutionEnvironment
+        """
+        env = SubstitutionEnvironment(XXX = 'x')
+        assert env['XXX'] == 'x', env['XXX']
+
+    def test___setitem__(self):
+        """Test deleting a variable from a SubstitutionEnvironment
+        """
+        env1 = SubstitutionEnvironment(XXX = 'x')
+        env2 = SubstitutionEnvironment(XXX = 'x', YYY = 'y')
+        env1['YYY'] = 'y'
+        assert env1 == env2
 
     def test_get(self):
-        """Test the get() method."""
-        env = Environment(aaa = 'AAA')
+        """Test the SubstitutionEnvironment get() method
+        """
+        env = SubstitutionEnvironment(XXX = 'x')
+        assert env.get('XXX') == 'x', env.get('XXX')
+        assert env.get('YYY') is None, env.get('YYY')
 
-        x = env.get('aaa')
-        assert x == 'AAA', x
-        x = env.get('aaa', 'XXX')
-        assert x == 'AAA', x
-        x = env.get('bbb')
-        assert x is None, x
-        x = env.get('bbb', 'XXX')
-        assert x == 'XXX', x
+    def test_has_key(self):
+        """Test the SubstitutionEnvironment has_key() method
+        """
+        env = SubstitutionEnvironment(XXX = 'x')
+        assert env.has_key('XXX')
+        assert not env.has_key('YYY')
+
+    def test_items(self):
+        """Test the SubstitutionEnvironment items() method
+        """
+        env = SubstitutionEnvironment(XXX = 'x', YYY = 'y')
+        items = env.items()
+        assert items == [('XXX','x'), ('YYY','y')], items
 
     def test_arg2nodes(self):
         """Test the arg2nodes method
         """
-        env = Environment()
+        env = SubstitutionEnvironment()
         dict = {}
         class X(SCons.Node.Node):
             pass
@@ -245,7 +281,7 @@ class EnvironmentTestCase(unittest.TestCase):
             else:
                 return None
 
-        env_ll = env.Copy()
+        env_ll = SubstitutionEnvironment()
         env_ll.lookup_list = [lookup_a, lookup_b]
 
         nodes = env_ll.arg2nodes(['aaa', 'bbb', 'ccc'], Factory)
@@ -295,23 +331,35 @@ class EnvironmentTestCase(unittest.TestCase):
         assert not hasattr(nodes[1], 'bbbb'), nodes[0]
         assert nodes[1].c == 1, nodes[1]
 
+    def test_gvars(self):
+        """Test the base class gvars() method"""
+        env = SubstitutionEnvironment()
+        gvars = env.gvars()
+        assert gvars == {'__env__' : env}, gvars
+
+    def test_lvars(self):
+        """Test the base class lvars() method"""
+        env = SubstitutionEnvironment()
+        lvars = env.lvars()
+        assert lvars == {}, lvars
+
     def test_subst(self):
         """Test substituting construction variables within strings
 
         Check various combinations, including recursive expansion
         of variables into other variables.
         """
-        env = Environment(AAA = 'a', BBB = 'b')
+        env = SubstitutionEnvironment(AAA = 'a', BBB = 'b')
         mystr = env.subst("$AAA ${AAA}A $BBBB $BBB")
         assert mystr == "a aA b", mystr
 
         # Changed the tests below to reflect a bug fix in
         # subst()
-        env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
         mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
         assert mystr == "b bA bB b", mystr
 
-        env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
         mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
         assert mystr == "c cA cB c", mystr
 
@@ -320,28 +368,26 @@ class EnvironmentTestCase(unittest.TestCase):
         s1 = DummyNode('s1')
         s2 = DummyNode('s2')
 
-        env = Environment(AAA = 'aaa')
+        env = SubstitutionEnvironment(AAA = 'aaa')
         s = env.subst('$AAA $TARGET $SOURCES', target=[t1, t2], source=[s1, s2])
         assert s == "aaa t1 s1 s2", s
         s = env.subst('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2])
         assert s == "aaa t1 t2 s1", s
-        s = env.subst('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2], dict={})
-        assert s == "aaa", s
 
-        # Test callables in the Environment
+        # Test callables in the SubstitutionEnvironment
         def foo(target, source, env, for_signature):
             assert str(target) == 't', target
             assert str(source) == 's', source
             return env["FOO"]
 
-        env = Environment(BAR=foo, FOO='baz')
+        env = SubstitutionEnvironment(BAR=foo, FOO='baz')
         t = DummyNode('t')
         s = DummyNode('s')
 
         subst = env.subst('test $BAR', target=t, source=s)
         assert subst == 'test baz', subst
 
-        # Test not calling callables in the Environment
+        # Test not calling callables in the SubstitutionEnvironment
         if 0:
             # This will take some serious surgery to subst() and
             # subst_list(), so just leave these tests out until we can
@@ -349,7 +395,7 @@ class EnvironmentTestCase(unittest.TestCase):
             def bar(arg):
                 pass
 
-            env = Environment(BAR=bar, FOO='$BAR')
+            env = SubstitutionEnvironment(BAR=bar, FOO='$BAR')
 
             subst = env.subst('$BAR', call=None)
             assert subst is bar, subst
@@ -358,8 +404,8 @@ class EnvironmentTestCase(unittest.TestCase):
             assert subst is bar, subst
 
     def test_subst_kw(self):
-       """Test substituting construction variables within dictionaries"""
-       env = Environment(AAA = 'a', BBB = 'b')
+        """Test substituting construction variables within dictionaries"""
+        env = SubstitutionEnvironment(AAA = 'a', BBB = 'b')
         kw = env.subst_kw({'$AAA' : 'aaa', 'bbb' : '$BBB'})
         assert len(kw) == 2, kw
         assert kw['a'] == 'aaa', kw['a']
@@ -368,21 +414,21 @@ class EnvironmentTestCase(unittest.TestCase):
     def test_subst_list(self):
         """Test substituting construction variables in command lists
         """
-        env = Environment(AAA = 'a', BBB = 'b')
+        env = SubstitutionEnvironment(AAA = 'a', BBB = 'b')
         l = env.subst_list("$AAA ${AAA}A $BBBB $BBB")
         assert l == [["a", "aA", "b"]], l
 
         # Changed the tests below to reflect a bug fix in
         # subst()
-        env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
         l = env.subst_list("$AAA ${AAA}A ${AAA}B $BBB")
         assert l == [["b", "bA", "bB", "b"]], l
 
-        env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
         l = env.subst_list("$AAA ${AAA}A ${AAA}B $BBB")
         assert l == [["c", "cA", "cB", "c"]], mystr
 
-        env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ])
+        env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ])
         lst = env.subst_list([ "$AAA", "B $CCC" ])
         assert lst == [[ "a", "b"], ["c", "B a", "b"], ["c"]], lst
 
@@ -391,28 +437,26 @@ class EnvironmentTestCase(unittest.TestCase):
         s1 = DummyNode('s1')
         s2 = DummyNode('s2')
 
-        env = Environment(AAA = 'aaa')
+        env = SubstitutionEnvironment(AAA = 'aaa')
         s = env.subst_list('$AAA $TARGET $SOURCES', target=[t1, t2], source=[s1, s2])
         assert s == [["aaa", "t1", "s1", "s2"]], s
         s = env.subst_list('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2])
         assert s == [["aaa", "t1", "t2", "s1"]], s
-        s = env.subst_list('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2], dict={})
-        assert s == [["aaa"]], s
 
-        # Test callables in the Environment
+        # Test callables in the SubstitutionEnvironment
         def foo(target, source, env, for_signature):
             assert str(target) == 't', target
             assert str(source) == 's', source
             return env["FOO"]
 
-        env = Environment(BAR=foo, FOO='baz')
+        env = SubstitutionEnvironment(BAR=foo, FOO='baz')
         t = DummyNode('t')
         s = DummyNode('s')
 
         lst = env.subst_list('test $BAR', target=t, source=s)
         assert lst == [['test', 'baz']], lst
 
-        # Test not calling callables in the Environment
+        # Test not calling callables in the SubstitutionEnvironment
         if 0:
             # This will take some serious surgery to subst() and
             # subst_list(), so just leave these tests out until we can
@@ -420,7 +464,7 @@ class EnvironmentTestCase(unittest.TestCase):
             def bar(arg):
                 pass
 
-            env = Environment(BAR=bar, FOO='$BAR')
+            env = SubstitutionEnvironment(BAR=bar, FOO='$BAR')
 
             subst = env.subst_list('$BAR', call=None)
             assert subst is bar, subst
@@ -440,7 +484,7 @@ class EnvironmentTestCase(unittest.TestCase):
         class MyObj:
             pass
 
-        env = Environment(FOO='foo', BAR='bar', PROXY=MyProxy('my1'))
+        env = SubstitutionEnvironment(FOO='foo', BAR='bar', PROXY=MyProxy('my1'))
 
         r = env.subst_path('$FOO')
         assert r == ['foo'], r
@@ -459,7 +503,7 @@ class EnvironmentTestCase(unittest.TestCase):
             def __str__(self):
                 return self.s
 
-        env = Environment(FOO=StringableObj("foo"),
+        env = SubstitutionEnvironment(FOO=StringableObj("foo"),
                           BAR=StringableObj("bar"))
 
         r = env.subst_path([ "${FOO}/bar", "${BAR}/baz" ])
@@ -471,6 +515,70 @@ class EnvironmentTestCase(unittest.TestCase):
         r = env.subst_path([ "bar/${FOO}/bar", "baz/${BAR}/baz" ])
         assert r == [ "bar/foo/bar", "baz/bar/baz" ]
 
+    def test_subst_target_source(self):
+        """Test the base environment subst_target_source() method"""
+        env = SubstitutionEnvironment(AAA = 'a', BBB = 'b')
+        mystr = env.subst_target_source("$AAA ${AAA}A $BBBB $BBB")
+        assert mystr == "a aA b", mystr
+
+    def test_Override(self):
+        "Test overriding construction variables"
+        env = SubstitutionEnvironment(ONE=1, TWO=2, THREE=3, FOUR=4)
+        assert env['ONE'] == 1, env['ONE']
+        assert env['TWO'] == 2, env['TWO']
+        assert env['THREE'] == 3, env['THREE']
+        assert env['FOUR'] == 4, env['FOUR']
+
+        env2 = env.Override({'TWO'   : '10',
+                             'THREE' :'x $THREE y',
+                             'FOUR'  : ['x', '$FOUR', 'y']})
+        assert env2['ONE'] == 1, env2['ONE']
+        assert env2['TWO'] == '10', env2['TWO']
+        assert env2['THREE'] == 'x 3 y', env2['THREE']
+        assert env2['FOUR'] == ['x', 4, 'y'], env2['FOUR']
+
+        assert env['ONE'] == 1, env['ONE']
+        assert env['TWO'] == 2, env['TWO']
+        assert env['THREE'] == 3, env['THREE']
+        assert env['FOUR'] == 4, env['FOUR']
+
+        env2.Replace(ONE = "won")
+        assert env2['ONE'] == "won", env2['ONE']
+        assert env['ONE'] == 1, env['ONE']
+
+        assert env['__env__'] is env, env['__env__']
+        assert env2['__env__'] is env2, env2['__env__']
+
+
+
+class BaseTestCase(unittest.TestCase):
+
+    def test___init__(self):
+        """Test construction Environment creation
+
+        Create two with identical arguments and check that
+        they compare the same.
+        """
+        env1 = Environment(XXX = 'x', YYY = 'y')
+        env2 = Environment(XXX = 'x', YYY = 'y')
+        assert env1 == env2, diff_env(env1, env2)
+
+        assert env1['__env__'] is env1, env1['__env__']
+        assert env2['__env__'] is env2, env2['__env__']
+
+    def test_get(self):
+        """Test the get() method."""
+        env = Environment(aaa = 'AAA')
+
+        x = env.get('aaa')
+        assert x == 'AAA', x
+        x = env.get('aaa', 'XXX')
+        assert x == 'AAA', x
+        x = env.get('bbb')
+        assert x is None, x
+        x = env.get('bbb', 'XXX')
+        assert x == 'XXX', x
+
     def test_Builder_calls(self):
         """Test Builder calls through different environments
         """
@@ -916,6 +1024,14 @@ def exists(env):
         x = s("${_concat(PRE, LIST, SUF, __env__)}")
         assert x == 'preasuf prebsuf', x
 
+    def test_gvars(self):
+        """Test the Environment gvars() method"""
+        env = Environment(XXX = 'x', YYY = 'y', ZZZ = 'z')
+        gvars = env.gvars()
+        assert gvars['XXX'] == 'x', gvars['XXX']
+        assert gvars['YYY'] == 'y', gvars['YYY']
+        assert gvars['ZZZ'] == 'z', gvars['ZZZ']
+
     def test__update(self):
         """Test the _update() method"""
         env = Environment(X = 'x', Y = 'y', Z = 'z')
@@ -1307,34 +1423,6 @@ def exists(env):
         assert None == env.FindIxes(paths, 'SHLIBPREFIX', 'SHLIBSUFFIX')
         assert paths[1] == env.FindIxes(paths, 'PREFIX', 'SUFFIX')
 
-    def test_Override(self):
-        "Test overriding construction variables"
-        env = Environment(ONE=1, TWO=2, THREE=3, FOUR=4)
-        assert env['ONE'] == 1, env['ONE']
-        assert env['TWO'] == 2, env['TWO']
-        assert env['THREE'] == 3, env['THREE']
-        assert env['FOUR'] == 4, env['FOUR']
-
-        env2 = env.Override({'TWO'   : '10',
-                             'THREE' :'x $THREE y',
-                             'FOUR'  : ['x', '$FOUR', 'y']})
-        assert env2['ONE'] == 1, env2['ONE']
-        assert env2['TWO'] == '10', env2['TWO']
-        assert env2['THREE'] == 'x 3 y', env2['THREE']
-        assert env2['FOUR'] == ['x', 4, 'y'], env2['FOUR']
-
-        assert env['ONE'] == 1, env['ONE']
-        assert env['TWO'] == 2, env['TWO']
-        assert env['THREE'] == 3, env['THREE']
-        assert env['FOUR'] == 4, env['FOUR']
-
-        env2.Replace(ONE = "won")
-        assert env2['ONE'] == "won", env2['ONE']
-        assert env['ONE'] == 1, env['ONE']
-
-        assert env['__env__'] is env, env['__env__']
-        assert env2['__env__'] is env2, env2['__env__']
-
     def test_ParseConfig(self):
         """Test the ParseConfig() method"""
         env = Environment(ASFLAGS='assembler',
@@ -2634,6 +2722,110 @@ f5: \
 
 
 
+class OverrideEnvironmentTestCase(unittest.TestCase):
+
+    def test___init__(self):
+        """Test OverrideEnvironment initialization"""
+        env = Environment(XXX = 'x', YYY = 'y')
+        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
+        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3'})
+        assert env['XXX'] == 'x', env['XXX']
+        assert env2['XXX'] == 'x2', env2['XXX']
+        assert env3['XXX'] == 'x3', env3['XXX']
+        assert env['YYY'] == 'y', env['YYY']
+        assert env2['YYY'] == 'y', env2['YYY']
+        assert env3['YYY'] == 'y3', env3['YYY']
+
+    def test_get(self):
+        """Test the OverrideEnvironment get() method"""
+        env = Environment(XXX = 'x', YYY = 'y')
+        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
+        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'})
+        assert env.get('XXX') == 'x', env.get('XXX')
+        assert env2.get('XXX') == 'x2', env2.get('XXX')
+        assert env3.get('XXX') == 'x3', env3.get('XXX')
+        assert env.get('YYY') == 'y', env.get('YYY')
+        assert env2.get('YYY') == 'y', env2.get('YYY')
+        assert env3.get('YYY') == 'y3', env3.get('YYY')
+        assert env.get('ZZZ') == None, env.get('ZZZ')
+        assert env2.get('ZZZ') == None, env2.get('ZZZ')
+        assert env3.get('ZZZ') == 'z3', env3.get('ZZZ')
+
+    def test_has_key(self):
+        """Test the OverrideEnvironment has_key() method"""
+        env = Environment(XXX = 'x', YYY = 'y')
+        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
+        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'})
+        assert env.has_key('XXX'), env.has_key('XXX')
+        assert env2.has_key('XXX'), env2.has_key('XXX')
+        assert env3.has_key('XXX'), env3.has_key('XXX')
+        assert env.has_key('YYY'), env.has_key('YYY')
+        assert env2.has_key('YYY'), env2.has_key('YYY')
+        assert env3.has_key('YYY'), env3.has_key('YYY')
+        assert not env.has_key('ZZZ'), env.has_key('ZZZ')
+        assert not env2.has_key('ZZZ'), env2.has_key('ZZZ')
+        assert env3.has_key('ZZZ'), env3.has_key('ZZZ')
+
+    def test_items(self):
+        """Test the OverrideEnvironment items() method"""
+        env = Environment(WWW = 'w', XXX = 'x', YYY = 'y')
+        env2 = OverrideEnvironment(env, {'XXX' : 'x2'})
+        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'})
+        items = env.items()
+        assert items == {'WWW' : 'w', 'XXX' : 'x', 'YYY' : 'y'}, items
+        items = env2.items()
+        assert items == {'WWW' : 'w', 'XXX' : 'x2', 'YYY' : 'y'}, items
+        items = env3.items()
+        assert items == {'WWW' : 'w', 'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'}, items
+
+    def test_gvars(self):
+        """Test the OverrideEnvironment gvars() method"""
+        env = Environment(XXX = 'x', YYY = 'y')
+        env2 = OverrideEnvironment(env, {'xxx' : 'x2'})
+        env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3'})
+        gvars = env.gvars()
+        assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars
+        gvars = env2.gvars()
+        assert gvars == {'XXX' : 'x2', 'YYY' : 'y'}, gvars
+        gvars = env3.gvars()
+        assert gvars == {'XXX' : 'x3', 'YYY' : 'y3'}, gvars
+
+    def test_lvars(self):
+        """Test the OverrideEnvironment lvars() method"""
+        env = Environment(XXX = 'x', YYY = 'y')
+        env2 = OverrideEnvironment(env, {'xxx' : 'x2'})
+        env3 = OverrideEnvironment(env2, {'xxx' : 'x3', 'YYY' : 'y3'})
+        lvars = env.lvars()
+        assert lvars == {}, lvars
+        lvars = env2.lvars()
+        assert lvars == {'XXX' : 'x2', 'YYY' : 'y'}, lvars
+        lvars = env3.lvars()
+        assert lvars == {'XXX' : 'x3', 'YYY' : 'y3'}, lvars
+
+    def test_Replace(self):
+        """Test the OverrideEnvironment Replace() method"""
+        env = Environment(XXX = 'x', YYY = 'y')
+        env2 = OverrideEnvironment(env, {'xxx' : 'x2'})
+        env3 = OverrideEnvironment(env2, {'xxx' : 'x3', 'YYY' : 'y3'})
+        assert env['XXX'] == 'x', env['XXX']
+        assert env2['XXX'] == 'x2', env2['XXX']
+        assert env3['XXX'] == 'x3', env3['XXX']
+        assert env['YYY'] == 'y', env['YYY']
+        assert env2['YYY'] == 'y', env2['YYY']
+        assert env3['YYY'] == 'y3', env3['YYY']
+
+        env.Replace(YYY = 'y4')
+
+        assert env['XXX'] == 'x', env['XXX']
+        assert env2['XXX'] == 'x2', env2['XXX']
+        assert env3['XXX'] == 'x3', env3['XXX']
+        assert env['YYY'] == 'y4', env['YYY']
+        assert env2['YYY'] == 'y4', env2['YYY']
+        assert env3['YYY'] == 'y3', env3['YYY']
+
+
+
+
 class NoSubstitutionProxyTestCase(unittest.TestCase):
 
     def test___init__(self):
@@ -2680,7 +2872,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
         assert x == '$XXX', x
 
         x = proxy.subst('$YYY', raw=7, target=None, source=None,
-                        dict=None, conv=None,
+                        conv=None,
                         extra_meaningless_keyword_argument=None)
         assert x == '$YYY', x
 
@@ -2714,8 +2906,7 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
         x = proxy.subst_list('$XXX')
         assert x == [[]], x
 
-        x = proxy.subst_list('$YYY', raw=0, target=None, source=None,
-                             dict=None, conv=None)
+        x = proxy.subst_list('$YYY', raw=0, target=None, source=None, conv=None)
         assert x == [[]], x
 
     def test_subst_target_source(self):
@@ -2739,7 +2930,8 @@ class NoSubstitutionProxyTestCase(unittest.TestCase):
 
 if __name__ == "__main__":
     suite = unittest.TestSuite()
-    tclasses = [ EnvironmentTestCase,
+    tclasses = [ SubstitutionTestCase,
+                 BaseTestCase,
                  NoSubstitutionProxyTestCase ]
     for tclass in tclasses:
         names = unittest.getTestCaseNames(tclass, 'test_')
index 6a8fe83fcb613b8dcd561544816beeeb90e297a1..1cb449c38c2e8b17b4bd43771ee72bf7e5160678 100644 (file)
@@ -81,9 +81,14 @@ class Executor:
             env = self.env or SCons.Defaults.DefaultEnvironment()
             self.build_env = env.Override(overrides)
 
-            # Now update the build environment with the things that we
-            # don't want expanded against the current construction
-            # variables.
+            # Update the overrides with the $TARGET/$SOURCE variables for
+            # this target+source pair, so that evaluations of arbitrary
+            # Python functions have them in the __env__ environment
+            # they're passed.  Note that the underlying substitution
+            # functions also override these with their own $TARGET/$SOURCE
+            # expansions, which is *usually* duplicated effort, but covers
+            # a corner case where an Action is called directly from within
+            # a function action with different target and source lists.
             self.build_env._update(SCons.Util.subst_dict(self.targets,
                                                          self.sources))
             return self.build_env
index 4248dc3fb530e0373cdc3bc8e99f9dc651a4ab97..b99aa985bbdd7d664e0527288a1ee5daa5087662 100644 (file)
@@ -37,15 +37,13 @@ class Environment:
     def __init__(self):
         self.dict = {}
     def subst(self, x):
-        return SCons.Util.scons_subst(x, self)
+        return SCons.Util.scons_subst(x, self, gvars=self.dict)
     def __setitem__(self, key, value):
         self.dict[key] = value
     def __getitem__(self, key):
         return self.dict[key]
     def has_key(self, key):
         return self.dict.has_key(key)
-    def Dictionary(self):
-        return self.dict
 
 
 def check(key, value, env):
index fa36cad77cc257decd6945a51569b113ff6515bf..512e5128bc4bfadaaaaaabea6da5ef4eceb5c13a 100644 (file)
@@ -47,9 +47,8 @@ def scan(node, env, libpath = (), fs = SCons.Node.FS.default_fs):
     for libraries specified in the LIBS variable, returning any
     files it finds as dependencies.
     """
-
     try:
-        libs = env.Dictionary('LIBS')
+        libs = env['LIBS']
     except KeyError:
         # There are no LIBS in this environment, so just return a null list:
         return []
@@ -61,14 +60,14 @@ def scan(node, env, libpath = (), fs = SCons.Node.FS.default_fs):
         libs = [libs]
 
     try:
-        prefix = env.Dictionary('LIBPREFIXES')
+        prefix = env['LIBPREFIXES']
         if not SCons.Util.is_List(prefix):
             prefix = [ prefix ]
     except KeyError:
         prefix = [ '' ]
 
     try:
-        suffix = env.Dictionary('LIBSUFFIXES')
+        suffix = env['LIBSUFFIXES']
         if not SCons.Util.is_List(suffix):
             suffix = [ suffix ]
     except KeyError:
index 982fdecec8caea5a023bc46854ab51018cc4e3c2..25713a8403fe3523782fb5ebc04c5a40d434eca4 100644 (file)
@@ -551,7 +551,7 @@ _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
 # space characters in the string result from the scons_subst() function.
 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
 
-def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=None, conv=None, gvars=None):
+def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
     """Expand a string containing construction variable substitutions.
 
     This is the work-horse function for substitutions in file names
@@ -693,12 +693,22 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=No
             else:
                 return self.expand(args, lvars)
 
-    if dict is None:
-        dict = subst_dict(target, source)
     if conv is None:
         conv = _strconv[mode]
-    if gvars is None:
-        gvars = env.Dictionary()
+
+    # Doing this every time is a bit of a waste, since the Executor
+    # has typically already populated the OverrideEnvironment with
+    # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
+    # because it supports existing behavior that allows us to call
+    # an Action directly with an arbitrary target+source pair, which
+    # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
+    # If we dropped that behavior (or found another way to cover it),
+    # we could get rid of this call completely and just rely on the
+    # Executor setting the variables.
+    d = subst_dict(target, source)
+    if d:
+        lvars = lvars.copy()
+        lvars.update(d)
 
     # We're (most likely) going to eval() things.  If Python doesn't
     # find a __builtin__ value in the global dictionary used for eval(),
@@ -709,7 +719,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=No
     gvars['__builtin__'] = __builtin__
 
     ss = StringSubber(env, mode, target, source, conv, gvars)
-    result = ss.substitute(strSubst, dict)
+    result = ss.substitute(strSubst, lvars)
 
     try:
         del gvars['__builtin__']
@@ -729,7 +739,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=No
 
     return result
 
-def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=None, conv=None, gvars=None):
+def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
     """Substitute construction variables in a string (or list or other
     object) and separate the arguments into a command list.
 
@@ -943,12 +953,22 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, di
             self.add_strip(x)
             self.in_strip = None
 
-    if dict is None:
-        dict = subst_dict(target, source)
     if conv is None:
         conv = _strconv[mode]
-    if gvars is None:
-        gvars = env.Dictionary()
+
+    # Doing this every time is a bit of a waste, since the Executor
+    # has typically already populated the OverrideEnvironment with
+    # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
+    # because it supports existing behavior that allows us to call
+    # an Action directly with an arbitrary target+source pair, which
+    # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
+    # If we dropped that behavior (or found another way to cover it),
+    # we could get rid of this call completely and just rely on the
+    # Executor setting the variables.
+    d = subst_dict(target, source)
+    if d:
+        lvars = lvars.copy()
+        lvars.update(d)
 
     # We're (most likely) going to eval() things.  If Python doesn't
     # find a __builtin__ value in the global dictionary used for eval(),
@@ -959,7 +979,7 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, di
     gvars['__builtins__'] = __builtins__
 
     ls = ListSubber(env, mode, target, source, conv, gvars)
-    ls.substitute(strSubst, dict, 0)
+    ls.substitute(strSubst, lvars, 0)
 
     try:
         del gvars['__builtins__']
index c28db37bfd79d308028ce434d3812d06edbf6f60..b6b04248be3a6109fda9a65a0bb37f415ea68a5d 100644 (file)
@@ -327,7 +327,8 @@ class UtilTestCase(unittest.TestCase):
             '$CALLABLE',            'callable-1',
         ]
 
-        kwargs = {'target' : target, 'source' : source}
+        kwargs = {'target' : target, 'source' : source,
+                  'gvars' : env.Dictionary()}
 
         failed = 0
         while cases:
@@ -341,6 +342,11 @@ class UtilTestCase(unittest.TestCase):
             del cases[:2]
         assert failed == 0, "%d subst() cases failed" % failed
 
+        # The expansion dictionary no longer comes from the construction
+        # environment automatically.
+        s = scons_subst('$AAA', env)
+        assert s == '', s
+
         # Tests of the various SUBST_* modes of substitution.
         subst_cases = [
             "test $xxx",
@@ -396,20 +402,22 @@ class UtilTestCase(unittest.TestCase):
                 "| | c 1",
         ]
 
+        gvars = env.Dictionary()
+
         failed = 0
         while subst_cases:
             input, eraw, ecmd, esig = subst_cases[:4]
-            result = scons_subst(input, env, mode=SUBST_RAW)
+            result = scons_subst(input, env, mode=SUBST_RAW, gvars=gvars)
             if result != eraw:
                 if failed == 0: print
                 print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
                 failed = failed + 1
-            result = scons_subst(input, env, mode=SUBST_CMD)
+            result = scons_subst(input, env, mode=SUBST_CMD, gvars=gvars)
             if result != ecmd:
                 if failed == 0: print
                 print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
                 failed = failed + 1
-            result = scons_subst(input, env, mode=SUBST_SIG)
+            result = scons_subst(input, env, mode=SUBST_SIG, gvars=gvars)
             if result != esig:
                 if failed == 0: print
                 print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
@@ -428,8 +436,8 @@ class UtilTestCase(unittest.TestCase):
         result = scons_subst("$TARGET $SOURCES", env,
                                   target=[t1, t2],
                                   source=[s1, s2],
-                                  dict={})
-        assert result == " ", result
+                                  gvars={})
+        assert result == "t1 s1 s2", result
 
         result = scons_subst("$TARGET $SOURCES", env, target=[], source=[])
         assert result == " ", result
@@ -438,7 +446,8 @@ class UtilTestCase(unittest.TestCase):
 
         # Test interpolating a callable.
         newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS",
-                             env, target=MyNode('t'), source=MyNode('s'))
+                             env, target=MyNode('t'), source=MyNode('s'),
+                             gvars=gvars)
         assert newcom == "test foo baz s t", newcom
 
         # Test that we handle syntax errors during expansion as expected.
@@ -457,25 +466,27 @@ class UtilTestCase(unittest.TestCase):
 
         n1 = MyNode('n1')
         env = DummyEnv({'NODE' : n1})
-        node = scons_subst("$NODE", env, mode=SUBST_RAW, conv=s)
+        gvars = env.Dictionary()
+        node = scons_subst("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
         assert node is n1, node
-        node = scons_subst("$NODE", env, mode=SUBST_CMD, conv=s)
+        node = scons_subst("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
         assert node is n1, node
-        node = scons_subst("$NODE", env, mode=SUBST_SIG, conv=s)
+        node = scons_subst("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
         assert node is n1, node
 
         # Test returning a function.
         #env = DummyEnv({'FUNCTION' : foo})
-        #func = scons_subst("$FUNCTION", env, mode=SUBST_RAW, call=None)
+        #gvars = env.Dictionary()
+        #func = scons_subst("$FUNCTION", env, mode=SUBST_RAW, call=None, gvars=gvars)
         #assert func is function_foo, func
-        #func = scons_subst("$FUNCTION", env, mode=SUBST_CMD, call=None)
+        #func = scons_subst("$FUNCTION", env, mode=SUBST_CMD, call=None, gvars=gvars)
         #assert func is function_foo, func
-        #func = scons_subst("$FUNCTION", env, mode=SUBST_SIG, call=None)
+        #func = scons_subst("$FUNCTION", env, mode=SUBST_SIG, call=None, gvars=gvars)
         #assert func is function_foo, func
 
         # Test supplying an overriding gvars dictionary.
         env = DummyEnv({'XXX' : 'xxx'})
-        result = scons_subst('$XXX', env)
+        result = scons_subst('$XXX', env, gvars=env.Dictionary())
         assert result == 'xxx', result
         result = scons_subst('$XXX', env, gvars={'XXX' : 'yyy'})
         assert result == 'yyy', result
@@ -694,7 +705,9 @@ class UtilTestCase(unittest.TestCase):
             '$CALLABLE',            [['callable-2']],
         ]
 
-        kwargs = {'target' : target, 'source' : source}
+        gvars = env.Dictionary()
+
+        kwargs = {'target' : target, 'source' : source, 'gvars' : gvars}
 
         failed = 0
         while cases:
@@ -708,31 +721,38 @@ class UtilTestCase(unittest.TestCase):
             del cases[:2]
         assert failed == 0, "%d subst_list() cases failed" % failed
 
+        # The expansion dictionary no longer comes from the construction
+        # environment automatically.
+        s = scons_subst_list('$AAA', env)
+        assert s == [[]], s
+
         t1 = MyNode('t1')
         t2 = MyNode('t2')
         s1 = MyNode('s1')
         s2 = MyNode('s2')
         result = scons_subst_list("$TARGET $SOURCES", env,
                                   target=[t1, t2],
-                                  source=[s1, s2])
+                                  source=[s1, s2],
+                                  gvars=gvars)
         assert result == [['t1', 's1', 's2']], result
         result = scons_subst_list("$TARGET $SOURCES", env,
                                   target=[t1, t2],
                                   source=[s1, s2],
-                                  dict={})
-        assert result == [[]], result
+                                  gvars={})
+        assert result == [['t1', 's1', 's2']], result
 
         # Test interpolating a callable.
         _t = DummyNode('t')
         _s = DummyNode('s')
         cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES",
-                                    env, target=_t, source=_s)
+                                    env, target=_t, source=_s,
+                                    gvars=gvars)
         assert cmd_list == [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list
 
         # Test escape functionality.
         def escape_func(foo):
             return '**' + foo + '**'
-        cmd_list = scons_subst_list("abc $LITERALS xyz", env)
+        cmd_list = scons_subst_list("abc $LITERALS xyz", env, gvars=gvars)
         assert cmd_list == [['abc',
                              'foo\nwith\nnewlines',
                              'bar\nwith\nnewlines',
@@ -746,7 +766,7 @@ class UtilTestCase(unittest.TestCase):
         c = cmd_list[0][3].escape(escape_func)
         assert c == 'xyz', c
 
-        cmd_list = scons_subst_list("abc${LITERALS}xyz", env)
+        cmd_list = scons_subst_list("abc${LITERALS}xyz", env, gvars=gvars)
         c = cmd_list[0][0].escape(escape_func)
         assert c == '**abcfoo\nwith\nnewlines**', c
         c = cmd_list[0][1].escape(escape_func)
@@ -807,23 +827,25 @@ class UtilTestCase(unittest.TestCase):
                 [["|", "|", "c", "1"]],
         ]
 
-        r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW)
+        gvars = env.Dictionary()
+
+        r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW, gvars=gvars)
         assert r == [[]], r
 
         failed = 0
         while subst_list_cases:
             input, eraw, ecmd, esig = subst_list_cases[:4]
-            result = scons_subst_list(input, env, mode=SUBST_RAW)
+            result = scons_subst_list(input, env, mode=SUBST_RAW, gvars=gvars)
             if result != eraw:
                 if failed == 0: print
                 print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
                 failed = failed + 1
-            result = scons_subst_list(input, env, mode=SUBST_CMD)
+            result = scons_subst_list(input, env, mode=SUBST_CMD, gvars=gvars)
             if result != ecmd:
                 if failed == 0: print
                 print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
                 failed = failed + 1
-            result = scons_subst_list(input, env, mode=SUBST_SIG)
+            result = scons_subst_list(input, env, mode=SUBST_SIG, gvars=gvars)
             if result != esig:
                 if failed == 0: print
                 print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
@@ -847,16 +869,17 @@ class UtilTestCase(unittest.TestCase):
 
         n1 = MyNode('n1')
         env = DummyEnv({'NODE' : n1})
-        node = scons_subst_list("$NODE", env, mode=SUBST_RAW, conv=s)
+        gvars=env.Dictionary()
+        node = scons_subst_list("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
         assert node == [[n1]], node
-        node = scons_subst_list("$NODE", env, mode=SUBST_CMD, conv=s)
+        node = scons_subst_list("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
         assert node == [[n1]], node
-        node = scons_subst_list("$NODE", env, mode=SUBST_SIG, conv=s)
+        node = scons_subst_list("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
         assert node == [[n1]], node
 
         # Test supplying an overriding gvars dictionary.
         env = DummyEnv({'XXX' : 'xxx'})
-        result = scons_subst_list('$XXX', env)
+        result = scons_subst_list('$XXX', env, gvars=env.Dictionary())
         assert result == [['xxx']], result
         result = scons_subst_list('$XXX', env, gvars={'XXX' : 'yyy'})
         assert result == [['yyy']], result
@@ -1290,28 +1313,28 @@ class UtilTestCase(unittest.TestCase):
     def test_Literal(self):
         """Test the Literal() function."""
         input_list = [ '$FOO', Literal('$BAR') ]
-        dummy_env = DummyEnv({ 'FOO' : 'BAZ', 'BAR' : 'BLAT' })
+        gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
 
         def escape_func(cmd):
             return '**' + cmd + '**'
 
-        cmd_list = scons_subst_list(input_list, dummy_env)
+        cmd_list = scons_subst_list(input_list, None, gvars=gvars)
         cmd_list = SCons.Util.escape_list(cmd_list[0], escape_func)
         assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
 
     def test_SpecialAttrWrapper(self):
         """Test the SpecialAttrWrapper() function."""
         input_list = [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
-        dummy_env = DummyEnv({ 'FOO' : 'BAZ', 'BAR' : 'BLAT' })
+        gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
 
         def escape_func(cmd):
             return '**' + cmd + '**'
 
-        cmd_list = scons_subst_list(input_list, dummy_env)
+        cmd_list = scons_subst_list(input_list, None, gvars=gvars)
         cmd_list = SCons.Util.escape_list(cmd_list[0], escape_func)
         assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
 
-        cmd_list = scons_subst_list(input_list, dummy_env, mode=SUBST_SIG)
+        cmd_list = scons_subst_list(input_list, None, mode=SUBST_SIG, gvars=gvars)
         cmd_list = SCons.Util.escape_list(cmd_list[0], escape_func)
         assert cmd_list == ['BAZ', '**BLEH**'], cmd_list
 
@@ -1584,10 +1607,10 @@ class UtilTestCase(unittest.TestCase):
 
         cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
 
-        newcmd = scons_subst(cmd, env)
+        newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
         assert newcmd == 'test foo bar call test', newcmd
 
-        cmd_list = scons_subst_list(cmd, env)
+        cmd_list = scons_subst_list(cmd, env, gvars=env.Dictionary())
         assert len(cmd_list) == 1, cmd_list
         assert cmd_list[0][0] == "test", cmd_list[0][0]
         assert cmd_list[0][1] == "foo", cmd_list[0][1]