dive any deeper into this subsystem.
The base class here is BuilderBase. This is a concrete base class which
-does, in fact, represent most Builder objects that we (or users) create.
+does, in fact, represent the Builder objects that we (or users) create.
-There is (at present) one subclasses:
-
- MultiStepBuilder
-
- This is a Builder that knows how to "chain" Builders so that
- users can specify a source file that requires multiple steps
- to turn into a target file. A canonical example is building a
- program from yacc input file, which requires invoking a builder
- to turn the .y into a .c, the .c into a .o, and the .o into an
- executable program.
-
-There is also two proxies that look like Builders:
+There is also a proxy that looks like a Builder:
CompositeBuilder
(compilers, compile options) for different flavors of source
files.
- ListBuilder
-
- This proxies for a Builder *invocation* where the target
- is a list of files, not a single file.
-
Builders and their proxies have the following public interface methods
used by other modules:
variable. This also takes care of warning about possible mistakes
in keyword arguments.
- targets()
- Returns the list of targets for a specific builder instance.
-
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
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
_null = _Null
+def match_splitext(path, suffixes = []):
+ if suffixes:
+ matchsuf = filter(lambda S,path=path: path[-len(S):] == S,
+ suffixes)
+ if matchsuf:
+ suf = max(map(None, map(len, matchsuf), 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
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 self.keys()
self[suffix] = action
def __call__(self, target, source, env, for_signature):
- ext = None
- for src in map(str, source):
- my_ext = SCons.Util.splitext(src)[1]
- if ext and my_ext != ext:
- raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" % (repr(map(str, target)), src, ext, my_ext))
- ext = my_ext
+ 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(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(map(str, target)), repr(map(str, source))))
try:
- ret = SCons.Util.Selector.__call__(self, env, source)
+ ret = SCons.Util.Selector.__call__(self, env, source, ext)
except KeyError, e:
raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e[0], e[1], e[2]))
if ret is None:
- raise UserError("While building `%s': Don't know how to build a file with suffix `%s'." % (repr(map(str, target)), ext))
+ 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(map(str, target)), repr(map(str, source)), ext, repr(self.keys())))
return ret
class CallableSelector(SCons.Util.Selector):
"""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 MultiStepBuilder
- call can actually invoke multiple builders as a result of a single
- user-level Builder call. This class only emits the warnings once,
- no matter how many Builders are invoked.
+ 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):
UserDict.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():
- try:
+ if misleading_keywords.has_key(k):
alt = misleading_keywords[k]
- except KeyError:
- pass
- else:
- SCons.Warnings.warn(SCons.Warnings.MisleadingKeywordsWarning,
- "Did you mean to use `%s' instead of `%s'?" % (alt, 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):
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'])
+ kw['action'] = SCons.Action.CommandGeneratorAction(kw['generator'], {})
del kw['generator']
- elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
- composite = DictCmdGenerator(kw['action'])
- kw['action'] = SCons.Action.CommandGenerator(composite)
- kw['src_suffix'] = composite.src_suffixes()
+ elif kw.has_key('action'):
+ source_ext_match = kw.get('source_ext_match', 1)
+ if kw.has_key('source_ext_match'):
+ 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 kw.has_key('emitter'):
emitter = kw['emitter']
elif SCons.Util.is_List(emitter):
kw['emitter'] = ListEmitter(emitter)
- if kw.has_key('src_builder'):
- ret = apply(MultiStepBuilder, (), kw)
- else:
- ret = apply(BuilderBase, (), kw)
+ result = apply(BuilderBase, (), kw)
if not composite is None:
- ret = CompositeBuilder(ret, composite)
+ result = CompositeBuilder(result, composite)
- return ret
+ return result
-def _init_nodes(builder, env, overrides, executor_kw, tlist, slist):
- """Initialize lists of target and source nodes with all of
- the proper Builder information.
+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" % str(t)
+ 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:
- t_contents = t.builder.action.get_contents(tlist, slist, t.env)
- contents = t.builder.action.get_contents(tlist, slist, 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:
- SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning,
- "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s"%(str(t), t.builder.action.genstring(tlist, slist, t.env)))
-
+ 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:
- raise UserError, "Two environments with different actions were specified for the same target: %s"%str(t)
-
- elif t.overrides != overrides:
- raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
-
- elif builder.target_scanner and t.target_scanner and builder.target_scanner != t.target_scanner:
- raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
-
+ msg = "Two environments with different actions were specified for the same target: %s" % t
+ raise UserError, msg
if builder.multi:
if t.builder != builder:
- if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
- raise UserError, "Two different target sets have a target in common: %s"%str(t)
- else:
- raise UserError, "Two different builders (%s and %s) were specified for the same target: %s"%(t.builder.get_name(env), builder.get_name(env), str(t))
- elif isinstance(t.builder, ListBuilder) ^ isinstance(builder, ListBuilder):
- raise UserError, "Cannot build same target `%s' as singular and list"%str(t)
+ 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, map(str, t.get_executor().get_all_targets()), map(str, tlist))
+ raise UserError, msg
elif t.sources != slist:
- raise UserError, "Multiple ways to build the same target were specified for: %s (from %s and from %s)" % (str(t), map(str,t.sources), map(str,slist))
+ msg = "Multiple ways to build the same target were specified for: %s (from %s and from %s)" % (t, map(str, t.sources), 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" % (map(str,tlist), map(str,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
- if builder.multi:
- try:
- executor = tlist[0].get_executor(create = 0)
- except AttributeError:
- pass
- else:
- executor.add_sources(slist)
- if executor is None:
- if not builder.action:
- raise UserError, "Builder %s must have an action to build %s."%(builder.get_name(env or builder.env), map(str,tlist))
- executor = SCons.Executor.Executor(builder.action,
- env or builder.env,
- [builder.overrides, overrides],
- tlist,
- slist,
- executor_kw)
-
- # Now set up the relevant information in the target Nodes themselves.
- for t in tlist:
- t.overrides = overrides
- t.cwd = SCons.Node.FS.default_fs.getcwd()
- t.builder_set(builder)
- t.env_set(env)
- t.add_source(slist)
- t.set_executor(executor)
- if builder.target_scanner:
- t.target_scanner = builder.target_scanner
- if t.source_scanner is None:
- t.source_scanner = builder.source_scanner
-
- # Add backup source scanners from the environment to the source
- # nodes. This may not be necessary if the node will have a real
- # source scanner added later (which is why these are the "backup"
- # source scanners, not the real ones), but because source nodes may
- # be used multiple times for different targets, it ends up being
- # more efficient to do this calculation once here, as opposed to
- # delaying it until later when we potentially have to calculate it
- # over and over and over.
- for s in slist:
- if s.source_scanner is None and s.backup_source_scanner is None:
- s.backup_source_scanner = env.get_scanner(s.scanner_key())
-
class EmitterProxy:
"""This is a callable class that can act as a
Builder emitter. It holds on to a string that
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 = SCons.Node.FS.default_fs.File,
- source_factory = SCons.Node.FS.default_fs.File,
+ target_factory = None,
+ source_factory = None,
target_scanner = None,
source_scanner = None,
emitter = None,
name = None,
chdir = _null,
is_explicit = 1,
+ src_builder = None,
+ ensure_suffix = False,
**overrides):
- if __debug__: logInstanceCreation(self, 'BuilderBase')
- self.action = SCons.Action.Action(action)
+ 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.suffix = suffix
self.env = env
self.single_source = single_source
if overrides.has_key('overrides'):
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.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"
try:
index = env['BUILDERS'].values().index(self)
return env['BUILDERS'].keys()[index]
- except (AttributeError, KeyError, ValueError):
+ except (AttributeError, KeyError, TypeError, ValueError):
try:
return self.name
except AttributeError:
if not env:
env = self.env
if env:
- matchsuf = filter(lambda S,path=path: path[-len(S):] == S,
- self.src_suffixes(env))
- if matchsuf:
- suf = max(map(None, map(len, matchsuf), matchsuf))[1]
- return [path[:-len(suf)], path[-len(suf):]]
- return SCons.Util.splitext(path)
-
- def _create_nodes(self, env, overwarn, target = None, source = None):
+ 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.
"""
- def _adjustixes(files, pre, suf):
- 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)
- result.append(f)
- return result
-
- overwarn.warn()
-
- env = env.Override(overwarn.data)
-
src_suf = self.get_src_suffix(env)
- source = _adjustixes(source, None, src_suf)
- slist = env.arg2nodes(source, self.source_factory)
+ 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)
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])
- splitext = lambda S,self=self,env=env: self.splitext(S,env)
- tlist = [ t_from_s(pre, suf, splitext) ]
+ except IndexError:
+ tlist = []
+ else:
+ splitext = lambda S,self=self,env=env: self.splitext(S,env)
+ tlist = [ t_from_s(pre, suf, splitext) ]
else:
- target = _adjustixes(target, pre, suf)
- tlist = env.arg2nodes(target, self.target_factory)
+ 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
new_targets = []
for t in tlist:
if not t.is_derived():
- t.builder = self
+ 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 _init_nodes() doesn't do weird stuff
+ # 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 = None
+ 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.
- slist = env.arg2nodes(source, self.source_factory)
- tlist = env.arg2nodes(target, self.target_factory)
+ 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=None, source=_null, overwarn={}, executor_kw={}):
- if source is _null:
- source = target
- target = None
+ 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
- SCons.Util.is_List(source) and
- len(source) > 1 and
- target is None):
+ if self.single_source and len(source) > 1 and target is None:
result = []
if target is None: target = [None]*len(source)
- for k in range(len(source)):
- t = self._execute(env, target[k], source[k], overwarn)
- if SCons.Util.is_List(t):
- result.extend(t)
- else:
- result.append(t)
- return result
-
- tlist, slist = self._create_nodes(env, overwarn, target, 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)
- if len(tlist) == 1:
- builder = self
- else:
- builder = ListBuilder(self, env, tlist)
- _init_nodes(builder, env, overwarn.data, executor_kw, tlist, slist)
+ 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.
- return tlist
+ executor = None
+ key = None
- def __call__(self, env, target=None, source=_null, chdir=_null, **kw):
+ 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),
+ 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 kw.has_key('srcdir'):
+ 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 = 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):
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)
- else:
- suffix = self.adjust_suffix(suffix)
return env.subst(suffix)
- def src_suffixes(self, env):
- return map(lambda x, s=self, e=env: e.subst(s.adjust_suffix(x)),
- self.src_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 = src_suffix
+ adjust = lambda suf, s=self: \
+ callable(suf) and suf or s.adjust_suffix(suf)
+ self.src_suffix = map(adjust, src_suffix)
def get_src_suffix(self, env):
"""Get the first src_suffix in the list of src_suffixes."""
return ''
return ret[0]
- def targets(self, node):
- """Return the list of targets for this builder instance.
-
- For most normal builders, this is just the supplied node.
- """
- return [ node ]
-
def add_emitter(self, suffix, emitter):
"""Add a suffix-emitter mapping to this Builder.
"""
self.emitter[suffix] = emitter
-class ListBuilder(SCons.Util.Proxy):
- """A Proxy to support building an array of targets (for example,
- foo.o and foo.h from foo.y) from a single Action execution.
- """
+ def add_src_builder(self, builder):
+ """
+ Add a new Builder to the list of src_builders.
- def __init__(self, builder, env, tlist):
- if __debug__: logInstanceCreation(self)
- SCons.Util.Proxy.__init__(self, builder)
- self.builder = builder
- self.target_scanner = builder.target_scanner
- self.source_scanner = builder.source_scanner
- self.env = env
- self.tlist = tlist
- self.multi = builder.multi
- self.single_source = builder.single_source
+ 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 targets(self, node):
- """Return the list of targets for this builder instance.
+ def _get_sdict(self, env):
"""
- return self.tlist
+ 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 get_name(self, env):
- """Attempts to get the name of the Builder."""
+ def src_builder_sources(self, env, source, overwarn={}):
+ sdict = self._get_sdict(env)
- return "ListBuilder(%s)" % self.builder.get_name(env)
+ src_suffixes = self.src_suffixes(env)
-class MultiStepBuilder(BuilderBase):
- """This is a builder subclass that can build targets in
- multiple steps. The src_builder parameter to the constructor
- accepts a builder that is called to build sources supplied to
- this builder. The targets of that first build then become
- the sources of this builder.
+ lengths = list(set(map(len, src_suffixes)))
+
+ def match_src_suffix(name, src_suffixes=src_suffixes, lengths=lengths):
+ node_suffixes = map(lambda l, n=name: n[-l:], 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:
+ mss = lambda t, m=match_src_suffix: m(t.name)
+ tlist = filter(mss, tlist)
+ result.extend(tlist)
+ else:
+ result.append(s)
- If this builder has a src_suffix supplied, then the src_builder
- builder is NOT invoked if the suffix of a source file matches
- src_suffix.
- """
- def __init__(self, src_builder,
- action = None,
- prefix = '',
- suffix = '',
- src_suffix = '',
- target_factory = SCons.Node.FS.default_fs.File,
- source_factory = SCons.Node.FS.default_fs.File,
- target_scanner = None,
- source_scanner = None,
- emitter=None,
- single_source=0):
- if __debug__: logInstanceCreation(self)
- BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
- target_factory, source_factory,
- target_scanner, source_scanner, emitter,
- single_source = single_source)
- if not SCons.Util.is_List(src_builder):
- src_builder = [ src_builder ]
- self.src_builder = src_builder
- self.sdict = {}
- self.cached_src_suffixes = {} # source suffixes keyed on id(env)
+ source_factory = env.get_factory(self.source_factory)
- def _execute(self, env, target = None, source = _null, overwarn={}, executor_kw={}):
- if source is _null:
- source = target
- target = None
+ return env.arg2nodes(result, source_factory)
- slist = env.arg2nodes(source, self.source_factory)
- final_sources = []
+ def _get_src_builders_key(self, env):
+ return id(env)
- try:
- sdict = self.sdict[id(env)]
- except KeyError:
- sdict = {}
- self.sdict[id(env)] = sdict
- for bld in self.src_builder:
- if SCons.Util.is_String(bld):
- try:
- bld = env['BUILDERS'][bld]
- except KeyError:
- continue
- for suf in bld.src_suffixes(env):
- sdict[suf] = bld
-
- src_suffixes = self.src_suffixes(env)
-
- for snode in slist:
- for srcsuf in src_suffixes:
- if str(snode)[-len(srcsuf):] == srcsuf and sdict.has_key(srcsuf):
- tgt = sdict[srcsuf]._execute(env, None, snode, 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(tgt) > 1:
- tgt = filter(lambda x, self=self, suf=src_suffixes, e=env:
- self.splitext(SCons.Util.to_String(x),e)[1] in suf,
- tgt)
- final_sources.extend(tgt)
- snode = None
- break
- if snode:
- final_sources.append(snode)
-
- return BuilderBase._execute(self, env, target, final_sources, overwarn)
+ memoizer_counters.append(SCons.Memoize.CountDict('get_src_builders', _get_src_builders_key))
def get_src_builders(self, env):
- """Return all the src_builders for this Builder.
+ """
+ Returns the list of source Builders for this Builder.
- This is essentially a recursive descent of the src_builder "tree."
+ This exists mainly to look up Builders referenced as
+ strings in the 'BUILDER' variable of the construction
+ environment and cache the result.
"""
- ret = []
+ 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):
- # All Environments should have a BUILDERS
- # variable, so no need to check for it.
try:
bld = env['BUILDERS'][bld]
except KeyError:
continue
- ret.append(bld)
- return ret
+ builders.append(bld)
- def src_suffixes(self, env):
- """Return a list of the src_suffix attributes for all
- src_builders of this Builder.
+ 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:
- return self.cached_src_suffixes[id(env)]
+ memo_dict = self._memo['subst_src_suffixes']
except KeyError:
- suffixes = BuilderBase.src_suffixes(self, env)
- for builder in self.get_src_builders(env):
- suffixes.extend(builder.src_suffixes(env))
- self.cached_src_suffixes[id(env)] = suffixes
- return suffixes
+ memo_dict = {}
+ self._memo['subst_src_suffixes'] = memo_dict
+ else:
+ try:
+ return memo_dict[memo_key]
+ except KeyError:
+ pass
+ suffixes = map(lambda x, s=self, e=env: e.subst(x), 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 not sdict.has_key(s):
+ sdict[s] = 1
+ suffixes.append(s)
+ return suffixes
class CompositeBuilder(SCons.Util.Proxy):
"""A Builder Proxy whose main purpose is to always have
"""
def __init__(self, builder, cmdgen):
- if __debug__: logInstanceCreation(self)
+ if __debug__: logInstanceCreation(self, 'Builder.CompositeBuilder')
SCons.Util.Proxy.__init__(self, builder)
# cmdgen should always be an instance of DictCmdGenerator.
def add_action(self, suffix, action):
self.cmdgen.add_action(suffix, action)
self.set_src_suffix(self.cmdgen.src_suffixes())
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: