diff options
author | Steven Knight <knight@baldmt.com> | 2002-04-15 18:43:38 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2002-04-15 18:43:38 (GMT) |
commit | a8176f609ff3ecc090f51830408d3b4dc6338d7e (patch) | |
tree | bab059042f2f8cbc85dcf7a619dbebbbe23dc4fb /src/engine | |
parent | 05029e336146444501a66b53e4699c09d6e08977 (diff) | |
download | SCons-a8176f609ff3ecc090f51830408d3b4dc6338d7e.zip SCons-a8176f609ff3ecc090f51830408d3b4dc6338d7e.tar.gz SCons-a8176f609ff3ecc090f51830408d3b4dc6338d7e.tar.bz2 |
Big change for shared libraries and a bunch of other flexibility. (Charles Crain)
Diffstat (limited to 'src/engine')
-rw-r--r-- | src/engine/SCons/Action.py | 125 | ||||
-rw-r--r-- | src/engine/SCons/ActionTests.py | 63 | ||||
-rw-r--r-- | src/engine/SCons/Builder.py | 302 | ||||
-rw-r--r-- | src/engine/SCons/BuilderTests.py | 146 | ||||
-rw-r--r-- | src/engine/SCons/Defaults.py | 231 | ||||
-rw-r--r-- | src/engine/SCons/Environment.py | 13 | ||||
-rw-r--r-- | src/engine/SCons/EnvironmentTests.py | 6 | ||||
-rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 10 | ||||
-rw-r--r-- | src/engine/SCons/Node/__init__.py | 39 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/Prog.py | 19 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/ProgTests.py | 5 | ||||
-rw-r--r-- | src/engine/SCons/Sig/MD5.py | 9 | ||||
-rw-r--r-- | src/engine/SCons/Sig/MD5Tests.py | 3 | ||||
-rw-r--r-- | src/engine/SCons/Util.py | 30 | ||||
-rw-r--r-- | src/engine/SCons/UtilTests.py | 46 |
15 files changed, 725 insertions, 322 deletions
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 3d52142..38b5413 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -38,6 +38,7 @@ import sys import UserDict import SCons.Util +import SCons.Errors print_actions = 1; execute_actions = 1; @@ -171,28 +172,53 @@ def GetCommandHandler(): class CommandGenerator: """ - Wrappes a command generator function so the Action() factory + Wraps a command generator function so the Action() factory function can tell a generator function from a function action. """ def __init__(self, generator): self.generator = generator +def _do_create_action(act): + """This is the actual "implementation" for the + Action factory method, below. This handles the + fact that passing lists to Action() itself has + different semantics than passing lists as elements + of lists. + + The former will create a ListAction, the latter + will create a CommandAction by converting the inner + list elements to strings.""" -def Action(act): - """A factory for action objects.""" if isinstance(act, ActionBase): return act + elif SCons.Util.is_List(act): + return CommandAction(act) elif isinstance(act, CommandGenerator): return CommandGeneratorAction(act.generator) elif callable(act): return FunctionAction(act) elif SCons.Util.is_String(act): - return CommandAction(act) - elif SCons.Util.is_List(act): - return ListAction(act) + listCmds = map(lambda x: CommandAction(string.split(x)), + string.split(act, '\n')) + if len(listCmds) == 1: + return listCmds[0] + else: + return ListAction(listCmds) else: return None +def Action(act): + """A factory for action objects.""" + if SCons.Util.is_List(act): + acts = filter(lambda x: not x is None, + map(_do_create_action, act)) + if len(acts) == 1: + return acts[0] + else: + return ListAction(acts) + else: + return _do_create_action(act) + class ActionBase: """Base class for actions that create output objects.""" def __cmp__(self, other): @@ -241,7 +267,7 @@ class ActionBase: t = [t] try: cwd = t[0].cwd - except AttributeError: + except (IndexError, AttributeError): pass dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, t))) if dict['TARGETS']: @@ -260,6 +286,16 @@ class ActionBase: return dict +def _string_from_cmd_list(cmd_list): + """Takes a list of command line arguments and returns a pretty + representation for printing.""" + cl = [] + for arg in cmd_list: + if ' ' in arg or '\t' in arg: + arg = '"' + arg + '"' + cl.append(arg) + return string.join(cl) + _rm = re.compile(r'\$[()]') _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)') @@ -286,22 +322,19 @@ class EnvDictProxy(UserDict.UserDict): class CommandAction(ActionBase): """Class for command-execution actions.""" - def __init__(self, string): - self.command = string + def __init__(self, cmd): + import SCons.Util + + self.cmd_list = map(SCons.Util.to_String, cmd) def execute(self, **kw): dict = apply(self.subst_dict, (), kw) import SCons.Util - cmd_list = SCons.Util.scons_subst_list(self.command, dict, {}, _rm) + cmd_list = SCons.Util.scons_subst_list(self.cmd_list, dict, {}, _rm) for cmd_line in cmd_list: if len(cmd_line): if print_actions: - cl = [] - for arg in cmd_line: - if ' ' in arg or '\t' in arg: - arg = '"' + arg + '"' - cl.append(arg) - self.show(string.join(cl)) + self.show(_string_from_cmd_list(cmd_line)) if execute_actions: try: ENV = kw['env']['ENV'] @@ -328,7 +361,8 @@ class CommandAction(ActionBase): def get_raw_contents(self, **kw): """Return the complete contents of this action's command line. """ - return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {}) + return SCons.Util.scons_subst(string.join(self.cmd_list), + self._sig_dict(kw), {}) def get_contents(self, **kw): """Return the signature contents of this action's command line. @@ -336,19 +370,16 @@ class CommandAction(ActionBase): This strips $(-$) and everything in between the string, since those parts don't affect signatures. """ - return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {}, _remove) + return SCons.Util.scons_subst(string.join(self.cmd_list), + self._sig_dict(kw), {}, _remove) class CommandGeneratorAction(ActionBase): """Class for command-generator actions.""" def __init__(self, generator): self.generator = generator - def execute(self, **kw): - # ensure that target is a list, to make it easier to write - # generator functions: + def __generate(self, kw): import SCons.Util - if kw.has_key("target") and not SCons.Util.is_List(kw["target"]): - kw["target"] = [kw["target"]] # Wrap the environment dictionary in an EnvDictProxy # object to make variable interpolation easier for the @@ -357,36 +388,19 @@ class CommandGeneratorAction(ActionBase): if args.has_key("env") and not isinstance(args["env"], EnvDictProxy): args["env"] = EnvDictProxy(args["env"]) - gen_list = apply(self.generator, (), args) - gen_list = map(lambda x: map(str, x), gen_list) + # ensure that target is a list, to make it easier to write + # generator functions: + if args.has_key("target") and not SCons.Util.is_List(args["target"]): + args["target"] = [args["target"]] - # Do environment variable substitution on returned command list - dict = apply(self.subst_dict, (), kw) - cmd_list = [ ] - for gen_line in gen_list: - cmd_list.append([]) - curr_line = cmd_list[-1] - for gen_arg in gen_line: - arg_list = SCons.Util.scons_subst_list(gen_arg, dict, {}) - curr_line.extend(arg_list[0]) - if(len(arg_list) > 1): - cmd_list.extend(arg_list[1:]) - curr_line = cmd_list[-1] - - for cmd_line in filter(lambda x: x, cmd_list): - if print_actions: - self.show(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 + ret = apply(self.generator, (), args) + gen_cmd = Action(ret) + if not gen_cmd: + raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret)) + return gen_cmd - return 0 + def execute(self, **kw): + return apply(self.__generate(kw).execute, (), kw) def get_contents(self, **kw): """Return the signature contents of this action's command line. @@ -394,14 +408,7 @@ class CommandGeneratorAction(ActionBase): This strips $(-$) and everything in between the string, since those parts don't affect signatures. """ - kw['source'] = ["__s1__", "__s2__"] - kw['target'] = ["__t1__", "__t2__"] - cmd_list = apply(self.generator, (), kw) - cmd_list = map(lambda x: map(str, x), cmd_list) - cmd_list = map(lambda x: string.join(x, "\0"), cmd_list) - cmd_list = map(lambda x: _remove.sub('', x), cmd_list) - cmd_list = map(lambda x: filter(lambda y: y, string.split(x, "\0")), cmd_list) - return cmd_list + return apply(self.__generate(kw).get_contents, (), kw) class FunctionAction(ActionBase): """Class for Python function actions.""" diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 1a0596f..648b77b 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -54,14 +54,27 @@ class ActionTestCase(unittest.TestCase): exec "a3 = SCons.Action.Action(u'string')" exec "assert isinstance(a3, SCons.Action.CommandAction), a3" - a4 = SCons.Action.Action(["x", a2, "y"]) + 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 isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1] + assert isinstance(a4.list[2], SCons.Action.CommandAction), a4.list[2] + 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 a5 = SCons.Action.Action(1) assert a5 is None, a5 a6 = SCons.Action.Action(a1) - assert a6 is a1 + assert a6 is a1, a6 + + a7 = SCons.Action.Action([[ "explicit", "command", "line" ]]) + assert isinstance(a7, SCons.Action.CommandAction), a7 + assert a7.cmd_list == [ "explicit", "command", "line" ], a7.cmd_list + + a8 = SCons.Action.Action(["a8"]) + assert isinstance(a8, SCons.Action.CommandAction), a8 + assert a8.cmd_list == [ "a8" ], a8.cmd_list class ActionBaseTestCase(unittest.TestCase): @@ -106,8 +119,8 @@ class CommandActionTestCase(unittest.TestCase): def test_init(self): """Test creation of a command Action """ - a = SCons.Action.CommandAction("xyzzy") - assert a.command == "xyzzy" + a = SCons.Action.CommandAction(["xyzzy"]) + assert a.cmd_list == [ "xyzzy" ], a.cmd_list def test_execute(self): """Test executing a command Action @@ -127,23 +140,27 @@ class CommandActionTestCase(unittest.TestCase): return 0 SCons.Action.SetCommandHandler(func) assert SCons.Action.spawn is func - a = SCons.Action.CommandAction("xyzzy") + a = SCons.Action.CommandAction(["xyzzy"]) a.execute() assert t.executed == 1 def test_get_raw_contents(self): """Test fetching the contents of a command Action """ - a = SCons.Action.CommandAction("| $( $foo | $bar $) |") - c = a.get_contents(foo = 'FFF', bar = 'BBB') + a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar", + "$)", "|"]) + c = a.get_contents(target=[], source=[], + foo = 'FFF', bar = 'BBB') assert c == "| $( FFF | BBB $) |" def test_get_contents(self): """Test fetching the contents of a command Action """ - a = SCons.Action.CommandAction("| $foo $( | $) $bar |") - c = a.get_contents(foo = 'FFF', bar = 'BBB') - assert c == "| FFF BBB |" + a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar", + "$)", "|"]) + c = a.get_contents(target=[], source=[], + foo = 'FFF', bar = 'BBB') + assert c == "| |", c class CommandGeneratorActionTestCase(unittest.TestCase): @@ -164,7 +181,14 @@ class CommandGeneratorActionTestCase(unittest.TestCase): assert env.subst('$FOO') == 'foo baz\nbar ack', env.subst('$FOO') assert env.subst_list('$FOO') == [ [ 'foo', 'baz' ], [ 'bar', 'ack' ] ], env.subst_list('$FOO') - return [["$FOO"]] + return "$FOO" + def func_action(env, dummy, self=self): + assert env.subst('$foo') == 'bar', env.subst('$foo') + assert env.subst_list('$foo') == [ [ 'bar' ] ], env.subst_list('$foo') + assert env.subst_list([ '$foo', 'bar' ]) == [[ 'bar', 'bar' ]], env.subst_list([ [ '$foo', 'bar' ] ]) + self.dummy=dummy + def f2(dummy, env, f=func_action): + return f def ch(cmd, args, env, self=self): self.cmd.append(cmd) self.args.append(args) @@ -182,6 +206,11 @@ class CommandGeneratorActionTestCase(unittest.TestCase): assert self.dummy == 1 assert self.cmd == [ 'foo', 'bar'], self.cmd assert self.args == [ [ 'foo', 'baz' ], [ 'bar', 'ack' ] ], self.args + + b=SCons.Action.CommandGeneratorAction(f2) + self.dummy = 0 + b.execute(dummy=2, env={ 'foo' : 'bar' }) + assert self.dummy==2, self.dummy del self.dummy def test_get_contents(self): @@ -191,8 +220,9 @@ class CommandGeneratorActionTestCase(unittest.TestCase): return [["guux", foo, "$(", "ignore", "$)", bar]] a = SCons.Action.CommandGeneratorAction(f) - c = a.get_contents(foo = 'FFF', bar = 'BBB') - assert c == [["guux", 'FFF', 'BBB']], c + c = a.get_contents(target=[], source=[], + foo = 'FFF', bar = 'BBB') + assert c == "guux FFF BBB", c class FunctionActionTestCase(unittest.TestCase): @@ -227,7 +257,7 @@ class FunctionActionTestCase(unittest.TestCase): """Test fetching the contents of a function Action """ a = SCons.Action.FunctionAction(Func) - c = a.get_contents() + c = a.get_contents(target=[], source=[]) assert c == "\177\036\000\177\037\000d\000\000S", repr(c) class ListActionTestCase(unittest.TestCase): @@ -241,8 +271,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 isinstance(a.list[2].list[0], SCons.Action.CommandAction) - assert isinstance(a.list[2].list[1], SCons.Action.CommandAction) + assert a.list[2].list[0].cmd_list == [ 'y' ] def test_execute(self): """Test executing a list of subsidiary Actions @@ -259,7 +288,7 @@ class ListActionTestCase(unittest.TestCase): """Test fetching the contents of a list of subsidiary Actions """ a = SCons.Action.ListAction(["x", "y", "z"]) - c = a.get_contents() + c = a.get_contents(target=[], source=[]) assert c == "xyz", c diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 2ccba00..2a1b7d8 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -33,32 +33,61 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os.path import string -from Errors import UserError +import copy +from SCons.Errors import UserError import SCons.Action import SCons.Node import SCons.Node.FS import SCons.Util +class DictCmdGenerator: + """This is a callable class that can be used as a + command generator function. It holds on to a dictionary + mapping file suffixes to Actions. It uses that dictionary + to return the proper action based on the file suffix of + the source file.""" + + def __init__(self, action_dict): + self.action_dict = action_dict + + def src_suffixes(self): + return self.action_dict.keys() + + def __call__(self, source, target, env, **kw): + ext = None + for src in map(str, source): + my_ext = os.path.splitext(src)[1] + if ext and my_ext != ext: + raise UserError("Cannot build multiple sources with different extensions.") + ext = my_ext + + if ext is None: + raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source))) + try: + # XXX Do we need to perform Environment substitution + # on the keys of action_dict before looking it up? + return self.action_dict[ext] + except KeyError: + raise UserError("Don't know how to build a file with suffix %s." % ext) def Builder(**kw): """A factory for builder objects.""" - if kw.has_key('generator'): if kw.has_key('action'): raise UserError, "You must not specify both an action and a generator." kw['action'] = SCons.Action.CommandGenerator(kw['generator']) del kw['generator'] - - if kw.has_key('action') and SCons.Util.is_Dict(kw['action']): - return apply(CompositeBuilder, (), kw) - elif kw.has_key('src_builder'): + elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']): + action_dict = kw['action'] + kw['action'] = SCons.Action.CommandGenerator(DictCmdGenerator(action_dict)) + kw['src_suffix'] = action_dict.keys() + + if kw.has_key('src_builder'): return apply(MultiStepBuilder, (), kw) else: return apply(BuilderBase, (), kw) - - def _init_nodes(builder, env, tlist, slist): """Initialize lists of target and source nodes with all of the proper Builder information. @@ -76,6 +105,29 @@ def _init_nodes(builder, env, tlist, slist): t.add_source(slist) if builder.scanner: t.target_scanner = builder.scanner + +class _callable_adaptor: + """When crteating a Builder, you can pass a string OR + a callable in for prefix, suffix, or src_suffix. + src_suffix even takes a list! + + If a string or list is passed, we use this class to + adapt it to a callable.""" + def __init__(self, static): + self.static = static + + def __call__(self, **kw): + return self.static + + def __cmp__(self, other): + if isinstance(other, _callable_adaptor): + return cmp(self.static, other.static) + return -1 + +def _adjust_suffix(suff): + if suff and not suff[0] in [ '.', '$' ]: + return '.' + suff + return suff class BuilderBase: """Base class for Builders, objects that create output @@ -90,27 +142,40 @@ class BuilderBase: node_factory = SCons.Node.FS.default_fs.File, target_factory = None, source_factory = None, - scanner = None): + scanner = None, + emitter = None): if name is None: raise UserError, "You must specify a name for the builder." self.name = name self.action = SCons.Action.Action(action) - self.prefix = prefix - self.suffix = suffix - self.src_suffix = src_suffix + if callable(prefix): + self.prefix = prefix + else: + self.prefix = _callable_adaptor(str(prefix)) + + if callable(suffix): + self.suffix = suffix + else: + self.suffix = _callable_adaptor(str(suffix)) + + if callable(src_suffix): + self.src_suffix = src_suffix + elif SCons.Util.is_String(src_suffix): + self.src_suffix = _callable_adaptor([ str(src_suffix) ]) + else: + self.src_suffix = _callable_adaptor(src_suffix) + self.target_factory = target_factory or node_factory self.source_factory = source_factory or node_factory self.scanner = scanner - if self.suffix and self.suffix[0] not in '.$': - self.suffix = '.' + self.suffix - if self.src_suffix and self.src_suffix[0] not in '.$': - self.src_suffix = '.' + self.src_suffix + + self.emitter = emitter def __cmp__(self, other): return cmp(self.__dict__, other.__dict__) - def _create_nodes(self, env, target = None, source = None): + def _create_nodes(self, env, args, target = None, source = None): """Create and return lists of target and source nodes. """ def adjustixes(files, pre, suf): @@ -122,25 +187,46 @@ class BuilderBase: if pre and f[:len(pre)] != pre: path, fn = os.path.split(os.path.normpath(f)) f = os.path.join(path, pre + fn) - if suf: - if f[-len(suf):] != suf: - f = f + suf - ret.append(f) - return ret - + # Only append a suffix if the file does not have one. + if suf and not os.path.splitext(f)[1]: + if f[-len(suf):] != suf: + f = f + suf + ret.append(f) + return ret + + pre = self.get_prefix(env, args) + suf = self.get_suffix(env, args) tlist = SCons.Node.arg2nodes(adjustixes(target, - env.subst(self.prefix), - env.subst(self.suffix)), + pre, suf), self.target_factory) - + src_suf = self.get_src_suffix(env, args) slist = SCons.Node.arg2nodes(adjustixes(source, None, - env.subst(self.src_suffix)), + src_suf), self.source_factory) + if self.emitter: + emit_args = { 'target' : tlist, + 'source' : slist, + 'env' : env } + emit_args.update(args) + target, source = apply(self.emitter, (), emit_args) + + # Have to run it through again in case the + # function returns non-Node targets/sources. + tlist = SCons.Node.arg2nodes(adjustixes(target, + pre, suf), + self.target_factory) + slist = SCons.Node.arg2nodes(adjustixes(source, + None, + src_suf), + self.source_factory) + + for t in tlist: + t.build_args = args return tlist, slist - def __call__(self, env, target = None, source = None): - tlist, slist = self._create_nodes(env, target, source) + def __call__(self, env, target = None, source = None, **kw): + tlist, slist = self._create_nodes(env, kw, target, source) if len(tlist) == 1: _init_nodes(self, env, tlist, slist) @@ -167,10 +253,23 @@ class BuilderBase: """ return apply(self.action.get_contents, (), kw) - def src_suffixes(self, env): - if self.src_suffix != '': - return [env.subst(self.src_suffix)] - return [] + def src_suffixes(self, env, args): + return map(lambda x, e=env: e.subst(_adjust_suffix(x)), + apply(self.src_suffix, (), args)) + + def get_src_suffix(self, env, args): + """Get the first src_suffix in the list of src_suffixes.""" + ret = self.src_suffixes(env, args) + if not ret: + return '' + else: + return ret[0] + + def get_suffix(self, env, args): + return env.subst(_adjust_suffix(apply(self.suffix, (), args))) + + def get_prefix(self, env, args): + return env.subst(apply(self.prefix, (), args)) def targets(self, node): """Return the list of targets for this builder instance. @@ -199,7 +298,7 @@ class ListBuilder: # unlink all targets and make all directories # before building anything t.prepare() - kw['target'] = self.tlist[0] + kw['target'] = self.tlist self.status = apply(self.builder.execute, (), kw) for t in self.tlist: if not t is kw['target']: @@ -212,8 +311,8 @@ class ListBuilder: def get_contents(self, **kw): return apply(self.builder.get_contents, (), kw) - def src_suffixes(self, env): - return self.builder.src_suffixes(env) + def src_suffixes(self, env, args): + return self.builder.src_suffixes(env, args) def targets(self, node): """Return the list of targets for this builder instance. @@ -240,112 +339,55 @@ class MultiStepBuilder(BuilderBase): node_factory = SCons.Node.FS.default_fs.File, target_factory = None, source_factory = None, - scanner=None): + scanner=None, + emitter=None): BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix, node_factory, target_factory, source_factory, - scanner) + scanner, emitter) + if not SCons.Util.is_List(src_builder): + src_builder = [ src_builder ] self.src_builder = src_builder + self.sdict = {} - def __call__(self, env, target = None, source = None): + def __call__(self, env, target = None, source = None, **kw): slist = SCons.Node.arg2nodes(source, self.source_factory) final_sources = [] - src_suffix = env.subst(self.src_suffix) - sdict = {} - for suff in self.src_builder.src_suffixes(env): - sdict[suff] = None + + r=repr(env) + try: + sdict = self.sdict[r] + except KeyError: + sdict = {} + self.sdict[r] = sdict + for bld in self.src_builder: + for suf in bld.src_suffixes(env, kw): + sdict[suf] = bld + for snode in slist: path, ext = os.path.splitext(snode.abspath) if sdict.has_key(ext): - tgt = self.src_builder(env, target = [ path ], source = snode) + src_bld = sdict[ext] + + dictArgs = copy.copy(kw) + dictArgs['target'] = [path] + dictArgs['source'] = snode + dictArgs['env'] = env + tgt = apply(src_bld, (), dictArgs) if not SCons.Util.is_List(tgt): final_sources.append(tgt) else: final_sources.extend(tgt) else: final_sources.append(snode) - return BuilderBase.__call__(self, env, target=target, - source=final_sources) - - def src_suffixes(self, env): - return BuilderBase.src_suffixes(self, env) + \ - self.src_builder.src_suffixes(env) - -class CompositeBuilder(BuilderBase): - """This is a convenient Builder subclass that can build different - files based on their suffixes. For each target, this builder - will examine the target's sources. If they are all the same - suffix, and that suffix is equal to one of the child builders' - src_suffix, then that child builder will be used. Otherwise, - UserError is thrown.""" - def __init__(self, name = None, - prefix='', - suffix='', - action = {}, - src_builder = []): - BuilderBase.__init__(self, name=name, prefix=prefix, - suffix=suffix) - if src_builder and not SCons.Util.is_List(src_builder): - src_builder = [src_builder] - self.src_builder = src_builder - self.action_dict = action - self.sdict = {} - self.sbuild = {} - - def __call__(self, env, target = None, source = None): - tlist, slist = BuilderBase._create_nodes(self, env, - target=target, source=source) - - r = repr(env) - if not self.sdict.has_key(r): - self.sdict[r] = {} - self.sbuild[r] = [] - for suff in self.src_suffixes(env): - suff = env.subst(suff) - self.sdict[r][suff] = suff - self.sbuild[r].extend(filter(lambda x, e=env, s=suff: - e.subst(x.suffix) == s, - self.src_builder)) - for sb in self.sbuild[r]: - suff = env.subst(sb.suffix) - for s in sb.src_suffixes(env): - self.sdict[r][env.subst(s)] = suff - - sufflist = map(lambda x, s=self.sdict[r]: - s[os.path.splitext(x.path)[1]], - slist) - last_suffix = '' - for suff in sufflist: - if last_suffix and last_suffix != suff: - raise UserError, "The builder for %s can only build source files of identical suffixes: %s." % \ - (tlist[0].path, - str(map(lambda t: str(t.path), tlist[0].sources))) - last_suffix = suff - - if last_suffix: - kw = { - 'name' : self.name, - 'action' : self.action_dict[last_suffix], - 'src_suffix' : last_suffix, - } - if self.sbuild[r]: - sb = filter(lambda x, e=env, s=last_suffix: - e.subst(x.suffix) == s, - self.sbuild[r]) - if sb: - kw['src_builder'] = sb[0] - # XXX We should be able to cache this - bld = apply(Builder, (), kw) - for tnode in tlist: - bld.__call__(env, target = tnode, source = slist) - - if len(tlist) == 1: - tlist = tlist[0] - return tlist - - def src_suffixes(self, env): - suffixes = map(lambda k, e=env: e.subst(k), self.action_dict.keys()) + \ - reduce(lambda x, y: x + y, - map(lambda b, e=env: b.src_suffixes(e), - self.src_builder), - []) - return suffixes + dictKwArgs = kw + dictKwArgs['target'] = target + dictKwArgs['source'] = final_sources + return apply(BuilderBase.__call__, + (self, env), dictKwArgs) + + def src_suffixes(self, env, args): + return BuilderBase.src_suffixes(self, env, args) + \ + reduce(lambda x, y: x + y, + map(lambda b, e=env, args=args: b.src_suffixes(e, args), + self.src_builder), + []) diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 0527fe0..e30079c 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -82,6 +82,11 @@ class Environment: return self.d.get(s, s) def get_scanner(self, ext): return env_scanner + def Dictionary(self): + return {} + def autogenerate(self, dir=''): + return {} + env = Environment() class BuilderTestCase(unittest.TestCase): @@ -129,20 +134,23 @@ class BuilderTestCase(unittest.TestCase): assert target.sources[0].name == 'n10' assert target.sources[1].name == 'n11' - if hasattr(types, 'UnicodeType'): - code = """if 1: - targets = builder(env, target = u'n12 n13', source = [u'n14 n15']) - assert targets[0].name == u'n12' - assert targets[0].sources[0].name == u'n14 n15' - assert targets[1].name == u'n13' - assert targets[1].sources[0].name == u'n14 n15' - - target = builder(env, target = [u'n16 n17'], source = u'n18 n19') - assert target.name == u'n16 n17' - assert target.sources[0].name == u'n18' - assert target.sources[1].name == u'n19' - \n""" - exec code + if not hasattr(types, 'UnicodeType'): + uni = str + else: + uni = unicode + + targets = builder(env, target = uni('n12 n13'), + source = [uni('n14 n15')]) + assert targets[0].name == uni('n12') + assert targets[0].sources[0].name == uni('n14 n15') + assert targets[1].name == uni('n13') + assert targets[1].sources[0].name == uni('n14 n15') + + target = builder(env, target = [uni('n16 n17')], + source = uni('n18 n19')) + assert target.name == uni('n16 n17') + assert target.sources[0].name == uni('n18') + assert target.sources[1].name == uni('n19') def test_noname(self): """Test error reporting for missing name @@ -163,7 +171,7 @@ class BuilderTestCase(unittest.TestCase): Verify that we can retrieve the supplied action attribute. """ builder = SCons.Builder.Builder(name="builder", action="foo") - assert builder.action.command == "foo" + assert builder.action.cmd_list == ["foo"] def test_generator(self): """Test Builder creation given a generator function.""" @@ -276,7 +284,8 @@ class BuilderTestCase(unittest.TestCase): def my_show(string): global show_string show_string = show_string + string + "\n" - builder.action.show = my_show + for action in builder.action.list: + action.show = my_show r = builder.execute() assert r == 0 @@ -341,7 +350,7 @@ class BuilderTestCase(unittest.TestCase): def __init__(self, **kw): open(kw['out'], 'a').write("class2b\n") - builder = MyBuilder(action = [cmd2, function2, class2a(), class2b], name = "clist") + builder = MyBuilder(action = SCons.Action.ListAction([cmd2, function2, class2a(), class2b]), name = "clist") r = builder.execute(out = outfile) assert r.__class__ == class2b c = test.read(outfile, 'r') @@ -384,7 +393,7 @@ class BuilderTestCase(unittest.TestCase): contents = b2.get_contents() assert contents == "\177\036\000\177\037\000d\000\000S", repr(contents) - b3 = SCons.Builder.Builder(name = "b3", action = ["foo", Func, "bar"]) + b3 = SCons.Builder.Builder(name = "b3", action = SCons.Action.ListAction(["foo", Func, "bar"])) contents = b3.get_contents() assert contents == "foo\177\036\000\177\037\000d\000\000Sbar", repr(contents) @@ -430,9 +439,9 @@ class BuilderTestCase(unittest.TestCase): Make sure that there is no '.' separator appended. """ builder = SCons.Builder.Builder(name = "builder", prefix = 'lib.') - assert builder.prefix == 'lib.' + assert builder.get_prefix(env,{}) == 'lib.' builder = SCons.Builder.Builder(name = "builder", prefix = 'lib') - assert builder.prefix == 'lib' + assert builder.get_prefix(env,{}) == 'lib' tgt = builder(env, target = 'tgt1', source = 'src1') assert tgt.path == 'libtgt1', \ "Target has unexpected name: %s" % tgt.path @@ -451,7 +460,7 @@ class BuilderTestCase(unittest.TestCase): env = Environment(XSUFFIX = '.x', YSUFFIX = '.y') b1 = SCons.Builder.Builder(name = "builder", src_suffix = '.c') - assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env) + assert b1.src_suffixes(env,{}) == ['.c'], b1.src_suffixes(env,{}) tgt = b1(env, target = 'tgt2', source = 'src2') assert tgt.sources[0].path == 'src2.c', \ @@ -466,19 +475,19 @@ class BuilderTestCase(unittest.TestCase): b2 = SCons.Builder.Builder(name = "b2", src_suffix = '.2', src_builder = b1) - assert b2.src_suffixes(env) == ['.2', '.c'], b2.src_suffixes(env) + assert b2.src_suffixes(env,{}) == ['.2', '.c'], b2.src_suffixes(env,{}) b3 = SCons.Builder.Builder(name = "b3", action = {'.3a' : '', '.3b' : ''}) - s = b3.src_suffixes(env) + s = b3.src_suffixes(env,{}) s.sort() assert s == ['.3a', '.3b'], s b4 = SCons.Builder.Builder(name = "b4", src_suffix = '$XSUFFIX') - assert b4.src_suffixes(env) == ['.x'], b4.src_suffixes(env) + assert b4.src_suffixes(env,{}) == ['.x'], b4.src_suffixes(env,{}) b5 = SCons.Builder.Builder(name = "b5", action = {'$YSUFFIX' : ''}) - assert b5.src_suffixes(env) == ['.y'], b5.src_suffixes(env) + assert b5.src_suffixes(env,{}) == ['.y'], b5.src_suffixes(env,{}) def test_suffix(self): """Test Builder creation with a specified target suffix @@ -487,9 +496,9 @@ class BuilderTestCase(unittest.TestCase): beginning if it isn't already present. """ builder = SCons.Builder.Builder(name = "builder", suffix = '.o') - assert builder.suffix == '.o' + assert builder.get_suffix(env,{}) == '.o', builder.get_suffix(env,{}) builder = SCons.Builder.Builder(name = "builder", suffix = 'o') - assert builder.suffix == '.o' + assert builder.get_suffix(env,{}) == '.o', builder.get_suffix(env,{}) tgt = builder(env, target = 'tgt3', source = 'src3') assert tgt.path == 'tgt3.o', \ "Target has unexpected name: %s" % tgt[0].path @@ -575,20 +584,24 @@ class BuilderTestCase(unittest.TestCase): def test_CompositeBuilder(self): """Testing CompositeBuilder class.""" + def func_action(target, source, env): + return 0 + builder = SCons.Builder.Builder(name = "builder", - action={ '.foo' : 'foo', - '.bar' : 'bar' }) + action={ '.foo' : func_action, + '.bar' : func_action }) - assert isinstance(builder, SCons.Builder.CompositeBuilder) + assert isinstance(builder, SCons.Builder.BuilderBase) + assert isinstance(builder.action, SCons.Action.CommandGeneratorAction) tgt = builder(env, target='test1', source='test1.foo') assert isinstance(tgt.builder, SCons.Builder.BuilderBase) - assert tgt.builder.action.command == 'foo' - tgt = builder(env, target='test2', source='test2.bar') - assert tgt.builder.action.command == 'bar' + assert isinstance(tgt.builder.action.generator, SCons.Builder.DictCmdGenerator) flag = 0 + tgt = builder(env, target='test2', source='test2.bar test1.foo') try: - tgt = builder(env, target='test2', source='test2.bar test1.foo') - except SCons.Errors.UserError: + tgt.build() + except SCons.Errors.BuildError, e: + assert e.args[0] == SCons.Errors.UserError flag = 1 assert flag, "UserError should be thrown when we build targets with files of different suffixes." @@ -601,7 +614,8 @@ class BuilderTestCase(unittest.TestCase): action = { '.foo' : 'foo', '.bar' : 'bar' }, src_builder = foo_bld) - assert isinstance(builder, SCons.Builder.CompositeBuilder) + assert isinstance(builder, SCons.Builder.MultiStepBuilder) + assert isinstance(builder.action, SCons.Action.CommandGeneratorAction) tgt = builder(env, target='t1', source='t1a.ina t1b.ina') assert isinstance(tgt.builder, SCons.Builder.BuilderBase) @@ -618,7 +632,8 @@ class BuilderTestCase(unittest.TestCase): action = { '.foo' : 'foo', '.bar' : 'bar' }, src_builder = [foo_bld, bar_bld]) - assert isinstance(builder, SCons.Builder.CompositeBuilder) + assert isinstance(builder, SCons.Builder.MultiStepBuilder) + assert isinstance(builder.action, SCons.Action.CommandGeneratorAction) tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina') assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder) @@ -627,26 +642,33 @@ class BuilderTestCase(unittest.TestCase): assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder) flag = 0 + tgt = builder(env, target='t5', source='test5a.foo test5b.inb') try: - tgt = builder(env, target='t5', source='test5a.foo test5b.inb') - except SCons.Errors.UserError: + tgt.build() + except SCons.Errors.BuildError, e: + assert e.args[0] == SCons.Errors.UserError flag = 1 assert flag, "UserError should be thrown when we build targets with files of different suffixes." flag = 0 + tgt = builder(env, target='t6', source='test6a.bar test6b.ina') try: - tgt = builder(env, target='t6', source='test6a.bar test6b.ina') - except SCons.Errors.UserError: + tgt.build() + except SCons.Errors.BuildError, e: + assert e.args[0] == SCons.Errors.UserError flag = 1 assert flag, "UserError should be thrown when we build targets with files of different suffixes." flag = 0 + tgt = builder(env, target='t4', source='test4a.ina test4b.inb') try: - tgt = builder(env, target='t4', source='test4a.ina test4b.inb') - except SCons.Errors.UserError: + tgt.build() + except SCons.Errors.BuildError, e: + assert e.args[0] == SCons.Errors.UserError flag = 1 assert flag, "UserError should be thrown when we build targets with files of different suffixes." + def test_build_scanner(self): """Testing ability to set a target scanner through a builder.""" global instanced @@ -683,6 +705,44 @@ class BuilderTestCase(unittest.TestCase): assert tgt.target_scanner != env_scanner, tgt.target_scanner assert src.source_scanner == env_scanner + def test_Builder_Args(self): + """Testing passing extra agrs to a builder.""" + def buildFunc(target, source, env, foo, bar, s=self): + s.foo=foo + s.bar=bar + + builder = SCons.Builder.Builder(name="builder", action=buildFunc) + tgt = builder(env, target='foo', source='bar', foo=1, bar=2) + tgt.build() + assert self.foo == 1, self.foo + assert self.bar == 2, self.bar + + def test_emitter(self): + """Test emitter functions.""" + def emit(target, source, env, foo=0, bar=0): + if foo: + target.append("bar") + if bar: + source.append("foo") + return ( target, source ) + + builder = SCons.Builder.Builder(name="builder", action='foo', + emitter=emit) + tgt = builder(env, target='foo', source='bar') + assert str(tgt) == 'foo', str(tgt) + assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0]) + + tgt = builder(env, target='foo', source='bar', foo=1) + assert len(tgt) == 2, len(tgt) + assert 'foo' in map(str, tgt), map(str, tgt) + assert 'bar' in map(str, tgt), map(str, tgt) + + tgt = builder(env, target='foo', source='bar', bar=1) + assert str(tgt) == 'foo', str(tgt) + assert len(tgt.sources) == 2, len(tgt.sources) + assert 'foo' in map(str, tgt.sources), map(str, tgt.sources) + assert 'bar' in map(str, tgt.sources), map(str, tgt.sources) + if __name__ == "__main__": suite = unittest.makeSuite(BuilderTestCase, 'test_') if not unittest.TextTestRunner().run(suite).wasSuccessful(): diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index 711130d..67317a3 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -40,6 +40,7 @@ import os import stat import string import sys +import os.path import SCons.Action import SCons.Builder @@ -50,8 +51,33 @@ import SCons.Scanner.C import SCons.Scanner.Prog import SCons.Util - - +class SharedCmdGenerator: + """A callable class that acts as a command generator. + It is designed to hold on to 2 actions, and return + one if the shared=1 keyword arg is supplied to the + Builder method, and the other if not. + + Also, all target nodes will have the shared attribute + set to match the vaue of the shared keyword argument, + zero by default.""" + def __init__(self, static, shared): + self.action_static = static + self.action_shared = shared + + def __call__(self, target, source, env, shared=0): + for src in source: + try: + if src.attributes.shared != shared: + raise UserError("Source file: %s must be built with shared=%s in order to be compatible with the selected target." % (src, str(shared))) + except AttributeError: + pass + for t in target: + t.attributes.shared = shared + if shared: + return self.action_shared + else: + return self.action_static + CFile = SCons.Builder.Builder(name = 'CFile', action = { '.l' : '$LEXCOM', '.y' : '$YACCCOM', @@ -64,29 +90,48 @@ CXXFile = SCons.Builder.Builder(name = 'CXXFile', }, suffix = '$CXXFILESUFFIX') -CPlusPlusAction = SCons.Action.Action('$CXXCOM') - -FortranAction = SCons.Action.Action('$F77COM') - -FortranPPAction = SCons.Action.Action('$F77PPCOM') - +CXXAction = SCons.Action.Action("$CXXCOM") +ShCXXAction = SCons.Action.Action("$SHCXXCOM") +F77Action = SCons.Action.Action("$F77COM") +ShF77Action = SCons.Action.Action("$SHF77COM") +F77PPAction = SCons.Action.Action("$F77PPCOM") +ShF77PPAction = SCons.Action.Action("$SHF77PPCOM") + +shared_obj = SCons.Builder.DictCmdGenerator({ ".C" : ShCXXAction, + ".cc" : ShCXXAction, + ".cpp" : ShCXXAction, + ".cxx" : ShCXXAction, + ".c++" : ShCXXAction, + ".C++" : ShCXXAction, + ".c" : "$SHCCCOM", + ".f" : ShF77Action, + ".for" : ShF77Action, + ".FOR" : ShF77Action, + ".F" : ShF77PPAction, + ".fpp" : ShF77PPAction, + ".FPP" : ShF77PPAction }) + +static_obj = SCons.Builder.DictCmdGenerator({ ".C" : CXXAction, + ".cc" : CXXAction, + ".cpp" : CXXAction, + ".cxx" : CXXAction, + ".c++" : CXXAction, + ".C++" : CXXAction, + ".c" : "$CCCOM", + ".f" : F77Action, + ".for" : F77Action, + ".F" : F77PPAction, + ".FOR" : F77Action, + ".fpp" : F77PPAction, + ".FPP" : F77PPAction }) + Object = SCons.Builder.Builder(name = 'Object', - action = { '.c' : '$CCCOM', - '.C' : CPlusPlusAction, - '.cc' : CPlusPlusAction, - '.cpp' : CPlusPlusAction, - '.cxx' : CPlusPlusAction, - '.c++' : CPlusPlusAction, - '.C++' : CPlusPlusAction, - '.f' : FortranAction, - '.for' : FortranAction, - '.FOR' : FortranAction, - '.F' : FortranPPAction, - '.fpp' : FortranPPAction, - '.FPP' : FortranPPAction, - }, + generator = \ + SharedCmdGenerator(static=SCons.Action.CommandGeneratorAction(static_obj), + shared=SCons.Action.CommandGeneratorAction(shared_obj)), prefix = '$OBJPREFIX', suffix = '$OBJSUFFIX', + src_suffix = static_obj.src_suffixes(), src_builder = [CFile, CXXFile]) Program = SCons.Builder.Builder(name = 'Program', @@ -97,12 +142,91 @@ Program = SCons.Builder.Builder(name = 'Program', src_builder = Object, scanner = SCons.Scanner.Prog.ProgScan()) -Library = SCons.Builder.Builder(name = 'Library', - action = '$ARCOM', - prefix = '$LIBPREFIX', - suffix = '$LIBSUFFIX', - src_suffix = '$OBJSUFFIX', - src_builder = Object) +class LibAffixGenerator: + def __init__(self, static, shared): + self.static_affix = static + self.shared_affix = shared + + def __call__(self, shared=0, win32=0): + if shared: + return self.shared_affix + return self.static_affix + +def win32LibGenerator(target, source, env, shared=1): + listCmd = [ "$SHLINK", "$SHLINKFLAGS" ] + + for tgt in target: + ext = os.path.splitext(str(tgt))[1] + if ext == env.subst("$LIBSUFFIX"): + # Put it on the command line as an import library. + listCmd.append("${WIN32IMPLIBPREFIX}%s" % tgt) + else: + listCmd.append("${WIN32DLLPREFIX}%s" % tgt) + + listCmd.extend([ '$_LIBDIRFLAGS', '$_LIBFLAGS' ]) + for src in source: + ext = os.path.splitext(str(src))[1] + if ext == env.subst("$WIN32DEFSUFFIX"): + # Treat this source as a .def file. + listCmd.append("${WIN32DEFPREFIX}%s" % src) + else: + # Just treat it as a generic source file. + listCmd.append(str(src)) + return listCmd + +def win32LibEmitter(target, source, env, shared=0): + if shared: + dll = None + for tgt in target: + ext = os.path.splitext(str(tgt))[1] + if ext == env.subst("$SHLIBSUFFIX"): + dll = tgt + break + if not dll: + raise UserError("A shared library should have exactly one target with the suffix: %s" % env.subst("$SHLIBSUFFIX")) + + if env.has_key("WIN32_INSERT_DEF") and \ + env["WIN32_INSERT_DEF"] and \ + not '.def' in map(lambda x: os.path.split(str(x))[1], + source): + + # append a def file to the list of sources + source.append("%s%s" % (os.path.splitext(str(dll))[0], + env.subst("$WIN32DEFSUFFIX"))) + if not env.subst("$LIBSUFFIX") in \ + map(lambda x: os.path.split(str(x))[1], target): + # Append an import library to the list of targets. + target.append("%s%s%s" % (env.subst("$LIBPREFIX"), + os.path.splitext(str(dll))[0], + env.subst("$LIBSUFFIX"))) + return (target, source) + +PosixLibrary = SCons.Builder.Builder(name = 'Library', + generator = \ + SharedCmdGenerator(shared="$SHLINKCOM", + static="$ARCOM"), + prefix = \ + LibAffixGenerator(static='$LIBPREFIX', + shared='$SHLIBPREFIX'), + suffix = \ + LibAffixGenerator(static='$LIBSUFFIX', + shared='$SHLIBSUFFIX'), + src_suffix = '$OBJSUFFIX', + src_builder = Object) + +Win32Library = SCons.Builder.Builder(name = 'Library', + generator = \ + SharedCmdGenerator(shared=SCons.Action.CommandGeneratorAction(win32LibGenerator), + static="$ARCOM"), + emitter = win32LibEmitter, + prefix = \ + LibAffixGenerator(static='$LIBPREFIX', + shared='$SHLIBPREFIX'), + suffix = \ + LibAffixGenerator(static='$LIBSUFFIX', + shared='$SHLIBSUFFIX'), + src_suffix = '$OBJSUFFIX', + src_builder = Object) LaTeXAction = SCons.Action.Action('$LATEXCOM') @@ -244,21 +368,36 @@ def make_win32_env_from_paths(include, lib, path): 'CC' : 'cl', 'CCFLAGS' : '/nologo', 'CCCOM' : '$CC $CCFLAGS $CPPFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET', + 'SHCC' : '$CC', + 'SHCCFLAGS' : '$CCFLAGS', + 'SHCCCOM' : '$SHCC $SHCCFLAGS $CPPFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET', 'CFILESUFFIX' : '.c', 'CXX' : '$CC', 'CXXFLAGS' : '$CCFLAGS', 'CXXCOM' : '$CXX $CXXFLAGS $CPPFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET', + 'SHCXX' : '$CXX', + 'SHCXXFLAGS' : '$CXXFLAGS', + 'SHCXXCOM' : '$SHCXX $SHCXXFLAGS $CPPFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET', 'CXXFILESUFFIX' : '.cc', 'F77' : 'g77', 'F77FLAGS' : '', 'F77COM' : '$F77 $F77FLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'F77PPCOM' : '$F77 $F77FLAGS $CPPFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', + 'SHF77' : '$F77', + 'SHF77FLAGS' : '$F77FLAGS', + 'SHF77COM' : '$SHF77 $SHF77FLAGS $_INCFLAGS -c -o $TARGET $SOURCES', + 'SHF77PPCOM' : '$SHF77 $SHF77FLAGS $CPPFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'LINK' : 'link', 'LINKFLAGS' : '/nologo', 'LINKCOM' : '$LINK $LINKFLAGS /OUT:$TARGET $_LIBDIRFLAGS $_LIBFLAGS $SOURCES', + 'SHLINK' : '$LINK', + 'SHLINKFLAGS': '$LINKFLAGS /dll', + 'SHLINKCOM' : '$SHLINK $SHLINKFLAGS /OUT:$TARGET $_LIBDIRFLAGS $_LIBFLAGS $SOURCES', 'AR' : 'lib', 'ARFLAGS' : '/nologo', 'ARCOM' : '$AR $ARFLAGS /OUT:$TARGET $SOURCES', + 'SHLIBPREFIX': '', + 'SHLIBSUFFIX': '.dll', 'LEX' : 'lex', 'LEXFLAGS' : '', 'LEXCOM' : '$LEX $LEXFLAGS -t $SOURCES > $TARGET', @@ -281,7 +420,7 @@ def make_win32_env_from_paths(include, lib, path): 'PSCOM' : '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES', 'PSPREFIX' : '', 'PSSUFFIX' : '.ps', - 'BUILDERS' : [Alias, CFile, CXXFile, DVI, Library, Object, + 'BUILDERS' : [Alias, CFile, CXXFile, DVI, Win32Library, Object, PDF, PostScript, Program], 'SCANNERS' : [CScan], 'OBJPREFIX' : '', @@ -289,13 +428,20 @@ def make_win32_env_from_paths(include, lib, path): 'PROGPREFIX' : '', 'PROGSUFFIX' : '.exe', 'LIBPREFIX' : '', + 'LIBPREFIXES': '$LIBPREFIX', 'LIBSUFFIX' : '.lib', + 'LIBSUFFIXES': '$LIBSUFFIX', 'LIBDIRPREFIX' : '/LIBPATH:', 'LIBDIRSUFFIX' : '', 'LIBLINKPREFIX' : '', 'LIBLINKSUFFIX' : '$LIBSUFFIX', 'INCPREFIX' : '/I', 'INCSUFFIX' : '', + 'WIN32DEFPREFIX' : '/def:', + 'WIN32DEFSUFFIX' : '.def', + 'WIN32DLLPREFIX' : '/out:', + 'WIN32IMPLIBPREFIX' : '/implib:', + 'WIN32_INSERT_DEF' : 1, 'ENV' : { 'INCLUDE' : include, 'LIB' : lib, @@ -316,7 +462,8 @@ def make_win32_env(version): if os.name == 'posix': - + Library = PosixLibrary + arcom = '$AR $ARFLAGS $TARGET $SOURCES' ranlib = 'ranlib' if SCons.Util.WhereIs(ranlib): @@ -326,23 +473,41 @@ if os.name == 'posix': 'CC' : 'cc', 'CCFLAGS' : '', 'CCCOM' : '$CC $CCFLAGS $CPPFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', + 'SHCC' : '$CC', + 'SHCCFLAGS' : '$CCFLAGS -fPIC', + 'SHCCCOM' : '$SHCC $SHCCFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'CFILESUFFIX' : '.c', 'CXX' : 'c++', 'CXXFLAGS' : '$CCFLAGS', 'CXXCOM' : '$CXX $CXXFLAGS $CPPFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'CXXFILESUFFIX' : '.cc', + 'SHCXX' : '$CXX', + 'SHCXXFLAGS' : '$CXXFLAGS -fPIC', + 'SHCXXCOM' : '$SHCXX $SHCXXFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'F77' : 'g77', 'F77FLAGS' : '', 'F77COM' : '$F77 $F77FLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'F77PPCOM' : '$F77 $F77FLAGS $CPPFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', + 'SHF77FLAGS' : '$F77FLAGS -fPIC', + 'SHF77COM' : '$F77 $SHF77FLAGS $_INCFLAGS -c -o $TARGET $SOURCES', + 'SHF77PPCOM' : '$F77 $SHF77FLAGS $CPPFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', + 'SHF77' : '$F77', + 'SHF77FLAGS' : '$F77FLAGS -fPIC', + 'SHF77COM' : '$SHF77 $SHF77FLAGS $_INCFLAGS -c -o $TARGET $SOURCES', + 'SHF77PPCOM' : '$SHF77 $SHF77FLAGS $CPPFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'LINK' : '$CXX', 'LINKFLAGS' : '', 'LINKCOM' : '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS', + 'SHLINK' : '$LINK', + 'SHLINKFLAGS': '$LINKFLAGS -shared', + 'SHLINKCOM' : '$SHLINK $SHLINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS', 'AR' : 'ar', 'ARFLAGS' : 'r', 'RANLIB' : ranlib, 'RANLIBFLAGS' : '', 'ARCOM' : arcom, + 'SHLIBPREFIX': '$LIBPREFIX', + 'SHLIBSUFFIX': '.so', 'LEX' : 'lex', 'LEXFLAGS' : '', 'LEXCOM' : '$LEX $LEXFLAGS -t $SOURCES > $TARGET', @@ -363,7 +528,7 @@ if os.name == 'posix': 'PSCOM' : '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES', 'PSPREFIX' : '', 'PSSUFFIX' : '.ps', - 'BUILDERS' : [Alias, CFile, CXXFile, DVI, Library, Object, + 'BUILDERS' : [Alias, CFile, CXXFile, DVI, PosixLibrary, Object, PDF, PostScript, Program], 'SCANNERS' : [CScan], 'OBJPREFIX' : '', @@ -371,7 +536,9 @@ if os.name == 'posix': 'PROGPREFIX' : '', 'PROGSUFFIX' : (sys.platform == 'cygwin') and '.exe' or '', 'LIBPREFIX' : 'lib', + 'LIBPREFIXES': '$LIBPREFIX', 'LIBSUFFIX' : '.a', + 'LIBSUFFIXES': [ '$LIBSUFFIX', '$SHLIBSUFFIX' ], 'LIBDIRPREFIX' : '-L', 'LIBDIRSUFFIX' : '', 'LIBLINKPREFIX' : '-l', @@ -382,6 +549,8 @@ if os.name == 'posix': } elif os.name == 'nt': + Library = Win32Library + versions = None try: versions = get_devstudio_versions() diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 04bf26d..b0d1457 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -167,8 +167,9 @@ class Environment: self.env = env self.builder = builder - def __call__(self, target = None, source = None): - return self.builder(self.env, target, source) + def __call__(self, target = None, source = None, **kw): + return apply(self.builder, (self.env, target, source), + kw) # This allows a Builder to be executed directly # through the Environment to which it's attached. @@ -238,6 +239,9 @@ class Environment: def __delitem__(self, key): del self._dict[key] + def has_key(self, key): + return self._dict.has_key(key) + def Command(self, target, source, action): """Builds the supplied target files from the supplied source files using the supplied action. Action may @@ -282,6 +286,11 @@ class Environment: """ return SCons.Util.scons_subst(string, self._dict, {}) + def subst_list(self, string): + """Calls through to SCons.Util.scons_subst_list(). See + the documentation for that function.""" + return SCons.Util.scons_subst_list(string, self._dict, {}) + def get_scanner(self, skey): """Find the appropriate scanner given a key (usually a file suffix). Does a linear search. Could be sped up by creating a dictionary if diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 654d432..24db460 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -296,7 +296,7 @@ class EnvironmentTestCase(unittest.TestCase): action='buildfoo $target $source') assert t.builder assert t.builder.action.__class__.__name__ == 'CommandAction' - assert t.builder.action.command == '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) @@ -328,6 +328,10 @@ class EnvironmentTestCase(unittest.TestCase): str = env.subst("$AAA ${AAA}A ${AAA}B $BBB") assert str == "c c", str + env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ]) + lst = env.subst_list([ "$AAA", "B $CCC" ]) + assert lst == [ [ "a", "b" ], [ "c", "B a", "b" ], [ "c" ] ], lst + def test_autogenerate(dict): """Test autogenerating variables in a dictionary.""" env = Environment(LIBS = [ 'foo', 'bar', 'baz' ], diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 447ef93..fac20e3 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -40,12 +40,13 @@ cycle_detected = None class Builder: def execute(self, **kw): - global built_it, built_target, built_source - built_it = 1 + global built_it, built_target, built_source, built_args + built_it = 1 built_target = kw['target'] built_source = kw['source'] + built_args = kw return 0 - def get_contents(self, env): + def get_contents(self, env, target, source): return 7 class NoneBuilder(Builder): @@ -160,11 +161,14 @@ class NodeTestCase(unittest.TestCase): node.env_set(Environment()) node.path = "qqq" node.sources = ["rrr", "sss"] + node.build_args = { "foo" : 1, "bar" : 2 } node.build() assert built_it assert type(built_target) == type(MyNode()), type(built_target) assert str(built_target) == "qqq", str(built_target) assert built_source == ["rrr", "sss"], built_source + assert built_args["foo"] == 1, built_args + assert built_args["bar"] == 2, built_args fff = MyNode() ggg = MyNode() diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index c8d5b2c..db52b3e 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -62,6 +62,9 @@ class Node: build, or use to build other Nodes. """ + class Attrs: + pass + def __init__(self): self.sources = [] # source files used to build node self.depends = [] # explicit dependencies (from Depends) @@ -80,6 +83,22 @@ class Node: self.precious = None self.found_includes = {} self.includes = None + self.build_args = {} + self.attributes = self.Attrs() # Generic place to stick information about the Node. + + def generate_build_args(self): + dict = copy.copy(self.env.Dictionary()) + if hasattr(self, 'dir'): + auto = self.env.autogenerate(dir = self.dir) + else: + auto = self.env.autogenerate() + dict.update(auto) + + dictArgs = { 'env' : dict, + 'target' : self, + 'source' : self.sources } + dictArgs.update(self.build_args) + return dictArgs def build(self): """Actually build the node. Return the status from the build.""" @@ -93,15 +112,8 @@ class Node: stat = self.builder.status except AttributeError: try: - dict = copy.copy(self.env.Dictionary()) - if hasattr(self, 'dir'): - auto = self.env.autogenerate(dir = self.dir) - else: - auto = self.env.autogenerate() - dict.update(auto) - stat = self.builder.execute(env = dict, - target = self, - source = self.sources) + stat = apply(self.builder.execute, (), + self.generate_build_args()) except: raise BuildError(self, "Exception", sys.exc_type, @@ -158,13 +170,8 @@ class Node: def __init__(self, node): self.node = node def get_contents(self): - dict = self.node.env.Dictionary() - dict.update(self.node.env.autogenerate()) - try: - dir = self.node.getcwd() - except AttributeError: - dir = None - return self.node.builder.get_contents(env = dict) + return apply(self.node.builder.get_contents, (), + self.node.generate_build_args()) return Adapter(self) def get_implicit_deps(self, env, scanner, target): diff --git a/src/engine/SCons/Scanner/Prog.py b/src/engine/SCons/Scanner/Prog.py index 76567ab..818aa86 100644 --- a/src/engine/SCons/Scanner/Prog.py +++ b/src/engine/SCons/Scanner/Prog.py @@ -66,14 +66,21 @@ def scan(node, env, target, fs): libs = string.split(libs) try: - prefix = env.Dictionary('LIBPREFIX') + prefix = env.Dictionary('LIBPREFIXES') + if not SCons.Util.is_List(prefix): + prefix = [ prefix ] except KeyError: - prefix = '' + prefix = [ '' ] try: - suffix = env.Dictionary('LIBSUFFIX') + suffix = env.Dictionary('LIBSUFFIXES') + if not SCons.Util.is_List(suffix): + suffix = [ suffix ] except KeyError: - suffix = '' + suffix = [ '' ] - libs = map(lambda x, s=suffix, p=prefix: p + x + s, libs) - return SCons.Node.FS.find_files(libs, libpath, fs.File) + ret = [] + for suf in map(env.subst, suffix): + for pref in map(env.subst, prefix): + ret.extend(map(lambda x, s=suf, p=pref: p + x + s, libs)) + return SCons.Node.FS.find_files(ret, libpath, fs.File) diff --git a/src/engine/SCons/Scanner/ProgTests.py b/src/engine/SCons/Scanner/ProgTests.py index ea3d00d..75ce697 100644 --- a/src/engine/SCons/Scanner/ProgTests.py +++ b/src/engine/SCons/Scanner/ProgTests.py @@ -49,7 +49,7 @@ class DummyTarget: class DummyEnvironment: def __init__(self, **kw): self._dict = kw - self._dict['LIBSUFFIX'] = '.lib' + self._dict['LIBSUFFIXES'] = '.lib' def Dictionary(self, *args): if not args: @@ -67,6 +67,9 @@ class DummyEnvironment: def __delitem__(self,key): del self.Dictionary()[key] + def subst(self, s): + return s + def deps_match(deps, libs): deps=map(str, deps) deps.sort() diff --git a/src/engine/SCons/Sig/MD5.py b/src/engine/SCons/Sig/MD5.py index c2331bd..251c1eb 100644 --- a/src/engine/SCons/Sig/MD5.py +++ b/src/engine/SCons/Sig/MD5.py @@ -78,12 +78,9 @@ def collect(signatures): def signature(obj): """Generate a signature for an object """ - try: - contents = str(obj.get_contents()) - except AttributeError, e: - raise AttributeError, \ - "unable to fetch contents of '%s': %s" % (str(obj), e) - return hexdigest(md5.new(contents).digest()) + if not hasattr(obj, 'get_contents'): + raise AttributeError, "unable to fetch contents of '%s'" % str(obj) + return hexdigest(md5.new(str(obj.get_contents())).digest()) def to_string(signature): """Convert a signature to a string""" diff --git a/src/engine/SCons/Sig/MD5Tests.py b/src/engine/SCons/Sig/MD5Tests.py index ecdb920..b5578b3 100644 --- a/src/engine/SCons/Sig/MD5Tests.py +++ b/src/engine/SCons/Sig/MD5Tests.py @@ -84,8 +84,7 @@ class MD5TestCase(unittest.TestCase): try: signature('string') except AttributeError, e: - # the error string should begin with "unable to fetch contents of 'string': " - assert string.find(str(e), "unable to fetch contents of 'string':") == 0 + assert string.find(str(e), "unable to fetch contents") == 0, str(e) else: raise AttributeError, "unexpected get_contents() attribute" diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 3612b9a..1f27ff8 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -153,6 +153,13 @@ def scons_subst_list(strSubst, globals, locals, remove=None): the argv array that should be passed to a spawn or exec function. + Also, this method can accept a list of strings as input + to strSubst, which explicitly denotes the command line + arguments. This is useful if you want to pass in + command line arguments with spaces or newlines in them. + Otheriwise, if you just passed in a string, they would + get split along the spaces and newlines. + 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- @@ -170,16 +177,23 @@ def scons_subst_list(strSubst, globals, locals, remove=None): if e is None: s = '' elif is_List(e): - s = string.join(map(str, e), '\0') + s = string.join(map(to_String, e), '\0') else: - s = _space_sep.sub('\0', str(e)) + s = _space_sep.sub('\0', to_String(e)) except NameError: s = '' return s n = 1 - # Tokenize the original string... - strSubst = _space_sep.sub('\0', str(strSubst)) + if is_List(strSubst): + # This looks like our input is a list of strings, + # as explained in the docstring above. Munge + # it into a tokenized string by concatenating + # the list with nulls. + strSubst = string.join(strSubst, '\0') + else: + # Tokenize the original string... + strSubst = _space_sep.sub('\0', to_String(strSubst)) # Now, do the substitution while n != 0: @@ -249,6 +263,14 @@ def is_Dict(e): def is_List(e): return type(e) is types.ListType or isinstance(e, UserList.UserList) +def to_String(s): + """Better than str() because it will preserve a unicode + object without converting it to ASCII.""" + if is_String(s): + return s + else: + return str(s) + def argmunge(arg): """This function converts a string or list into a list of strings or Nodes. It follows the rules outlined in the SCons design document by accepting diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 8be22f8..fdffe4d 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -93,7 +93,9 @@ class UtilTestCase(unittest.TestCase): assert newcom == cvt("test %s/foo/bar.exe"%os.getcwd()), newcom newcom = scons_subst("test ${SOURCES.abspath}", loc, {}) - assert newcom == cvt("test %s/foo/blah.cpp /bar/ack.cpp %s/foo/ack.c"%(os.getcwd(),os.path.normpath(os.getcwd()+"/.."))), newcom + assert newcom == cvt("test %s/foo/blah.cpp %s %s/foo/ack.c"%(os.getcwd(), + os.path.abspath(os.path.normpath("/bar/ack.cpp")), + os.path.normpath(os.getcwd()+"/.."))), newcom newcom = scons_subst("test ${SOURCE.abspath}", loc, {}) assert newcom == cvt("test %s/foo/blah.cpp"%os.getcwd()), newcom @@ -160,12 +162,24 @@ class UtilTestCase(unittest.TestCase): assert cmd_list[1][0] == 'after', cmd_list[1][0] assert cmd_list[0][2] == cvt('../foo/ack.cbefore'), cmd_list[0][2] + # Test inputting a list to scons_subst_list() + cmd_list = scons_subst_list([ "$SOURCES$NEWLINE", "$TARGETS", + "This is a test" ], + loc, {}) + 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 cmd_list[0] == ['test', '3', '2', '4', 'test'], cmd_list + + + def test_render_tree(self): class Node: def __init__(self, name, children=[]): @@ -236,6 +250,36 @@ class UtilTestCase(unittest.TestCase): assert not is_String({}) assert not is_String([]) + def test_to_String(self): + """Test the to_String() method.""" + assert to_String(1) == "1", to_String(1) + assert to_String([ 1, 2, 3]) == str([1, 2, 3]), to_String([1,2,3]) + assert to_String("foo") == "foo", to_String("foo") + + try: + import UserString + + s1=UserString.UserString('blah') + assert to_String(s1) is s1, s1 + assert to_String(s1) == 'blah', s1 + + class Derived(UserString.UserString): + pass + s2 = Derived('foo') + assert to_String(s2) is s2, s2 + assert to_String(s2) == 'foo', s2 + + if hasattr(types, 'UnicodeType'): + s3=UserString.UserString(unicode('bar')) + assert to_String(s3) is s3, s3 + assert to_String(s3) == unicode('bar'), s3 + except ImportError: + pass + + if hasattr(types, 'UnicodeType'): + s4 = unicode('baz') + assert to_String(s4) == unicode('baz'), to_String(s4) + def test_WhereIs(self): test = TestCmd.TestCmd(workdir = '') |