From: stevenknight Date: Thu, 13 Mar 2003 14:18:35 +0000 (+0000) Subject: Support using construction variables as re-usable, callable command generators. ... X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=f46fd66332acbb237dc574ea405400ca350d81a4;p=scons.git Support using construction variables as re-usable, callable command generators. (Charles Crain) git-svn-id: http://scons.tigris.org/svn/scons/trunk@611 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 7fc4f8e4..bc28d332 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -3924,6 +3924,55 @@ ${TARGET.suffix} => .x ${TARGET.abspath} => /top/dir/sub/dir/file.x .EE +Lastly, a variable name +may be a callable Python function +associated with a +construction variable in the environment. +The function should +take three arguments: +.I target +- a list of target nodes, +.I source +- a list of source nodes, +.I env +- the construction environment. +SCons will insert whatever +the called function returns +into the expanded string: + +.ES +def foo(target, source, env): + return "bar" + +# Will expand to $BAR to "bar baz" +env=Environment(FOO=foo, BAR="$FOO baz") +.EE + +You can use this feature to pass arguments to a +Python function by creating a callable class +that stores one or more arguments in an object, +and then uses them when the +.B __call__() +method is called. +Note that in this case, +the entire variable expansion must +be enclosed by curly braces +so that the arguments will +be associated with the +instantiation of the class: + +.ES +class foo: + def __init__(self, arg): + self.arg = arg + + def __call__(self, target, source, env): + return arg + " bar" + +# Will expand $BAR to "my argument bar baz" +env=Environment(FOO=foo, BAR="${FOO('my argument')} baz") +.EE + .LP The special pseudo-variables .R $( diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 3db1264d..3f9c1b49 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -21,6 +21,9 @@ RELEASE 0.12 - XXX that allows explicit specification of where the source files for an SConscript file can be found. + - Support more easily re-usable flavors of command generators by + calling callable variables when strings are expanded. + From Steven Knight: - Added an INSTALL construction variable that can be set to a function diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index ecad9b0b..45f4c980 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -126,8 +126,7 @@ def _do_create_action(act, strfunction=_null, varlist=[]): # like a function or a CommandGenerator in that variable # instead of a string. return CommandGeneratorAction(LazyCmdGenerator(var)) - listCmds = map(lambda x: CommandAction(string.split(x)), - string.split(act, '\n')) + listCmds = map(lambda x: CommandAction(x), string.split(act, '\n')) if len(listCmds) == 1: return listCmds[0] else: @@ -161,49 +160,6 @@ class ActionBase: def get_actions(self): return [self] - def subst_dict(self, target, source, env): - """Create a dictionary for substitution of construction - variables. - - This translates the following special arguments: - - env - the construction environment itself, - the values of which (CC, CCFLAGS, etc.) - are copied straight into the dictionary - - 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 = {} - - for k,v in env.items(): dict[k] = v - - if not SCons.Util.is_List(target): - target = [target] - - dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, target))) - if dict['TARGETS']: - dict['TARGET'] = dict['TARGETS'][0] - - def rstr(x): - try: - return x.rstr() - except AttributeError: - return str(x) - if not SCons.Util.is_List(source): - source = [source] - dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(rstr, source))) - if dict['SOURCES']: - dict['SOURCE'] = dict['SOURCES'][0] - - return dict - def __add__(self, other): return _actionAppend(self, other) @@ -226,11 +182,14 @@ _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)') class CommandAction(ActionBase): """Class for command-execution actions.""" def __init__(self, cmd): + # Cmd list can actually be a list or a single item...basically + # anything that we could pass in as the first arg to + # scons_subst_list(). self.cmd_list = cmd def strfunction(self, target, source, env): - dict = self.subst_dict(target, source, env) - cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm) + cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env, _rm, + target, source) return map(_string_from_cmd_list, cmd_list) def __call__(self, target, source, env): @@ -256,15 +215,15 @@ class CommandAction(ActionBase): else: raise SCons.Errors.UserError('Missing SPAWN construction variable.') - dict = self.subst_dict(target, source, env) - cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm) + cmd_list = SCons.Util.scons_subst_list(self.cmd_list, env, _rm, + target, source) for cmd_line in cmd_list: if len(cmd_line): if print_actions: self.show(_string_from_cmd_list(cmd_line)) if execute_actions: try: - ENV = dict['ENV'] + ENV = env['ENV'] except KeyError: global default_ENV if not default_ENV: @@ -280,16 +239,6 @@ class CommandAction(ActionBase): return ret return 0 - def _sig_dict(self, target, source, env): - """Supply a dictionary for use in computing signatures. - - For signature purposes, it doesn't matter what targets or - sources we use, so long as we use the same ones every time - so the signature stays the same. We supply an array of two - of each to allow for distinction between TARGET and TARGETS. - """ - return self.subst_dict(['__t1__', '__t2__'], ['__s1__', '__s2__'], env) - def get_raw_contents(self, target, source, env): """Return the complete contents of this action's command line. """ @@ -303,9 +252,11 @@ class CommandAction(ActionBase): #return SCons.Util.scons_subst(string.join(self.cmd_list), # self.subst_dict(target, source, env), # {}) - return SCons.Util.scons_subst(string.join(self.cmd_list), - env.sig_dict(), - {}) + cmd = self.cmd_list + if not SCons.Util.is_List(cmd): + cmd = [ cmd ] + return SCons.Util.scons_subst(string.join(map(str, cmd)), + env) def get_contents(self, target, source, env): """Return the signature contents of this action's command line. @@ -324,9 +275,11 @@ class CommandAction(ActionBase): # self.subst_dict(target, source, env), # {}, # _remove) - return SCons.Util.scons_subst(string.join(map(str, self.cmd_list)), - env.sig_dict(), - {}, + cmd = self.cmd_list + if not SCons.Util.is_List(cmd): + cmd = [ cmd ] + return SCons.Util.scons_subst(string.join(map(str, cmd)), + env, _remove) class CommandGeneratorAction(ActionBase): diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index de157ae7..eedc8655 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -100,6 +100,8 @@ class Environment: return self.d.get(key, value) def items(self): return self.d.items() + def Dictionary(self): + return self.d def sig_dict(self): d = {} for k,v in self.items(): d[k] = v @@ -129,7 +131,7 @@ class ActionTestCase(unittest.TestCase): a2 = SCons.Action.Action("string") assert isinstance(a2, SCons.Action.CommandAction), a2 - assert a2.cmd_list == ["string"], a2.cmd_list + assert a2.cmd_list == "string", a2.cmd_list if hasattr(types, 'UnicodeType'): exec "a3 = SCons.Action.Action(u'string')" @@ -138,11 +140,11 @@ class ActionTestCase(unittest.TestCase): a4 = SCons.Action.Action(["x", "y", "z", [ "a", "b", "c"]]) assert isinstance(a4, SCons.Action.ListAction), a4 assert isinstance(a4.list[0], SCons.Action.CommandAction), a4.list[0] - assert a4.list[0].cmd_list == ["x"], a4.list[0].cmd_list + assert a4.list[0].cmd_list == "x", a4.list[0].cmd_list assert isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1] - assert a4.list[1].cmd_list == ["y"], a4.list[1].cmd_list + assert a4.list[1].cmd_list == "y", a4.list[1].cmd_list assert isinstance(a4.list[2], SCons.Action.CommandAction), a4.list[2] - assert a4.list[2].cmd_list == ["z"], a4.list[2].cmd_list + assert a4.list[2].cmd_list == "z", a4.list[2].cmd_list assert isinstance(a4.list[3], SCons.Action.CommandAction), a4.list[3] assert a4.list[3].cmd_list == [ "a", "b", "c" ], a4.list[3].cmd_list @@ -158,25 +160,25 @@ class ActionTestCase(unittest.TestCase): a8 = SCons.Action.Action(["a8"]) assert isinstance(a8, SCons.Action.CommandAction), a8 - assert a8.cmd_list == [ "a8" ], a8.cmd_list + assert a8.cmd_list == "a8", a8.cmd_list a9 = SCons.Action.Action("x\ny\nz") assert isinstance(a9, SCons.Action.ListAction), a9 assert isinstance(a9.list[0], SCons.Action.CommandAction), a9.list[0] - assert a9.list[0].cmd_list == ["x"], a9.list[0].cmd_list + assert a9.list[0].cmd_list == "x", a9.list[0].cmd_list assert isinstance(a9.list[1], SCons.Action.CommandAction), a9.list[1] - assert a9.list[1].cmd_list == ["y"], a9.list[1].cmd_list + assert a9.list[1].cmd_list == "y", a9.list[1].cmd_list assert isinstance(a9.list[2], SCons.Action.CommandAction), a9.list[2] - assert a9.list[2].cmd_list == ["z"], a9.list[2].cmd_list + assert a9.list[2].cmd_list == "z", a9.list[2].cmd_list a10 = SCons.Action.Action(["x", foo, "z"]) assert isinstance(a10, SCons.Action.ListAction), a10 assert isinstance(a10.list[0], SCons.Action.CommandAction), a10.list[0] - assert a10.list[0].cmd_list == ["x"], a10.list[0].cmd_list + assert a10.list[0].cmd_list == "x", a10.list[0].cmd_list assert isinstance(a10.list[1], SCons.Action.FunctionAction), a10.list[1] assert a10.list[1].execfunction == foo, a10.list[1].execfunction assert isinstance(a10.list[2], SCons.Action.CommandAction), a10.list[2] - assert a10.list[2].cmd_list == ["z"], a10.list[2].cmd_list + assert a10.list[2].cmd_list == "z", a10.list[2].cmd_list a11 = SCons.Action.Action(foo, strfunction=bar) assert isinstance(a11, SCons.Action.FunctionAction), a11 @@ -228,51 +230,6 @@ class ActionBaseTestCase(unittest.TestCase): l = a.get_actions() assert l == [a], l - def test_subst_dict(self): - """Test substituting dictionary values in an Action - """ - a = SCons.Action.Action("x") - - d = a.subst_dict([], [], Environment(a = 'A', b = 'B')) - assert d['a'] == 'A', d - assert d['b'] == 'B', d - - d = a.subst_dict(target = 't', source = 's', env = Environment()) - assert str(d['TARGETS']) == 't', d['TARGETS'] - assert str(d['TARGET']) == 't', d['TARGET'] - assert str(d['SOURCES']) == 's', d['SOURCES'] - assert str(d['SOURCE']) == 's', d['SOURCE'] - - d = a.subst_dict(target = ['t1', 't2'], - source = ['s1', 's2'], - env = Environment()) - 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 N: - def __init__(self, name): - self.name = name - def __str__(self): - return self.name - def rstr(self): - return 'rstr-' + self.name - - d = a.subst_dict(target = [N('t3'), 't4'], - source = ['s3', N('s4')], - env = Environment()) - TARGETS = map(lambda x: str(x), d['TARGETS']) - TARGETS.sort() - assert TARGETS == ['t3', 't4'], d['TARGETS'] - SOURCES = map(lambda x: str(x), d['SOURCES']) - SOURCES.sort() - assert SOURCES == ['rstr-s4', 's3'], d['SOURCES'] - def test_add(self): """Test adding Actions to stuff.""" # Adding actions to other Actions or to stuff that can @@ -547,11 +504,18 @@ class CommandActionTestCase(unittest.TestCase): def test_get_raw_contents(self): """Test fetching the contents of a command Action """ + def CmdGen(target, source, env): + assert target is None, target + return "%s %s" % \ + (env["foo"], env["bar"]) + + # The number 1 is there to make sure all args get converted to strings. a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar", - "$)", "|"]) + "$)", "|", "$baz", 1]) c = a.get_raw_contents(target=[], source=[], - env=Environment(foo = 'FFF', bar = 'BBB')) - assert c == "| $( FFF | BBB $) |", c + env=Environment(foo = 'FFF', bar = 'BBB', + baz = CmdGen)) + assert c == "| $( FFF | BBB $) | FFF BBB 1", c # We've discusssed using the real target and source names in a # CommandAction's signature contents. This would have have the @@ -601,11 +565,18 @@ class CommandActionTestCase(unittest.TestCase): def test_get_contents(self): """Test fetching the contents of a command Action """ + def CmdGen(target, source, env): + assert target is None, target + return "%s %s" % \ + (env["foo"], env["bar"]) + + # The number 1 is there to make sure all args get converted to strings. a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar", - "$)", "|"]) + "$)", "|", "$baz", 1]) c = a.get_contents(target=[], source=[], - env=Environment(foo = 'FFF', bar = 'BBB')) - assert c == "| |", c + env=Environment(foo = 'FFF', bar = 'BBB', + baz = CmdGen)) + assert c == "| | FFF BBB 1", c # We've discusssed using the real target and source names in a # CommandAction's signature contents. This would have have the @@ -848,7 +819,7 @@ class ListActionTestCase(unittest.TestCase): assert isinstance(a.list[0], SCons.Action.CommandAction) assert isinstance(a.list[1], SCons.Action.FunctionAction) assert isinstance(a.list[2], SCons.Action.ListAction) - assert a.list[2].list[0].cmd_list == [ 'y' ] + assert a.list[2].list[0].cmd_list == 'y' def test_get_actions(self): """Test the get_actions() method for ListActions diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 433fadf7..2f172278 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -199,7 +199,7 @@ class BuilderTestCase(unittest.TestCase): Verify that we can retrieve the supplied action attribute. """ builder = SCons.Builder.Builder(action="foo") - assert builder.action.cmd_list == ["foo"] + assert builder.action.cmd_list == "foo" def func(): pass diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index f0c2163f..f4bd637c 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -193,23 +193,23 @@ def copyFunc(dest, source, env): os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) return 0 -def _concat(prefix, list, suffix, locals, globals, f=lambda x: x): +def _concat(prefix, list, suffix, env, f=lambda x: x): """Creates a new list from 'list' by first interpolating each element in the list using 'locals' and 'globals' and then calling f on the list, and finally concatenating 'prefix' and 'suffix' onto each element of the list. A trailing space on 'prefix' or leading space on 'suffix' will cause them to be put into seperate list elements rather than being concatenated.""" - + if not list: return list if not SCons.Util.is_List(list): list = [list] - def subst(x, locals=locals, globals=globals): + def subst(x, env = env): if SCons.Util.is_String(x): - return SCons.Util.scons_subst(x, locals, globals) + return SCons.Util.scons_subst(x, env) else: return x @@ -239,11 +239,12 @@ def _concat(prefix, list, suffix, locals, globals, f=lambda x: x): return ret -def _stripixes(prefix, list, suffix, locals, globals, stripprefix, stripsuffix, c=_concat): +def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=_concat): """This is a wrapper around _concat() that checks for the existence of prefixes or suffixes on list elements and strips them where it finds them. This is used by tools (like the GNU linker) that need to turn something like 'libfoo.a' into '-lfoo'.""" + def f(list, sp=stripprefix, ss=stripsuffix): ret = [] for l in list: @@ -253,7 +254,25 @@ def _stripixes(prefix, list, suffix, locals, globals, stripprefix, stripsuffix, l = l[:-len(ss)] ret.append(l) return ret - return c(prefix, list, suffix, locals, globals, f) + return c(prefix, list, suffix, env, f) + +class NullCmdGenerator: + """This is a callable class that can be used in place of other + command generators if you don't want them to do anything. + + The __call__ method for this class simply returns the thing + you instantiated it with. + + Example usage: + env["DO_NOTHING"] = NullCmdGenerator + env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}" + """ + + def __init__(self, cmd): + self.cmd = cmd + + def __call__(self, target, source, env): + return self.cmd ConstructionEnvironment = { 'BUILDERS' : { 'SharedLibrary' : SharedLibrary, @@ -270,8 +289,9 @@ ConstructionEnvironment = { 'INSTALL' : copyFunc, '_concat' : _concat, '_stripixes' : _stripixes, - '_LIBFLAGS' : '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, locals(), globals())}', - '_LIBDIRFLAGS' : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, locals(), globals(), RDirs)} $)', - '_CPPINCFLAGS' : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, locals(), globals(), RDirs)} $)', - '_F77INCFLAGS' : '$( ${_concat(INCPREFIX, F77PATH, INCSUFFIX, locals(), globals(), RDirs)} $)' + '_LIBFLAGS' : '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}', + '_LIBDIRFLAGS' : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs)} $)', + '_CPPINCFLAGS' : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs)} $)', + '_F77INCFLAGS' : '$( ${_concat(INCPREFIX, F77PATH, INCSUFFIX, __env__, RDirs)} $)', + 'TEMPFILE' : NullCmdGenerator } diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 8336f745..37182c46 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -143,6 +143,32 @@ class BuilderDict(UserDict): _rm = re.compile(r'\$[()]') +class _lister: + """This class is used to implement dummy targets and sources + for signature calculation.""" + def __init__(self, fmt): + self.fmt = fmt + def _format(self, index): + # For some reason, I originally made the fake names of + # the targets and sources 1-based (['__t1__, '__t2__']), + # not 0-based. We preserve this behavior by adding one + # to the returned item names, so everyone's targets + # won't get recompiled if they were using an old + # version. + return self.fmt % (index + 1) + def __getitem__(self, index): + return SCons.Util.PathList([self._format(index)])[0] + def __getslice__(self, i, j): + slice = [] + for x in range(i, j): + slice.append(self._format(x)) + return SCons.Util.PathList(slice) + def __getattr__(self, name): + # If we don't find an attribute in this class, let's + # look in PathList. self[0:2] returns a PathList instance + # via __getslice__ + return getattr(self[0:2], name) + class Environment: """Base class for construction Environments. These are the primary objects used to communicate dependency and @@ -404,7 +430,7 @@ class Environment: else: return side_effects - def subst(self, string, raw=0): + def subst(self, string, raw=0, target=None, source=None): """Recursively interpolates construction variables from the Environment into the specified string, returning the expanded result. Construction variables are specified by a $ prefix @@ -418,16 +444,18 @@ class Environment: regex_remove = None else: regex_remove = _rm - return SCons.Util.scons_subst(string, self._dict, {}, regex_remove) + return SCons.Util.scons_subst(string, self, regex_remove, + target, source) - def subst_list(self, string, raw=0): + def subst_list(self, string, raw=0, target=None, source=None): """Calls through to SCons.Util.scons_subst_list(). See the documentation for that function.""" if raw: regex_remove = None else: regex_remove = _rm - return SCons.Util.scons_subst_list(string, self._dict, {}, regex_remove) + return SCons.Util.scons_subst_list(string, self, regex_remove, + target, source) def get_scanner(self, skey): """Find the appropriate scanner given a key (usually a file suffix). @@ -549,10 +577,9 @@ class Environment: This fills in static TARGET, TARGETS, SOURCE and SOURCES variables so that signatures stay the same every time. """ - dict = {} - for k,v in self.items(): dict[k] = v - dict['TARGETS'] = SCons.Util.Lister('__t%d__') + dict = self._dict.copy() + dict['TARGETS'] = _lister('__t%d__') dict['TARGET'] = dict['TARGETS'][0] - dict['SOURCES'] = SCons.Util.Lister('__s%d__') + dict['SOURCES'] = _lister('__s%d__') dict['SOURCE'] = dict['SOURCES'][0] return dict diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index e590d7d2..9a6eb1cb 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -477,7 +477,7 @@ class EnvironmentTestCase(unittest.TestCase): action='buildfoo $target $source') assert not t.builder is None assert t.builder.action.__class__.__name__ == 'CommandAction' - assert t.builder.action.cmd_list == ['buildfoo', '$target', '$source'] + assert t.builder.action.cmd_list == 'buildfoo $target $source' assert 'foo1.in' in map(lambda x: x.path, t.sources) assert 'foo2.in' in map(lambda x: x.path, t.sources) @@ -540,6 +540,21 @@ class EnvironmentTestCase(unittest.TestCase): lst = env.subst_list([ "$AAA", "B $CCC" ]) assert lst == [ [ "a", "b" ], [ "c", "B a", "b" ], [ "c" ] ], lst + # Test callables in the Environment + def foo(target, source, env): + assert target == 1, target + assert source == 2, source + return env["FOO"] + + env = Environment(BAR=foo, FOO='baz') + + subst = env.subst('test $BAR', target=1, source=2) + assert subst == 'test baz', subst + + lst = env.subst_list('test $BAR', target=1, source=2) + assert lst[0][0] == 'test', lst[0][0] + assert lst[0][1] == 'baz', lst[0][1] + def test_autogenerate(dict): """Test autogenerating variables in a dictionary.""" @@ -816,6 +831,9 @@ class EnvironmentTestCase(unittest.TestCase): assert s == '', s s = map(str, d['TARGETS'][3:5]) assert s == ['__t4__', '__t5__'], s + s = map(lambda x: os.path.normcase(str(x)), d['TARGETS'].abspath) + assert s == map(os.path.normcase, [ os.path.join(os.getcwd(), '__t1__'), + os.path.join(os.getcwd(), '__t2__') ]) s = str(d['SOURCE']) assert s == '__s1__', s @@ -833,6 +851,9 @@ class EnvironmentTestCase(unittest.TestCase): assert s == '', s s = map(str, d['SOURCES'][3:5]) assert s == ['__s4__', '__s5__'], s + s = map(lambda x: os.path.normcase(str(x)), d['SOURCES'].abspath) + assert s == map(os.path.normcase, [ os.path.join(os.getcwd(), '__s1__'), + os.path.join(os.getcwd(), '__s2__') ]) if __name__ == "__main__": diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py index 6a14a7a9..870dd996 100644 --- a/src/engine/SCons/Platform/win32.py +++ b/src/engine/SCons/Platform/win32.py @@ -38,29 +38,34 @@ import os.path import string import sys -# +class TempFileMunge: + """A callable class. You can set an Environment variable to this, + then call it with a string argument, then it will perform temporary + file substitution on it. This is used to circumvent the win32 long command + line limitation. -def TempFileMunge(env, cmd_list, for_signature): - """Given a list of command line arguments, see if it is too - long to pass to the win32 command line interpreter. If so, - create a temp file, then pass "@tempfile" as the sole argument - to the supplied command (which is the first element of cmd_list). - Otherwise, just return [cmd_list].""" - cmd = env.subst_list(cmd_list)[0] - if for_signature or \ - (reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= 2048: - return [cmd_list] - else: - import tempfile - # We do a normpath because mktemp() has what appears to be - # a bug in Win32 that will use a forward slash as a path - # delimiter. Win32's link mistakes that for a command line - # switch and barfs. - tmp = os.path.normpath(tempfile.mktemp()) - args = map(SCons.Util.quote_spaces, cmd[1:]) - open(tmp, 'w').write(string.join(args, " ") + "\n") - return [ [cmd[0], '@' + tmp], - ['del', tmp] ] + Example usage: + env["TEMPFILE"] = TempFileMunge + env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES')}" + """ + def __init__(self, cmd): + self.cmd = cmd + + def __call__(self, target, source, env): + cmd = env.subst_list(self.cmd, 0, target, source)[0] + if target is None or \ + (reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= 2048: + return self.cmd + else: + import tempfile + # We do a normpath because mktemp() has what appears to be + # a bug in Win32 that will use a forward slash as a path + # delimiter. Win32's link mistakes that for a command line + # switch and barfs. + tmp = os.path.normpath(tempfile.mktemp()) + args = map(SCons.Util.quote_spaces, cmd[1:]) + open(tmp, 'w').write(string.join(args, " ") + "\n") + return [ cmd[0], '@' + tmp + '\ndel', tmp ] # The upshot of all this is that, if you are using Python 1.5.2, # you had better have cmd or command.com in your PATH when you run @@ -128,4 +133,5 @@ def generate(env): env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ] env['SPAWN'] = spawn env['SHELL'] = cmd_interp + env['TEMPFILE'] = TempFileMunge env['ESCAPE'] = escape diff --git a/src/engine/SCons/Tool/gnulink.py b/src/engine/SCons/Tool/gnulink.py index db7ff4bb..9f782c16 100644 --- a/src/engine/SCons/Tool/gnulink.py +++ b/src/engine/SCons/Tool/gnulink.py @@ -52,7 +52,7 @@ def generate(env, platform): env['LINKCOM'] = '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' env['LIBDIRPREFIX']='-L' env['LIBDIRSUFFIX']='' - env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, locals(), globals(), LIBPREFIX, LIBSUFFIX)}' + env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIX, LIBSUFFIX, __env__)}' env['LIBLINKPREFIX']='-l' env['LIBLINKSUFFIX']='' diff --git a/src/engine/SCons/Tool/linkloc.py b/src/engine/SCons/Tool/linkloc.py index 51ce60cc..d55a4eeb 100644 --- a/src/engine/SCons/Tool/linkloc.py +++ b/src/engine/SCons/Tool/linkloc.py @@ -43,7 +43,6 @@ import SCons.Defaults import SCons.Errors import SCons.Util -from SCons.Platform.win32 import TempFileMunge from SCons.Tool.msvc import get_msdev_paths from SCons.Tool.PharLapCommon import addPharLapPaths @@ -66,8 +65,8 @@ class LinklocGenerator: def __init__(self, cmdline): self.cmdline = cmdline - def __call__(self, env, target, source, for_signature): - if for_signature: + def __call__(self, env, target, source): + if target is None: # Expand the contents of any linker command files recursively subs = 1 strsub = env.subst(self.cmdline) @@ -75,23 +74,21 @@ class LinklocGenerator: strsub, subs = _re_linker_command.subn(repl_linker_command, strsub) return strsub else: - return TempFileMunge(env, string.split(self.cmdline), 0) - -_linklocLinkAction = SCons.Action.Action(SCons.Action.CommandGenerator(LinklocGenerator("$LINK $LINKFLAGS $( $_LIBDIRFLAGS $) $_LIBFLAGS -exe $TARGET $SOURCES"))) -_linklocShLinkAction = SCons.Action.Action(SCons.Action.CommandGenerator(LinklocGenerator("$SHLINK $SHLINKFLAGS $( $_LIBDIRFLAGS $) $_LIBFLAGS -dll $TARGET $SOURCES"))) + return "${TEMPFILE('" + self.cmdline + "')}" def generate(env, platform): """Add Builders and construction variables for ar to an Environment.""" env['BUILDERS']['SharedLibrary'] = SCons.Defaults.SharedLibrary env['BUILDERS']['Program'] = SCons.Defaults.Program + env['SUBST_CMD_FILE'] = LinklocGenerator env['SHLINK'] = '$LINK' env['SHLINKFLAGS'] = '$LINKFLAGS' - env['SHLINKCOM'] = _linklocShLinkAction + env['SHLINKCOM'] = '${SUBST_CMD_FILE("$SHLINK $SHLINKFLAGS $( $_LIBDIRFLAGS $) $_LIBFLAGS -dll $TARGET $SOURCES")}' env['SHLIBEMITTER']= None env['LINK'] = "linkloc" env['LINKFLAGS'] = '' - env['LINKCOM'] = _linklocLinkAction + env['LINKCOM'] = '${SUBST_CMD_FILE("$LINK $LINKFLAGS $( $_LIBDIRFLAGS $) $_LIBFLAGS -exe $TARGET $SOURCES")}' env['LIBDIRPREFIX']='-libpath ' env['LIBDIRSUFFIX']='' env['LIBLINKPREFIX']='-lib ' diff --git a/src/engine/SCons/Tool/mslib.py b/src/engine/SCons/Tool/mslib.py index e8667eef..958218fd 100644 --- a/src/engine/SCons/Tool/mslib.py +++ b/src/engine/SCons/Tool/mslib.py @@ -35,15 +35,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import SCons.Defaults -from SCons.Platform.win32 import TempFileMunge - -def win32ArGenerator(env, target, source, for_signature, **kw): - args = [ '$AR', '$ARFLAGS', '/OUT:%s' % target[0]] - args.extend(map(SCons.Util.to_String, source)) - return TempFileMunge(env, args, for_signature) - -ArAction = SCons.Action.CommandGenerator(win32ArGenerator) - def generate(env, platform): """Add Builders and construction variables for lib to an Environment.""" env['BUILDERS']['Library'] = SCons.Defaults.StaticLibrary @@ -51,7 +42,7 @@ def generate(env, platform): env['AR'] = 'lib' env['ARFLAGS'] = '/nologo' - env['ARCOM'] = ArAction + env['ARCOM'] = "${TEMPFILE('$AR $ARFLAGS /OUT:$TARGET $SOURCES')}" def exists(env): return env.Detect('lib') diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py index 6ef15423..fa174e40 100644 --- a/src/engine/SCons/Tool/mslink.py +++ b/src/engine/SCons/Tool/mslink.py @@ -42,44 +42,42 @@ import SCons.Errors import SCons.Util import msvc -from SCons.Platform.win32 import TempFileMunge from SCons.Tool.msvc import get_msdev_paths - -def win32LinkGenerator(env, target, source, for_signature): - args = [ '$LINK', '$LINKFLAGS', '/OUT:%s' % target[0], - '$(', '$_LIBDIRFLAGS', '$)', '$_LIBFLAGS' ] - - if env.has_key('PDB') and env['PDB']: - args.extend(['/PDB:%s'%target[0].File(env['PDB']), '/DEBUG']) - args.extend(map(SCons.Util.to_String, source)) - return TempFileMunge(env, args, for_signature) +def pdbGenerator(env, target, source): + if target and env.has_key('PDB') and env['PDB']: + return ['/PDB:%s'%target[0].File(env['PDB']), '/DEBUG'] -def win32LibGenerator(target, source, env, for_signature): - listCmd = [ "$SHLINK", "$SHLINKFLAGS" ] +def win32ShlinkTargets(target, source, env): + if target: + listCmd = [] + dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX') + if dll: listCmd.append("/out:%s"%dll) - if env.has_key('PDB') and env['PDB']: - listCmd.extend(['/PDB:%s'%target[0].File(env['PDB']), '/DEBUG']) - - dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX') - if dll: listCmd.append("/out:%s"%dll) - - implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX') - if implib: listCmd.append("/implib:%s"%implib) - - listCmd.extend([ '$_LIBDIRFLAGS', '$_LIBFLAGS' ]) + implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX') + if implib: listCmd.append("/implib:%s"%implib) - deffile = env.FindIxes(source, "WIN32DEFPREFIX", "WIN32DEFSUFFIX") - - for src in source: - if src == deffile: - # Treat this source as a .def file. - listCmd.append("/def:%s" % src) - else: - # Just treat it as a generic source file. - listCmd.append(str(src)) - - return TempFileMunge(env, listCmd, for_signature) + return listCmd + else: + # For signature calculation + return '/out:$TARGET' + +def win32ShlinkSources(target, source, env): + if target: + listCmd = [] + + deffile = env.FindIxes(source, "WIN32DEFPREFIX", "WIN32DEFSUFFIX") + for src in source: + if src == deffile: + # Treat this source as a .def file. + listCmd.append("/def:%s" % src) + else: + # Just treat it as a generic source file. + listCmd.append(str(src)) + return listCmd + else: + # For signature calculation + return "$SOURCES" def win32LibEmitter(target, source, env): msvc.validate_vars(env) @@ -120,9 +118,6 @@ def prog_emitter(target, source, env): return (target,source) -ShLibAction = SCons.Action.CommandGenerator(win32LibGenerator) -LinkAction = SCons.Action.CommandGenerator(win32LinkGenerator) - def generate(env, platform): """Add Builders and construction variables for ar to an Environment.""" env['BUILDERS']['SharedLibrary'] = SCons.Defaults.SharedLibrary @@ -130,14 +125,14 @@ def generate(env, platform): env['SHLINK'] = '$LINK' env['SHLINKFLAGS'] = '$LINKFLAGS /dll' - env['SHLINKCOM'] = ShLibAction + env['_SHLINK_TARGETS'] = win32ShlinkTargets + env['_SHLINK_SOURCES'] = win32ShlinkSources + env['SHLINKCOM'] = '${TEMPFILE("$SHLINK $SHLINKFLAGS $_SHLINK_TARGETS $( $_LIBDIRFLAGS $) $_LIBFLAGS $_PDB $_SHLINK_SOURCES")}' env['SHLIBEMITTER']= win32LibEmitter env['LINK'] = 'link' env['LINKFLAGS'] = '/nologo' - if str(platform) == 'cygwin': - env['LINKCOM'] = '$LINK $LINKFLAGS /OUT:$TARGET $( $_LIBDIRFLAGS $) $_LIBFLAGS $SOURCES' - else: - env['LINKCOM'] = LinkAction + env['_PDB'] = pdbGenerator + env['LINKCOM'] = '${TEMPFILE("$LINK $LINKFLAGS /OUT:$TARGET $( $_LIBDIRFLAGS $) $_LIBFLAGS $_PDB $SOURCES")}' env['PROGEMITTER'] = prog_emitter env['LIBDIRPREFIX']='/LIBPATH:' env['LIBDIRSUFFIX']='' diff --git a/src/engine/SCons/Tool/sgilink.py b/src/engine/SCons/Tool/sgilink.py index 6d04ec2e..8304bb6c 100644 --- a/src/engine/SCons/Tool/sgilink.py +++ b/src/engine/SCons/Tool/sgilink.py @@ -52,7 +52,7 @@ def generate(env, platform): env['LINKCOM'] = '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' env['LIBDIRPREFIX']='-L' env['LIBDIRSUFFIX']='' - env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, locals(), globals(), LIBPREFIX, LIBSUFFIX)}' + env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIX, LIBSUFFIX, __env__)}' env['LIBLINKPREFIX']='-l' env['LIBLINKSUFFIX']='' diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index b03ff0f9..16ff3886 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -194,50 +194,7 @@ class PathList(UserList.UserList): # suffix and basepath. return self.__class__([ UserList.UserList.__getitem__(self, item), ]) -class Lister(PathList): - """A special breed of fake list that not only supports the inherited - "path dissection" attributes of PathList, but also uses a supplied - format string to generate arbitrary (slices of) individually-named - elements on the fly. - """ - def __init__(self, fmt): - self.fmt = fmt - PathList.__init__(self, [ self._element(0), self._element(1) ]) - def __getitem__(self, index): - return PathList([self._element(index)]) - def _element(self, index): - """Generate the index'th element in this list.""" - # For some reason, I originally made the fake names of - # the targets and sources 1-based (['__t1__, '__t2__']), - # not 0-based. We preserve this behavior by adding one - # to the returned item names, so everyone's targets - # won't get recompiled if they were using an old version. - return self.fmt % (index + 1) - def __iter__(self): - """Return an iterator object for Python 2.2.""" - class Lister_iter: - def __init__(self, data): - self.data = data - self.index = 0 - def __iter__(self): - return self - def next(self): - try: - element = self.data[self.index] - except IndexError: - raise StopIteration - self.index = self.index + 1 - return element - return Lister_iter(self.data) - def __getslice__(self, i, j): - slice = [] - if j == sys.maxint: - j = i + 2 - for x in range(i, j): - slice.append(self._element(x)) - return PathList(slice) - -_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[^}]*})$') +_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$') def get_environment_var(varstr): """Given a string, first determine if it looks like a reference @@ -272,7 +229,7 @@ def quote_spaces(arg): # a command line. # # \0\2 signifies a division between multiple distinct -# commands +# commands, i.e., a newline # # \0\3 This string should be interpreted literally. # This code occurring anywhere in the string means @@ -282,7 +239,7 @@ def quote_spaces(arg): # \0\4 A literal dollar sign '$' # # \0\5 Placed before and after interpolated variables -# so that we do not accidentally smush to variables +# so that we do not accidentally smush two variables # together during the recursive interpolation process. _cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})') @@ -397,7 +354,49 @@ class DisplayEngine: self.__call__ = self.dont_print -def scons_subst_list(strSubst, globals, locals, remove=None): +def subst_dict(target, source, env): + """Create a dictionary for substitution of construction + variables. + + This translates the following special arguments: + + env - the construction environment itself, + the values of which (CC, CCFLAGS, etc.) + are copied straight into the dictionary + + 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 = env.Dictionary().copy() + + if not is_List(target): + target = [target] + + dict['TARGETS'] = PathList(map(os.path.normpath, map(str, target))) + if dict['TARGETS']: + dict['TARGET'] = dict['TARGETS'][0] + + def rstr(x): + try: + return x.rstr() + except AttributeError: + return str(x) + if not is_List(source): + source = [source] + dict['SOURCES'] = PathList(map(os.path.normpath, map(rstr, source))) + if dict['SOURCES']: + dict['SOURCE'] = dict['SOURCES'][0] + + return dict + +def scons_subst_list(strSubst, env, remove=None, target=None, + source=None): """ This function serves the same purpose as scons_subst(), except this function returns the interpolated list as a list of lines, where @@ -409,7 +408,7 @@ def scons_subst_list(strSubst, globals, locals, remove=None): function. There are a few simple rules this function follows in order to - determine how to parse strSubst and consruction variables into lines + determine how to parse strSubst and construction variables into lines and arguments: 1) A string is interpreted as a space delimited list of arguments. @@ -422,33 +421,34 @@ def scons_subst_list(strSubst, globals, locals, remove=None): (e.g. file names) to contain embedded newline characters. """ - def convert(x): - """This function is used to convert construction variable - values or the value of strSubst to a string for interpolation. - This function follows the rules outlined in the documentaion - for scons_subst_list()""" - if x is None: - return '' - elif is_String(x): - return _space_sep.sub('\0', x) - elif is_List(x): - try: - return x.to_String() - except AttributeError: - return string.join(map(to_String, x), '\0') - else: - return to_String(x) - - def repl(m, globals=globals, locals=locals): + if target != None: + dict = subst_dict(target, source, env) + else: + dict = env.sig_dict() + + def repl(m, + target=target, + source=source, + env=env, + local_vars = dict, + global_vars = { "__env__" : env }): key = m.group(1) if key[0] == '{': key = key[1:-1] try: - e = eval(key, globals, locals) - # The \0\5 escape code keeps us from smushing two or more - # variables together during recusrive substitution, i.e. - # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad) - return "\0\5" + _convert(e) + "\0\5" + e = eval(key, global_vars, local_vars) + if callable(e): + # We wait to evaluate callables until the end of everything + # else. For now, we instert a special escape sequence + # that we will look for later. + return '\0\5' + _convert(e(target=target, + source=source, + env=env)) + '\0\5' + else: + # The \0\5 escape code keeps us from smushing two or more + # variables together during recusrive substitution, i.e. + # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad) + return "\0\5" + _convert(e) + "\0\5" except NameError: return '\0\5' @@ -474,7 +474,8 @@ def scons_subst_list(strSubst, globals, locals, remove=None): return map(lambda x: map(CmdStringHolder, filter(lambda y:y, string.split(x, '\0\1'))), listLines) -def scons_subst(strSubst, globals, locals, remove=None): +def scons_subst(strSubst, env, remove=None, target=None, + source=None): """Recursively interpolates dictionary variables into the specified string, returning the expanded result. Variables are specified by a $ prefix in the string and @@ -487,12 +488,24 @@ def scons_subst(strSubst, globals, locals, remove=None): # This function needs to be fast, so don't call scons_subst_list - def repl(m, globals=globals, locals=locals): + if target != None: + dict = subst_dict(target, source, env) + else: + dict = env.sig_dict() + + def repl(m, + target=target, + source=source, + env=env, + local_vars = dict, + global_vars = { '__env__' : env }): key = m.group(1) if key[0] == '{': key = key[1:-1] try: - e = eval(key, globals, locals) + e = eval(key, global_vars, local_vars) + if callable(e): + e = e(target=target, source=source, env=env) if e is None: s = '' elif is_List(e): diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index f77265a7..603631cc 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -43,22 +43,57 @@ class OutBuffer: def write(self, str): self.buffer = self.buffer + str +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 sig_dict(self): + dict = self.dict.copy() + dict["TARGETS"] = 'tsig' + dict["SOURCES"] = 'ssig' + return dict + +def CmdGen1(target, source, env): + # Nifty trick...since Environment references are interpolated, + # instantiate an instance of a callable class with this one, + # which will then get evaluated. + assert target == 't', target + assert source == 's', source + return "${CMDGEN2('foo')}" + +class CmdGen2: + def __init__(self, mystr): + self.mystr = mystr + + def __call__(self, target, source, env): + assert target == 't', target + assert source == 's', source + return [ self.mystr, env.Dictionary('BAR') ] class UtilTestCase(unittest.TestCase): - def test_subst_PathList(self): - """Test the subst function with PathLists""" + def test_subst(self): + """Test the subst function""" loc = {} - loc['TARGETS'] = PathList(map(os.path.normpath, [ "./foo/bar.exe", - "/bar/baz.obj", - "../foo/baz.obj" ])) - loc['TARGET'] = loc['TARGETS'][0] - loc['SOURCES'] = PathList(map(os.path.normpath, [ "./foo/blah.cpp", - "/bar/ack.cpp", - "../foo/ack.c" ])) - loc['SOURCE'] = loc['SOURCES'][0] + target = [ "./foo/bar.exe", + "/bar/baz.obj", + "../foo/baz.obj" ] + source = [ "./foo/blah.cpp", + "/bar/ack.cpp", + "../foo/ack.c" ] loc['xxx'] = None loc['zero'] = 0 loc['one'] = 1 + loc['BAR'] = 'baz' + + loc['CMDGEN1'] = CmdGen1 + loc['CMDGEN2'] = CmdGen2 + + env = DummyEnv(loc) if os.sep == '/': def cvt(str): @@ -67,151 +102,102 @@ class UtilTestCase(unittest.TestCase): def cvt(str): return string.replace(str, '/', os.sep) - newcom = scons_subst("test $TARGETS $SOURCES", loc, {}) + newcom = scons_subst("test $TARGETS $SOURCES", env, + target=target, source=source) assert newcom == cvt("test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c") - newcom = scons_subst("test ${TARGETS[:]} ${SOURCES[0]}", loc, {}) + newcom = scons_subst("test ${TARGETS[:]} ${SOURCES[0]}", env, + target=target, source=source) assert newcom == cvt("test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp") - newcom = scons_subst("test ${TARGETS[1:]}v", loc, {}) + newcom = scons_subst("test ${TARGETS[1:]}v", env, + target=target, source=source) assert newcom == cvt("test /bar/baz.obj ../foo/baz.objv") - newcom = scons_subst("test $TARGET", loc, {}) + newcom = scons_subst("test $TARGET", env, + target=target, source=source) assert newcom == cvt("test foo/bar.exe") - newcom = scons_subst("test $TARGET$FOO[0]", loc, {}) + newcom = scons_subst("test $TARGET$FOO[0]", env, + target=target, source=source) assert newcom == cvt("test foo/bar.exe[0]") - newcom = scons_subst("test ${TARGET.file}", loc, {}) + newcom = scons_subst("test ${TARGET.file}", env, + target=target, source=source) assert newcom == cvt("test bar.exe") - newcom = scons_subst("test ${TARGET.filebase}", loc, {}) + newcom = scons_subst("test ${TARGET.filebase}", env, + target=target, source=source) assert newcom == cvt("test bar") - newcom = scons_subst("test ${TARGET.suffix}", loc, {}) + newcom = scons_subst("test ${TARGET.suffix}", env, + target=target, source=source) assert newcom == cvt("test .exe") - newcom = scons_subst("test ${TARGET.base}", loc, {}) + newcom = scons_subst("test ${TARGET.base}", env, + target=target, source=source) assert newcom == cvt("test foo/bar") - newcom = scons_subst("test ${TARGET.dir}", loc, {}) + newcom = scons_subst("test ${TARGET.dir}", env, + target=target, source=source) assert newcom == cvt("test foo") - cwd = SCons.Util.updrive(os.getcwd()) - - newcom = scons_subst("test ${TARGET.abspath}", loc, {}) - assert newcom == cvt("test %s/foo/bar.exe" % (cwd)), newcom + newcom = scons_subst("test ${TARGET.abspath}", env, + target=target, source=source) + assert newcom == cvt("test %s/foo/bar.exe"%SCons.Util.updrive(os.getcwd())), newcom - newcom = scons_subst("test ${SOURCES.abspath}", loc, {}) - assert newcom == cvt("test %s/foo/blah.cpp %s %s/foo/ack.c"%(cwd, + newcom = scons_subst("test ${SOURCES.abspath}", env, + target=target, source=source) + assert newcom == cvt("test %s/foo/blah.cpp %s %s/foo/ack.c"%(SCons.Util.updrive(os.getcwd()), SCons.Util.updrive(os.path.abspath(os.path.normpath("/bar/ack.cpp"))), SCons.Util.updrive(os.path.normpath(os.getcwd()+"/..")))), newcom - newcom = scons_subst("test ${SOURCE.abspath}", loc, {}) - assert newcom == cvt("test %s/foo/blah.cpp" % (cwd)), newcom + newcom = scons_subst("test ${SOURCE.abspath}", env, + target=target, source=source) + assert newcom == cvt("test %s/foo/blah.cpp"%SCons.Util.updrive(os.getcwd())), newcom - newcom = scons_subst("test $xxx", loc, {}) + newcom = scons_subst("test $xxx", env) assert newcom == cvt("test"), newcom - newcom = scons_subst("test $($xxx$)", loc, {}) + newcom = scons_subst("test $($xxx$)", env) assert newcom == cvt("test $($)"), newcom - newcom = scons_subst("test $( $xxx $)", loc, {}) + newcom = scons_subst("test $( $xxx $)", env) assert newcom == cvt("test $( $)"), newcom - newcom = scons_subst("test $($xxx$)", loc, {}, re.compile('\$[()]')) + newcom = scons_subst("test $($xxx$)", env, re.compile('\$[()]')) assert newcom == cvt("test"), newcom - newcom = scons_subst("test $( $xxx $)", loc, {}, re.compile('\$[()]')) + newcom = scons_subst("test $( $xxx $)", env, re.compile('\$[()]')) assert newcom == cvt("test"), newcom - newcom = scons_subst("test $zero", loc, {}) + newcom = scons_subst("test $zero", env) assert newcom == cvt("test 0"), newcom - newcom = scons_subst("test $one", loc, {}) + newcom = scons_subst("test $one", env) assert newcom == cvt("test 1"), newcom - newcom = scons_subst("test aXbXcXd", loc, {}, re.compile('X')) + newcom = scons_subst("test aXbXcXd", env, re.compile('X')) assert newcom == cvt("test abcd"), newcom - glob = { 'a' : 1, 'b' : 2 } - loc = {'a' : 3, 'c' : 4 } - newcom = scons_subst("test $a $b $c $d test", glob, loc) - assert newcom == "test 3 2 4 test", newcom + newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS", + env, target='t', source='s') + assert newcom == cvt("test foo baz s t"), newcom # Test against a former bug in scons_subst_list() glob = { "FOO" : "$BAR", "BAR" : "BAZ", "BLAT" : "XYX", "BARXYX" : "BADNEWS" } - newcom = scons_subst("$FOO$BLAT", glob, {}) + newcom = scons_subst("$FOO$BLAT", DummyEnv(glob)) assert newcom == "BAZXYX", newcom # Test for double-dollar-sign behavior glob = { "FOO" : "BAR", "BAZ" : "BLAT" } - newcom = scons_subst("$$FOO$BAZ", glob, {}) + newcom = scons_subst("$$FOO$BAZ", DummyEnv(glob)) assert newcom == "$FOOBLAT", newcom - def test_subst_Lister(self): - """Test the subst function with Listers""" - loc = {} - loc['TARGETS'] = Lister('t%d') - loc['TARGET'] = loc['TARGETS'][0] - loc['SOURCES'] = Lister('s%d') - loc['SOURCE'] = loc['SOURCES'][0] - loc['xxx'] = None - loc['zero'] = 0 - loc['one'] = 1 - - if os.sep == '/': - def cvt(str): - return str - else: - def cvt(str): - return string.replace(str, '/', os.sep) - - newcom = scons_subst("test $TARGETS $SOURCES", loc, {}) - assert newcom == cvt("test t1 t2 s1 s2"), newcom - - newcom = scons_subst("test ${TARGETS[:]} ${SOURCES[0]}", loc, {}) - assert newcom == cvt("test t1 t2 s1"), newcom - - newcom = scons_subst("test ${TARGETS[1:]}v", loc, {}) - assert newcom == cvt("test t2 t3v"), newcom - - newcom = scons_subst("test $TARGET", loc, {}) - assert newcom == cvt("test t1"), newcom - - newcom = scons_subst("test $TARGET$FOO[0]", loc, {}) - assert newcom == cvt("test t1[0]"), newcom - - newcom = scons_subst("test ${TARGET.file}", loc, {}) - assert newcom == cvt("test t1"), newcom - - newcom = scons_subst("test ${TARGET.filebase}", loc, {}) - assert newcom == cvt("test t1"), newcom - - newcom = scons_subst("test ${TARGET.suffix}", loc, {}) - assert newcom == cvt("test"), newcom - - newcom = scons_subst("test ${TARGET.base}", loc, {}) - assert newcom == cvt("test t1"), newcom - - newcom = scons_subst("test ${TARGET.dir}", loc, {}) - assert newcom == cvt("test"), newcom - - cwd = SCons.Util.updrive(os.getcwd()) - - newcom = scons_subst("test ${TARGET.abspath}", loc, {}) - assert newcom == cvt("test %s/t1" % (cwd)), newcom - - newcom = scons_subst("test ${SOURCES.abspath}", loc, {}) - assert newcom == cvt("test %s/s1 %s/s2" % (cwd, cwd)), newcom - - newcom = scons_subst("test ${SOURCE.abspath}", loc, {}) - assert newcom == cvt("test %s/s1" % cwd), newcom - def test_splitext(self): assert splitext('foo') == ('foo','') assert splitext('foo.bar') == ('foo','.bar') @@ -229,13 +215,12 @@ class UtilTestCase(unittest.TestCase): return 1 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" ])) + target = [ "./foo/bar.exe", + "/bar/baz with spaces.obj", + "../foo/baz.obj" ] + source = [ "./foo/blah with spaces.cpp", + "/bar/ack.cpp", + "../foo/ack.c" ] loc['xxx'] = None loc['NEWLINE'] = 'before\nafter' @@ -244,6 +229,11 @@ class UtilTestCase(unittest.TestCase): loc['BAR'] = Node('bar with spaces.out') loc['CRAZY'] = Node('crazy\nfile.in') + loc['CMDGEN1'] = CmdGen1 + loc['CMDGEN2'] = CmdGen2 + + env = DummyEnv(loc) + if os.sep == '/': def cvt(str): return str @@ -251,20 +241,26 @@ class UtilTestCase(unittest.TestCase): def cvt(str): return string.replace(str, '/', os.sep) - cmd_list = scons_subst_list("$TARGETS", loc, {}) + cmd_list = scons_subst_list("$TARGETS", env, + target=target, + source=source) assert cmd_list[0][1] == cvt("/bar/baz with spaces.obj"), cmd_list[0][1] - cmd_list = scons_subst_list("$SOURCES $NEWLINE $TARGETS", loc, {}) + cmd_list = scons_subst_list("$SOURCES $NEWLINE $TARGETS", env, + target=target, + source=source) 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, {}) + cmd_list = scons_subst_list("$SOURCES$NEWLINE", env, + target=target, + source=source) 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] - cmd_list = scons_subst_list("$DO --in=$FOO --out=$BAR", loc, {}) + cmd_list = scons_subst_list("$DO --in=$FOO --out=$BAR", env) assert len(cmd_list) == 1, cmd_list assert len(cmd_list[0]) == 3, cmd_list assert cmd_list[0][0] == 'do something', cmd_list[0][0] @@ -272,7 +268,7 @@ class UtilTestCase(unittest.TestCase): assert cmd_list[0][2] == '--out=bar with spaces.out', cmd_list[0][2] # This test is now fixed, and works like it should. - cmd_list = scons_subst_list("$DO --in=$CRAZY --out=$BAR", loc, {}) + cmd_list = scons_subst_list("$DO --in=$CRAZY --out=$BAR", env) assert len(cmd_list) == 1, map(str, cmd_list[0]) assert len(cmd_list[0]) == 3, cmd_list assert cmd_list[0][0] == 'do something', cmd_list[0][0] @@ -282,30 +278,37 @@ class UtilTestCase(unittest.TestCase): # Test inputting a list to scons_subst_list() cmd_list = scons_subst_list([ "$SOURCES$NEWLINE", "$TARGETS", "This is a test" ], - loc, {}) + env, + target=target, + source=source) assert len(cmd_list) == 2, len(cmd_list) assert cmd_list[0][0] == cvt('foo/blah with spaces.cpp'), cmd_list[0][0] assert cmd_list[1][0] == cvt("after"), cmd_list[1] assert cmd_list[1][4] == "This is a test", cmd_list[1] - glob = { 'a' : 1, 'b' : 2 } - loc = {'a' : 3, 'c' : 4 } - cmd_list = scons_subst_list("test $a $b $c $d test", glob, loc) - assert len(cmd_list) == 1, cmd_list - assert map(str, cmd_list[0]) == ['test', '3', '2', '4', 'test'], map(str, cmd_list[0]) + # Test interpolating a callable. + cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES", env, + target='t', source='s') + assert len(cmd_list) == 1, len(cmd_list) + assert cmd_list[0][0] == 'testing', cmd_list[0][0] + assert cmd_list[0][1] == 'foo', cmd_list[0][1] + assert cmd_list[0][2] == 'bar with spaces.out', cmd_list[0][2] + assert cmd_list[0][3] == 't', cmd_list[0][3] + assert cmd_list[0][4] == 's', cmd_list[0][4] + # Test against a former bug in scons_subst_list() glob = { "FOO" : "$BAR", "BAR" : "BAZ", "BLAT" : "XYX", "BARXYX" : "BADNEWS" } - cmd_list = scons_subst_list("$FOO$BLAT", glob, {}) + cmd_list = scons_subst_list("$FOO$BLAT", DummyEnv(glob)) assert cmd_list[0][0] == "BAZXYX", cmd_list[0][0] # Test for double-dollar-sign behavior glob = { "FOO" : "BAR", "BAZ" : "BLAT" } - cmd_list = scons_subst_list("$$FOO$BAZ", glob, {}) + cmd_list = scons_subst_list("$$FOO$BAZ", DummyEnv(glob)) assert cmd_list[0][0] == "$FOOBLAT", cmd_list[0][0] # Now test escape functionality @@ -315,7 +318,7 @@ class UtilTestCase(unittest.TestCase): return foo glob = { "FOO" : PathList([ 'foo\nwith\nnewlines', 'bar\nwith\nnewlines' ]) } - cmd_list = scons_subst_list("$FOO", glob, {}) + cmd_list = scons_subst_list("$FOO", DummyEnv(glob)) assert cmd_list[0][0] == 'foo\nwith\nnewlines', cmd_list[0][0] cmd_list[0][0].escape(escape_func) assert cmd_list[0][0] == '**foo\nwith\nnewlines**', cmd_list[0][0] @@ -519,9 +522,13 @@ class UtilTestCase(unittest.TestCase): """Testing get_environment_var().""" assert get_environment_var("$FOO") == "FOO", get_environment_var("$FOO") assert get_environment_var("${BAR}") == "BAR", get_environment_var("${BAR}") + assert get_environment_var("$FOO_BAR1234") == "FOO_BAR1234", get_environment_var("$FOO_BAR1234") + assert get_environment_var("${BAR_FOO1234}") == "BAR_FOO1234", get_environment_var("${BAR_FOO1234}") assert get_environment_var("${BAR}FOO") == None, get_environment_var("${BAR}FOO") assert get_environment_var("$BAR ") == None, get_environment_var("$BAR ") assert get_environment_var("FOO$BAR") == None, get_environment_var("FOO$BAR") + assert get_environment_var("$FOO[0]") == None, get_environment_var("$FOO[0]") + assert get_environment_var("${some('complex expression')}") == None, get_environment_var("${some('complex expression')}") def test_Proxy(self): """Test generic Proxy class.""" @@ -553,8 +560,8 @@ class UtilTestCase(unittest.TestCase): """Test the Literal() function.""" cmd_list = [ '$FOO', Literal('$BAR') ] cmd_list = scons_subst_list(cmd_list, - { 'FOO' : 'BAZ', - 'BAR' : 'BLAT' }, {}) + DummyEnv({ 'FOO' : 'BAZ', + 'BAR' : 'BLAT' })) def escape_func(cmd): return '**' + cmd + '**' @@ -624,6 +631,49 @@ class UtilTestCase(unittest.TestCase): test._dirlist = None sys.stdout = old_stdout + def test_subst_dict(self): + """Test substituting dictionary values in an Action + """ + d = subst_dict([], [], DummyEnv({'a' : 'A', 'b' : 'B'})) + assert d['a'] == 'A', d + assert d['b'] == 'B', d + + d = subst_dict(target = 't', source = 's', env=DummyEnv()) + assert str(d['TARGETS']) == 't', d['TARGETS'] + assert str(d['TARGET']) == 't', d['TARGET'] + assert str(d['SOURCES']) == 's', d['SOURCES'] + assert str(d['SOURCE']) == 's', d['SOURCE'] + + d = subst_dict(target = ['t1', 't2'], + source = ['s1', 's2'], + env = DummyEnv()) + 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 N: + def __init__(self, name): + self.name = name + def __str__(self): + return self.name + def rstr(self): + return 'rstr-' + self.name + + d = subst_dict(target = [N('t3'), 't4'], + source = ['s3', N('s4')], + env = DummyEnv()) + TARGETS = map(lambda x: str(x), d['TARGETS']) + TARGETS.sort() + assert TARGETS == ['t3', 't4'], d['TARGETS'] + SOURCES = map(lambda x: str(x), d['SOURCES']) + SOURCES.sort() + assert SOURCES == ['rstr-s4', 's3'], d['SOURCES'] + if __name__ == "__main__": suite = unittest.makeSuite(UtilTestCase, 'test_') diff --git a/test/scan-once.py b/test/scan-once.py index b9104384..ffd379be 100644 --- a/test/scan-once.py +++ b/test/scan-once.py @@ -303,7 +303,7 @@ import re for k in fromdict.keys(): if k != "ENV" and k != "SCANNERS" and k != "CFLAGS" and k != "CXXFLAGS" \ and not SCons.Util.is_Dict(fromdict[k]): - todict[k] = SCons.Util.scons_subst(str(fromdict[k]), fromdict, {}) + todict[k] = env.subst(str(fromdict[k])) todict["CFLAGS"] = fromdict["CPPFLAGS"] + " " + \ string.join(map(lambda x: "-I" + x, env["CPPPATH"])) + " " + \ string.join(map(lambda x: "-L" + x, env["LIBPATH"]))