Index: scons-2.0.1/engine/SCons/Builder.py |
=================================================================== |
--- scons-2.0.1/engine/SCons/Builder.py (revision 0) |
+++ scons-2.0.1/engine/SCons/Builder.py (revision 0) |
@@ -0,0 +1,877 @@ |
+"""SCons.Builder |
+ |
+Builder object subsystem. |
+ |
+A Builder object is a callable that encapsulates information about how |
+to execute actions to create a target Node (file) from source Nodes |
+(files), and how to create those dependencies for tracking. |
+ |
+The main entry point here is the Builder() factory method. This provides |
+a procedural interface that creates the right underlying Builder object |
+based on the keyword arguments supplied and the types of the arguments. |
+ |
+The goal is for this external interface to be simple enough that the |
+vast majority of users can create new Builders as necessary to support |
+building new types of files in their configurations, without having to |
+dive any deeper into this subsystem. |
+ |
+The base class here is BuilderBase. This is a concrete base class which |
+does, in fact, represent the Builder objects that we (or users) create. |
+ |
+There is also a proxy that looks like a Builder: |
+ |
+ CompositeBuilder |
+ |
+ This proxies for a Builder with an action that is actually a |
+ dictionary that knows how to map file suffixes to a specific |
+ action. This is so that we can invoke different actions |
+ (compilers, compile options) for different flavors of source |
+ files. |
+ |
+Builders and their proxies have the following public interface methods |
+used by other modules: |
+ |
+ __call__() |
+ THE public interface. Calling a Builder object (with the |
+ use of internal helper methods) sets up the target and source |
+ dependencies, appropriate mapping to a specific action, and the |
+ environment manipulation necessary for overridden construction |
+ variable. This also takes care of warning about possible mistakes |
+ in keyword arguments. |
+ |
+ add_emitter() |
+ Adds an emitter for a specific file suffix, used by some Tool |
+ modules to specify that (for example) a yacc invocation on a .y |
+ can create a .h *and* a .c file. |
+ |
+ add_action() |
+ Adds an action for a specific file suffix, heavily used by |
+ Tool modules to add their specific action(s) for turning |
+ a source file into an object file to the global static |
+ and shared object file Builders. |
+ |
+There are the following methods for internal use within this module: |
+ |
+ _execute() |
+ The internal method that handles the heavily lifting when a |
+ Builder is called. This is used so that the __call__() methods |
+ can set up warning about possible mistakes in keyword-argument |
+ overrides, and *then* execute all of the steps necessary so that |
+ the warnings only occur once. |
+ |
+ get_name() |
+ Returns the Builder's name within a specific Environment, |
+ primarily used to try to return helpful information in error |
+ messages. |
+ |
+ adjust_suffix() |
+ get_prefix() |
+ get_suffix() |
+ get_src_suffix() |
+ set_src_suffix() |
+ Miscellaneous stuff for handling the prefix and suffix |
+ manipulation we use in turning source file names into target |
+ file names. |
+ |
+""" |
+ |
+# |
+# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation |
+# |
+# Permission is hereby granted, free of charge, to any person obtaining |
+# a copy of this software and associated documentation files (the |
+# "Software"), to deal in the Software without restriction, including |
+# without limitation the rights to use, copy, modify, merge, publish, |
+# distribute, sublicense, and/or sell copies of the Software, and to |
+# permit persons to whom the Software is furnished to do so, subject to |
+# the following conditions: |
+# |
+# The above copyright notice and this permission notice shall be included |
+# in all copies or substantial portions of the Software. |
+# |
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
+ |
+__revision__ = "src/engine/SCons/Builder.py 5134 2010/08/16 23:02:40 bdeegan" |
+ |
+import collections |
+ |
+import SCons.Action |
+from SCons.Debug import logInstanceCreation |
+from SCons.Errors import InternalError, UserError |
+import SCons.Executor |
+import SCons.Memoize |
+import SCons.Node |
+import SCons.Node.FS |
+import SCons.Util |
+import SCons.Warnings |
+ |
+class _Null(object): |
+ pass |
+ |
+_null = _Null |
+ |
+def match_splitext(path, suffixes = []): |
+ if suffixes: |
+ matchsuf = [S for S in suffixes if path[-len(S):] == S] |
+ if matchsuf: |
+ suf = max([(len(_f),_f) for _f in matchsuf])[1] |
+ return [path[:-len(suf)], path[-len(suf):]] |
+ return SCons.Util.splitext(path) |
+ |
+class DictCmdGenerator(SCons.Util.Selector): |
+ """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, dict=None, source_ext_match=1): |
+ SCons.Util.Selector.__init__(self, dict) |
+ self.source_ext_match = source_ext_match |
+ |
+ def src_suffixes(self): |
+ return list(self.keys()) |
+ |
+ def add_action(self, suffix, action): |
+ """Add a suffix-action pair to the mapping. |
+ """ |
+ self[suffix] = action |
+ |
+ def __call__(self, target, source, env, for_signature): |
+ if not source: |
+ return [] |
+ |
+ if self.source_ext_match: |
+ suffixes = self.src_suffixes() |
+ ext = None |
+ for src in map(str, source): |
+ my_ext = match_splitext(src, suffixes)[1] |
+ if ext and my_ext != ext: |
+ raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" |
+ % (repr(list(map(str, target))), src, ext, my_ext)) |
+ ext = my_ext |
+ else: |
+ ext = match_splitext(str(source[0]), self.src_suffixes())[1] |
+ |
+ if not ext: |
+ #return ext |
+ raise UserError("While building `%s': " |
+ "Cannot deduce file extension from source files: %s" |
+ % (repr(list(map(str, target))), repr(list(map(str, source))))) |
+ |
+ try: |
+ ret = SCons.Util.Selector.__call__(self, env, source, ext) |
+ except KeyError, e: |
+ raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e.args[0], e.args[1], e.args[2])) |
+ if ret is None: |
+ raise UserError("While building `%s' from `%s': Don't know how to build from a source file with suffix `%s'. Expected a suffix in this list: %s." % \ |
+ (repr(list(map(str, target))), repr(list(map(str, source))), ext, repr(list(self.keys())))) |
+ return ret |
+ |
+class CallableSelector(SCons.Util.Selector): |
+ """A callable dictionary that will, in turn, call the value it |
+ finds if it can.""" |
+ def __call__(self, env, source): |
+ value = SCons.Util.Selector.__call__(self, env, source) |
+ if callable(value): |
+ value = value(env, source) |
+ return value |
+ |
+class DictEmitter(SCons.Util.Selector): |
+ """A callable dictionary that maps file suffixes to emitters. |
+ When called, it finds the right emitter in its dictionary for the |
+ suffix of the first source file, and calls that emitter to get the |
+ right lists of targets and sources to return. If there's no emitter |
+ for the suffix in its dictionary, the original target and source are |
+ returned. |
+ """ |
+ def __call__(self, target, source, env): |
+ emitter = SCons.Util.Selector.__call__(self, env, source) |
+ if emitter: |
+ target, source = emitter(target, source, env) |
+ return (target, source) |
+ |
+class ListEmitter(collections.UserList): |
+ """A callable list of emitters that calls each in sequence, |
+ returning the result. |
+ """ |
+ def __call__(self, target, source, env): |
+ for e in self.data: |
+ target, source = e(target, source, env) |
+ return (target, source) |
+ |
+# These are a common errors when calling a Builder; |
+# they are similar to the 'target' and 'source' keyword args to builders, |
+# so we issue warnings when we see them. The warnings can, of course, |
+# be disabled. |
+misleading_keywords = { |
+ 'targets' : 'target', |
+ 'sources' : 'source', |
+} |
+ |
+class OverrideWarner(collections.UserDict): |
+ """A class for warning about keyword arguments that we use as |
+ overrides in a Builder call. |
+ |
+ This class exists to handle the fact that a single Builder call |
+ can actually invoke multiple builders. This class only emits the |
+ warnings once, no matter how many Builders are invoked. |
+ """ |
+ def __init__(self, dict): |
+ collections.UserDict.__init__(self, dict) |
+ if __debug__: logInstanceCreation(self, 'Builder.OverrideWarner') |
+ self.already_warned = None |
+ def warn(self): |
+ if self.already_warned: |
+ return |
+ for k in self.keys(): |
+ if k in misleading_keywords: |
+ alt = misleading_keywords[k] |
+ msg = "Did you mean to use `%s' instead of `%s'?" % (alt, k) |
+ SCons.Warnings.warn(SCons.Warnings.MisleadingKeywordsWarning, msg) |
+ self.already_warned = 1 |
+ |
+def Builder(**kw): |
+ """A factory for builder objects.""" |
+ composite = None |
+ if 'generator' in kw: |
+ if 'action' in kw: |
+ raise UserError("You must not specify both an action and a generator.") |
+ kw['action'] = SCons.Action.CommandGeneratorAction(kw['generator'], {}) |
+ del kw['generator'] |
+ elif 'action' in kw: |
+ source_ext_match = kw.get('source_ext_match', 1) |
+ if 'source_ext_match' in kw: |
+ del kw['source_ext_match'] |
+ if SCons.Util.is_Dict(kw['action']): |
+ composite = DictCmdGenerator(kw['action'], source_ext_match) |
+ kw['action'] = SCons.Action.CommandGeneratorAction(composite, {}) |
+ kw['src_suffix'] = composite.src_suffixes() |
+ else: |
+ kw['action'] = SCons.Action.Action(kw['action']) |
+ |
+ if 'emitter' in kw: |
+ emitter = kw['emitter'] |
+ if SCons.Util.is_String(emitter): |
+ # This allows users to pass in an Environment |
+ # variable reference (like "$FOO") as an emitter. |
+ # We will look in that Environment variable for |
+ # a callable to use as the actual emitter. |
+ var = SCons.Util.get_environment_var(emitter) |
+ if not var: |
+ raise UserError("Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter) |
+ kw['emitter'] = EmitterProxy(var) |
+ elif SCons.Util.is_Dict(emitter): |
+ kw['emitter'] = DictEmitter(emitter) |
+ elif SCons.Util.is_List(emitter): |
+ kw['emitter'] = ListEmitter(emitter) |
+ |
+ result = BuilderBase(**kw) |
+ |
+ if not composite is None: |
+ result = CompositeBuilder(result, composite) |
+ |
+ return result |
+ |
+def _node_errors(builder, env, tlist, slist): |
+ """Validate that the lists of target and source nodes are |
+ legal for this builder and environment. Raise errors or |
+ issue warnings as appropriate. |
+ """ |
+ |
+ # First, figure out if there are any errors in the way the targets |
+ # were specified. |
+ for t in tlist: |
+ if t.side_effect: |
+ raise UserError("Multiple ways to build the same target were specified for: %s" % t) |
+ if t.has_explicit_builder(): |
+ if not t.env is None and not t.env is env: |
+ action = t.builder.action |
+ t_contents = action.get_contents(tlist, slist, t.env) |
+ contents = action.get_contents(tlist, slist, env) |
+ |
+ if t_contents == contents: |
+ msg = "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s" % (t, action.genstring(tlist, slist, t.env)) |
+ SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning, msg) |
+ else: |
+ msg = "Two environments with different actions were specified for the same target: %s" % t |
+ raise UserError(msg) |
+ if builder.multi: |
+ if t.builder != builder: |
+ msg = "Two different builders (%s and %s) were specified for the same target: %s" % (t.builder.get_name(env), builder.get_name(env), t) |
+ raise UserError(msg) |
+ # TODO(batch): list constructed each time! |
+ if t.get_executor().get_all_targets() != tlist: |
+ msg = "Two different target lists have a target in common: %s (from %s and from %s)" % (t, list(map(str, t.get_executor().get_all_targets())), list(map(str, tlist))) |
+ raise UserError(msg) |
+ elif t.sources != slist: |
+ msg = "Multiple ways to build the same target were specified for: %s (from %s and from %s)" % (t, list(map(str, t.sources)), list(map(str, slist))) |
+ raise UserError(msg) |
+ |
+ if builder.single_source: |
+ if len(slist) > 1: |
+ raise UserError("More than one source given for single-source builder: targets=%s sources=%s" % (list(map(str,tlist)), list(map(str,slist)))) |
+ |
+class EmitterProxy(object): |
+ """This is a callable class that can act as a |
+ Builder emitter. It holds on to a string that |
+ is a key into an Environment dictionary, and will |
+ look there at actual build time to see if it holds |
+ a callable. If so, we will call that as the actual |
+ emitter.""" |
+ def __init__(self, var): |
+ self.var = SCons.Util.to_String(var) |
+ |
+ def __call__(self, target, source, env): |
+ emitter = self.var |
+ |
+ # Recursively substitute the variable. |
+ # We can't use env.subst() because it deals only |
+ # in strings. Maybe we should change that? |
+ while SCons.Util.is_String(emitter) and emitter in env: |
+ emitter = env[emitter] |
+ if callable(emitter): |
+ target, source = emitter(target, source, env) |
+ elif SCons.Util.is_List(emitter): |
+ for e in emitter: |
+ target, source = e(target, source, env) |
+ |
+ return (target, source) |
+ |
+ |
+ def __cmp__(self, other): |
+ return cmp(self.var, other.var) |
+ |
+class BuilderBase(object): |
+ """Base class for Builders, objects that create output |
+ nodes (files) from input nodes (files). |
+ """ |
+ |
+ if SCons.Memoize.use_memoizer: |
+ __metaclass__ = SCons.Memoize.Memoized_Metaclass |
+ |
+ memoizer_counters = [] |
+ |
+ def __init__(self, action = None, |
+ prefix = '', |
+ suffix = '', |
+ src_suffix = '', |
+ target_factory = None, |
+ source_factory = None, |
+ target_scanner = None, |
+ source_scanner = None, |
+ emitter = None, |
+ multi = 0, |
+ env = None, |
+ single_source = 0, |
+ name = None, |
+ chdir = _null, |
+ is_explicit = 1, |
+ src_builder = None, |
+ ensure_suffix = False, |
+ **overrides): |
+ if __debug__: logInstanceCreation(self, 'Builder.BuilderBase') |
+ self._memo = {} |
+ self.action = action |
+ self.multi = multi |
+ if SCons.Util.is_Dict(prefix): |
+ prefix = CallableSelector(prefix) |
+ self.prefix = prefix |
+ if SCons.Util.is_Dict(suffix): |
+ suffix = CallableSelector(suffix) |
+ self.env = env |
+ self.single_source = single_source |
+ if 'overrides' in overrides: |
+ SCons.Warnings.warn(SCons.Warnings.DeprecatedBuilderKeywordsWarning, |
+ "The \"overrides\" keyword to Builder() creation has been deprecated;\n" +\ |
+ "\tspecify the items as keyword arguments to the Builder() call instead.") |
+ overrides.update(overrides['overrides']) |
+ del overrides['overrides'] |
+ if 'scanner' in overrides: |
+ SCons.Warnings.warn(SCons.Warnings.DeprecatedBuilderKeywordsWarning, |
+ "The \"scanner\" keyword to Builder() creation has been deprecated;\n" |
+ "\tuse: source_scanner or target_scanner as appropriate.") |
+ del overrides['scanner'] |
+ self.overrides = overrides |
+ |
+ self.set_suffix(suffix) |
+ self.set_src_suffix(src_suffix) |
+ self.ensure_suffix = ensure_suffix |
+ |
+ self.target_factory = target_factory |
+ self.source_factory = source_factory |
+ self.target_scanner = target_scanner |
+ self.source_scanner = source_scanner |
+ |
+ self.emitter = emitter |
+ |
+ # Optional Builder name should only be used for Builders |
+ # that don't get attached to construction environments. |
+ if name: |
+ self.name = name |
+ self.executor_kw = {} |
+ if not chdir is _null: |
+ self.executor_kw['chdir'] = chdir |
+ self.is_explicit = is_explicit |
+ |
+ if src_builder is None: |
+ src_builder = [] |
+ elif not SCons.Util.is_List(src_builder): |
+ src_builder = [ src_builder ] |
+ self.src_builder = src_builder |
+ |
+ def __nonzero__(self): |
+ raise InternalError("Do not test for the Node.builder attribute directly; use Node.has_builder() instead") |
+ |
+ def get_name(self, env): |
+ """Attempts to get the name of the Builder. |
+ |
+ Look at the BUILDERS variable of env, expecting it to be a |
+ dictionary containing this Builder, and return the key of the |
+ dictionary. If there's no key, then return a directly-configured |
+ name (if there is one) or the name of the class (by default).""" |
+ |
+ try: |
+ index = list(env['BUILDERS'].values()).index(self) |
+ return list(env['BUILDERS'].keys())[index] |
+ except (AttributeError, KeyError, TypeError, ValueError): |
+ try: |
+ return self.name |
+ except AttributeError: |
+ return str(self.__class__) |
+ |
+ def __cmp__(self, other): |
+ return cmp(self.__dict__, other.__dict__) |
+ |
+ def splitext(self, path, env=None): |
+ if not env: |
+ env = self.env |
+ if env: |
+ suffixes = self.src_suffixes(env) |
+ else: |
+ suffixes = [] |
+ return match_splitext(path, suffixes) |
+ |
+ def _adjustixes(self, files, pre, suf, ensure_suffix=False): |
+ if not files: |
+ return [] |
+ result = [] |
+ if not SCons.Util.is_List(files): |
+ files = [files] |
+ |
+ for f in files: |
+ if SCons.Util.is_String(f): |
+ f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix) |
+ result.append(f) |
+ return result |
+ |
+ def _create_nodes(self, env, target = None, source = None): |
+ """Create and return lists of target and source nodes. |
+ """ |
+ src_suf = self.get_src_suffix(env) |
+ |
+ target_factory = env.get_factory(self.target_factory) |
+ source_factory = env.get_factory(self.source_factory) |
+ |
+ source = self._adjustixes(source, None, src_suf) |
+ slist = env.arg2nodes(source, source_factory) |
+ |
+ pre = self.get_prefix(env, slist) |
+ suf = self.get_suffix(env, slist) |
+ |
+ if target is None: |
+ try: |
+ t_from_s = slist[0].target_from_source |
+ except AttributeError: |
+ raise UserError("Do not know how to create a target from source `%s'" % slist[0]) |
+ except IndexError: |
+ tlist = [] |
+ else: |
+ splitext = lambda S: self.splitext(S,env) |
+ tlist = [ t_from_s(pre, suf, splitext) ] |
+ else: |
+ target = self._adjustixes(target, pre, suf, self.ensure_suffix) |
+ tlist = env.arg2nodes(target, target_factory, target=target, source=source) |
+ |
+ if self.emitter: |
+ # The emitter is going to do str(node), but because we're |
+ # being called *from* a builder invocation, the new targets |
+ # don't yet have a builder set on them and will look like |
+ # source files. Fool the emitter's str() calls by setting |
+ # up a temporary builder on the new targets. |
+ new_targets = [] |
+ for t in tlist: |
+ if not t.is_derived(): |
+ t.builder_set(self) |
+ new_targets.append(t) |
+ |
+ orig_tlist = tlist[:] |
+ orig_slist = slist[:] |
+ |
+ target, source = self.emitter(target=tlist, source=slist, env=env) |
+ |
+ # Now delete the temporary builders that we attached to any |
+ # new targets, so that _node_errors() doesn't do weird stuff |
+ # to them because it thinks they already have builders. |
+ for t in new_targets: |
+ if t.builder is self: |
+ # Only delete the temporary builder if the emitter |
+ # didn't change it on us. |
+ t.builder_set(None) |
+ |
+ # Have to call arg2nodes yet again, since it is legal for |
+ # emitters to spit out strings as well as Node instances. |
+ tlist = env.arg2nodes(target, target_factory, |
+ target=orig_tlist, source=orig_slist) |
+ slist = env.arg2nodes(source, source_factory, |
+ target=orig_tlist, source=orig_slist) |
+ |
+ return tlist, slist |
+ |
+ def _execute(self, env, target, source, overwarn={}, executor_kw={}): |
+ # We now assume that target and source are lists or None. |
+ if self.src_builder: |
+ source = self.src_builder_sources(env, source, overwarn) |
+ |
+ if self.single_source and len(source) > 1 and target is None: |
+ result = [] |
+ if target is None: target = [None]*len(source) |
+ for tgt, src in zip(target, source): |
+ if not tgt is None: tgt = [tgt] |
+ if not src is None: src = [src] |
+ result.extend(self._execute(env, tgt, src, overwarn)) |
+ return SCons.Node.NodeList(result) |
+ |
+ overwarn.warn() |
+ |
+ tlist, slist = self._create_nodes(env, target, source) |
+ |
+ # Check for errors with the specified target/source lists. |
+ _node_errors(self, env, tlist, slist) |
+ |
+ # The targets are fine, so find or make the appropriate Executor to |
+ # build this particular list of targets from this particular list of |
+ # sources. |
+ |
+ executor = None |
+ key = None |
+ |
+ if self.multi: |
+ try: |
+ executor = tlist[0].get_executor(create = 0) |
+ except (AttributeError, IndexError): |
+ pass |
+ else: |
+ executor.add_sources(slist) |
+ |
+ if executor is None: |
+ if not self.action: |
+ fmt = "Builder %s must have an action to build %s." |
+ raise UserError(fmt % (self.get_name(env or self.env), |
+ list(map(str,tlist)))) |
+ key = self.action.batch_key(env or self.env, tlist, slist) |
+ if key: |
+ try: |
+ executor = SCons.Executor.GetBatchExecutor(key) |
+ except KeyError: |
+ pass |
+ else: |
+ executor.add_batch(tlist, slist) |
+ |
+ if executor is None: |
+ executor = SCons.Executor.Executor(self.action, env, [], |
+ tlist, slist, executor_kw) |
+ if key: |
+ SCons.Executor.AddBatchExecutor(key, executor) |
+ |
+ # Now set up the relevant information in the target Nodes themselves. |
+ for t in tlist: |
+ t.cwd = env.fs.getcwd() |
+ t.builder_set(self) |
+ t.env_set(env) |
+ t.add_source(slist) |
+ t.set_executor(executor) |
+ t.set_explicit(self.is_explicit) |
+ |
+ return SCons.Node.NodeList(tlist) |
+ |
+ def __call__(self, env, target=None, source=None, chdir=_null, **kw): |
+ # We now assume that target and source are lists or None. |
+ # The caller (typically Environment.BuilderWrapper) is |
+ # responsible for converting any scalar values to lists. |
+ if chdir is _null: |
+ ekw = self.executor_kw |
+ else: |
+ ekw = self.executor_kw.copy() |
+ ekw['chdir'] = chdir |
+ if kw: |
+ if 'srcdir' in kw: |
+ def prependDirIfRelative(f, srcdir=kw['srcdir']): |
+ import os.path |
+ if SCons.Util.is_String(f) and not os.path.isabs(f): |
+ f = os.path.join(srcdir, f) |
+ return f |
+ if not SCons.Util.is_List(source): |
+ source = [source] |
+ source = list(map(prependDirIfRelative, source)) |
+ del kw['srcdir'] |
+ if self.overrides: |
+ env_kw = self.overrides.copy() |
+ env_kw.update(kw) |
+ else: |
+ env_kw = kw |
+ else: |
+ env_kw = self.overrides |
+ env = env.Override(env_kw) |
+ return self._execute(env, target, source, OverrideWarner(kw), ekw) |
+ |
+ def adjust_suffix(self, suff): |
+ if suff and not suff[0] in [ '.', '_', '$' ]: |
+ return '.' + suff |
+ return suff |
+ |
+ def get_prefix(self, env, sources=[]): |
+ prefix = self.prefix |
+ if callable(prefix): |
+ prefix = prefix(env, sources) |
+ return env.subst(prefix) |
+ |
+ def set_suffix(self, suffix): |
+ if not callable(suffix): |
+ suffix = self.adjust_suffix(suffix) |
+ self.suffix = suffix |
+ |
+ def get_suffix(self, env, sources=[]): |
+ suffix = self.suffix |
+ if callable(suffix): |
+ suffix = suffix(env, sources) |
+ return env.subst(suffix) |
+ |
+ def set_src_suffix(self, src_suffix): |
+ if not src_suffix: |
+ src_suffix = [] |
+ elif not SCons.Util.is_List(src_suffix): |
+ src_suffix = [ src_suffix ] |
+ self.src_suffix = [callable(suf) and suf or self.adjust_suffix(suf) for suf in src_suffix] |
+ |
+ def get_src_suffix(self, env): |
+ """Get the first src_suffix in the list of src_suffixes.""" |
+ ret = self.src_suffixes(env) |
+ if not ret: |
+ return '' |
+ return ret[0] |
+ |
+ def add_emitter(self, suffix, emitter): |
+ """Add a suffix-emitter mapping to this Builder. |
+ |
+ This assumes that emitter has been initialized with an |
+ appropriate dictionary type, and will throw a TypeError if |
+ not, so the caller is responsible for knowing that this is an |
+ appropriate method to call for the Builder in question. |
+ """ |
+ self.emitter[suffix] = emitter |
+ |
+ def add_src_builder(self, builder): |
+ """ |
+ Add a new Builder to the list of src_builders. |
+ |
+ This requires wiping out cached values so that the computed |
+ lists of source suffixes get re-calculated. |
+ """ |
+ self._memo = {} |
+ self.src_builder.append(builder) |
+ |
+ def _get_sdict(self, env): |
+ """ |
+ Returns a dictionary mapping all of the source suffixes of all |
+ src_builders of this Builder to the underlying Builder that |
+ should be called first. |
+ |
+ This dictionary is used for each target specified, so we save a |
+ lot of extra computation by memoizing it for each construction |
+ environment. |
+ |
+ Note that this is re-computed each time, not cached, because there |
+ might be changes to one of our source Builders (or one of their |
+ source Builders, and so on, and so on...) that we can't "see." |
+ |
+ The underlying methods we call cache their computed values, |
+ though, so we hope repeatedly aggregating them into a dictionary |
+ like this won't be too big a hit. We may need to look for a |
+ better way to do this if performance data show this has turned |
+ into a significant bottleneck. |
+ """ |
+ sdict = {} |
+ for bld in self.get_src_builders(env): |
+ for suf in bld.src_suffixes(env): |
+ sdict[suf] = bld |
+ return sdict |
+ |
+ def src_builder_sources(self, env, source, overwarn={}): |
+ sdict = self._get_sdict(env) |
+ |
+ src_suffixes = self.src_suffixes(env) |
+ |
+ lengths = list(set(map(len, src_suffixes))) |
+ |
+ def match_src_suffix(name, src_suffixes=src_suffixes, lengths=lengths): |
+ node_suffixes = [name[-l:] for l in lengths] |
+ for suf in src_suffixes: |
+ if suf in node_suffixes: |
+ return suf |
+ return None |
+ |
+ result = [] |
+ for s in SCons.Util.flatten(source): |
+ if SCons.Util.is_String(s): |
+ match_suffix = match_src_suffix(env.subst(s)) |
+ if not match_suffix and not '.' in s: |
+ src_suf = self.get_src_suffix(env) |
+ s = self._adjustixes(s, None, src_suf)[0] |
+ else: |
+ match_suffix = match_src_suffix(s.name) |
+ if match_suffix: |
+ try: |
+ bld = sdict[match_suffix] |
+ except KeyError: |
+ result.append(s) |
+ else: |
+ tlist = bld._execute(env, None, [s], overwarn) |
+ # If the subsidiary Builder returned more than one |
+ # target, then filter out any sources that this |
+ # Builder isn't capable of building. |
+ if len(tlist) > 1: |
+ tlist = [t for t in tlist if match_src_suffix(t.name)] |
+ result.extend(tlist) |
+ else: |
+ result.append(s) |
+ |
+ source_factory = env.get_factory(self.source_factory) |
+ |
+ return env.arg2nodes(result, source_factory) |
+ |
+ def _get_src_builders_key(self, env): |
+ return id(env) |
+ |
+ memoizer_counters.append(SCons.Memoize.CountDict('get_src_builders', _get_src_builders_key)) |
+ |
+ def get_src_builders(self, env): |
+ """ |
+ Returns the list of source Builders for this Builder. |
+ |
+ This exists mainly to look up Builders referenced as |
+ strings in the 'BUILDER' variable of the construction |
+ environment and cache the result. |
+ """ |
+ memo_key = id(env) |
+ try: |
+ memo_dict = self._memo['get_src_builders'] |
+ except KeyError: |
+ memo_dict = {} |
+ self._memo['get_src_builders'] = memo_dict |
+ else: |
+ try: |
+ return memo_dict[memo_key] |
+ except KeyError: |
+ pass |
+ |
+ builders = [] |
+ for bld in self.src_builder: |
+ if SCons.Util.is_String(bld): |
+ try: |
+ bld = env['BUILDERS'][bld] |
+ except KeyError: |
+ continue |
+ builders.append(bld) |
+ |
+ memo_dict[memo_key] = builders |
+ return builders |
+ |
+ def _subst_src_suffixes_key(self, env): |
+ return id(env) |
+ |
+ memoizer_counters.append(SCons.Memoize.CountDict('subst_src_suffixes', _subst_src_suffixes_key)) |
+ |
+ def subst_src_suffixes(self, env): |
+ """ |
+ The suffix list may contain construction variable expansions, |
+ so we have to evaluate the individual strings. To avoid doing |
+ this over and over, we memoize the results for each construction |
+ environment. |
+ """ |
+ memo_key = id(env) |
+ try: |
+ memo_dict = self._memo['subst_src_suffixes'] |
+ except KeyError: |
+ memo_dict = {} |
+ self._memo['subst_src_suffixes'] = memo_dict |
+ else: |
+ try: |
+ return memo_dict[memo_key] |
+ except KeyError: |
+ pass |
+ suffixes = [env.subst(x) for x in self.src_suffix] |
+ memo_dict[memo_key] = suffixes |
+ return suffixes |
+ |
+ def src_suffixes(self, env): |
+ """ |
+ Returns the list of source suffixes for all src_builders of this |
+ Builder. |
+ |
+ This is essentially a recursive descent of the src_builder "tree." |
+ (This value isn't cached because there may be changes in a |
+ src_builder many levels deep that we can't see.) |
+ """ |
+ sdict = {} |
+ suffixes = self.subst_src_suffixes(env) |
+ for s in suffixes: |
+ sdict[s] = 1 |
+ for builder in self.get_src_builders(env): |
+ for s in builder.src_suffixes(env): |
+ if s not in sdict: |
+ sdict[s] = 1 |
+ suffixes.append(s) |
+ return suffixes |
+ |
+class CompositeBuilder(SCons.Util.Proxy): |
+ """A Builder Proxy whose main purpose is to always have |
+ a DictCmdGenerator as its action, and to provide access |
+ to the DictCmdGenerator's add_action() method. |
+ """ |
+ |
+ def __init__(self, builder, cmdgen): |
+ if __debug__: logInstanceCreation(self, 'Builder.CompositeBuilder') |
+ SCons.Util.Proxy.__init__(self, builder) |
+ |
+ # cmdgen should always be an instance of DictCmdGenerator. |
+ self.cmdgen = cmdgen |
+ self.builder = builder |
+ |
+ __call__ = SCons.Util.Delegate('__call__') |
+ |
+ def add_action(self, suffix, action): |
+ self.cmdgen.add_action(suffix, action) |
+ self.set_src_suffix(self.cmdgen.src_suffixes()) |
+ |
+def is_a_Builder(obj): |
+ """"Returns True iff the specified obj is one of our Builder classes. |
+ |
+ The test is complicated a bit by the fact that CompositeBuilder |
+ is a proxy, not a subclass of BuilderBase. |
+ """ |
+ return (isinstance(obj, BuilderBase) |
+ or isinstance(obj, CompositeBuilder) |
+ or callable(obj)) |
+ |
+# Local Variables: |
+# tab-width:4 |
+# indent-tabs-mode:nil |
+# End: |
+# vim: set expandtab tabstop=4 shiftwidth=4: |
Property changes on: scons-2.0.1/engine/SCons/Builder.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |