diff options
-rw-r--r-- | src/engine/SCons/Builder.py | 30 | ||||
-rw-r--r-- | src/engine/SCons/BuilderTests.py | 38 | ||||
-rw-r--r-- | src/engine/SCons/Defaults.py | 8 | ||||
-rw-r--r-- | src/engine/SCons/Environment.py | 56 | ||||
-rw-r--r-- | src/engine/SCons/EnvironmentTests.py | 21 | ||||
-rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 19 | ||||
-rw-r--r-- | src/engine/SCons/Node/__init__.py | 6 | ||||
-rw-r--r-- | src/engine/SCons/Util.py | 65 | ||||
-rw-r--r-- | src/engine/SCons/UtilTests.py | 35 |
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(): |