summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2004-01-20 16:48:03 (GMT)
committerSteven Knight <knight@baldmt.com>2004-01-20 16:48:03 (GMT)
commitc31d933609202d40181640364bebfa8662b3df85 (patch)
tree88857e74b8c72c99aa59e8a52a3c4585f478dcdf /src
parent18d748a479233b689144db11a2ac3643a6577273 (diff)
downloadSCons-c31d933609202d40181640364bebfa8662b3df85.zip
SCons-c31d933609202d40181640364bebfa8662b3df85.tar.gz
SCons-c31d933609202d40181640364bebfa8662b3df85.tar.bz2
Refactor variable substitution for more scalable expansion of , etc.
Diffstat (limited to 'src')
-rw-r--r--src/RELEASE.txt9
-rw-r--r--src/engine/SCons/Action.py47
-rw-r--r--src/engine/SCons/ActionTests.py130
-rw-r--r--src/engine/SCons/Environment.py30
-rw-r--r--src/engine/SCons/EnvironmentTests.py105
-rw-r--r--src/engine/SCons/Util.py141
-rw-r--r--src/engine/SCons/UtilTests.py119
7 files changed, 392 insertions, 189 deletions
diff --git a/src/RELEASE.txt b/src/RELEASE.txt
index 4bb0a8f..b52d3ad 100644
--- a/src/RELEASE.txt
+++ b/src/RELEASE.txt
@@ -34,6 +34,15 @@ RELEASE 0.95 - XXX
variable to 0 (or zipfile.ZIP_STORED, if you import the
underlying zipfile module).
+ - The meaning of some of the values of the "mode" argument to
+ the SCons.Util.scons_subst() and SCons.Util.scons_subst_list()
+ functions have been swapped. The wrapper Environment.subst()
+ and Environment.subst_list() methods remain completely unchanged,
+ this so this should have no external effect. (We've listed the
+ change here because it might cause a problem if you're reaching
+ into the SCons internals and calling either of the SCons.Util
+ functions directly.)
+
SCons is developed with an extensive regression test suite, and a
rigorous development methodology for continually improving that suite.
Because of this, SCons is of sufficient quality that you can use it
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 719e213..e2ceb2a 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -172,7 +172,7 @@ class CommandAction(ActionBase):
def __init__(self, cmd):
# Cmd list can actually be a list or a single item...basically
# anything that we could pass in as the first arg to
- # scons_subst_list().
+ # Environment.subst_list().
self.cmd_list = cmd
def strfunction(self, target, source, env):
@@ -263,32 +263,28 @@ class CommandAction(ActionBase):
return ret
return 0
- def get_raw_contents(self, target, source, env):
+ def get_raw_contents(self, target, source, env, dict=None):
"""Return the complete contents of this action's command line.
"""
cmd = self.cmd_list
- if not SCons.Util.is_List(cmd):
- cmd = [ cmd ]
- return SCons.Util.scons_subst(string.join(map(str, cmd)),
- env,
- SCons.Util.SUBST_RAW,
- SCons.Util.target_prep(target),
- SCons.Util.source_prep(source))
-
- def get_contents(self, target, source, env):
+ if SCons.Util.is_List(cmd):
+ cmd = string.join(map(str, cmd))
+ else:
+ cmd = str(cmd)
+ return env.subst(cmd, SCons.Util.SUBST_RAW, target, source, dict)
+
+ def get_contents(self, target, source, env, dict=None):
"""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.
"""
cmd = self.cmd_list
- if not SCons.Util.is_List(cmd):
- cmd = [ cmd ]
- return SCons.Util.scons_subst(string.join(map(str, cmd)),
- env,
- SCons.Util.SUBST_SIG,
- SCons.Util.target_prep(target),
- SCons.Util.source_prep(source))
+ if SCons.Util.is_List(cmd):
+ cmd = string.join(map(str, cmd))
+ else:
+ cmd = str(cmd)
+ return env.subst(cmd, SCons.Util.SUBST_SIG, target, source, dict)
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
@@ -321,13 +317,13 @@ class CommandGeneratorAction(ActionBase):
act = self.__generate(target, source, env, 0)
return act(target, rsources, env)
- def get_contents(self, target, source, env):
+ def get_contents(self, target, source, env, dict=None):
"""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)
+ return self.__generate(target, source, env, 1).get_contents(target, source, env, dict=None)
class LazyCmdGenerator:
"""This is a simple callable class that acts as a command generator.
@@ -393,7 +389,7 @@ class FunctionAction(ActionBase):
r = self.execfunction(target=target, source=rsources, env=env)
return r
- def get_contents(self, target, source, env):
+ def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this callable action.
By providing direct access to the code object of the
@@ -433,14 +429,13 @@ class ListAction(ActionBase):
return r
return 0
- def get_contents(self, target, source, env):
+ def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this action list.
Simple concatenation of the signatures of the elements.
"""
- target = SCons.Util.target_prep(target)
- source = SCons.Util.source_prep(source)
- return string.join(map(lambda x, t=target, s=source, e=env:
- x.get_contents(t, s, e),
+ dict = SCons.Util.subst_dict(target, source, env)
+ return string.join(map(lambda x, t=target, s=source, e=env, d=dict:
+ x.get_contents(t, s, e, d),
self.list),
"")
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index 7396118..480b299 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -125,70 +125,11 @@ class Environment:
self.d['ESCAPE'] = scons_env['ESCAPE']
for k, v in kw.items():
self.d[k] = v
- def subst_dict(self, target, source):
- dict = self.d.copy()
- if not SCons.Util.is_List(target):
- target = [target]
- if not SCons.Util.is_List(source):
- source = [source]
- dict['TARGETS'] = target
- dict['SOURCES'] = source
- try:
- dict['TARGET'] = target[0]
- except IndexError:
- dict['TARGET'] = ''
- try:
- dict['SOURCE'] = source[0]
- except IndexError:
- dict['SOURCE'] = ''
- return dict
- def subst(self, strSubst):
- if not SCons.Util.is_String(strSubst):
- return strSubst
- try:
- s0, s1 = strSubst[:2]
- except (IndexError, ValueError):
- return strSubst
- if s0 == '$':
- if s1 == '{':
- return self.d.get(strSubst[2:-1], '')
- else:
- return self.d.get(strSubst[1:], '')
- return strSubst
- def subst_list(self, strSubst, raw=0, target=[], source=[]):
- dict = self.subst_dict(target, source)
- if SCons.Util.is_String(strSubst):
- strSubst = string.split(strSubst)
- elif not SCons.Util.is_List(strSubst):
- return strSubst
- result = []
- for s in strSubst:
- if SCons.Util.is_String(s):
- try:
- s0, s1 = s[:2]
- except (IndexError, ValueError):
- result.append(s)
- else:
- if s0 == '$':
- if s1 == '{':
- s = eval(s[2:-1], {}, dict)
- else:
- s = dict.get(s[1:], '')
- if s:
- if not SCons.Util.is_List(s):
- s = [s]
- result.extend(s)
- else:
- result.append(s)
- def l(obj):
- try:
- l = obj.is_literal
- except AttributeError:
- literal = None
- else:
- literal = l()
- return CmdStringHolder(str(obj), literal)
- return [map(l, result)]
+ # 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_list(self, strSubst, raw=0, target=[], source=[], dict=None):
+ return SCons.Util.scons_subst_list(strSubst, self, raw, target, source, dict)
def __getitem__(self, item):
return self.d[item]
def __setitem__(self, item, value):
@@ -434,33 +375,35 @@ class CommandActionTestCase(unittest.TestCase):
"""Test fetching the string representation of command Actions
"""
+ env = Environment()
+ t1 = DummyNode('t1')
+ t2 = DummyNode('t2')
+ s1 = DummyNode('s1')
+ s2 = DummyNode('s2')
act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
- s = act.strfunction([], [], Environment())
+ s = act.strfunction([], [], env)
assert s == ['xyzzy'], s
- s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
- assert s == ['xyzzy target source'], s
- s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
- [DummyNode('s1'), DummyNode('s2')], Environment())
+ s = act.strfunction([t1], [s1], env)
+ assert s == ['xyzzy t1 s1'], s
+ s = act.strfunction([t1, t2], [s1, s2], env)
assert s == ['xyzzy t1 s1'], s
act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES')
- s = act.strfunction([], [], Environment())
+ s = act.strfunction([], [], env)
assert s == ['xyzzy'], s
- s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
- assert s == ['xyzzy target source'], s
- s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
- [DummyNode('s1'), DummyNode('s2')], Environment())
+ s = act.strfunction([t1], [s1], env)
+ assert s == ['xyzzy t1 s1'], s
+ s = act.strfunction([t1, t2], [s1, s2], env)
assert s == ['xyzzy t1 t2 s1 s2'], s
act = SCons.Action.CommandAction(['xyzzy',
'$TARGET', '$SOURCE',
'$TARGETS', '$SOURCES'])
- s = act.strfunction([], [], Environment())
+ s = act.strfunction([], [], env)
assert s == ['xyzzy'], s
- s = act.strfunction([DummyNode('target')], [DummyNode('source')], Environment())
- assert s == ['xyzzy target source target source'], s
- s = act.strfunction([DummyNode('t1'), DummyNode('t2')],
- [DummyNode('s1'), DummyNode('s2')], Environment())
+ s = act.strfunction([t1], [s1], env)
+ assert s == ['xyzzy t1 s1 t1 s1'], s
+ s = act.strfunction([t1, t2], [s1, s2], env)
assert s == ['xyzzy t1 s1 t1 t2 s1 s2'], s
def test_execute(self):
@@ -787,10 +730,14 @@ 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)
@@ -803,10 +750,14 @@ 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)
@@ -906,10 +857,12 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
assert mystr == "$( foo $bar $)", mystr
return "test"
+ env = Environment(foo = 'FFF', bar = 'BBB',
+ ignore = 'foo', test=test)
a = SCons.Action.CommandGeneratorAction(f)
- c = a.get_contents(target=[], source=[],
- env=Environment(foo = 'FFF', bar = 'BBB',
- ignore = 'foo', test=test))
+ 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
@@ -1022,6 +975,8 @@ class FunctionActionTestCase(unittest.TestCase):
a = SCons.Action.FunctionAction(Func)
c = a.get_contents(target=[], source=[], env=Environment())
assert c == "\177\036\000\177\037\000d\000\000S", repr(c)
+ c = a.get_contents(target=[], source=[], env=Environment(), dict={})
+ assert c == "\177\036\000\177\037\000d\000\000S", repr(c)
a = SCons.Action.FunctionAction(Func, varlist=['XYZ'])
c = a.get_contents(target=[], source=[], env=Environment())
@@ -1111,6 +1066,9 @@ 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):
@@ -1151,9 +1109,11 @@ class LazyActionTestCase(unittest.TestCase):
"""Test fetching the contents of a lazy-evaluation Action
"""
a = SCons.Action.Action("${FOO}")
- c = a.get_contents(target=[], source=[],
- env = Environment(FOO = [["This", "is", "$(", "$a", "$)", "test"]]))
- assert c == "This is test", c
+ 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
if __name__ == "__main__":
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 02f2f33..3819c27 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -363,7 +363,7 @@ class Base:
return scanner
return None
- def subst(self, string, raw=0, target=None, source=None):
+ def subst(self, string, raw=0, target=None, source=None, dict=None):
"""Recursively interpolates construction variables from the
Environment into the specified string, returning the expanded
result. Construction variables are specified by a $ prefix
@@ -373,33 +373,21 @@ class Base:
may be surrounded by curly braces to separate the name from
trailing characters.
"""
- if raw:
- mode = SCons.Util.SUBST_RAW
- else:
- mode = SCons.Util.SUBST_CMD
- return SCons.Util.scons_subst(string, self, mode, target, source)
-
- def subst_kw(self, kw, raw=0, target=None, source=None):
- if raw:
- mode = SCons.Util.SUBST_RAW
- else:
- mode = SCons.Util.SUBST_CMD
+ return SCons.Util.scons_subst(string, self, raw, target, source, dict)
+
+ def subst_kw(self, kw, raw=0, target=None, source=None, dict=None):
nkw = {}
for k, v in kw.items():
- k = SCons.Util.scons_subst(k, self, mode, target, source)
+ k = self.subst(k, raw, target, source, dict)
if SCons.Util.is_String(v):
- v = SCons.Util.scons_subst(v, self, mode, target, source)
+ v = self.subst(v, raw, target, source, dict)
nkw[k] = v
return nkw
-
- def subst_list(self, string, raw=0, target=None, source=None):
+
+ def subst_list(self, string, raw=0, target=None, source=None, dict=None):
"""Calls through to SCons.Util.scons_subst_list(). See
the documentation for that function."""
- if raw:
- mode = SCons.Util.SUBST_RAW
- else:
- mode = SCons.Util.SUBST_CMD
- return SCons.Util.scons_subst_list(string, self, mode, target, source)
+ return SCons.Util.scons_subst_list(string, self, raw, target, source, dict)
def use_build_signature(self):
try:
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index 4e0b380..f81a102 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -275,14 +275,11 @@ class EnvironmentTestCase(unittest.TestCase):
env = Environment(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')
mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
assert mystr == "c cA cB c", mystr
- env = Environment(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
-
class DummyNode:
def __init__(self, name):
self.name = name
@@ -293,6 +290,19 @@ class EnvironmentTestCase(unittest.TestCase):
def get_subst_proxy(self):
return self
+ t1 = DummyNode('t1')
+ t2 = DummyNode('t2')
+ s1 = DummyNode('s1')
+ s2 = DummyNode('s2')
+
+ env = Environment(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
def foo(target, source, env, for_signature):
assert str(target) == 't', target
@@ -300,14 +310,12 @@ class EnvironmentTestCase(unittest.TestCase):
return env["FOO"]
env = Environment(BAR=foo, FOO='baz')
+ t = DummyNode('t')
+ s = DummyNode('s')
- subst = env.subst('test $BAR', target=DummyNode('t'), source=DummyNode('s'))
+ subst = env.subst('test $BAR', target=t, source=s)
assert subst == 'test baz', subst
- lst = env.subst_list('test $BAR', target=DummyNode('t'), source=DummyNode('s'))
- assert lst[0][0] == 'test', lst[0][0]
- assert lst[0][1] == 'baz', lst[0][1]
-
# Test not calling callables in the Environment
if 0:
# This will take some serious surgery to subst() and
@@ -324,12 +332,6 @@ class EnvironmentTestCase(unittest.TestCase):
subst = env.subst('$FOO', call=None)
assert subst is bar, subst
- subst = env.subst_list('$BAR', call=None)
- assert subst is bar, subst
-
- subst = env.subst_list('$FOO', call=None)
- assert subst is bar, subst
-
def test_subst_kw(self):
"""Test substituting construction variables within dictionaries"""
env = Environment(AAA = 'a', BBB = 'b')
@@ -338,6 +340,79 @@ class EnvironmentTestCase(unittest.TestCase):
assert kw['a'] == 'aaa', kw['a']
assert kw['bbb'] == 'b', kw['bbb']
+ def test_subst_list(self):
+ """Test substituting construction variables in command lists
+ """
+ env = Environment(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')
+ 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')
+ 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' ])
+ lst = env.subst_list([ "$AAA", "B $CCC" ])
+ assert lst == [[ "a", "b"], ["c", "B a", "b"], ["c"]], lst
+
+ class DummyNode:
+ def __init__(self, name):
+ self.name = name
+ def __str__(self):
+ return self.name
+ def rfile(self):
+ return self
+ def get_subst_proxy(self):
+ return self
+
+ t1 = DummyNode('t1')
+ t2 = DummyNode('t2')
+ s1 = DummyNode('s1')
+ s2 = DummyNode('s2')
+
+ env = Environment(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
+ 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')
+ 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
+ if 0:
+ # This will take some serious surgery to subst() and
+ # subst_list(), so just leave these tests out until we can
+ # do that.
+ def bar(arg):
+ pass
+
+ env = Environment(BAR=bar, FOO='$BAR')
+
+ subst = env.subst_list('$BAR', call=None)
+ assert subst is bar, subst
+
+ subst = env.subst_list('$FOO', call=None)
+ assert subst is bar, subst
+
def test_Builder_calls(self):
"""Test Builder calls through different environments
"""
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 00a9bed..0af98fd 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -348,19 +348,96 @@ def escape_list(list, escape_func):
return e(escape_func)
return map(escape, list)
-def target_prep(target):
- if target and not isinstance(target, NodeList):
- if not is_List(target):
- target = [target]
- target = NodeList(map(lambda x: x.get_subst_proxy(), target))
- return target
-
-def source_prep(source):
- if source and not isinstance(source, NodeList):
- if not is_List(source):
- source = [source]
- source = NodeList(map(lambda x: x.rfile().get_subst_proxy(), source))
- return source
+class NLWrapper:
+ """A wrapper class that delays turning a list of sources or targets
+ into a NodeList until it's needed. The specified function supplied
+ when the object is initialized is responsible for turning raw nodes
+ into proxies that implement the special attributes like .abspath,
+ .source, etc. This way, we avoid creating those proxies just
+ "in case" someone is going to use $TARGET or the like, and only
+ go through the trouble if we really have to.
+
+ In practice, this might be a wash performance-wise, but it's a little
+ cleaner conceptually...
+ """
+
+ def __init__(self, list, func):
+ self.list = list
+ self.func = func
+ def _create_nodelist(self):
+ try:
+ return self.nodelist
+ except AttributeError:
+ list = self.list
+ if list is None:
+ list = []
+ elif not is_List(list):
+ list = [list]
+ # The map(self.func) call is what actually turns
+ # a list into appropriate proxies.
+ self.nodelist = NodeList(map(self.func, list))
+ return self.nodelist
+
+class Targets_or_Sources(UserList.UserList):
+ """A class that implements $TARGETS or $SOURCES expansions by in turn
+ wrapping a NLWrapper. This class handles the different methods used
+ to access the list, calling the NLWrapper to create proxies on demand.
+
+ Note that we subclass UserList.UserList purely so that the is_List()
+ function will identify an object of this class as a list during
+ variable expansion. We're not really using any UserList.UserList
+ methods in practice.
+ """
+ def __init__(self, nl):
+ self.nl = nl
+ def __getattr__(self, attr):
+ nl = self.nl._create_nodelist()
+ return getattr(nl, attr)
+ def __getitem__(self, i):
+ nl = self.nl._create_nodelist()
+ return nl[i]
+ def __getslice__(self, i, j):
+ nl = self.nl._create_nodelist()
+ i = max(i, 0); j = max(j, 0)
+ return nl[i:j]
+ def __str__(self):
+ nl = self.nl._create_nodelist()
+ return str(nl)
+ def __repr__(self):
+ nl = self.nl._create_nodelist()
+ return repr(nl)
+
+class Target_or_Source:
+ """A class that implements $TARGET or $SOURCE expansions by in turn
+ wrapping a NLWrapper. This class handles the different methods used
+ to access an individual proxy Node, calling the NLWrapper to create
+ a proxy on demand.
+ """
+ def __init__(self, nl):
+ self.nl = nl
+ def __getattr__(self, attr):
+ nl = self.nl._create_nodelist()
+ try:
+ nl0 = nl[0]
+ except IndexError:
+ # If there is nothing in the list, then we have no attributes to
+ # pass through, so raise AttributeError for everything.
+ raise AttributeError, "NodeList has no attribute: %s" % attr
+ return getattr(nl0, attr)
+ def __str__(self):
+ nl = self.nl._create_nodelist()
+ try:
+ nl0 = nl[0]
+ except IndexError:
+ return ''
+ return str(nl0)
+ def __repr__(self):
+ nl = self.nl._create_nodelist()
+ try:
+ nl0 = nl[0]
+ except IndexError:
+ return ''
+ return repr(nl0)
def subst_dict(target, source, env):
"""Create a dictionary for substitution of special
@@ -380,17 +457,17 @@ def subst_dict(target, source, env):
build, which is made available as the __env__
construction variable
"""
- dict = { '__env__' : env }
+ dict = { '__env__' : env, }
- target = target_prep(target)
- dict['TARGETS'] = target
- if dict['TARGETS']:
- dict['TARGET'] = dict['TARGETS'][0]
+ if target:
+ tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
+ dict['TARGETS'] = Targets_or_Sources(tnl)
+ dict['TARGET'] = Target_or_Source(tnl)
- source = source_prep(source)
- dict['SOURCES'] = source
- if dict['SOURCES']:
- dict['SOURCE'] = dict['SOURCES'][0]
+ if source:
+ snl = NLWrapper(source, lambda x: x.rfile().get_subst_proxy())
+ dict['SOURCES'] = Targets_or_Sources(snl)
+ dict['SOURCE'] = Target_or_Source(snl)
return dict
@@ -399,15 +476,15 @@ def subst_dict(target, source, env):
# gives a command line suitable for passing to a shell. SUBST_SIG
# gives a command line appropriate for calculating the signature
# of a command line...if this changes, we should rebuild.
-SUBST_RAW = 0
-SUBST_CMD = 1
+SUBST_CMD = 0
+SUBST_RAW = 1
SUBST_SIG = 2
_rm = re.compile(r'\$[()]')
_remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
# Indexed by the SUBST_* constants above.
-_regex_remove = [ None, _rm, _remove ]
+_regex_remove = [ _rm, None, _remove ]
# This regular expression splits a string into the following types of
# arguments for use by the scons_subst() and scons_subst_list() functions:
@@ -427,7 +504,7 @@ _separate_args = re.compile(r'(\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}|\s+|[^\s\
# 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):
+def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=None):
"""Expand a string containing construction variable substitutions.
This is the work-horse function for substitutions in file names
@@ -522,8 +599,11 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None):
else:
return self.expand(args, lvars)
+ if dict is None:
+ dict = subst_dict(target, source, env)
+
ss = StringSubber(env, mode, target, source)
- result = ss.substitute(strSubst, subst_dict(target, source, env))
+ result = ss.substitute(strSubst, dict)
if is_String(result):
# Remove $(-$) pairs and any stuff in between,
@@ -538,7 +618,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None):
return result
-def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None):
+def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, dict=None):
"""Substitute construction variables in a string (or list or other
object) and separate the arguments into a command list.
@@ -694,8 +774,11 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None):
self.add_strip(x)
self.in_strip = None
+ if dict is None:
+ dict = subst_dict(target, source, env)
+
ls = ListSubber(env, mode, target, source)
- ls.substitute(strSubst, subst_dict(target, source, env), 0)
+ ls.substitute(strSubst, dict, 0)
return ls.data
diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py
index 3dbeebc..ca5f649 100644
--- a/src/engine/SCons/UtilTests.py
+++ b/src/engine/SCons/UtilTests.py
@@ -180,6 +180,9 @@ class UtilTestCase(unittest.TestCase):
'FUNC1' : lambda x: x,
'FUNC2' : lambda target, source, env, for_signature: ['x$CCC'],
+ # Various tests refactored from ActionTests.py.
+ 'LIST' : [["This", "is", "$(", "$a", "$)", "test"]],
+
# Test recursion.
'RECURSE' : 'foo $RECURSE bar',
'RRR' : 'foo $SSS bar',
@@ -338,6 +341,28 @@ class UtilTestCase(unittest.TestCase):
"foo bar",
"foo bar",
"foo bar",
+
+ # Verify what happens with no target or source nodes.
+ "$TARGET $SOURCES",
+ " ",
+ "",
+ "",
+
+ "$TARGETS $SOURCE",
+ " ",
+ "",
+ "",
+
+ # Various tests refactored from ActionTests.py.
+ "${LIST}",
+ "This is $( $) test",
+ "This is test",
+ "This is test",
+
+ ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
+ "| $( a | b $) | c 1",
+ "| a | b | c 1",
+ "| | c 1",
]
failed = 0
@@ -361,6 +386,25 @@ class UtilTestCase(unittest.TestCase):
del subst_cases[:4]
assert failed == 0, "%d subst() mode cases failed" % failed
+ t1 = MyNode('t1')
+ t2 = MyNode('t2')
+ s1 = MyNode('s1')
+ s2 = MyNode('s2')
+ result = scons_subst("$TARGET $SOURCES", env,
+ target=[t1, t2],
+ source=[s1, s2])
+ assert result == "t1 s1 s2", result
+ result = scons_subst("$TARGET $SOURCES", env,
+ target=[t1, t2],
+ source=[s1, s2],
+ dict={})
+ assert result == " ", result
+
+ result = scons_subst("$TARGET $SOURCES", env, target=[], source=[])
+ assert result == " ", result
+ result = scons_subst("$TARGETS $SOURCE", env, target=[], source=[])
+ assert result == " ", result
+
# Test interpolating a callable.
newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS",
env, target=MyNode('t'), source=MyNode('s'))
@@ -435,6 +479,9 @@ class UtilTestCase(unittest.TestCase):
'FUNC1' : lambda x: x,
'FUNC2' : lambda target, source, env, for_signature: ['x$CCC'],
+ # Various tests refactored from ActionTests.py.
+ 'LIST' : [["This", "is", "$(", "$a", "$)", "test"]],
+
# Test recursion.
'RECURSE' : 'foo $RECURSE bar',
'RRR' : 'foo $SSS bar',
@@ -568,6 +615,20 @@ class UtilTestCase(unittest.TestCase):
del cases[:2]
assert failed == 0, "%d subst_list() cases failed" % failed
+ 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])
+ assert result == [['t1', 's1', 's2']], result
+ result = scons_subst_list("$TARGET $SOURCES", env,
+ target=[t1, t2],
+ source=[s1, s2],
+ dict={})
+ assert result == [[]], result
+
# Test interpolating a callable.
_t = DummyNode('t')
_s = DummyNode('s')
@@ -615,16 +676,41 @@ class UtilTestCase(unittest.TestCase):
[["a", "aA", "b"]],
"$RECURSE",
- [["foo", "bar"]],
- [["foo", "bar"]],
- [["foo", "bar"]],
+ [["foo", "bar"]],
+ [["foo", "bar"]],
+ [["foo", "bar"]],
"$RRR",
- [["foo", "bar"]],
- [["foo", "bar"]],
- [["foo", "bar"]],
+ [["foo", "bar"]],
+ [["foo", "bar"]],
+ [["foo", "bar"]],
+
+ # Verify what happens with no target or source nodes.
+ "$TARGET $SOURCES",
+ [[]],
+ [[]],
+ [[]],
+
+ "$TARGETS $SOURCE",
+ [[]],
+ [[]],
+ [[]],
+
+ # Various test refactored from ActionTests.py
+ "${LIST}",
+ [['This', 'is', '$(', '$)', 'test']],
+ [['This', 'is', 'test']],
+ [['This', 'is', 'test']],
+
+ ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
+ [["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]],
+ [["|", "a", "|", "b", "|", "c", "1"]],
+ [["|", "|", "c", "1"]],
]
+ r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW)
+ assert r == [[]], r
+
failed = 0
while subst_list_cases:
input, eraw, ecmd, esig = subst_list_cases[:4]
@@ -1027,15 +1113,20 @@ class UtilTestCase(unittest.TestCase):
d = subst_dict([], [], env)
assert d['__env__'] is env, d['__env__']
- d = subst_dict(target = DummyNode('t'), source = DummyNode('s'), env=DummyEnv())
+ t = DummyNode('t')
+ s = DummyNode('s')
+ env = DummyEnv()
+ d = subst_dict(target=t, source=s, env=env)
assert str(d['TARGETS'][0]) == 't', d['TARGETS']
assert str(d['TARGET']) == 't', d['TARGET']
assert str(d['SOURCES'][0]) == 's', d['SOURCES']
assert str(d['SOURCE']) == 's', d['SOURCE']
- d = subst_dict(target = [DummyNode('t1'), DummyNode('t2')],
- source = [DummyNode('s1'), DummyNode('s2')],
- env = DummyEnv())
+ t1 = DummyNode('t1')
+ t2 = DummyNode('t2')
+ s1 = DummyNode('s1')
+ s2 = DummyNode('s2')
+ d = subst_dict(target=[t1, t2], source=[s1, s2], env=env)
TARGETS = map(lambda x: str(x), d['TARGETS'])
TARGETS.sort()
assert TARGETS == ['t1', 't2'], d['TARGETS']
@@ -1055,9 +1146,11 @@ class UtilTestCase(unittest.TestCase):
def get_subst_proxy(self):
return self
- d = subst_dict(target = [N('t3'), DummyNode('t4')],
- source = [DummyNode('s3'), N('s4')],
- env = DummyEnv())
+ t3 = N('t3')
+ t4 = DummyNode('t4')
+ s3 = DummyNode('s3')
+ s4 = N('s4')
+ d = subst_dict(target=[t3, t4], source=[s3, s4], env=env)
TARGETS = map(lambda x: str(x), d['TARGETS'])
TARGETS.sort()
assert TARGETS == ['t3', 't4'], d['TARGETS']