"""SCons.Builder
-XXX
+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 most 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:
+
+ 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.
+
+ 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:
+
+ __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.
+
+ 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
+ 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 Steven Knight
+# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
-
-import os.path
-import string
-from Errors import UserError
+import UserDict
+import UserList
import SCons.Action
+from SCons.Debug import logInstanceCreation
+from SCons.Errors import InternalError, UserError
+import SCons.Executor
+import SCons.Node
import SCons.Node.FS
import SCons.Util
+import SCons.Warnings
+
+class _Null:
+ pass
+
+_null = _Null
+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 src_suffixes(self):
+ return 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):
+ 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 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)
+ 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))
+ 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(UserList.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(UserDict.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 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.
+ """
+ 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:
+ 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))
+ self.already_warned = 1
def Builder(**kw):
"""A factory for builder objects."""
-
+ composite = None
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'):
- return apply(MultiStepBuilder, (), kw)
+ 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()
+
+ if kw.has_key('emitter'):
+ 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)
+
+ if kw.has_key('src_builder'):
+ ret = apply(MultiStepBuilder, (), kw)
else:
- return apply(BuilderBase, (), kw)
+ ret = apply(BuilderBase, (), kw)
+ if not composite is None:
+ ret = CompositeBuilder(ret, composite)
+ return ret
-def _init_nodes(builder, env, tlist, slist):
+def _init_nodes(builder, env, overrides, executor_kw, tlist, slist):
"""Initialize lists of target and source nodes with all of
the proper Builder information.
"""
+
+ # 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)
+ 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)
+
+ 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)))
+
+ else:
+ raise UserError, "Two environments with different actions were specified for the same target: %s"%str(t)
+
+ 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)
+ 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))
+
+ 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,
+ [], # env already has overrides
+ tlist,
+ slist,
+ executor_kw)
+
+ # Now set up the relevant information in the target Nodes themselves.
for t in tlist:
- t.cwd = SCons.Node.FS.default_fs.getcwd() # XXX
+ t.cwd = SCons.Node.FS.default_fs.getcwd()
t.builder_set(builder)
t.env_set(env)
t.add_source(slist)
- if builder.scanner:
- t.scanner_set(builder.scanner.instance(env))
-
- for s in slist:
- s.env_set(env, 1)
- scanner = env.get_scanner(os.path.splitext(s.name)[1])
- if scanner:
- s.scanner_set(scanner.instance(env))
+ t.set_executor(executor)
+ t.set_explicit(builder.is_explicit)
+
+class EmitterProxy:
+ """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 env.has_key(emitter):
+ 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:
"""Base class for Builders, objects that create output
nodes (files) from input nodes (files).
"""
- def __init__(self, name = None,
- action = None,
- prefix = '',
- suffix = '',
- src_suffix = '',
- node_factory = SCons.Node.FS.default_fs.File,
- scanner = 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
- self.node_factory = 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
+ __metaclass__ = SCons.Memoize.Memoized_Metaclass
+
+ 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_scanner = None,
+ source_scanner = None,
+ emitter = None,
+ multi = 0,
+ env = None,
+ single_source = 0,
+ name = None,
+ chdir = _null,
+ is_explicit = 1,
+ **overrides):
+ if __debug__: logInstanceCreation(self, 'Builder.BuilderBase')
+ self.action = SCons.Action.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'):
+ SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
+ "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 overrides.has_key('scanner'):
+ SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
+ "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_src_suffix(src_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
+
+ 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 = env['BUILDERS'].values().index(self)
+ return env['BUILDERS'].keys()[index]
+ except (AttributeError, KeyError, ValueError):
+ try:
+ return self.name
+ except AttributeError:
+ return str(self.__class__)
def __cmp__(self, other):
- return cmp(self.__dict__, other.__dict__)
-
- def _create_nodes(self, env, target = None, source = None):
+ return cmp(self.__dict__, other.__dict__)
+
+ def splitext(self, path, env=None):
+ 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):
"""Create and return lists of target and source nodes.
"""
- def adjustixes(files, pre, suf):
- ret = []
- if SCons.Util.is_String(files):
- files = string.split(files)
+ def _adjustixes(files, pre, suf):
+ if not files:
+ return []
+ result = []
if not SCons.Util.is_List(files):
- files = [files]
- for f in files:
+ files = [files]
+
+ for f in files:
if SCons.Util.is_String(f):
- 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
-
- tlist = SCons.Util.scons_str2nodes(adjustixes(target,
- env.subst(self.prefix),
- env.subst(self.suffix)),
- self.node_factory)
-
- slist = SCons.Util.scons_str2nodes(adjustixes(source,
- None,
- env.subst(self.src_suffix)),
- self.node_factory)
- return tlist, slist
+ f = SCons.Util.adjustixes(f, pre, suf)
+ result.append(f)
+ return result
- def __call__(self, env, target = None, source = None):
- tlist, slist = self._create_nodes(env, target, source)
+ overwarn.warn()
- if len(tlist) == 1:
- _init_nodes(self, env, tlist, slist)
- tlist = tlist[0]
+ src_suf = self.get_src_suffix(env)
+
+ source = _adjustixes(source, None, src_suf)
+ slist = env.arg2nodes(source, self.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])
+ splitext = lambda S,self=self,env=env: self.splitext(S,env)
+ tlist = [ t_from_s(pre, suf, splitext) ]
else:
- _init_nodes(ListBuilder(self, env, tlist), env, tlist, slist)
+ target = _adjustixes(target, pre, suf)
+ tlist = env.arg2nodes(target, self.target_factory)
+
+ 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)
+
+ 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
+ # 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.
+ slist = env.arg2nodes(source, self.source_factory)
+ tlist = env.arg2nodes(target, self.target_factory)
- return tlist
+ 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.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 result
+
+ tlist, slist = self._create_nodes(env, overwarn, target, source)
- def execute(self, **kw):
- """Execute a builder's action to create an output object.
- """
- return apply(self.action.execute, (), kw)
+ if len(tlist) == 1:
+ builder = self
+ else:
+ builder = ListBuilder(self, env, tlist)
+ _init_nodes(builder, env, overwarn.data, executor_kw, tlist, slist)
- def get_raw_contents(self, **kw):
- """Fetch the "contents" of the builder's action.
- """
- return apply(self.action.get_raw_contents, (), kw)
+ return SCons.Node.NodeList(tlist)
- def get_contents(self, **kw):
- """Fetch the "contents" of the builder's action
- (for signature calculation).
- """
- return apply(self.action.get_contents, (), kw)
+ 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 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 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):
- if self.src_suffix != '':
- return [env.subst(self.src_suffix)]
- return []
+ 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
+
+ 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 targets(self, node):
"""Return the list of targets for this builder instance.
"""
return [ node ]
-class ListBuilder:
- """This is technically not a Builder object, but a wrapper
- around another Builder object. This is designed to look
- like a Builder object, though, for purposes of building an
- array of targets from a single Action execution.
+ 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
+
+if not SCons.Memoize.has_metaclass:
+ _Base = BuilderBase
+ class BuilderBase(SCons.Memoize.Memoizer, _Base):
+ "Cache-backed version of BuilderBase"
+ def __init__(self, *args, **kw):
+ apply(_Base.__init__, (self,)+args, kw)
+ SCons.Memoize.Memoizer.__init__(self)
+
+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 __init__(self, builder, env, tlist):
+ if __debug__: logInstanceCreation(self, 'Builder.ListBuilder')
+ SCons.Util.Proxy.__init__(self, builder)
self.builder = builder
- self.scanner = builder.scanner
+ self.target_scanner = builder.target_scanner
+ self.source_scanner = builder.source_scanner
self.env = env
self.tlist = tlist
-
- def execute(self, **kw):
- if hasattr(self, 'status'):
- return self.status
- for t in self.tlist:
- # unlink all targets and make all directories
- # before building anything
- t.prepare()
- kw['target'] = self.tlist[0]
- self.status = apply(self.builder.execute, (), kw)
- for t in self.tlist:
- if not t is kw['target']:
- t.build()
- return self.status
-
- def get_raw_contents(self, **kw):
- return apply(self.builder.get_raw_contents, (), kw)
-
- def get_contents(self, **kw):
- return apply(self.builder.get_contents, (), kw)
-
- def src_suffixes(self, env):
- return self.builder.src_suffixes(env)
+ self.multi = builder.multi
+ self.single_source = builder.single_source
def targets(self, node):
"""Return the list of targets for this builder instance.
"""
return self.tlist
+ def get_name(self, env):
+ """Attempts to get the name of the Builder."""
+
+ return "ListBuilder(%s)" % self.builder.get_name(env)
+
class MultiStepBuilder(BuilderBase):
"""This is a builder subclass that can build targets in
multiple steps. The src_builder parameter to the constructor
src_suffix.
"""
def __init__(self, src_builder,
- name = None,
- action = None,
- prefix = '',
- suffix = '',
- src_suffix = '',
- node_factory = SCons.Node.FS.default_fs.File,
- scanner=None):
- BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
- node_factory, scanner)
+ 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, 'Builder.MultiStepBuilder')
+ 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
- def __call__(self, env, target = None, source = None):
- slist = SCons.Util.scons_str2nodes(source, self.node_factory)
- final_sources = []
- src_suffix = env.subst(self.src_suffix)
+ def _get_sdict(self, env):
+ "__cacheable__"
sdict = {}
- for suff in self.src_builder.src_suffixes(env):
- sdict[suff] = None
+ 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
+ return sdict
+
+ def _execute(self, env, target, source, overwarn={}, executor_kw={}):
+ # We now assume that target and source are lists or None.
+ slist = env.arg2nodes(source, self.source_factory)
+ final_sources = []
+
+ sdict = self._get_sdict(env)
+
+ src_suffixes = self.src_suffixes(env)
+
+ def match_src_suffix(node, src_suffixes=src_suffixes):
+ # This reaches directly into the Node.name attribute (instead
+ # of using an accessor function) for performance reasons.
+ return filter(lambda s, n=node.name:
+ n[-len(s):] == s,
+ src_suffixes)
+
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)
- if not SCons.Util.is_List(tgt):
- final_sources.append(tgt)
+ name = snode.name
+ match = match_src_suffix(snode)
+ if match:
+ try:
+ bld = sdict[match[0]]
+ except KeyError:
+ final_sources.append(snode)
else:
- final_sources.extend(tgt)
+ tlist = bld._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(tlist) > 1:
+ tlist = filter(match_src_suffix, tlist)
+ final_sources.extend(tlist)
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)
+ return BuilderBase._execute(self, env, target, final_sources, overwarn)
- if len(tlist) == 1:
- tlist = tlist[0]
- return tlist
+ def get_src_builders(self, env):
+ """Return all the src_builders for this Builder.
+
+ This is essentially a recursive descent of the src_builder "tree."
+ """
+ ret = []
+ 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
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 a list of the src_suffix attributes for all
+ src_builders of this Builder.
+ __cacheable__
+ """
+ suffixes = BuilderBase.src_suffixes(self, env)
+ for builder in self.get_src_builders(env):
+ suffixes.extend(builder.src_suffixes(env))
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
+
+ def add_action(self, suffix, action):
+ self.cmdgen.add_action(suffix, action)
+ self.set_src_suffix(self.cmdgen.src_suffixes())