From: stevenknight Date: Wed, 29 Dec 2004 16:26:58 +0000 (+0000) Subject: Enhance OverrideEnvironment, and rename the base class to an enhanced and maybe-even... X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=69b34327e13b37afb4ee7a2a48781b700639215c;p=scons.git Enhance OverrideEnvironment, and rename the base class to an enhanced and maybe-even-useful SubstitutionEnvironment, to eliminate copying of construction environment variables. git-svn-id: http://scons.tigris.org/svn/scons/trunk@1195 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 6db1e6af..83e42082 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -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. diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 705daff2..4c1d521b 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -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): diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 75b1ecad..4af4ced9 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -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)) diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 71383e7a..92b89c0e 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -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_') diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index 6a8fe83f..1cb449c3 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -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 diff --git a/src/engine/SCons/Options/OptionsTests.py b/src/engine/SCons/Options/OptionsTests.py index 4248dc3f..b99aa985 100644 --- a/src/engine/SCons/Options/OptionsTests.py +++ b/src/engine/SCons/Options/OptionsTests.py @@ -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): diff --git a/src/engine/SCons/Scanner/Prog.py b/src/engine/SCons/Scanner/Prog.py index fa36cad7..512e5128 100644 --- a/src/engine/SCons/Scanner/Prog.py +++ b/src/engine/SCons/Scanner/Prog.py @@ -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: diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 982fdece..25713a84 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -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__'] diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index c28db37b..b6b04248 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -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]