summaryrefslogtreecommitdiffstats
path: root/src/engine
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2005-09-19 11:13:56 (GMT)
committerSteven Knight <knight@baldmt.com>2005-09-19 11:13:56 (GMT)
commit15051d1132414b32b3d7d51163a7d4b74d523807 (patch)
tree458cbda69597f4d731acb4ea8d68802708d7055b /src/engine
parent8e37c60510ca640662f9e9a2be04c60a015d4db6 (diff)
downloadSCons-15051d1132414b32b3d7d51163a7d4b74d523807.zip
SCons-15051d1132414b32b3d7d51163a7d4b74d523807.tar.gz
SCons-15051d1132414b32b3d7d51163a7d4b74d523807.tar.bz2
Give the subst logic its own SCons.Subst module. It's big enough.
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/MANIFEST.in1
-rw-r--r--src/engine/SCons/Action.py6
-rw-r--r--src/engine/SCons/ActionTests.py4
-rw-r--r--src/engine/SCons/Defaults.py5
-rw-r--r--src/engine/SCons/Environment.py17
-rw-r--r--src/engine/SCons/Node/FS.py15
-rw-r--r--src/engine/SCons/Options/OptionsTests.py4
-rw-r--r--src/engine/SCons/Platform/__init__.py2
-rw-r--r--src/engine/SCons/Subst.py824
-rw-r--r--src/engine/SCons/SubstTests.py1135
-rw-r--r--src/engine/SCons/Util.py803
-rw-r--r--src/engine/SCons/UtilTests.py1069
12 files changed, 2001 insertions, 1884 deletions
diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in
index 4888736..17839ea 100644
--- a/src/engine/MANIFEST.in
+++ b/src/engine/MANIFEST.in
@@ -50,6 +50,7 @@ SCons/Script/__init__.py
SCons/Sig/__init__.py
SCons/Sig/MD5.py
SCons/Sig/TimeStamp.py
+SCons/Subst.py
SCons/Taskmaster.py
SCons/Tool/__init__.py
SCons/Tool/386asm.py
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 59ab261..0bbcc3a 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -422,7 +422,8 @@ class CommandAction(_ActionAction):
handle lists of commands, even though that's not how we use it
externally.
"""
- from SCons.Util import is_String, is_List, flatten, escape_list
+ from SCons.Subst import escape_list
+ from SCons.Util import is_String, is_List, flatten
try:
shell = env['SHELL']
@@ -478,12 +479,13 @@ class CommandAction(_ActionAction):
This strips $(-$) and everything in between the string,
since those parts don't affect signatures.
"""
+ from SCons.Subst import SUBST_SIG
cmd = self.cmd_list
if SCons.Util.is_List(cmd):
cmd = string.join(map(str, cmd))
else:
cmd = str(cmd)
- return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source)
+ return env.subst_target_source(cmd, SUBST_SIG, target, source)
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index f5aa67a..ff23cd7 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -136,10 +136,10 @@ class Environment:
self.d[k] = v
# Just use the underlying scons_subst*() utility methods.
def subst(self, strSubst, raw=0, target=[], source=[]):
- return SCons.Util.scons_subst(strSubst, self, raw, target, source, self.d)
+ return SCons.Subst.scons_subst(strSubst, self, raw, target, source, self.d)
subst_target_source = subst
def subst_list(self, strSubst, raw=0, target=[], source=[]):
- return SCons.Util.scons_subst_list(strSubst, self, raw, target, source, self.d)
+ return SCons.Subst.scons_subst_list(strSubst, self, raw, target, source, self.d)
def __getitem__(self, item):
return self.d[item]
def __setitem__(self, item, value):
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 88a167a..c1b9d3f 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -50,6 +50,7 @@ import SCons.Builder
import SCons.Environment
import SCons.Tool
import SCons.Sig
+import SCons.Subst
# A placeholder for a default Environment (for fetching source files
# from source code management systems and the like). This must be
@@ -211,8 +212,8 @@ def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
result = []
# ensure that prefix and suffix are strings
- prefix = str(env.subst(prefix, SCons.Util.SUBST_RAW))
- suffix = str(env.subst(suffix, SCons.Util.SUBST_RAW))
+ prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW))
+ suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW))
for x in list:
if isinstance(x, SCons.Node.FS.File):
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 57066c1..0833f5f 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -55,6 +55,7 @@ import SCons.SConsign
import SCons.Sig
import SCons.Sig.MD5
import SCons.Sig.TimeStamp
+import SCons.Subst
import SCons.Tool
import SCons.Util
import SCons.Warnings
@@ -374,7 +375,7 @@ class SubstitutionEnvironment:
gvars = self.gvars()
lvars = self.lvars()
lvars['__env__'] = self
- return SCons.Util.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
+ return SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
def subst_kw(self, kw, raw=0, target=None, source=None):
nkw = {}
@@ -386,12 +387,12 @@ class SubstitutionEnvironment:
return nkw
def subst_list(self, string, raw=0, target=None, source=None, conv=None):
- """Calls through to SCons.Util.scons_subst_list(). See
+ """Calls through to SCons.Subst.scons_subst_list(). See
the documentation for that function."""
gvars = self.gvars()
lvars = self.lvars()
lvars['__env__'] = self
- return SCons.Util.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
+ return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
def subst_path(self, path, target=None, source=None):
"""Substitute a path list, turning EntryProxies into Nodes
@@ -450,7 +451,7 @@ class SubstitutionEnvironment:
o = copy_non_reserved_keywords(overrides)
overrides = {}
for key, value in o.items():
- overrides[key] = SCons.Util.scons_subst_once(value, self, key)
+ overrides[key] = SCons.Subst.scons_subst_once(value, self, key)
if overrides:
env = OverrideEnvironment(self, overrides)
return env
@@ -774,7 +775,7 @@ class Base(SubstitutionEnvironment):
kw = copy_non_reserved_keywords(kw)
new = {}
for key, value in kw.items():
- new[key] = SCons.Util.scons_subst_once(value, self, key)
+ new[key] = SCons.Subst.scons_subst_once(value, self, key)
apply(clone.Replace, (), new)
if __debug__: logInstanceCreation(self, 'Environment.EnvironmentCopy')
return clone
@@ -1351,7 +1352,7 @@ class Base(SubstitutionEnvironment):
return result
def Literal(self, string):
- return SCons.Util.Literal(string)
+ return SCons.Subst.Literal(string)
def Local(self, *targets):
ret = []
@@ -1596,13 +1597,13 @@ def NoSubstitutionProxy(subject):
nkw = kwargs.copy()
nkw['gvars'] = {}
self.raw_to_mode(nkw)
- return apply(SCons.Util.scons_subst_list, nargs, nkw)
+ return apply(SCons.Subst.scons_subst_list, nargs, nkw)
def subst_target_source(self, string, *args, **kwargs):
nargs = (string, self,) + args
nkw = kwargs.copy()
nkw['gvars'] = {}
self.raw_to_mode(nkw)
- return apply(SCons.Util.scons_subst, nargs, nkw)
+ return apply(SCons.Subst.scons_subst, nargs, nkw)
return _NoSubstitutionProxy(subject)
if SCons.Memoize.use_old_memoization():
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 6eb61d9..9ecac89 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -49,6 +49,7 @@ from SCons.Debug import logInstanceCreation
import SCons.Errors
import SCons.Node
import SCons.Sig.MD5
+import SCons.Subst
import SCons.Util
import SCons.Warnings
@@ -362,28 +363,28 @@ def diskcheck_types():
class EntryProxy(SCons.Util.Proxy):
def __get_abspath(self):
entry = self.get()
- return SCons.Util.SpecialAttrWrapper(entry.get_abspath(),
+ return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
entry.name + "_abspath")
def __get_filebase(self):
name = self.get().name
- return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
+ return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
name + "_filebase")
def __get_suffix(self):
name = self.get().name
- return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
+ return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
name + "_suffix")
def __get_file(self):
name = self.get().name
- return SCons.Util.SpecialAttrWrapper(name, name + "_file")
+ return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
def __get_base_path(self):
"""Return the file's directory and file name, with the
suffix stripped."""
entry = self.get()
- return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
+ return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
entry.name + "_base")
def __get_posix_path(self):
@@ -394,7 +395,7 @@ class EntryProxy(SCons.Util.Proxy):
else:
entry = self.get()
r = string.replace(entry.get_path(), os.sep, '/')
- return SCons.Util.SpecialAttrWrapper(r, entry.name + "_posix")
+ return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
def __get_win32_path(self):
"""Return the path with \ as the path separator,
@@ -404,7 +405,7 @@ class EntryProxy(SCons.Util.Proxy):
else:
entry = self.get()
r = string.replace(entry.get_path(), os.sep, '\\')
- return SCons.Util.SpecialAttrWrapper(r, entry.name + "_win32")
+ return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_win32")
def __get_srcnode(self):
return EntryProxy(self.get().srcnode())
diff --git a/src/engine/SCons/Options/OptionsTests.py b/src/engine/SCons/Options/OptionsTests.py
index b908568..aeffe2d 100644
--- a/src/engine/SCons/Options/OptionsTests.py
+++ b/src/engine/SCons/Options/OptionsTests.py
@@ -29,7 +29,7 @@ import unittest
import TestSCons
import SCons.Options
-import SCons.Util
+import SCons.Subst
import SCons.Warnings
@@ -37,7 +37,7 @@ class Environment:
def __init__(self):
self.dict = {}
def subst(self, x):
- return SCons.Util.scons_subst(x, self, gvars=self.dict)
+ return SCons.Subst.scons_subst(x, self, gvars=self.dict)
def __setitem__(self, key, value):
self.dict[key] = value
def __getitem__(self, key):
diff --git a/src/engine/SCons/Platform/__init__.py b/src/engine/SCons/Platform/__init__.py
index 385f0c9..7a2f1c3 100644
--- a/src/engine/SCons/Platform/__init__.py
+++ b/src/engine/SCons/Platform/__init__.py
@@ -178,7 +178,7 @@ class TempFileMunge:
if not prefix:
prefix = '@'
- args = map(SCons.Util.quote_spaces, cmd[1:])
+ args = map(SCons.Subst.quote_spaces, cmd[1:])
open(tmp, 'w').write(string.join(args, " ") + "\n")
# XXX Using the SCons.Action.print_actions value directly
# like this is bogus, but expedient. This class should
diff --git a/src/engine/SCons/Subst.py b/src/engine/SCons/Subst.py
new file mode 100644
index 0000000..4767403
--- /dev/null
+++ b/src/engine/SCons/Subst.py
@@ -0,0 +1,824 @@
+"""SCons.Subst
+
+SCons string substitution.
+
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import __builtin__
+import re
+import string
+import types
+import UserList
+
+import SCons.Errors
+
+from SCons.Util import is_String, is_List
+
+# Indexed by the SUBST_* constants below.
+_strconv = [SCons.Util.to_String,
+ SCons.Util.to_String,
+ SCons.Util.to_String_for_signature]
+
+class Literal:
+ """A wrapper for a string. If you use this object wrapped
+ around a string, then it will be interpreted as literal.
+ When passed to the command interpreter, all special
+ characters will be escaped."""
+ def __init__(self, lstr):
+ self.lstr = lstr
+
+ def __str__(self):
+ return self.lstr
+
+ def escape(self, escape_func):
+ return escape_func(self.lstr)
+
+ def for_signature(self):
+ return self.lstr
+
+ def is_literal(self):
+ return 1
+
+class SpecialAttrWrapper:
+ """This is a wrapper for what we call a 'Node special attribute.'
+ This is any of the attributes of a Node that we can reference from
+ Environment variable substitution, such as $TARGET.abspath or
+ $SOURCES[1].filebase. We implement the same methods as Literal
+ so we can handle special characters, plus a for_signature method,
+ such that we can return some canonical string during signature
+ calculation to avoid unnecessary rebuilds."""
+
+ def __init__(self, lstr, for_signature=None):
+ """The for_signature parameter, if supplied, will be the
+ canonical string we return from for_signature(). Else
+ we will simply return lstr."""
+ self.lstr = lstr
+ if for_signature:
+ self.forsig = for_signature
+ else:
+ self.forsig = lstr
+
+ def __str__(self):
+ return self.lstr
+
+ def escape(self, escape_func):
+ return escape_func(self.lstr)
+
+ def for_signature(self):
+ return self.forsig
+
+ def is_literal(self):
+ return 1
+
+def quote_spaces(arg):
+ """Generic function for putting double quotes around any string that
+ has white space in it."""
+ if ' ' in arg or '\t' in arg:
+ return '"%s"' % arg
+ else:
+ return str(arg)
+
+class CmdStringHolder(SCons.Util.UserString):
+ """This is a special class used to hold strings generated by
+ scons_subst() and scons_subst_list(). It defines a special method
+ escape(). When passed a function with an escape algorithm for a
+ particular platform, it will return the contained string with the
+ proper escape sequences inserted.
+
+ This should really be a subclass of UserString, but that module
+ doesn't exist in Python 1.5.2."""
+ def __init__(self, cmd, literal=None):
+ SCons.Util.UserString.__init__(self, cmd)
+ self.literal = literal
+
+ def is_literal(self):
+ return self.literal
+
+ def escape(self, escape_func, quote_func=quote_spaces):
+ """Escape the string with the supplied function. The
+ function is expected to take an arbitrary string, then
+ return it with all special characters escaped and ready
+ for passing to the command interpreter.
+
+ After calling this function, the next call to str() will
+ return the escaped string.
+ """
+
+ if self.is_literal():
+ return escape_func(self.data)
+ elif ' ' in self.data or '\t' in self.data:
+ return quote_func(self.data)
+ else:
+ return self.data
+
+def escape_list(list, escape_func):
+ """Escape a list of arguments by running the specified escape_func
+ on every object in the list that has an escape() method."""
+ def escape(obj, escape_func=escape_func):
+ try:
+ e = obj.escape
+ except AttributeError:
+ return obj
+ else:
+ return e(escape_func)
+ return map(escape, list)
+
+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 _return_nodelist(self):
+ return self.nodelist
+ def _gen_nodelist(self):
+ 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 = SCons.Util.NodeList(map(self.func, list))
+ self._create_nodelist = self._return_nodelist
+ return self.nodelist
+ _create_nodelist = _gen_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()
+ if nl:
+ return str(nl[0])
+ return ''
+ def __repr__(self):
+ nl = self.nl._create_nodelist()
+ if nl:
+ return repr(nl[0])
+ return ''
+
+def subst_dict(target, source):
+ """Create a dictionary for substitution of special
+ construction variables.
+
+ This translates the following special arguments:
+
+ target - the target (object or array of objects),
+ used to generate the TARGET and TARGETS
+ construction variables
+
+ source - the source (object or array of objects),
+ used to generate the SOURCES and SOURCE
+ construction variables
+ """
+ dict = {}
+
+ if target:
+ tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
+ dict['TARGETS'] = Targets_or_Sources(tnl)
+ dict['TARGET'] = Target_or_Source(tnl)
+ else:
+ dict['TARGETS'] = None
+ dict['TARGET'] = None
+
+ if source:
+ def get_src_subst_proxy(node):
+ try:
+ rfile = node.rfile
+ except AttributeError:
+ pass
+ else:
+ node = rfile()
+ return node.get_subst_proxy()
+ snl = NLWrapper(source, get_src_subst_proxy)
+ dict['SOURCES'] = Targets_or_Sources(snl)
+ dict['SOURCE'] = Target_or_Source(snl)
+ else:
+ dict['SOURCES'] = None
+ dict['SOURCE'] = None
+
+ return dict
+
+# Constants for the "mode" parameter to scons_subst_list() and
+# scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
+# 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_CMD = 0
+SUBST_RAW = 1
+SUBST_SIG = 2
+
+_rm = re.compile(r'\$[()]')
+_remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
+
+# Indexed by the SUBST_* constants above.
+_regex_remove = [ _rm, None, _remove ]
+
+# Regular expressions for splitting strings and handling substitutions,
+# for use by the scons_subst() and scons_subst_list() functions:
+#
+# The first expression compiled matches all of the $-introduced tokens
+# that we need to process in some way, and is used for substitutions.
+# The expressions it matches are:
+#
+# "$$"
+# "$("
+# "$)"
+# "$variable" [must begin with alphabetic or underscore]
+# "${any stuff}"
+#
+# The second expression compiled is used for splitting strings into tokens
+# to be processed, and it matches all of the tokens listed above, plus
+# the following that affect how arguments do or don't get joined together:
+#
+# " " [white space]
+# "non-white-space" [without any dollar signs]
+# "$" [single dollar sign]
+#
+_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
+_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
+_separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
+
+# This regular expression is used to replace strings of multiple white
+# 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, gvars={}, lvars={}, conv=None):
+ """Expand a string containing construction variable substitutions.
+
+ This is the work-horse function for substitutions in file names
+ and the like. The companion scons_subst_list() function (below)
+ handles separating command lines into lists of arguments, so see
+ that function if that's what you're looking for.
+ """
+ if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
+ return strSubst
+
+ class StringSubber:
+ """A class to construct the results of a scons_subst() call.
+
+ This binds a specific construction environment, mode, target and
+ source with two methods (substitute() and expand()) that handle
+ the expansion.
+ """
+ def __init__(self, env, mode, target, source, conv, gvars):
+ self.env = env
+ self.mode = mode
+ self.target = target
+ self.source = source
+ self.conv = conv
+ self.gvars = gvars
+
+ def expand(self, s, lvars):
+ """Expand a single "token" as necessary, returning an
+ appropriate string containing the expansion.
+
+ This handles expanding different types of things (strings,
+ lists, callables) appropriately. It calls the wrapper
+ substitute() method to re-expand things as necessary, so that
+ the results of expansions of side-by-side strings still get
+ re-evaluated separately, not smushed together.
+ """
+ if is_String(s):
+ try:
+ s0, s1 = s[:2]
+ except (IndexError, ValueError):
+ return s
+ if s0 != '$':
+ return s
+ if s1 == '$':
+ return '$'
+ elif s1 in '()':
+ return s
+ else:
+ key = s[1:]
+ if key[0] == '{' or string.find(key, '.') >= 0:
+ if key[0] == '{':
+ key = key[1:-1]
+ try:
+ s = eval(key, self.gvars, lvars)
+ except AttributeError, e:
+ raise SCons.Errors.UserError, \
+ "Error trying to evaluate `%s': %s" % (s, e)
+ except (IndexError, NameError, TypeError):
+ return ''
+ except SyntaxError,e:
+ if self.target:
+ raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
+ else:
+ raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
+ else:
+ if lvars.has_key(key):
+ s = lvars[key]
+ elif self.gvars.has_key(key):
+ s = self.gvars[key]
+ else:
+ return ''
+
+ # Before re-expanding the result, handle
+ # recursive expansion by copying the local
+ # variable dictionary and overwriting a null
+ # string for the value of the variable name
+ # we just expanded.
+ #
+ # This could potentially be optimized by only
+ # copying lvars when s contains more expansions,
+ # but lvars is usually supposed to be pretty
+ # small, and deeply nested variable expansions
+ # are probably more the exception than the norm,
+ # so it should be tolerable for now.
+ lv = lvars.copy()
+ var = string.split(key, '.')[0]
+ lv[var] = ''
+ return self.substitute(s, lv)
+ elif is_List(s):
+ def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
+ return conv(substitute(l, lvars))
+ r = map(func, s)
+ return string.join(r)
+ elif callable(s):
+ try:
+ s = s(target=self.target,
+ source=self.source,
+ env=self.env,
+ for_signature=(self.mode != SUBST_CMD))
+ except TypeError:
+ # This probably indicates that it's a callable
+ # object that doesn't match our calling arguments
+ # (like an Action).
+ s = str(s)
+ return self.substitute(s, lvars)
+ elif s is None:
+ return ''
+ else:
+ return s
+
+ def substitute(self, args, lvars):
+ """Substitute expansions in an argument or list of arguments.
+
+ This serves as a wrapper for splitting up a string into
+ separate tokens.
+ """
+ if is_String(args) and not isinstance(args, CmdStringHolder):
+ try:
+ def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
+ return conv(expand(match.group(1), lvars))
+ result = _dollar_exps.sub(sub_match, args)
+ except TypeError:
+ # If the internal conversion routine doesn't return
+ # strings (it could be overridden to return Nodes, for
+ # example), then the 1.5.2 re module will throw this
+ # exception. Back off to a slower, general-purpose
+ # algorithm that works for all data types.
+ args = _separate_args.findall(args)
+ result = []
+ for a in args:
+ result.append(self.conv(self.expand(a, lvars)))
+ try:
+ result = string.join(result, '')
+ except TypeError:
+ if len(result) == 1:
+ result = result[0]
+ return result
+ else:
+ return self.expand(args, lvars)
+
+ if conv is None:
+ conv = _strconv[mode]
+
+ # 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(),
+ # it copies the current __builtin__ values for you. Avoid this by
+ # setting it explicitly and then deleting, so we don't pollute the
+ # construction environment Dictionary(ies) that are typically used
+ # for expansion.
+ gvars['__builtin__'] = __builtin__
+
+ ss = StringSubber(env, mode, target, source, conv, gvars)
+ result = ss.substitute(strSubst, lvars)
+
+ try:
+ del gvars['__builtin__']
+ except KeyError:
+ pass
+
+ if is_String(result):
+ # Remove $(-$) pairs and any stuff in between,
+ # if that's appropriate.
+ remove = _regex_remove[mode]
+ if remove:
+ result = remove.sub('', result)
+ if mode != SUBST_RAW:
+ # Compress strings of white space characters into
+ # a single space.
+ result = string.strip(_space_sep.sub(' ', result))
+
+ return result
+
+#Subst_List_Strings = {}
+
+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.
+
+ The companion scons_subst() function (above) handles basic
+ substitutions within strings, so see that function instead
+ if that's what you're looking for.
+ """
+# try:
+# Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
+# except KeyError:
+# Subst_List_Strings[strSubst] = 1
+# import SCons.Debug
+# SCons.Debug.caller(1)
+ class ListSubber(UserList.UserList):
+ """A class to construct the results of a scons_subst_list() call.
+
+ Like StringSubber, this class binds a specific construction
+ environment, mode, target and source with two methods
+ (substitute() and expand()) that handle the expansion.
+
+ In addition, however, this class is used to track the state of
+ the result(s) we're gathering so we can do the appropriate thing
+ whenever we have to append another word to the result--start a new
+ line, start a new word, append to the current word, etc. We do
+ this by setting the "append" attribute to the right method so
+ that our wrapper methods only need ever call ListSubber.append(),
+ and the rest of the object takes care of doing the right thing
+ internally.
+ """
+ def __init__(self, env, mode, target, source, conv, gvars):
+ UserList.UserList.__init__(self, [])
+ self.env = env
+ self.mode = mode
+ self.target = target
+ self.source = source
+ self.conv = conv
+ self.gvars = gvars
+
+ if self.mode == SUBST_RAW:
+ self.add_strip = lambda x, s=self: s.append(x)
+ else:
+ self.add_strip = lambda x, s=self: None
+ self.in_strip = None
+ self.next_line()
+
+ def expand(self, s, lvars, within_list):
+ """Expand a single "token" as necessary, appending the
+ expansion to the current result.
+
+ This handles expanding different types of things (strings,
+ lists, callables) appropriately. It calls the wrapper
+ substitute() method to re-expand things as necessary, so that
+ the results of expansions of side-by-side strings still get
+ re-evaluated separately, not smushed together.
+ """
+
+ if is_String(s):
+ try:
+ s0, s1 = s[:2]
+ except (IndexError, ValueError):
+ self.append(s)
+ return
+ if s0 != '$':
+ self.append(s)
+ return
+ if s1 == '$':
+ self.append('$')
+ elif s1 == '(':
+ self.open_strip('$(')
+ elif s1 == ')':
+ self.close_strip('$)')
+ else:
+ key = s[1:]
+ if key[0] == '{' or string.find(key, '.') >= 0:
+ if key[0] == '{':
+ key = key[1:-1]
+ try:
+ s = eval(key, self.gvars, lvars)
+ except AttributeError, e:
+ raise SCons.Errors.UserError, \
+ "Error trying to evaluate `%s': %s" % (s, e)
+ except (IndexError, NameError, TypeError):
+ return
+ except SyntaxError,e:
+ if self.target:
+ raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
+ else:
+ raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
+ else:
+ if lvars.has_key(key):
+ s = lvars[key]
+ elif self.gvars.has_key(key):
+ s = self.gvars[key]
+ else:
+ return
+
+ # Before re-expanding the result, handle
+ # recursive expansion by copying the local
+ # variable dictionary and overwriting a null
+ # string for the value of the variable name
+ # we just expanded.
+ lv = lvars.copy()
+ var = string.split(key, '.')[0]
+ lv[var] = ''
+ self.substitute(s, lv, 0)
+ self.this_word()
+ elif is_List(s):
+ for a in s:
+ self.substitute(a, lvars, 1)
+ self.next_word()
+ elif callable(s):
+ try:
+ s = s(target=self.target,
+ source=self.source,
+ env=self.env,
+ for_signature=(self.mode != SUBST_CMD))
+ except TypeError:
+ # This probably indicates that it's a callable
+ # object that doesn't match our calling arguments
+ # (like an Action).
+ s = str(s)
+ self.substitute(s, lvars, within_list)
+ elif s is None:
+ self.this_word()
+ else:
+ self.append(s)
+
+ def substitute(self, args, lvars, within_list):
+ """Substitute expansions in an argument or list of arguments.
+
+ This serves as a wrapper for splitting up a string into
+ separate tokens.
+ """
+
+ if is_String(args) and not isinstance(args, CmdStringHolder):
+ args = _separate_args.findall(args)
+ for a in args:
+ if a[0] in ' \t\n\r\f\v':
+ if '\n' in a:
+ self.next_line()
+ elif within_list:
+ self.append(a)
+ else:
+ self.next_word()
+ else:
+ self.expand(a, lvars, within_list)
+ else:
+ self.expand(args, lvars, within_list)
+
+ def next_line(self):
+ """Arrange for the next word to start a new line. This
+ is like starting a new word, except that we have to append
+ another line to the result."""
+ UserList.UserList.append(self, [])
+ self.next_word()
+
+ def this_word(self):
+ """Arrange for the next word to append to the end of the
+ current last word in the result."""
+ self.append = self.add_to_current_word
+
+ def next_word(self):
+ """Arrange for the next word to start a new word."""
+ self.append = self.add_new_word
+
+ def add_to_current_word(self, x):
+ """Append the string x to the end of the current last word
+ in the result. If that is not possible, then just add
+ it as a new word. Make sure the entire concatenated string
+ inherits the object attributes of x (in particular, the
+ escape function) by wrapping it as CmdStringHolder."""
+
+ if not self.in_strip or self.mode != SUBST_SIG:
+ try:
+ current_word = self[-1][-1]
+ except IndexError:
+ self.add_new_word(x)
+ else:
+ # All right, this is a hack and it should probably
+ # be refactored out of existence in the future.
+ # The issue is that we want to smoosh words together
+ # and make one file name that gets escaped if
+ # we're expanding something like foo$EXTENSION,
+ # but we don't want to smoosh them together if
+ # it's something like >$TARGET, because then we'll
+ # treat the '>' like it's part of the file name.
+ # So for now, just hard-code looking for the special
+ # command-line redirection characters...
+ try:
+ last_char = str(current_word)[-1]
+ except IndexError:
+ last_char = '\0'
+ if last_char in '<>|':
+ self.add_new_word(x)
+ else:
+ y = current_word + x
+ literal1 = self.literal(self[-1][-1])
+ literal2 = self.literal(x)
+ y = self.conv(y)
+ if is_String(y):
+ y = CmdStringHolder(y, literal1 or literal2)
+ self[-1][-1] = y
+
+ def add_new_word(self, x):
+ if not self.in_strip or self.mode != SUBST_SIG:
+ literal = self.literal(x)
+ x = self.conv(x)
+ if is_String(x):
+ x = CmdStringHolder(x, literal)
+ self[-1].append(x)
+ self.append = self.add_to_current_word
+
+ def literal(self, x):
+ try:
+ l = x.is_literal
+ except AttributeError:
+ return None
+ else:
+ return l()
+
+ def open_strip(self, x):
+ """Handle the "open strip" $( token."""
+ self.add_strip(x)
+ self.in_strip = 1
+
+ def close_strip(self, x):
+ """Handle the "close strip" $) token."""
+ self.add_strip(x)
+ self.in_strip = None
+
+ if conv is None:
+ conv = _strconv[mode]
+
+ # 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(),
+ # it copies the current __builtin__ values for you. Avoid this by
+ # setting it explicitly and then deleting, so we don't pollute the
+ # construction environment Dictionary(ies) that are typically used
+ # for expansion.
+ gvars['__builtins__'] = __builtins__
+
+ ls = ListSubber(env, mode, target, source, conv, gvars)
+ ls.substitute(strSubst, lvars, 0)
+
+ try:
+ del gvars['__builtins__']
+ except KeyError:
+ pass
+
+ return ls.data
+
+def scons_subst_once(strSubst, env, key):
+ """Perform single (non-recursive) substitution of a single
+ construction variable keyword.
+
+ This is used when setting a variable when copying or overriding values
+ in an Environment. We want to capture (expand) the old value before
+ we override it, so people can do things like:
+
+ env2 = env.Copy(CCFLAGS = '$CCFLAGS -g')
+
+ We do this with some straightforward, brute-force code here...
+ """
+ if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
+ return strSubst
+
+ matchlist = ['$' + key, '${' + key + '}']
+ val = env.get(key, '')
+ def sub_match(match, val=val, matchlist=matchlist):
+ a = match.group(1)
+ if a in matchlist:
+ a = val
+ if is_List(a):
+ return string.join(map(str, a))
+ else:
+ return str(a)
+
+ if is_List(strSubst):
+ result = []
+ for arg in strSubst:
+ if is_String(arg):
+ if arg in matchlist:
+ arg = val
+ if is_List(arg):
+ result.extend(arg)
+ else:
+ result.append(arg)
+ else:
+ result.append(_dollar_exps.sub(sub_match, arg))
+ else:
+ result.append(arg)
+ return result
+ elif is_String(strSubst):
+ return _dollar_exps.sub(sub_match, strSubst)
+ else:
+ return strSubst
diff --git a/src/engine/SCons/SubstTests.py b/src/engine/SCons/SubstTests.py
new file mode 100644
index 0000000..64a2fe5
--- /dev/null
+++ b/src/engine/SCons/SubstTests.py
@@ -0,0 +1,1135 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import os.path
+import string
+import StringIO
+import sys
+import types
+import unittest
+
+from UserDict import UserDict
+
+import SCons.Errors
+
+from SCons.Subst import *
+
+class DummyNode:
+ """Simple node work-alike."""
+ def __init__(self, name):
+ self.name = os.path.normpath(name)
+ def __str__(self):
+ return self.name
+ def is_literal(self):
+ return 1
+ def rfile(self):
+ return self
+ def get_subst_proxy(self):
+ return self
+
+class DummyEnv:
+ def __init__(self, dict={}):
+ self.dict = dict
+
+ def Dictionary(self, key = None):
+ if not key:
+ return self.dict
+ return self.dict[key]
+
+ def __getitem__(self, key):
+ return self.dict[key]
+
+ def get(self, key, default):
+ return self.dict.get(key, default)
+
+ def sig_dict(self):
+ dict = self.dict.copy()
+ dict["TARGETS"] = 'tsig'
+ dict["SOURCES"] = 'ssig'
+ return dict
+
+def cs(target=None, source=None, env=None, for_signature=None):
+ return 'cs'
+
+def cl(target=None, source=None, env=None, for_signature=None):
+ return ['cl']
+
+def CmdGen1(target, source, env, for_signature):
+ # Nifty trick...since Environment references are interpolated,
+ # instantiate an instance of a callable class with this one,
+ # which will then get evaluated.
+ assert str(target) == 't', target
+ assert str(source) == 's', source
+ return "${CMDGEN2('foo', %d)}" % for_signature
+
+class CmdGen2:
+ def __init__(self, mystr, forsig):
+ self.mystr = mystr
+ self.expect_for_signature = forsig
+
+ def __call__(self, target, source, env, for_signature):
+ assert str(target) == 't', target
+ assert str(source) == 's', source
+ assert for_signature == self.expect_for_signature, for_signature
+ return [ self.mystr, env.Dictionary('BAR') ]
+
+if os.sep == '/':
+ def cvt(str):
+ return str
+else:
+ def cvt(str):
+ return string.replace(str, '/', os.sep)
+
+class SubstTestCase(unittest.TestCase):
+ def test_subst(self):
+ """Test the subst() function"""
+ class MyNode(DummyNode):
+ """Simple node work-alike with some extra stuff for testing."""
+ def __init__(self, name):
+ DummyNode.__init__(self, name)
+ class Attribute:
+ pass
+ self.attribute = Attribute()
+ self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
+ self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
+ def get_stuff(self, extra):
+ return self.name + extra
+ foo = 1
+
+ class TestLiteral:
+ def __init__(self, literal):
+ self.literal = literal
+ def __str__(self):
+ return self.literal
+ def is_literal(self):
+ return 1
+
+ class TestCallable:
+ def __init__(self, value):
+ self.value = value
+ def __call__(self):
+ pass
+ def __str__(self):
+ return self.value
+
+ def function_foo(arg):
+ pass
+
+ target = [ MyNode("./foo/bar.exe"),
+ MyNode("/bar/baz.obj"),
+ MyNode("../foo/baz.obj") ]
+ source = [ MyNode("./foo/blah.cpp"),
+ MyNode("/bar/ack.cpp"),
+ MyNode("../foo/ack.c") ]
+
+ loc = {
+ 'xxx' : None,
+ 'null' : '',
+ 'zero' : 0,
+ 'one' : 1,
+ 'BAR' : 'baz',
+ 'ONE' : '$TWO',
+ 'TWO' : '$THREE',
+ 'THREE' : 'four',
+
+ 'AAA' : 'a',
+ 'BBB' : 'b',
+ 'CCC' : 'c',
+
+ # $XXX$HHH should expand to GGGIII, not BADNEWS.
+ 'XXX' : '$FFF',
+ 'FFF' : 'GGG',
+ 'HHH' : 'III',
+ 'FFFIII' : 'BADNEWS',
+
+ 'LITERAL' : TestLiteral("$XXX"),
+
+ # Test that we can expand to and return a function.
+ #'FUNCTION' : function_foo,
+
+ 'CMDGEN1' : CmdGen1,
+ 'CMDGEN2' : CmdGen2,
+
+ 'NOTHING' : "",
+ 'NONE' : None,
+
+ # Test various combinations of strings, lists and functions.
+ 'N' : None,
+ 'X' : 'x',
+ 'Y' : '$X',
+ 'R' : '$R',
+ 'S' : 'x y',
+ 'LS' : ['x y'],
+ 'L' : ['x', 'y'],
+ 'CS' : cs,
+ 'CL' : cl,
+
+ # Test function calls within ${}.
+ 'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}',
+ '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',
+ 'SSS' : '$RRR',
+
+ # Test callables that don't match the calling arguments.
+ 'CALLABLE' : TestCallable('callable-1'),
+ }
+
+ env = DummyEnv(loc)
+
+ # Basic tests of substitution functionality.
+ cases = [
+ # Basics: strings without expansions are left alone, and
+ # the simplest possible expansion to a null-string value.
+ "test", "test",
+ "$null", "",
+
+ # Test expansion of integer values.
+ "test $zero", "test 0",
+ "test $one", "test 1",
+
+ # Test multiple re-expansion of values.
+ "test $ONE", "test four",
+
+ # Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions.
+ "test $TARGETS $SOURCES",
+ "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c",
+
+ "test ${TARGETS[:]} ${SOURCES[0]}",
+ "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp",
+
+ "test ${TARGETS[1:]}v",
+ "test /bar/baz.obj ../foo/baz.objv",
+
+ "test $TARGET",
+ "test foo/bar.exe",
+
+ "test $TARGET$FOO[0]",
+ "test foo/bar.exe[0]",
+
+ "test $TARGETS.foo",
+ "test 1 1 1",
+
+ "test ${SOURCES[0:2].foo}",
+ "test 1 1",
+
+ "test $SOURCE.foo",
+ "test 1",
+
+ "test ${TARGET.get_stuff('blah')}",
+ "test foo/bar.exeblah",
+
+ "test ${SOURCES.get_stuff('blah')}",
+ "test foo/blah.cppblah /bar/ack.cppblah ../foo/ack.cblah",
+
+ "test ${SOURCES[0:2].get_stuff('blah')}",
+ "test foo/blah.cppblah /bar/ack.cppblah",
+
+ "test ${SOURCES[0:2].get_stuff('blah')}",
+ "test foo/blah.cppblah /bar/ack.cppblah",
+
+ "test ${SOURCES.attribute.attr1}",
+ "test attr$1-blah.cpp attr$1-ack.cpp attr$1-ack.c",
+
+ "test ${SOURCES.attribute.attr2}",
+ "test attr$2-blah.cpp attr$2-ack.cpp attr$2-ack.c",
+
+ # Test adjacent expansions.
+ "foo$BAR",
+ "foobaz",
+
+ "foo${BAR}",
+ "foobaz",
+
+ # Test that adjacent expansions don't get re-interpreted
+ # together. The correct disambiguated expansion should be:
+ # $XXX$HHH => ${FFF}III => GGGIII
+ # not:
+ # $XXX$HHH => ${FFFIII} => BADNEWS
+ "$XXX$HHH", "GGGIII",
+
+ # Test double-dollar-sign behavior.
+ "$$FFF$HHH", "$FFFIII",
+
+ # Test that a Literal will stop dollar-sign substitution.
+ "$XXX $LITERAL $FFF", "GGG $XXX GGG",
+
+ # Test that we don't blow up even if they subscript
+ # something in ways they "can't."
+ "${FFF[0]}", "G",
+ "${FFF[7]}", "",
+ "${NOTHING[1]}", "",
+ "${NONE[2]}", "",
+
+ # Test various combinations of strings and lists.
+ #None, '',
+ '', '',
+ 'x', 'x',
+ 'x y', 'x y',
+ '$N', '',
+ '$X', 'x',
+ '$Y', 'x',
+ '$R', '',
+ '$S', 'x y',
+ '$LS', 'x y',
+ '$L', 'x y',
+ '$S z', 'x y z',
+ '$LS z', 'x y z',
+ '$L z', 'x y z',
+ #cs, 'cs',
+ #cl, 'cl',
+ '$CS', 'cs',
+ '$CL', 'cl',
+
+ # Test function calls within ${}.
+ '$FUNCCALL', 'a xc b',
+
+ # Bug reported by Christoph Wiedemann.
+ cvt('$xxx/bin'), '/bin',
+
+ # Tests callables that don't match our calling arguments.
+ '$CALLABLE', 'callable-1',
+
+ # Test handling of quotes.
+ 'aaa "bbb ccc" ddd', 'aaa "bbb ccc" ddd',
+ ]
+
+ kwargs = {'target' : target, 'source' : source,
+ 'gvars' : env.Dictionary()}
+
+ failed = 0
+ while cases:
+ input, expect = cases[:2]
+ expect = cvt(expect)
+ result = apply(scons_subst, (input, env), kwargs)
+ if result != expect:
+ if failed == 0: print
+ print " input %s => %s did not match %s" % (repr(input), repr(result), repr(expect))
+ failed = failed + 1
+ 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",
+ "test ",
+ "test",
+ "test",
+
+ "test $($xxx$)",
+ "test $($)",
+ "test",
+ "test",
+
+ "test $( $xxx $)",
+ "test $( $)",
+ "test",
+ "test",
+
+ "$AAA ${AAA}A $BBBB $BBB",
+ "a aA b",
+ "a aA b",
+ "a aA b",
+
+ "$RECURSE",
+ "foo bar",
+ "foo bar",
+ "foo bar",
+
+ "$RRR",
+ "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",
+ ]
+
+ gvars = env.Dictionary()
+
+ failed = 0
+ while subst_cases:
+ input, eraw, ecmd, esig = subst_cases[:4]
+ 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, 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, gvars=gvars)
+ if result != esig:
+ if failed == 0: print
+ print " input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
+ failed = failed + 1
+ 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],
+ gvars={})
+ assert result == "t1 s1 s2", 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'),
+ gvars=gvars)
+ assert newcom == "test foo baz s t", newcom
+
+ # Test that we handle attribute errors during expansion as expected.
+ try:
+ class Foo:
+ pass
+ scons_subst('${foo.bar}', env, gvars={'foo':Foo()})
+ except SCons.Errors.UserError, e:
+ expect = [
+ "Error trying to evaluate `${foo.bar}': bar",
+ "Error trying to evaluate `${foo.bar}': Foo instance has no attribute 'bar'",
+ ]
+ assert str(e) in expect, e
+ else:
+ raise AssertionError, "did not catch expected UserError"
+
+ # Test that we handle syntax errors during expansion as expected.
+ try:
+ scons_subst('$foo.bar.3.0', env)
+ except SCons.Errors.UserError, e:
+ expect1 = "Syntax error `invalid syntax' trying to evaluate `$foo.bar.3.0'"
+ expect2 = "Syntax error `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'"
+ assert str(e) in [expect1, expect2], e
+ else:
+ raise AssertionError, "did not catch expected UserError"
+
+ # Test how we handle overriding the internal conversion routines.
+ def s(obj):
+ return obj
+
+ n1 = MyNode('n1')
+ env = DummyEnv({'NODE' : n1})
+ 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, gvars=gvars)
+ assert node is n1, node
+ 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})
+ #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, gvars=gvars)
+ #assert func is function_foo, func
+ #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, gvars=env.Dictionary())
+ assert result == 'xxx', result
+ result = scons_subst('$XXX', env, gvars={'XXX' : 'yyy'})
+ assert result == 'yyy', result
+
+ def test_CLVar(self):
+ """Test scons_subst() and scons_subst_list() with CLVar objects"""
+
+ loc = {}
+ loc['FOO'] = 'foo'
+ loc['BAR'] = SCons.Util.CLVar('bar')
+ loc['CALL'] = lambda target, source, env, for_signature: 'call'
+ env = DummyEnv(loc)
+
+ cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
+
+ newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
+ assert newcmd == 'test foo bar call test', newcmd
+
+ 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]
+ assert cmd_list[0][2] == "bar", cmd_list[0][2]
+ assert cmd_list[0][3] == "call", cmd_list[0][3]
+ assert cmd_list[0][4] == "test", cmd_list[0][4]
+
+ def test_subst_list(self):
+ """Testing the scons_subst_list() method..."""
+ class MyNode(DummyNode):
+ """Simple node work-alike with some extra stuff for testing."""
+ def __init__(self, name):
+ DummyNode.__init__(self, name)
+ class Attribute:
+ pass
+ self.attribute = Attribute()
+ self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
+ self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
+
+ class TestCallable:
+ def __init__(self, value):
+ self.value = value
+ def __call__(self):
+ pass
+ def __str__(self):
+ return self.value
+
+ target = [ MyNode("./foo/bar.exe"),
+ MyNode("/bar/baz with spaces.obj"),
+ MyNode("../foo/baz.obj") ]
+ source = [ MyNode("./foo/blah with spaces.cpp"),
+ MyNode("/bar/ack.cpp"),
+ MyNode("../foo/ack.c") ]
+
+ def _defines(defs):
+ l = []
+ for d in defs:
+ if SCons.Util.is_List(d) or type(d) is types.TupleType:
+ l.append(str(d[0]) + '=' + str(d[1]))
+ else:
+ l.append(str(d))
+ return l
+
+ loc = {
+ 'xxx' : None,
+ 'NEWLINE' : 'before\nafter',
+
+ 'AAA' : 'a',
+ 'BBB' : 'b',
+ 'CCC' : 'c',
+
+ 'DO' : DummyNode('do something'),
+ 'FOO' : DummyNode('foo.in'),
+ 'BAR' : DummyNode('bar with spaces.out'),
+ 'CRAZY' : DummyNode('crazy\nfile.in'),
+
+ # $XXX$HHH should expand to GGGIII, not BADNEWS.
+ 'XXX' : '$FFF',
+ 'FFF' : 'GGG',
+ 'HHH' : 'III',
+ 'FFFIII' : 'BADNEWS',
+
+ 'CMDGEN1' : CmdGen1,
+ 'CMDGEN2' : CmdGen2,
+
+ 'LITERALS' : [ Literal('foo\nwith\nnewlines'),
+ Literal('bar\nwith\nnewlines') ],
+
+ # Test various combinations of strings, lists and functions.
+ 'N' : None,
+ 'X' : 'x',
+ 'Y' : '$X',
+ 'R' : '$R',
+ 'S' : 'x y',
+ 'LS' : ['x y'],
+ 'L' : ['x', 'y'],
+ 'CS' : cs,
+ 'CL' : cl,
+
+ # Test function calls within ${}.
+ 'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}',
+ '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',
+ 'SSS' : '$RRR',
+
+ # Test callable objects that don't match our calling arguments.
+ 'CALLABLE' : TestCallable('callable-2'),
+
+ '_defines' : _defines,
+ 'DEFS' : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ],
+ }
+
+ env = DummyEnv(loc)
+
+ cases = [
+ "$TARGETS",
+ [
+ ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
+ ],
+
+ "$SOURCES $NEWLINE $TARGETS",
+ [
+ ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
+ ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
+ ],
+
+ "$SOURCES$NEWLINE",
+ [
+ ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
+ ["after"],
+ ],
+
+ "foo$FFF",
+ [
+ ["fooGGG"],
+ ],
+
+ "foo${FFF}",
+ [
+ ["fooGGG"],
+ ],
+
+ "test ${SOURCES.attribute.attr1}",
+ [
+ ["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"],
+ ],
+
+ "test ${SOURCES.attribute.attr2}",
+ [
+ ["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"],
+ ],
+
+ "$DO --in=$FOO --out=$BAR",
+ [
+ ["do something", "--in=foo.in", "--out=bar with spaces.out"],
+ ],
+
+ # This test is now fixed, and works like it should.
+ "$DO --in=$CRAZY --out=$BAR",
+ [
+ ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
+ ],
+
+ # Try passing a list to scons_subst_list().
+ [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
+ [
+ ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
+ ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
+ ],
+
+ # Test against a former bug in scons_subst_list().
+ "$XXX$HHH",
+ [
+ ["GGGIII"],
+ ],
+
+ # Test double-dollar-sign behavior.
+ "$$FFF$HHH",
+ [
+ ["$FFFIII"],
+ ],
+
+ # Test various combinations of strings, lists and functions.
+ None, [[]],
+ [None], [[]],
+ '', [[]],
+ [''], [[]],
+ 'x', [['x']],
+ ['x'], [['x']],
+ 'x y', [['x', 'y']],
+ ['x y'], [['x y']],
+ ['x', 'y'], [['x', 'y']],
+ '$N', [[]],
+ ['$N'], [[]],
+ '$X', [['x']],
+ ['$X'], [['x']],
+ '$Y', [['x']],
+ ['$Y'], [['x']],
+ #'$R', [[]],
+ #['$R'], [[]],
+ '$S', [['x', 'y']],
+ '$S z', [['x', 'y', 'z']],
+ ['$S'], [['x', 'y']],
+ ['$S z'], [['x', 'y z']], # XXX - IS THIS BEST?
+ ['$S', 'z'], [['x', 'y', 'z']],
+ '$LS', [['x y']],
+ '$LS z', [['x y', 'z']],
+ ['$LS'], [['x y']],
+ ['$LS z'], [['x y z']],
+ ['$LS', 'z'], [['x y', 'z']],
+ '$L', [['x', 'y']],
+ '$L z', [['x', 'y', 'z']],
+ ['$L'], [['x', 'y']],
+ ['$L z'], [['x', 'y z']], # XXX - IS THIS BEST?
+ ['$L', 'z'], [['x', 'y', 'z']],
+ cs, [['cs']],
+ [cs], [['cs']],
+ cl, [['cl']],
+ [cl], [['cl']],
+ '$CS', [['cs']],
+ ['$CS'], [['cs']],
+ '$CL', [['cl']],
+ ['$CL'], [['cl']],
+
+ # Test function calls within ${}.
+ '$FUNCCALL', [['a', 'xc', 'b']],
+
+ # Test handling of newlines in white space.
+ 'foo\nbar', [['foo'], ['bar']],
+ 'foo\n\nbar', [['foo'], ['bar']],
+ 'foo \n \n bar', [['foo'], ['bar']],
+ 'foo \nmiddle\n bar', [['foo'], ['middle'], ['bar']],
+
+ # Bug reported by Christoph Wiedemann.
+ cvt('$xxx/bin'), [['/bin']],
+
+ # Test variables smooshed together with different prefixes.
+ 'foo$AAA', [['fooa']],
+ '<$AAA', [['<', 'a']],
+ '>$AAA', [['>', 'a']],
+ '|$AAA', [['|', 'a']],
+
+ # Test callables that don't match our calling arguments.
+ '$CALLABLE', [['callable-2']],
+
+ # Test
+
+ # Test handling of quotes.
+ # XXX Find a way to handle this in the future.
+ #'aaa "bbb ccc" ddd', [['aaa', 'bbb ccc', 'ddd']],
+
+ '${_defines(DEFS)}', [['Q1="q1"', 'Q2="a"']],
+ ]
+
+ gvars = env.Dictionary()
+
+ kwargs = {'target' : target, 'source' : source, 'gvars' : gvars}
+
+ failed = 0
+ while cases:
+ input, expect = cases[:2]
+ expect = map(lambda l: map(cvt, l), expect)
+ result = apply(scons_subst_list, (input, env), kwargs)
+ if result != expect:
+ if failed == 0: print
+ print " input %s => %s did not match %s" % (repr(input), result, repr(expect))
+ failed = failed + 1
+ 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],
+ gvars=gvars)
+ assert result == [['t1', 's1', 's2']], result
+ result = scons_subst_list("$TARGET $SOURCES", env,
+ target=[t1, t2],
+ source=[s1, s2],
+ 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,
+ 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, gvars=gvars)
+ assert cmd_list == [['abc',
+ 'foo\nwith\nnewlines',
+ 'bar\nwith\nnewlines',
+ 'xyz']], cmd_list
+ c = cmd_list[0][0].escape(escape_func)
+ assert c == 'abc', c
+ c = cmd_list[0][1].escape(escape_func)
+ assert c == '**foo\nwith\nnewlines**', c
+ c = cmd_list[0][2].escape(escape_func)
+ assert c == '**bar\nwith\nnewlines**', c
+ c = cmd_list[0][3].escape(escape_func)
+ assert c == 'xyz', c
+
+ 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)
+ assert c == '**bar\nwith\nnewlinesxyz**', c
+
+ # Tests of the various SUBST_* modes of substitution.
+ subst_list_cases = [
+ "test $xxx",
+ [["test"]],
+ [["test"]],
+ [["test"]],
+
+ "test $($xxx$)",
+ [["test", "$($)"]],
+ [["test"]],
+ [["test"]],
+
+ "test $( $xxx $)",
+ [["test", "$(", "$)"]],
+ [["test"]],
+ [["test"]],
+
+ "$AAA ${AAA}A $BBBB $BBB",
+ [["a", "aA", "b"]],
+ [["a", "aA", "b"]],
+ [["a", "aA", "b"]],
+
+ "$RECURSE",
+ [["foo", "bar"]],
+ [["foo", "bar"]],
+ [["foo", "bar"]],
+
+ "$RRR",
+ [["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"]],
+ ]
+
+ 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, 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, 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, gvars=gvars)
+ if result != esig:
+ if failed == 0: print
+ print " input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
+ failed = failed + 1
+ del subst_list_cases[:4]
+ assert failed == 0, "%d subst() mode cases failed" % failed
+
+ # Test that we handle attribute errors during expansion as expected.
+ try:
+ class Foo:
+ pass
+ scons_subst_list('${foo.bar}', env, gvars={'foo':Foo()})
+ except SCons.Errors.UserError, e:
+ expect = [
+ "Error trying to evaluate `${foo.bar}': bar",
+ "Error trying to evaluate `${foo.bar}': Foo instance has no attribute 'bar'",
+ ]
+ assert str(e) in expect, e
+ else:
+ raise AssertionError, "did not catch expected UserError"
+
+ # Test that we handle syntax errors during expansion as expected.
+ try:
+ scons_subst_list('$foo.bar.3.0', env)
+ except SCons.Errors.UserError, e:
+ expect1 = "Syntax error `invalid syntax' trying to evaluate `$foo.bar.3.0'"
+ expect2 = "Syntax error `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'"
+ assert str(e) in [expect1, expect2], e
+ else:
+ raise AssertionError, "did not catch expected SyntaxError"
+
+ # Test we handle overriding the internal conversion routines.
+ def s(obj):
+ return obj
+
+ n1 = MyNode('n1')
+ env = DummyEnv({'NODE' : n1})
+ 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, gvars=gvars)
+ assert node == [[n1]], node
+ 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, gvars=env.Dictionary())
+ assert result == [['xxx']], result
+ result = scons_subst_list('$XXX', env, gvars={'XXX' : 'yyy'})
+ assert result == [['yyy']], result
+
+ def test_subst_once(self):
+ """Testing the scons_subst_once() method"""
+
+ loc = {
+ 'CCFLAGS' : '-DFOO',
+ 'ONE' : 1,
+ 'RECURSE' : 'r $RECURSE r',
+ 'LIST' : ['a', 'b', 'c'],
+ }
+
+ env = DummyEnv(loc)
+
+ cases = [
+ '$CCFLAGS -DBAR',
+ 'OTHER_KEY',
+ '$CCFLAGS -DBAR',
+
+ '$CCFLAGS -DBAR',
+ 'CCFLAGS',
+ '-DFOO -DBAR',
+
+ 'x $ONE y',
+ 'ONE',
+ 'x 1 y',
+
+ 'x $RECURSE y',
+ 'RECURSE',
+ 'x r $RECURSE r y',
+
+ '$LIST',
+ 'LIST',
+ 'a b c',
+
+ ['$LIST'],
+ 'LIST',
+ ['a', 'b', 'c'],
+
+ ['x', '$LIST', 'y'],
+ 'LIST',
+ ['x', 'a', 'b', 'c', 'y'],
+
+ ['x', 'x $LIST y', 'y'],
+ 'LIST',
+ ['x', 'x a b c y', 'y'],
+
+ ['x', 'x $CCFLAGS y', 'y'],
+ 'LIST',
+ ['x', 'x $CCFLAGS y', 'y'],
+
+ ['x', 'x $RECURSE y', 'y'],
+ 'LIST',
+ ['x', 'x $RECURSE y', 'y'],
+ ]
+
+ failed = 0
+ while cases:
+ input, key, expect = cases[:3]
+ result = scons_subst_once(input, env, key)
+ if result != expect:
+ if failed == 0: print
+ print " input %s (%s) => %s did not match %s" % (repr(input), repr(key), repr(result), repr(expect))
+ failed = failed + 1
+ del cases[:3]
+ assert failed == 0, "%d subst() cases failed" % failed
+
+ def test_quote_spaces(self):
+ """Testing the quote_spaces() method..."""
+ q = quote_spaces('x')
+ assert q == 'x', q
+
+ q = quote_spaces('x x')
+ assert q == '"x x"', q
+
+ q = quote_spaces('x\tx')
+ assert q == '"x\tx"', q
+
+ class Node:
+ def __init__(self, name, children=[]):
+ self.children = children
+ self.name = name
+ def __str__(self):
+ return self.name
+ def exists(self):
+ return 1
+ def rexists(self):
+ return 1
+ def has_builder(self):
+ return 1
+ def has_explicit_builder(self):
+ return 1
+ def side_effect(self):
+ return 1
+ def precious(self):
+ return 1
+ def always_build(self):
+ return 1
+ def current(self):
+ return 1
+
+ def test_Literal(self):
+ """Test the Literal() function."""
+ input_list = [ '$FOO', Literal('$BAR') ]
+ gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
+
+ def escape_func(cmd):
+ return '**' + cmd + '**'
+
+ cmd_list = scons_subst_list(input_list, None, gvars=gvars)
+ cmd_list = 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') ]
+ gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
+
+ def escape_func(cmd):
+ return '**' + cmd + '**'
+
+ cmd_list = scons_subst_list(input_list, None, gvars=gvars)
+ cmd_list = escape_list(cmd_list[0], escape_func)
+ assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
+
+ cmd_list = scons_subst_list(input_list, None, mode=SUBST_SIG, gvars=gvars)
+ cmd_list = escape_list(cmd_list[0], escape_func)
+ assert cmd_list == ['BAZ', '**BLEH**'], cmd_list
+
+ def test_subst_dict(self):
+ """Test substituting dictionary values in an Action
+ """
+ t = DummyNode('t')
+ s = DummyNode('s')
+ d = subst_dict(target=t, source=s)
+ 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']
+
+ t1 = DummyNode('t1')
+ t2 = DummyNode('t2')
+ s1 = DummyNode('s1')
+ s2 = DummyNode('s2')
+ d = subst_dict(target=[t1, t2], source=[s1, s2])
+ TARGETS = map(lambda x: str(x), d['TARGETS'])
+ TARGETS.sort()
+ assert TARGETS == ['t1', 't2'], d['TARGETS']
+ assert str(d['TARGET']) == 't1', d['TARGET']
+ SOURCES = map(lambda x: str(x), d['SOURCES'])
+ SOURCES.sort()
+ assert SOURCES == ['s1', 's2'], d['SOURCES']
+ assert str(d['SOURCE']) == 's1', d['SOURCE']
+
+ class V:
+ # Fake Value node with no rfile() method.
+ def __init__(self, name):
+ self.name = name
+ def __str__(self):
+ return 'v-'+self.name
+ def get_subst_proxy(self):
+ return self
+
+ class N(V):
+ def rfile(self):
+ return self.__class__('rstr-' + self.name)
+
+ t3 = N('t3')
+ t4 = DummyNode('t4')
+ t5 = V('t5')
+ s3 = DummyNode('s3')
+ s4 = N('s4')
+ s5 = V('s5')
+ d = subst_dict(target=[t3, t4, t5], source=[s3, s4, s5])
+ TARGETS = map(lambda x: str(x), d['TARGETS'])
+ TARGETS.sort()
+ assert TARGETS == ['t4', 'v-t3', 'v-t5'], TARGETS
+ SOURCES = map(lambda x: str(x), d['SOURCES'])
+ SOURCES.sort()
+ assert SOURCES == ['s3', 'v-rstr-s4', 'v-s5'], SOURCES
+
+if __name__ == "__main__":
+ suite = unittest.makeSuite(SubstTestCase, 'test_')
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 1731655..90e5393 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -29,20 +29,18 @@ Various utility functions go here.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
+import __builtin__
import copy
import os
import os.path
import re
-import stat
import string
import sys
+import stat
import types
-from UserDict import UserDict
-import UserList
-
-import SCons.Errors
+from UserDict import UserDict
+from UserList import UserList
try:
from UserString import UserString
@@ -96,7 +94,6 @@ except ImportError:
__rmul__ = __mul__
#
-import __builtin__
try:
__builtin__.zip
except AttributeError:
@@ -187,61 +184,7 @@ def to_String_for_signature(obj):
else:
return f()
-# Indexed by the SUBST_* constants below.
-_strconv = [to_String, to_String, to_String_for_signature]
-
-class Literal:
- """A wrapper for a string. If you use this object wrapped
- around a string, then it will be interpreted as literal.
- When passed to the command interpreter, all special
- characters will be escaped."""
- def __init__(self, lstr):
- self.lstr = lstr
-
- def __str__(self):
- return self.lstr
-
- def escape(self, escape_func):
- return escape_func(self.lstr)
-
- def for_signature(self):
- return self.lstr
-
- def is_literal(self):
- return 1
-
-class SpecialAttrWrapper:
- """This is a wrapper for what we call a 'Node special attribute.'
- This is any of the attributes of a Node that we can reference from
- Environment variable substitution, such as $TARGET.abspath or
- $SOURCES[1].filebase. We implement the same methods as Literal
- so we can handle special characters, plus a for_signature method,
- such that we can return some canonical string during signature
- calculation to avoid unnecessary rebuilds."""
-
- def __init__(self, lstr, for_signature=None):
- """The for_signature parameter, if supplied, will be the
- canonical string we return from for_signature(). Else
- we will simply return lstr."""
- self.lstr = lstr
- if for_signature:
- self.forsig = for_signature
- else:
- self.forsig = lstr
-
- def __str__(self):
- return self.lstr
-
- def escape(self, escape_func):
- return escape_func(self.lstr)
-
- def for_signature(self):
- return self.forsig
-
- def is_literal(self):
- return 1
-
-class CallableComposite(UserList.UserList):
+class CallableComposite(UserList):
"""A simple composite callable class that, when called, will invoke all
of its contained callables with the same arguments."""
def __call__(self, *args, **kwargs):
@@ -253,7 +196,7 @@ class CallableComposite(UserList.UserList):
return self.__class__(retvals)
return NodeList(retvals)
-class NodeList(UserList.UserList):
+class NodeList(UserList):
"""This class is almost exactly like a regular list of Nodes
(actually it can hold any object), with one important difference.
If you try to get an attribute from this list, it will return that
@@ -311,47 +254,6 @@ def get_environment_var(varstr):
else:
return None
-def quote_spaces(arg):
- """Generic function for putting double quotes around any string that
- has white space in it."""
- if ' ' in arg or '\t' in arg:
- return '"%s"' % arg
- else:
- return str(arg)
-
-class CmdStringHolder(UserString):
- """This is a special class used to hold strings generated by
- scons_subst() and scons_subst_list(). It defines a special method
- escape(). When passed a function with an escape algorithm for a
- particular platform, it will return the contained string with the
- proper escape sequences inserted.
-
- This should really be a subclass of UserString, but that module
- doesn't exist in Python 1.5.2."""
- def __init__(self, cmd, literal=None):
- UserString.__init__(self, cmd)
- self.literal = literal
-
- def is_literal(self):
- return self.literal
-
- def escape(self, escape_func, quote_func=quote_spaces):
- """Escape the string with the supplied function. The
- function is expected to take an arbitrary string, then
- return it with all special characters escaped and ready
- for passing to the command interpreter.
-
- After calling this function, the next call to str() will
- return the escaped string.
- """
-
- if self.is_literal():
- return escape_func(self.data)
- elif ' ' in self.data or '\t' in self.data:
- return quote_func(self.data)
- else:
- return self.data
-
class DisplayEngine:
def __init__(self):
self.__call__ = self.print_it
@@ -369,693 +271,6 @@ class DisplayEngine:
else:
self.__call__ = self.dont_print
-def escape_list(list, escape_func):
- """Escape a list of arguments by running the specified escape_func
- on every object in the list that has an escape() method."""
- def escape(obj, escape_func=escape_func):
- try:
- e = obj.escape
- except AttributeError:
- return obj
- else:
- return e(escape_func)
- return map(escape, list)
-
-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 _return_nodelist(self):
- return self.nodelist
- def _gen_nodelist(self):
- 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))
- self._create_nodelist = self._return_nodelist
- return self.nodelist
- _create_nodelist = _gen_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()
- if nl:
- return str(nl[0])
- return ''
- def __repr__(self):
- nl = self.nl._create_nodelist()
- if nl:
- return repr(nl[0])
- return ''
-
-def subst_dict(target, source):
- """Create a dictionary for substitution of special
- construction variables.
-
- This translates the following special arguments:
-
- target - the target (object or array of objects),
- used to generate the TARGET and TARGETS
- construction variables
-
- source - the source (object or array of objects),
- used to generate the SOURCES and SOURCE
- construction variables
- """
- dict = {}
-
- if target:
- tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
- dict['TARGETS'] = Targets_or_Sources(tnl)
- dict['TARGET'] = Target_or_Source(tnl)
- else:
- dict['TARGETS'] = None
- dict['TARGET'] = None
-
- if source:
- def get_src_subst_proxy(node):
- try:
- rfile = node.rfile
- except AttributeError:
- pass
- else:
- node = rfile()
- return node.get_subst_proxy()
- snl = NLWrapper(source, get_src_subst_proxy)
- dict['SOURCES'] = Targets_or_Sources(snl)
- dict['SOURCE'] = Target_or_Source(snl)
- else:
- dict['SOURCES'] = None
- dict['SOURCE'] = None
-
- return dict
-
-# Constants for the "mode" parameter to scons_subst_list() and
-# scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
-# 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_CMD = 0
-SUBST_RAW = 1
-SUBST_SIG = 2
-
-_rm = re.compile(r'\$[()]')
-_remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
-
-# Indexed by the SUBST_* constants above.
-_regex_remove = [ _rm, None, _remove ]
-
-# Regular expressions for splitting strings and handling substitutions,
-# for use by the scons_subst() and scons_subst_list() functions:
-#
-# The first expression compiled matches all of the $-introduced tokens
-# that we need to process in some way, and is used for substitutions.
-# The expressions it matches are:
-#
-# "$$"
-# "$("
-# "$)"
-# "$variable" [must begin with alphabetic or underscore]
-# "${any stuff}"
-#
-# The second expression compiled is used for splitting strings into tokens
-# to be processed, and it matches all of the tokens listed above, plus
-# the following that affect how arguments do or don't get joined together:
-#
-# " " [white space]
-# "non-white-space" [without any dollar signs]
-# "$" [single dollar sign]
-#
-_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
-_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
-_separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
-
-# This regular expression is used to replace strings of multiple white
-# 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, gvars={}, lvars={}, conv=None):
- """Expand a string containing construction variable substitutions.
-
- This is the work-horse function for substitutions in file names
- and the like. The companion scons_subst_list() function (below)
- handles separating command lines into lists of arguments, so see
- that function if that's what you're looking for.
- """
- if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
- return strSubst
-
- class StringSubber:
- """A class to construct the results of a scons_subst() call.
-
- This binds a specific construction environment, mode, target and
- source with two methods (substitute() and expand()) that handle
- the expansion.
- """
- def __init__(self, env, mode, target, source, conv, gvars):
- self.env = env
- self.mode = mode
- self.target = target
- self.source = source
- self.conv = conv
- self.gvars = gvars
-
- def expand(self, s, lvars):
- """Expand a single "token" as necessary, returning an
- appropriate string containing the expansion.
-
- This handles expanding different types of things (strings,
- lists, callables) appropriately. It calls the wrapper
- substitute() method to re-expand things as necessary, so that
- the results of expansions of side-by-side strings still get
- re-evaluated separately, not smushed together.
- """
- if is_String(s):
- try:
- s0, s1 = s[:2]
- except (IndexError, ValueError):
- return s
- if s0 != '$':
- return s
- if s1 == '$':
- return '$'
- elif s1 in '()':
- return s
- else:
- key = s[1:]
- if key[0] == '{' or string.find(key, '.') >= 0:
- if key[0] == '{':
- key = key[1:-1]
- try:
- s = eval(key, self.gvars, lvars)
- except AttributeError, e:
- raise SCons.Errors.UserError, \
- "Error trying to evaluate `%s': %s" % (s, e)
- except (IndexError, NameError, TypeError):
- return ''
- except SyntaxError,e:
- if self.target:
- raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
- else:
- raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
- else:
- if lvars.has_key(key):
- s = lvars[key]
- elif self.gvars.has_key(key):
- s = self.gvars[key]
- else:
- return ''
-
- # Before re-expanding the result, handle
- # recursive expansion by copying the local
- # variable dictionary and overwriting a null
- # string for the value of the variable name
- # we just expanded.
- #
- # This could potentially be optimized by only
- # copying lvars when s contains more expansions,
- # but lvars is usually supposed to be pretty
- # small, and deeply nested variable expansions
- # are probably more the exception than the norm,
- # so it should be tolerable for now.
- lv = lvars.copy()
- var = string.split(key, '.')[0]
- lv[var] = ''
- return self.substitute(s, lv)
- elif is_List(s):
- def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
- return conv(substitute(l, lvars))
- r = map(func, s)
- return string.join(r)
- elif callable(s):
- try:
- s = s(target=self.target,
- source=self.source,
- env=self.env,
- for_signature=(self.mode != SUBST_CMD))
- except TypeError:
- # This probably indicates that it's a callable
- # object that doesn't match our calling arguments
- # (like an Action).
- s = str(s)
- return self.substitute(s, lvars)
- elif s is None:
- return ''
- else:
- return s
-
- def substitute(self, args, lvars):
- """Substitute expansions in an argument or list of arguments.
-
- This serves as a wrapper for splitting up a string into
- separate tokens.
- """
- if is_String(args) and not isinstance(args, CmdStringHolder):
- try:
- def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
- return conv(expand(match.group(1), lvars))
- result = _dollar_exps.sub(sub_match, args)
- except TypeError:
- # If the internal conversion routine doesn't return
- # strings (it could be overridden to return Nodes, for
- # example), then the 1.5.2 re module will throw this
- # exception. Back off to a slower, general-purpose
- # algorithm that works for all data types.
- args = _separate_args.findall(args)
- result = []
- for a in args:
- result.append(self.conv(self.expand(a, lvars)))
- try:
- result = string.join(result, '')
- except TypeError:
- if len(result) == 1:
- result = result[0]
- return result
- else:
- return self.expand(args, lvars)
-
- if conv is None:
- conv = _strconv[mode]
-
- # 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(),
- # it copies the current __builtin__ values for you. Avoid this by
- # setting it explicitly and then deleting, so we don't pollute the
- # construction environment Dictionary(ies) that are typically used
- # for expansion.
- gvars['__builtin__'] = __builtin__
-
- ss = StringSubber(env, mode, target, source, conv, gvars)
- result = ss.substitute(strSubst, lvars)
-
- try:
- del gvars['__builtin__']
- except KeyError:
- pass
-
- if is_String(result):
- # Remove $(-$) pairs and any stuff in between,
- # if that's appropriate.
- remove = _regex_remove[mode]
- if remove:
- result = remove.sub('', result)
- if mode != SUBST_RAW:
- # Compress strings of white space characters into
- # a single space.
- result = string.strip(_space_sep.sub(' ', result))
-
- return result
-
-#Subst_List_Strings = {}
-
-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.
-
- The companion scons_subst() function (above) handles basic
- substitutions within strings, so see that function instead
- if that's what you're looking for.
- """
-# try:
-# Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
-# except KeyError:
-# Subst_List_Strings[strSubst] = 1
-# import SCons.Debug
-# SCons.Debug.caller(1)
- class ListSubber(UserList.UserList):
- """A class to construct the results of a scons_subst_list() call.
-
- Like StringSubber, this class binds a specific construction
- environment, mode, target and source with two methods
- (substitute() and expand()) that handle the expansion.
-
- In addition, however, this class is used to track the state of
- the result(s) we're gathering so we can do the appropriate thing
- whenever we have to append another word to the result--start a new
- line, start a new word, append to the current word, etc. We do
- this by setting the "append" attribute to the right method so
- that our wrapper methods only need ever call ListSubber.append(),
- and the rest of the object takes care of doing the right thing
- internally.
- """
- def __init__(self, env, mode, target, source, conv, gvars):
- UserList.UserList.__init__(self, [])
- self.env = env
- self.mode = mode
- self.target = target
- self.source = source
- self.conv = conv
- self.gvars = gvars
-
- if self.mode == SUBST_RAW:
- self.add_strip = lambda x, s=self: s.append(x)
- else:
- self.add_strip = lambda x, s=self: None
- self.in_strip = None
- self.next_line()
-
- def expand(self, s, lvars, within_list):
- """Expand a single "token" as necessary, appending the
- expansion to the current result.
-
- This handles expanding different types of things (strings,
- lists, callables) appropriately. It calls the wrapper
- substitute() method to re-expand things as necessary, so that
- the results of expansions of side-by-side strings still get
- re-evaluated separately, not smushed together.
- """
-
- if is_String(s):
- try:
- s0, s1 = s[:2]
- except (IndexError, ValueError):
- self.append(s)
- return
- if s0 != '$':
- self.append(s)
- return
- if s1 == '$':
- self.append('$')
- elif s1 == '(':
- self.open_strip('$(')
- elif s1 == ')':
- self.close_strip('$)')
- else:
- key = s[1:]
- if key[0] == '{' or string.find(key, '.') >= 0:
- if key[0] == '{':
- key = key[1:-1]
- try:
- s = eval(key, self.gvars, lvars)
- except AttributeError, e:
- raise SCons.Errors.UserError, \
- "Error trying to evaluate `%s': %s" % (s, e)
- except (IndexError, NameError, TypeError):
- return
- except SyntaxError,e:
- if self.target:
- raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
- else:
- raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
- else:
- if lvars.has_key(key):
- s = lvars[key]
- elif self.gvars.has_key(key):
- s = self.gvars[key]
- else:
- return
-
- # Before re-expanding the result, handle
- # recursive expansion by copying the local
- # variable dictionary and overwriting a null
- # string for the value of the variable name
- # we just expanded.
- lv = lvars.copy()
- var = string.split(key, '.')[0]
- lv[var] = ''
- self.substitute(s, lv, 0)
- self.this_word()
- elif is_List(s):
- for a in s:
- self.substitute(a, lvars, 1)
- self.next_word()
- elif callable(s):
- try:
- s = s(target=self.target,
- source=self.source,
- env=self.env,
- for_signature=(self.mode != SUBST_CMD))
- except TypeError:
- # This probably indicates that it's a callable
- # object that doesn't match our calling arguments
- # (like an Action).
- s = str(s)
- self.substitute(s, lvars, within_list)
- elif s is None:
- self.this_word()
- else:
- self.append(s)
-
- def substitute(self, args, lvars, within_list):
- """Substitute expansions in an argument or list of arguments.
-
- This serves as a wrapper for splitting up a string into
- separate tokens.
- """
-
- if is_String(args) and not isinstance(args, CmdStringHolder):
- args = _separate_args.findall(args)
- for a in args:
- if a[0] in ' \t\n\r\f\v':
- if '\n' in a:
- self.next_line()
- elif within_list:
- self.append(a)
- else:
- self.next_word()
- else:
- self.expand(a, lvars, within_list)
- else:
- self.expand(args, lvars, within_list)
-
- def next_line(self):
- """Arrange for the next word to start a new line. This
- is like starting a new word, except that we have to append
- another line to the result."""
- UserList.UserList.append(self, [])
- self.next_word()
-
- def this_word(self):
- """Arrange for the next word to append to the end of the
- current last word in the result."""
- self.append = self.add_to_current_word
-
- def next_word(self):
- """Arrange for the next word to start a new word."""
- self.append = self.add_new_word
-
- def add_to_current_word(self, x):
- """Append the string x to the end of the current last word
- in the result. If that is not possible, then just add
- it as a new word. Make sure the entire concatenated string
- inherits the object attributes of x (in particular, the
- escape function) by wrapping it as CmdStringHolder."""
-
- if not self.in_strip or self.mode != SUBST_SIG:
- try:
- current_word = self[-1][-1]
- except IndexError:
- self.add_new_word(x)
- else:
- # All right, this is a hack and it should probably
- # be refactored out of existence in the future.
- # The issue is that we want to smoosh words together
- # and make one file name that gets escaped if
- # we're expanding something like foo$EXTENSION,
- # but we don't want to smoosh them together if
- # it's something like >$TARGET, because then we'll
- # treat the '>' like it's part of the file name.
- # So for now, just hard-code looking for the special
- # command-line redirection characters...
- try:
- last_char = str(current_word)[-1]
- except IndexError:
- last_char = '\0'
- if last_char in '<>|':
- self.add_new_word(x)
- else:
- y = current_word + x
- literal1 = self.literal(self[-1][-1])
- literal2 = self.literal(x)
- y = self.conv(y)
- if is_String(y):
- y = CmdStringHolder(y, literal1 or literal2)
- self[-1][-1] = y
-
- def add_new_word(self, x):
- if not self.in_strip or self.mode != SUBST_SIG:
- literal = self.literal(x)
- x = self.conv(x)
- if is_String(x):
- x = CmdStringHolder(x, literal)
- self[-1].append(x)
- self.append = self.add_to_current_word
-
- def literal(self, x):
- try:
- l = x.is_literal
- except AttributeError:
- return None
- else:
- return l()
-
- def open_strip(self, x):
- """Handle the "open strip" $( token."""
- self.add_strip(x)
- self.in_strip = 1
-
- def close_strip(self, x):
- """Handle the "close strip" $) token."""
- self.add_strip(x)
- self.in_strip = None
-
- if conv is None:
- conv = _strconv[mode]
-
- # 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(),
- # it copies the current __builtin__ values for you. Avoid this by
- # setting it explicitly and then deleting, so we don't pollute the
- # construction environment Dictionary(ies) that are typically used
- # for expansion.
- gvars['__builtins__'] = __builtins__
-
- ls = ListSubber(env, mode, target, source, conv, gvars)
- ls.substitute(strSubst, lvars, 0)
-
- try:
- del gvars['__builtins__']
- except KeyError:
- pass
-
- return ls.data
-
-def scons_subst_once(strSubst, env, key):
- """Perform single (non-recursive) substitution of a single
- construction variable keyword.
-
- This is used when setting a variable when copying or overriding values
- in an Environment. We want to capture (expand) the old value before
- we override it, so people can do things like:
-
- env2 = env.Copy(CCFLAGS = '$CCFLAGS -g')
-
- We do this with some straightforward, brute-force code here...
- """
- if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
- return strSubst
-
- matchlist = ['$' + key, '${' + key + '}']
- val = env.get(key, '')
- def sub_match(match, val=val, matchlist=matchlist):
- a = match.group(1)
- if a in matchlist:
- a = val
- if is_List(a):
- return string.join(map(str, a))
- else:
- return str(a)
-
- if is_List(strSubst):
- result = []
- for arg in strSubst:
- if is_String(arg):
- if arg in matchlist:
- arg = val
- if is_List(arg):
- result.extend(arg)
- else:
- result.append(arg)
- else:
- result.append(_dollar_exps.sub(sub_match, arg))
- else:
- result.append(arg)
- return result
- elif is_String(strSubst):
- return _dollar_exps.sub(sub_match, strSubst)
- else:
- return strSubst
-
def render_tree(root, child_func, prune=0, margin=[0], visited={}):
"""
Render a tree of nodes into an ASCII tree view.
@@ -1167,7 +382,7 @@ def is_Dict(e):
return type(e) is types.DictType or isinstance(e, UserDict)
def is_List(e):
- return type(e) is types.ListType or isinstance(e, UserList.UserList)
+ return type(e) is types.ListType or isinstance(e, UserList)
if hasattr(types, 'UnicodeType'):
def is_String(e):
@@ -1500,7 +715,7 @@ def Split(arg):
else:
return [arg]
-class CLVar(UserList.UserList):
+class CLVar(UserList):
"""A class for command-line construction variables.
This is a list that uses Split() to split an initial string along
@@ -1511,7 +726,7 @@ class CLVar(UserList.UserList):
command-line construction variable.
"""
def __init__(self, seq = []):
- UserList.UserList.__init__(self, Split(seq))
+ UserList.__init__(self, Split(seq))
def __coerce__(self, other):
return (self, CLVar(other))
def __str__(self):
diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py
index b20ff62..724c51d 100644
--- a/src/engine/SCons/UtilTests.py
+++ b/src/engine/SCons/UtilTests.py
@@ -30,13 +30,15 @@ import StringIO
import sys
import types
import unittest
+
from UserDict import UserDict
-from SCons.Util import *
import TestCmd
import SCons.Errors
+from SCons.Util import *
+
class OutBuffer:
def __init__(self):
self.buffer = ""
@@ -44,978 +46,12 @@ class OutBuffer:
def write(self, str):
self.buffer = self.buffer + str
-class DummyNode:
- """Simple node work-alike."""
- def __init__(self, name):
- self.name = os.path.normpath(name)
- def __str__(self):
- return self.name
- def is_literal(self):
- return 1
- def rfile(self):
- return self
- def get_subst_proxy(self):
- return self
-
-class DummyEnv:
- def __init__(self, dict={}):
- self.dict = dict
-
- def Dictionary(self, key = None):
- if not key:
- return self.dict
- return self.dict[key]
-
- def __getitem__(self, key):
- return self.dict[key]
-
- def get(self, key, default):
- return self.dict.get(key, default)
-
- def sig_dict(self):
- dict = self.dict.copy()
- dict["TARGETS"] = 'tsig'
- dict["SOURCES"] = 'ssig'
- return dict
-
-def cs(target=None, source=None, env=None, for_signature=None):
- return 'cs'
-
-def cl(target=None, source=None, env=None, for_signature=None):
- return ['cl']
-
-def CmdGen1(target, source, env, for_signature):
- # Nifty trick...since Environment references are interpolated,
- # instantiate an instance of a callable class with this one,
- # which will then get evaluated.
- assert str(target) == 't', target
- assert str(source) == 's', source
- return "${CMDGEN2('foo', %d)}" % for_signature
-
-class CmdGen2:
- def __init__(self, mystr, forsig):
- self.mystr = mystr
- self.expect_for_signature = forsig
-
- def __call__(self, target, source, env, for_signature):
- assert str(target) == 't', target
- assert str(source) == 's', source
- assert for_signature == self.expect_for_signature, for_signature
- return [ self.mystr, env.Dictionary('BAR') ]
-
-if os.sep == '/':
- def cvt(str):
- return str
-else:
- def cvt(str):
- return string.replace(str, '/', os.sep)
-
class UtilTestCase(unittest.TestCase):
- def test_subst(self):
- """Test the subst() function"""
- class MyNode(DummyNode):
- """Simple node work-alike with some extra stuff for testing."""
- def __init__(self, name):
- DummyNode.__init__(self, name)
- class Attribute:
- pass
- self.attribute = Attribute()
- self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
- self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
- def get_stuff(self, extra):
- return self.name + extra
- foo = 1
-
- class TestLiteral:
- def __init__(self, literal):
- self.literal = literal
- def __str__(self):
- return self.literal
- def is_literal(self):
- return 1
-
- class TestCallable:
- def __init__(self, value):
- self.value = value
- def __call__(self):
- pass
- def __str__(self):
- return self.value
-
- def function_foo(arg):
- pass
-
- target = [ MyNode("./foo/bar.exe"),
- MyNode("/bar/baz.obj"),
- MyNode("../foo/baz.obj") ]
- source = [ MyNode("./foo/blah.cpp"),
- MyNode("/bar/ack.cpp"),
- MyNode("../foo/ack.c") ]
-
- loc = {
- 'xxx' : None,
- 'null' : '',
- 'zero' : 0,
- 'one' : 1,
- 'BAR' : 'baz',
- 'ONE' : '$TWO',
- 'TWO' : '$THREE',
- 'THREE' : 'four',
-
- 'AAA' : 'a',
- 'BBB' : 'b',
- 'CCC' : 'c',
-
- # $XXX$HHH should expand to GGGIII, not BADNEWS.
- 'XXX' : '$FFF',
- 'FFF' : 'GGG',
- 'HHH' : 'III',
- 'FFFIII' : 'BADNEWS',
-
- 'LITERAL' : TestLiteral("$XXX"),
-
- # Test that we can expand to and return a function.
- #'FUNCTION' : function_foo,
-
- 'CMDGEN1' : CmdGen1,
- 'CMDGEN2' : CmdGen2,
-
- 'NOTHING' : "",
- 'NONE' : None,
-
- # Test various combinations of strings, lists and functions.
- 'N' : None,
- 'X' : 'x',
- 'Y' : '$X',
- 'R' : '$R',
- 'S' : 'x y',
- 'LS' : ['x y'],
- 'L' : ['x', 'y'],
- 'CS' : cs,
- 'CL' : cl,
-
- # Test function calls within ${}.
- 'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}',
- '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',
- 'SSS' : '$RRR',
-
- # Test callables that don't match the calling arguments.
- 'CALLABLE' : TestCallable('callable-1'),
- }
-
- env = DummyEnv(loc)
-
- # Basic tests of substitution functionality.
- cases = [
- # Basics: strings without expansions are left alone, and
- # the simplest possible expansion to a null-string value.
- "test", "test",
- "$null", "",
-
- # Test expansion of integer values.
- "test $zero", "test 0",
- "test $one", "test 1",
-
- # Test multiple re-expansion of values.
- "test $ONE", "test four",
-
- # Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions.
- "test $TARGETS $SOURCES",
- "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c",
-
- "test ${TARGETS[:]} ${SOURCES[0]}",
- "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp",
-
- "test ${TARGETS[1:]}v",
- "test /bar/baz.obj ../foo/baz.objv",
-
- "test $TARGET",
- "test foo/bar.exe",
-
- "test $TARGET$FOO[0]",
- "test foo/bar.exe[0]",
-
- "test $TARGETS.foo",
- "test 1 1 1",
-
- "test ${SOURCES[0:2].foo}",
- "test 1 1",
-
- "test $SOURCE.foo",
- "test 1",
-
- "test ${TARGET.get_stuff('blah')}",
- "test foo/bar.exeblah",
-
- "test ${SOURCES.get_stuff('blah')}",
- "test foo/blah.cppblah /bar/ack.cppblah ../foo/ack.cblah",
-
- "test ${SOURCES[0:2].get_stuff('blah')}",
- "test foo/blah.cppblah /bar/ack.cppblah",
-
- "test ${SOURCES[0:2].get_stuff('blah')}",
- "test foo/blah.cppblah /bar/ack.cppblah",
-
- "test ${SOURCES.attribute.attr1}",
- "test attr$1-blah.cpp attr$1-ack.cpp attr$1-ack.c",
-
- "test ${SOURCES.attribute.attr2}",
- "test attr$2-blah.cpp attr$2-ack.cpp attr$2-ack.c",
-
- # Test adjacent expansions.
- "foo$BAR",
- "foobaz",
-
- "foo${BAR}",
- "foobaz",
-
- # Test that adjacent expansions don't get re-interpreted
- # together. The correct disambiguated expansion should be:
- # $XXX$HHH => ${FFF}III => GGGIII
- # not:
- # $XXX$HHH => ${FFFIII} => BADNEWS
- "$XXX$HHH", "GGGIII",
-
- # Test double-dollar-sign behavior.
- "$$FFF$HHH", "$FFFIII",
-
- # Test that a Literal will stop dollar-sign substitution.
- "$XXX $LITERAL $FFF", "GGG $XXX GGG",
-
- # Test that we don't blow up even if they subscript
- # something in ways they "can't."
- "${FFF[0]}", "G",
- "${FFF[7]}", "",
- "${NOTHING[1]}", "",
- "${NONE[2]}", "",
-
- # Test various combinations of strings and lists.
- #None, '',
- '', '',
- 'x', 'x',
- 'x y', 'x y',
- '$N', '',
- '$X', 'x',
- '$Y', 'x',
- '$R', '',
- '$S', 'x y',
- '$LS', 'x y',
- '$L', 'x y',
- '$S z', 'x y z',
- '$LS z', 'x y z',
- '$L z', 'x y z',
- #cs, 'cs',
- #cl, 'cl',
- '$CS', 'cs',
- '$CL', 'cl',
-
- # Test function calls within ${}.
- '$FUNCCALL', 'a xc b',
-
- # Bug reported by Christoph Wiedemann.
- cvt('$xxx/bin'), '/bin',
-
- # Tests callables that don't match our calling arguments.
- '$CALLABLE', 'callable-1',
-
- # Test handling of quotes.
- 'aaa "bbb ccc" ddd', 'aaa "bbb ccc" ddd',
- ]
-
- kwargs = {'target' : target, 'source' : source,
- 'gvars' : env.Dictionary()}
-
- failed = 0
- while cases:
- input, expect = cases[:2]
- expect = cvt(expect)
- result = apply(scons_subst, (input, env), kwargs)
- if result != expect:
- if failed == 0: print
- print " input %s => %s did not match %s" % (repr(input), repr(result), repr(expect))
- failed = failed + 1
- 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",
- "test ",
- "test",
- "test",
-
- "test $($xxx$)",
- "test $($)",
- "test",
- "test",
-
- "test $( $xxx $)",
- "test $( $)",
- "test",
- "test",
-
- "$AAA ${AAA}A $BBBB $BBB",
- "a aA b",
- "a aA b",
- "a aA b",
-
- "$RECURSE",
- "foo bar",
- "foo bar",
- "foo bar",
-
- "$RRR",
- "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",
- ]
-
- gvars = env.Dictionary()
-
- failed = 0
- while subst_cases:
- input, eraw, ecmd, esig = subst_cases[:4]
- 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, 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, gvars=gvars)
- if result != esig:
- if failed == 0: print
- print " input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
- failed = failed + 1
- 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],
- gvars={})
- assert result == "t1 s1 s2", 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'),
- gvars=gvars)
- assert newcom == "test foo baz s t", newcom
-
- # Test that we handle attribute errors during expansion as expected.
- try:
- class Foo:
- pass
- scons_subst('${foo.bar}', env, gvars={'foo':Foo()})
- except SCons.Errors.UserError, e:
- expect = [
- "Error trying to evaluate `${foo.bar}': bar",
- "Error trying to evaluate `${foo.bar}': Foo instance has no attribute 'bar'",
- ]
- assert str(e) in expect, e
- else:
- raise AssertionError, "did not catch expected UserError"
-
- # Test that we handle syntax errors during expansion as expected.
- try:
- scons_subst('$foo.bar.3.0', env)
- except SCons.Errors.UserError, e:
- expect1 = "Syntax error `invalid syntax' trying to evaluate `$foo.bar.3.0'"
- expect2 = "Syntax error `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'"
- assert str(e) in [expect1, expect2], e
- else:
- raise AssertionError, "did not catch expected UserError"
-
- # Test how we handle overriding the internal conversion routines.
- def s(obj):
- return obj
-
- n1 = MyNode('n1')
- env = DummyEnv({'NODE' : n1})
- 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, gvars=gvars)
- assert node is n1, node
- 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})
- #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, gvars=gvars)
- #assert func is function_foo, func
- #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, gvars=env.Dictionary())
- assert result == 'xxx', result
- result = scons_subst('$XXX', env, gvars={'XXX' : 'yyy'})
- assert result == 'yyy', result
-
- def test_subst_list(self):
- """Testing the scons_subst_list() method..."""
- class MyNode(DummyNode):
- """Simple node work-alike with some extra stuff for testing."""
- def __init__(self, name):
- DummyNode.__init__(self, name)
- class Attribute:
- pass
- self.attribute = Attribute()
- self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
- self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
-
- class TestCallable:
- def __init__(self, value):
- self.value = value
- def __call__(self):
- pass
- def __str__(self):
- return self.value
-
- target = [ MyNode("./foo/bar.exe"),
- MyNode("/bar/baz with spaces.obj"),
- MyNode("../foo/baz.obj") ]
- source = [ MyNode("./foo/blah with spaces.cpp"),
- MyNode("/bar/ack.cpp"),
- MyNode("../foo/ack.c") ]
-
- def _defines(defs):
- l = []
- for d in defs:
- if SCons.Util.is_List(d) or type(d) is types.TupleType:
- l.append(str(d[0]) + '=' + str(d[1]))
- else:
- l.append(str(d))
- return l
-
- loc = {
- 'xxx' : None,
- 'NEWLINE' : 'before\nafter',
-
- 'AAA' : 'a',
- 'BBB' : 'b',
- 'CCC' : 'c',
-
- 'DO' : DummyNode('do something'),
- 'FOO' : DummyNode('foo.in'),
- 'BAR' : DummyNode('bar with spaces.out'),
- 'CRAZY' : DummyNode('crazy\nfile.in'),
-
- # $XXX$HHH should expand to GGGIII, not BADNEWS.
- 'XXX' : '$FFF',
- 'FFF' : 'GGG',
- 'HHH' : 'III',
- 'FFFIII' : 'BADNEWS',
-
- 'CMDGEN1' : CmdGen1,
- 'CMDGEN2' : CmdGen2,
-
- 'LITERALS' : [ Literal('foo\nwith\nnewlines'),
- Literal('bar\nwith\nnewlines') ],
-
- # Test various combinations of strings, lists and functions.
- 'N' : None,
- 'X' : 'x',
- 'Y' : '$X',
- 'R' : '$R',
- 'S' : 'x y',
- 'LS' : ['x y'],
- 'L' : ['x', 'y'],
- 'CS' : cs,
- 'CL' : cl,
-
- # Test function calls within ${}.
- 'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}',
- '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',
- 'SSS' : '$RRR',
-
- # Test callable objects that don't match our calling arguments.
- 'CALLABLE' : TestCallable('callable-2'),
-
- '_defines' : _defines,
- 'DEFS' : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ],
- }
-
- env = DummyEnv(loc)
-
- cases = [
- "$TARGETS",
- [
- ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
- ],
-
- "$SOURCES $NEWLINE $TARGETS",
- [
- ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
- ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
- ],
-
- "$SOURCES$NEWLINE",
- [
- ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
- ["after"],
- ],
-
- "foo$FFF",
- [
- ["fooGGG"],
- ],
-
- "foo${FFF}",
- [
- ["fooGGG"],
- ],
-
- "test ${SOURCES.attribute.attr1}",
- [
- ["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"],
- ],
-
- "test ${SOURCES.attribute.attr2}",
- [
- ["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"],
- ],
-
- "$DO --in=$FOO --out=$BAR",
- [
- ["do something", "--in=foo.in", "--out=bar with spaces.out"],
- ],
-
- # This test is now fixed, and works like it should.
- "$DO --in=$CRAZY --out=$BAR",
- [
- ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
- ],
-
- # Try passing a list to scons_subst_list().
- [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
- [
- ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
- ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
- ],
-
- # Test against a former bug in scons_subst_list().
- "$XXX$HHH",
- [
- ["GGGIII"],
- ],
-
- # Test double-dollar-sign behavior.
- "$$FFF$HHH",
- [
- ["$FFFIII"],
- ],
-
- # Test various combinations of strings, lists and functions.
- None, [[]],
- [None], [[]],
- '', [[]],
- [''], [[]],
- 'x', [['x']],
- ['x'], [['x']],
- 'x y', [['x', 'y']],
- ['x y'], [['x y']],
- ['x', 'y'], [['x', 'y']],
- '$N', [[]],
- ['$N'], [[]],
- '$X', [['x']],
- ['$X'], [['x']],
- '$Y', [['x']],
- ['$Y'], [['x']],
- #'$R', [[]],
- #['$R'], [[]],
- '$S', [['x', 'y']],
- '$S z', [['x', 'y', 'z']],
- ['$S'], [['x', 'y']],
- ['$S z'], [['x', 'y z']], # XXX - IS THIS BEST?
- ['$S', 'z'], [['x', 'y', 'z']],
- '$LS', [['x y']],
- '$LS z', [['x y', 'z']],
- ['$LS'], [['x y']],
- ['$LS z'], [['x y z']],
- ['$LS', 'z'], [['x y', 'z']],
- '$L', [['x', 'y']],
- '$L z', [['x', 'y', 'z']],
- ['$L'], [['x', 'y']],
- ['$L z'], [['x', 'y z']], # XXX - IS THIS BEST?
- ['$L', 'z'], [['x', 'y', 'z']],
- cs, [['cs']],
- [cs], [['cs']],
- cl, [['cl']],
- [cl], [['cl']],
- '$CS', [['cs']],
- ['$CS'], [['cs']],
- '$CL', [['cl']],
- ['$CL'], [['cl']],
-
- # Test function calls within ${}.
- '$FUNCCALL', [['a', 'xc', 'b']],
-
- # Test handling of newlines in white space.
- 'foo\nbar', [['foo'], ['bar']],
- 'foo\n\nbar', [['foo'], ['bar']],
- 'foo \n \n bar', [['foo'], ['bar']],
- 'foo \nmiddle\n bar', [['foo'], ['middle'], ['bar']],
-
- # Bug reported by Christoph Wiedemann.
- cvt('$xxx/bin'), [['/bin']],
-
- # Test variables smooshed together with different prefixes.
- 'foo$AAA', [['fooa']],
- '<$AAA', [['<', 'a']],
- '>$AAA', [['>', 'a']],
- '|$AAA', [['|', 'a']],
-
- # Test callables that don't match our calling arguments.
- '$CALLABLE', [['callable-2']],
-
- # Test
-
- # Test handling of quotes.
- # XXX Find a way to handle this in the future.
- #'aaa "bbb ccc" ddd', [['aaa', 'bbb ccc', 'ddd']],
-
- '${_defines(DEFS)}', [['Q1="q1"', 'Q2="a"']],
- ]
-
- gvars = env.Dictionary()
-
- kwargs = {'target' : target, 'source' : source, 'gvars' : gvars}
-
- failed = 0
- while cases:
- input, expect = cases[:2]
- expect = map(lambda l: map(cvt, l), expect)
- result = apply(scons_subst_list, (input, env), kwargs)
- if result != expect:
- if failed == 0: print
- print " input %s => %s did not match %s" % (repr(input), result, repr(expect))
- failed = failed + 1
- 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],
- gvars=gvars)
- assert result == [['t1', 's1', 's2']], result
- result = scons_subst_list("$TARGET $SOURCES", env,
- target=[t1, t2],
- source=[s1, s2],
- 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,
- 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, gvars=gvars)
- assert cmd_list == [['abc',
- 'foo\nwith\nnewlines',
- 'bar\nwith\nnewlines',
- 'xyz']], cmd_list
- c = cmd_list[0][0].escape(escape_func)
- assert c == 'abc', c
- c = cmd_list[0][1].escape(escape_func)
- assert c == '**foo\nwith\nnewlines**', c
- c = cmd_list[0][2].escape(escape_func)
- assert c == '**bar\nwith\nnewlines**', c
- c = cmd_list[0][3].escape(escape_func)
- assert c == 'xyz', c
-
- 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)
- assert c == '**bar\nwith\nnewlinesxyz**', c
-
- # Tests of the various SUBST_* modes of substitution.
- subst_list_cases = [
- "test $xxx",
- [["test"]],
- [["test"]],
- [["test"]],
-
- "test $($xxx$)",
- [["test", "$($)"]],
- [["test"]],
- [["test"]],
-
- "test $( $xxx $)",
- [["test", "$(", "$)"]],
- [["test"]],
- [["test"]],
-
- "$AAA ${AAA}A $BBBB $BBB",
- [["a", "aA", "b"]],
- [["a", "aA", "b"]],
- [["a", "aA", "b"]],
-
- "$RECURSE",
- [["foo", "bar"]],
- [["foo", "bar"]],
- [["foo", "bar"]],
-
- "$RRR",
- [["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"]],
- ]
-
- 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, 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, 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, gvars=gvars)
- if result != esig:
- if failed == 0: print
- print " input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
- failed = failed + 1
- del subst_list_cases[:4]
- assert failed == 0, "%d subst() mode cases failed" % failed
-
- # Test that we handle attribute errors during expansion as expected.
- try:
- class Foo:
- pass
- scons_subst_list('${foo.bar}', env, gvars={'foo':Foo()})
- except SCons.Errors.UserError, e:
- expect = [
- "Error trying to evaluate `${foo.bar}': bar",
- "Error trying to evaluate `${foo.bar}': Foo instance has no attribute 'bar'",
- ]
- assert str(e) in expect, e
- else:
- raise AssertionError, "did not catch expected UserError"
-
- # Test that we handle syntax errors during expansion as expected.
- try:
- scons_subst_list('$foo.bar.3.0', env)
- except SCons.Errors.UserError, e:
- expect1 = "Syntax error `invalid syntax' trying to evaluate `$foo.bar.3.0'"
- expect2 = "Syntax error `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'"
- assert str(e) in [expect1, expect2], e
- else:
- raise AssertionError, "did not catch expected SyntaxError"
-
- # Test we handle overriding the internal conversion routines.
- def s(obj):
- return obj
-
- n1 = MyNode('n1')
- env = DummyEnv({'NODE' : n1})
- 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, gvars=gvars)
- assert node == [[n1]], node
- 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, gvars=env.Dictionary())
- assert result == [['xxx']], result
- result = scons_subst_list('$XXX', env, gvars={'XXX' : 'yyy'})
- assert result == [['yyy']], result
-
- def test_subst_once(self):
- """Testing the scons_subst_once() method"""
-
- loc = {
- 'CCFLAGS' : '-DFOO',
- 'ONE' : 1,
- 'RECURSE' : 'r $RECURSE r',
- 'LIST' : ['a', 'b', 'c'],
- }
-
- env = DummyEnv(loc)
-
- cases = [
- '$CCFLAGS -DBAR',
- 'OTHER_KEY',
- '$CCFLAGS -DBAR',
-
- '$CCFLAGS -DBAR',
- 'CCFLAGS',
- '-DFOO -DBAR',
-
- 'x $ONE y',
- 'ONE',
- 'x 1 y',
-
- 'x $RECURSE y',
- 'RECURSE',
- 'x r $RECURSE r y',
-
- '$LIST',
- 'LIST',
- 'a b c',
-
- ['$LIST'],
- 'LIST',
- ['a', 'b', 'c'],
-
- ['x', '$LIST', 'y'],
- 'LIST',
- ['x', 'a', 'b', 'c', 'y'],
-
- ['x', 'x $LIST y', 'y'],
- 'LIST',
- ['x', 'x a b c y', 'y'],
-
- ['x', 'x $CCFLAGS y', 'y'],
- 'LIST',
- ['x', 'x $CCFLAGS y', 'y'],
-
- ['x', 'x $RECURSE y', 'y'],
- 'LIST',
- ['x', 'x $RECURSE y', 'y'],
- ]
-
- failed = 0
- while cases:
- input, key, expect = cases[:3]
- result = scons_subst_once(input, env, key)
- if result != expect:
- if failed == 0: print
- print " input %s (%s) => %s did not match %s" % (repr(input), repr(key), repr(result), repr(expect))
- failed = failed + 1
- del cases[:3]
- assert failed == 0, "%d subst() cases failed" % failed
-
def test_splitext(self):
assert splitext('foo') == ('foo','')
assert splitext('foo.bar') == ('foo','.bar')
assert splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'')
- def test_quote_spaces(self):
- """Testing the quote_spaces() method..."""
- q = quote_spaces('x')
- assert q == 'x', q
-
- q = quote_spaces('x x')
- assert q == '"x x"', q
-
- q = quote_spaces('x\tx')
- assert q == '"x\tx"', q
-
class Node:
def __init__(self, name, children=[]):
self.children = children
@@ -1361,34 +397,6 @@ class UtilTestCase(unittest.TestCase):
assert p.baz == 5, p.baz
assert p.get() == s, p.get()
- def test_Literal(self):
- """Test the Literal() function."""
- input_list = [ '$FOO', Literal('$BAR') ]
- gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
-
- def escape_func(cmd):
- return '**' + cmd + '**'
-
- 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') ]
- gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
-
- def escape_func(cmd):
- return '**' + cmd + '**'
-
- 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, None, mode=SUBST_SIG, gvars=gvars)
- cmd_list = SCons.Util.escape_list(cmd_list[0], escape_func)
- assert cmd_list == ['BAZ', '**BLEH**'], cmd_list
-
def test_display(self):
old_stdout = sys.stdout
sys.stdout = OutBuffer()
@@ -1419,58 +427,6 @@ class UtilTestCase(unittest.TestCase):
except OSError:
pass
- def test_subst_dict(self):
- """Test substituting dictionary values in an Action
- """
- t = DummyNode('t')
- s = DummyNode('s')
- d = subst_dict(target=t, source=s)
- 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']
-
- t1 = DummyNode('t1')
- t2 = DummyNode('t2')
- s1 = DummyNode('s1')
- s2 = DummyNode('s2')
- d = subst_dict(target=[t1, t2], source=[s1, s2])
- TARGETS = map(lambda x: str(x), d['TARGETS'])
- TARGETS.sort()
- assert TARGETS == ['t1', 't2'], d['TARGETS']
- assert str(d['TARGET']) == 't1', d['TARGET']
- SOURCES = map(lambda x: str(x), d['SOURCES'])
- SOURCES.sort()
- assert SOURCES == ['s1', 's2'], d['SOURCES']
- assert str(d['SOURCE']) == 's1', d['SOURCE']
-
- class V:
- # Fake Value node with no rfile() method.
- def __init__(self, name):
- self.name = name
- def __str__(self):
- return 'v-'+self.name
- def get_subst_proxy(self):
- return self
-
- class N(V):
- def rfile(self):
- return self.__class__('rstr-' + self.name)
-
- t3 = N('t3')
- t4 = DummyNode('t4')
- t5 = V('t5')
- s3 = DummyNode('s3')
- s4 = N('s4')
- s5 = V('s5')
- d = subst_dict(target=[t3, t4, t5], source=[s3, s4, s5])
- TARGETS = map(lambda x: str(x), d['TARGETS'])
- TARGETS.sort()
- assert TARGETS == ['t4', 'v-t3', 'v-t5'], TARGETS
- SOURCES = map(lambda x: str(x), d['SOURCES'])
- SOURCES.sort()
- assert SOURCES == ['s3', 'v-rstr-s4', 'v-s5'], SOURCES
-
def test_PrependPath(self):
"""Test prepending to a path"""
p1 = r'C:\dir\num\one;C:\dir\num\two'
@@ -1617,25 +573,6 @@ class UtilTestCase(unittest.TestCase):
assert r.data == ['a', 'b', ' c', 'd'], r.data
assert str(r) == 'a b c d', str(r)
- loc = {}
- loc['FOO'] = 'foo'
- loc['BAR'] = SCons.Util.CLVar('bar')
- loc['CALL'] = lambda target, source, env, for_signature: 'call'
- env = DummyEnv(loc)
-
- cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
-
- newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
- assert newcmd == 'test foo bar call test', newcmd
-
- 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]
- assert cmd_list[0][2] == "bar", cmd_list[0][2]
- assert cmd_list[0][3] == "call", cmd_list[0][3]
- assert cmd_list[0][4] == "test", cmd_list[0][4]
-
def test_Selector(self):
"""Test the Selector class"""