summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/engine/SCons/Builder.py30
-rw-r--r--src/engine/SCons/BuilderTests.py38
-rw-r--r--src/engine/SCons/Defaults.py8
-rw-r--r--src/engine/SCons/Environment.py56
-rw-r--r--src/engine/SCons/EnvironmentTests.py21
-rw-r--r--src/engine/SCons/Node/NodeTests.py19
-rw-r--r--src/engine/SCons/Node/__init__.py6
-rw-r--r--src/engine/SCons/Util.py65
-rw-r--r--src/engine/SCons/UtilTests.py35
9 files changed, 222 insertions, 56 deletions
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index c9556aa..4719951 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -34,7 +34,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import os.path
import SCons.Node.FS
-from SCons.Util import PathList, scons_str2nodes, scons_subst
+from SCons.Util import PathList, scons_str2nodes, scons_subst, scons_subst_list
import string
import types
from UserList import UserList
@@ -363,20 +363,20 @@ class CommandAction(ActionBase):
def execute(self, **kw):
dict = apply(self.subst_dict, (), kw)
- cmd_str = scons_subst(self.command, dict, {})
- for cmd in string.split(cmd_str, '\n'):
- if print_actions:
- self.show(cmd)
- if execute_actions:
- args = string.split(cmd)
- try:
- ENV = kw['env']['ENV']
- except:
- import SCons.Defaults
- ENV = SCons.Defaults.ConstructionEnvironment['ENV']
- ret = spawn(args[0], args, ENV)
- if ret:
- return ret
+ cmd_list = scons_subst_list(self.command, dict, {})
+ for cmd_line in cmd_list:
+ if len(cmd_line):
+ if print_actions:
+ self.show(string.join(cmd_line))
+ if execute_actions:
+ try:
+ ENV = kw['env']['ENV']
+ except:
+ import SCons.Defaults
+ ENV = SCons.Defaults.ConstructionEnvironment['ENV']
+ ret = spawn(cmd_line[0], cmd_line, ENV)
+ if ret:
+ return ret
return 0
def get_contents(self, **kw):
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
index 5668095..3910323 100644
--- a/src/engine/SCons/BuilderTests.py
+++ b/src/engine/SCons/BuilderTests.py
@@ -48,10 +48,10 @@ test = TestCmd.TestCmd(workdir = '')
test.write('act.py', """import os, string, sys
f = open(sys.argv[1], 'w')
-f.write("act.py: " + string.join(sys.argv[2:]) + "\\n")
+f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n")
try:
if sys.argv[3]:
- f.write("act.py: " + os.environ[sys.argv[3]] + "\\n")
+ f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n")
except:
pass
f.close()
@@ -61,6 +61,8 @@ sys.exit(0)
act_py = test.workpath('act.py')
outfile = test.workpath('outfile')
+show_string = None
+
class Environment:
def subst(self, s):
return s
@@ -127,7 +129,7 @@ class BuilderTestCase(unittest.TestCase):
r = builder.execute()
assert r == 0
c = test.read(outfile, 'r')
- assert c == "act.py: xyzzy\n", c
+ assert c == "act.py: 'xyzzy'\n", c
cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
@@ -135,7 +137,7 @@ class BuilderTestCase(unittest.TestCase):
r = builder.execute(target = 'foo')
assert r == 0
c = test.read(outfile, 'r')
- assert c == "act.py: foo\n", c
+ assert c == "act.py: 'foo'\n", c
cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
@@ -143,7 +145,7 @@ class BuilderTestCase(unittest.TestCase):
r = builder.execute(target = ['aaa', 'bbb'])
assert r == 0
c = test.read(outfile, 'r')
- assert c == "act.py: aaa bbb\n", c
+ assert c == "act.py: 'aaa' 'bbb'\n", c
cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
@@ -151,7 +153,7 @@ class BuilderTestCase(unittest.TestCase):
r = builder.execute(source = ['one', 'two'])
assert r == 0
c = test.read(outfile, 'r')
- assert c == "act.py: one two\n", c
+ assert c == "act.py: 'one' 'two'\n", c
cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile)
@@ -159,7 +161,7 @@ class BuilderTestCase(unittest.TestCase):
r = builder.execute(source = ['three', 'four', 'five'])
assert r == 0
c = test.read(outfile, 'r')
- assert c == "act.py: three four\n", c
+ assert c == "act.py: 'three' 'four'\n", c
cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile)
@@ -167,7 +169,25 @@ class BuilderTestCase(unittest.TestCase):
r = builder.execute(target = 'out5', env = {'ENV' : {'XYZZY' : 'xyzzy'}})
assert r == 0
c = test.read(outfile, 'r')
- assert c == "act.py: out5 XYZZY\nact.py: xyzzy\n", c
+ assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c
+
+ cmd7 = '%s %s %s one\n\n%s %s %s two' % (python, act_py, outfile,
+ python, act_py, outfile)
+ expect7 = '%s %s %s one\n%s %s %s two\n' % (python, act_py, outfile,
+ python, act_py, outfile)
+
+ builder = SCons.Builder.Builder(action = cmd7)
+
+ global show_string
+ show_string = ""
+ def my_show(string):
+ global show_string
+ show_string = show_string + string + "\n"
+ builder.action.show = my_show
+
+ r = builder.execute()
+ assert r == 0
+ assert show_string == expect7, show_string
def function1(**kw):
open(kw['out'], 'w').write("function1\n")
@@ -219,7 +239,7 @@ class BuilderTestCase(unittest.TestCase):
r = builder.execute(out = outfile)
assert r.__class__ == class2b
c = test.read(outfile, 'r')
- assert c == "act.py: syzygy\nfunction2\nclass2a\nclass2b\n", c
+ assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
def test_get_contents(self):
"""Test returning the signature contents of a Builder
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 114216e..b330dae 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -90,6 +90,10 @@ if os.name == 'posix':
'PROGSUFFIX' : '',
'LIBPREFIX' : 'lib',
'LIBSUFFIX' : '.a',
+ 'LIBDIRPREFIX' : '-L',
+ 'LIBDIRSUFFIX' : '',
+ 'LIBLINKPREFIX' : '-l',
+ 'LIBLINKSUFFIX' : '',
'ENV' : { 'PATH' : '/usr/local/bin:/bin:/usr/bin' },
}
@@ -116,6 +120,10 @@ elif os.name == 'nt':
'PROGSUFFIX' : '.exe',
'LIBPREFIX' : '',
'LIBSUFFIX' : '.lib',
+ 'LIBDIRPREFIX' : '/L',
+ 'LIBDIRSUFFIX' : '',
+ 'LIBLINKPREFIX' : '',
+ 'LIBLINKSUFFIX' : '$LIBSUFFIX',
'ENV' : {
'PATH' : r'C:\Python20;C:\WINNT\system32;C:\WINNT;C:\Program Files\Microsoft Visual Studio\VC98\Bin\;',
'PATHEXT' : '.COM;.EXE;.BAT;.CMD',
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 5bf31b4..e603b43 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -37,6 +37,7 @@ import types
import SCons.Util
import SCons.Builder
from SCons.Errors import UserError
+from UserList import UserList
def Command():
pass # XXX
@@ -71,6 +72,17 @@ class Environment:
Environment.
"""
+ # See the documentation for the __autogenerate() method
+ # for an explanation of this variable...
+ AUTO_GEN_VARS = ( ( '_LIBFLAGS',
+ 'LIBS',
+ 'LIBLINKPREFIX',
+ 'LIBLINKSUFFIX' ),
+ ( '_LIBDIRFLAGS',
+ 'LIBPATH',
+ 'LIBDIRPREFIX',
+ 'LIBDIRSUFFIX' ) )
+
def __init__(self, **kw):
import SCons.Defaults
self._dict = copy.deepcopy(SCons.Defaults.ConstructionEnvironment)
@@ -79,6 +91,7 @@ class Environment:
if kw.has_key('SCANNERS') and type(kw['SCANNERS']) != type([]):
kw['SCANNERS'] = [kw['SCANNERS']]
self._dict.update(copy.deepcopy(kw))
+ self.__autogenerate()
class BuilderWrapper:
"""Wrapper class that allows an environment to
@@ -108,6 +121,48 @@ class Environment:
for s in self._dict['SCANNERS']:
setattr(self, s.name, s)
+ def __autogenerate(self):
+ """Autogenerate the "interpolated" environment variables.
+ We read a static structure that tells us how. AUTO_GEN_VARS
+ is a tuple of tuples. Each inner tuple has four elements,
+ each strings referring to an environment variable, and describing
+ how to autogenerate a particular variable. The elements are:
+
+ 0 - The variable to generate
+ 1 - The "source" variable, usually a list
+ 2 - The "prefix" variable
+ 3 - The "suffix" variable
+
+ The autogenerated variable is a list, consisting of every
+ element of the source list, or a single element if the source
+ is a string, with the prefix and suffix
+ concatenated."""
+
+ for strVarAuto, strSrc, strPref, strSuff, in self.AUTO_GEN_VARS:
+ if self._dict.has_key(strSrc):
+ src_var = self._dict[strSrc]
+ if type(src_var) is types.ListType or \
+ isinstance(src_var, UserList):
+ src_var = map(str, src_var)
+ else:
+ src_var = [ str(src_var), ]
+ else:
+ src_var = []
+
+ try:
+ prefix = str(self._dict[strPref])
+ except KeyError:
+ prefix=''
+
+ try:
+ suffix = str(self._dict[strSuff])
+ except KeyError:
+ suffix =''
+
+ self._dict[strVarAuto] = map(lambda x, suff=suffix, pref=prefix: \
+ pref + str(x) + suff,
+ src_var)
+
def __cmp__(self, other):
return cmp(self._dict, other._dict)
@@ -134,6 +189,7 @@ class Environment:
construction variables and/or values.
"""
self._dict.update(copy.deepcopy(kw))
+ self.__autogenerate()
def Depends(self, target, dependency):
"""Explicity specify that 'target's depend on 'dependency'."""
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index aff9b01..1fbdb8c 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -248,15 +248,26 @@ class EnvironmentTestCase(unittest.TestCase):
"""
env = Environment(AAA = 'a', BBB = 'b')
str = env.subst("$AAA ${AAA}A $BBBB $BBB")
- assert str == "a aA b", str
+ assert str == "a aA b", str
env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
str = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
- assert str == "b foo b", str
+ assert str == "b foo b", str
env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
str = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
- assert str == "c c", str
-
-
+ assert str == "c c", str
+
+ def test_autogenerate(self):
+ """Test autogenerated environment variables."""
+ env = Environment(LIBS = [ 'foo', 'bar', 'baz' ],
+ LIBLINKPREFIX = 'foo',
+ LIBLINKSUFFIX = 'bar')
+ assert len(env.Dictionary('_LIBFLAGS')) == 3, env.Dictionary('_LIBFLAGS')
+ assert env.Dictionary('_LIBFLAGS')[0] == 'foofoobar', \
+ env.Dictionary('_LIBFLAGS')[0]
+ assert env.Dictionary('_LIBFLAGS')[1] == 'foobarbar', \
+ env.Dictionary('_LIBFLAGS')[1]
+ assert env.Dictionary('_LIBFLAGS')[2] == 'foobazbar', \
+ env.Dictionary('_LIBFLAGS')[2]
if __name__ == "__main__":
suite = unittest.makeSuite(EnvironmentTestCase, 'test_')
diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
index 24ad955..373571b 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -33,11 +33,15 @@ import SCons.Node
built_it = None
+built_target = None
+built_source = None
class Builder:
def execute(self, **kw):
- global built_it
+ global built_it, built_target, built_source
built_it = 1
+ built_target = kw['target']
+ built_source = kw['source']
return 0
def get_contents(self, env):
return 7
@@ -70,18 +74,23 @@ class NodeTestCase(unittest.TestCase):
def test_build(self):
"""Test building a node
"""
+ class MyNode(SCons.Node.Node):
+ def __str__(self):
+ return self.path
# Make sure it doesn't blow up if no builder is set.
- node = SCons.Node.Node()
+ node = MyNode()
node.build()
assert built_it == None
- node = SCons.Node.Node()
+ node = MyNode()
node.builder_set(Builder())
node.env_set(Environment())
- node.path = "xxx" # XXX FAKE SUBCLASS ATTRIBUTE
- node.sources = "yyy" # XXX FAKE SUBCLASS ATTRIBUTE
+ node.path = "xxx"
+ node.sources = ["yyy", "zzz"]
node.build()
assert built_it
+ assert built_target == "xxx", built_target
+ assert built_source == ["yyy", "zzz"], built_source
def test_builder_set(self):
"""Test setting a Node's Builder
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 860061c..6364bf8 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -71,9 +71,9 @@ class Node:
"""Actually build the node. Return the status from the build."""
if not self.builder:
return None
- sources_str = string.join(map(lambda x: str(x), self.sources))
- stat = self.builder.execute(env = self.env.Dictionary(),
- target = str(self), source = sources_str)
+ sources = map(lambda x: str(x), self.sources)
+ stat = self.builder.execute(env = self.env.Dictionary(),
+ target = str(self), source = sources)
if stat != 0:
raise BuildError(node = self, stat = stat)
return stat
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 64930b0..daeb243 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -36,6 +36,7 @@ import string
import re
from UserList import UserList
import SCons.Node.FS
+import cStringIO
def scons_str2nodes(arg, node_factory=SCons.Node.FS.default_fs.File):
"""This function converts a string or list into a list of Node instances.
@@ -157,36 +158,66 @@ class PathList(UserList):
# suffix and basepath.
return self.__class__([ UserList.__getitem__(self, item), ])
+_cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})')
+_space_sep = re.compile(r'[\t ]+(?![^{]*})')
-
-_tok = r'[_a-zA-Z]\w*'
-_cv = re.compile(r'\$(%s|{%s(\[[-0-9:]*\])?(\.\w+)?})' % (_tok, _tok))
-
-def scons_subst(string, locals, globals):
- """Recursively interpolates dictionary variables into
- the specified string, returning the expanded result.
- Variables are specified by a $ prefix in the string and
- begin with an initial underscore or alphabetic character
- followed by any number of underscores or alphanumeric
- characters. The construction variable names may be
- surrounded by curly braces to separate the name from
- trailing characters.
+def scons_subst_list(strSubst, locals, globals):
"""
+ This function is similar to scons_subst(), but with
+ one important difference. Instead of returning a single
+ string, this function returns a list of lists.
+ The first (outer) list is a list of lines, where the
+ substituted stirng has been broken along newline characters.
+ The inner lists are lists of command line arguments, i.e.,
+ the argv array that should be passed to a spawn or exec
+ function.
+
+ One important thing this guy does is preserve environment
+ variables that are lists. For instance, if you have
+ an environment variable that is a Python list (or UserList-
+ derived class) that contains path names with spaces in them,
+ then the entire path will be returned as a single argument.
+ This is the only way to know where the 'split' between arguments
+ is for executing a command line."""
+
def repl(m, locals=locals, globals=globals):
key = m.group(1)
if key[:1] == '{' and key[-1:] == '}':
key = key[1:-1]
try:
e = eval(key, locals, globals)
- if e is None:
+ if not e:
s = ''
+ elif type(e) is types.ListType or \
+ isinstance(e, UserList):
+ s = string.join(map(str, e), '\0')
else:
- s = str(e)
+ s = _space_sep.sub('\0', str(e))
except NameError:
s = ''
return s
n = 1
+
+ # Tokenize the original string...
+ strSubst = _space_sep.sub('\0', strSubst)
+
+ # Now, do the substitution
while n != 0:
- string, n = _cv.subn(repl, string)
- return string
+ strSubst, n = _cv.subn(repl, strSubst)
+ # Now parse the whole list into tokens.
+ listLines = string.split(strSubst, '\n')
+ return map(lambda x: filter(lambda y: y, string.split(x, '\0')),
+ listLines)
+def scons_subst(strSubst, locals, globals):
+ """Recursively interpolates dictionary variables into
+ the specified string, returning the expanded result.
+ Variables are specified by a $ prefix in the string and
+ begin with an initial underscore or alphabetic character
+ followed by any number of underscores or alphanumeric
+ characters. The construction variable names may be
+ surrounded by curly braces to separate the name from
+ trailing characters.
+ """
+ cmd_list = scons_subst_list(strSubst, locals, globals)
+ return string.join(map(string.join, cmd_list), '\n')
diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py
index 59ce344..01d3031 100644
--- a/src/engine/SCons/UtilTests.py
+++ b/src/engine/SCons/UtilTests.py
@@ -30,7 +30,7 @@ import sys
import unittest
import SCons.Node
import SCons.Node.FS
-from SCons.Util import scons_str2nodes, scons_subst, PathList
+from SCons.Util import scons_str2nodes, scons_subst, PathList, scons_subst_list
class UtilTestCase(unittest.TestCase):
@@ -125,10 +125,41 @@ class UtilTestCase(unittest.TestCase):
assert newcom == cvt("test foo")
newcom = scons_subst("test $xxx", loc, {})
- assert newcom == cvt("test "), newcom
+ assert newcom == cvt("test"), newcom
+ def test_subst_list(self):
+ """Testing the scons_subst_list() method..."""
+ loc = {}
+ loc['TARGETS'] = PathList(map(os.path.normpath, [ "./foo/bar.exe",
+ "/bar/baz with spaces.obj",
+ "../foo/baz.obj" ]))
+ loc['TARGET'] = loc['TARGETS'][0]
+ loc['SOURCES'] = PathList(map(os.path.normpath, [ "./foo/blah with spaces.cpp",
+ "/bar/ack.cpp",
+ "../foo/ack.c" ]))
+ loc['xxx'] = None
+ loc['NEWLINE'] = 'before\nafter'
+
+ if os.sep == '/':
+ def cvt(str):
+ return str
+ else:
+ def cvt(str):
+ return string.replace(str, '/', os.sep)
+
+ cmd_list = scons_subst_list("$TARGETS", loc, {})
+ assert cmd_list[0][1] == cvt("/bar/baz with spaces.obj"), cmd_list[0][1]
+ cmd_list = scons_subst_list("$SOURCES $NEWLINE $TARGETS", loc, {})
+ assert len(cmd_list) == 2, cmd_list
+ assert cmd_list[0][0] == cvt('foo/blah with spaces.cpp'), cmd_list[0][0]
+ assert cmd_list[1][2] == cvt("/bar/baz with spaces.obj"), cmd_list[1]
+ cmd_list = scons_subst_list("$SOURCES$NEWLINE", loc, {})
+ assert len(cmd_list) == 2, cmd_list
+ assert cmd_list[1][0] == 'after', cmd_list[1][0]
+ assert cmd_list[0][2] == cvt('../foo/ack.cbefore'), cmd_list[0][2]
+
if __name__ == "__main__":
suite = unittest.makeSuite(UtilTestCase, 'test_')
if not unittest.TextTestRunner().run(suite).wasSuccessful():