3 Builder object subsystem.
5 A Builder object is a callable that encapsulates information about how
6 to execute actions to create a Node (file) from other Nodes (files), and
7 how to create those dependencies for tracking.
9 The main entry point here is the Builder() factory method. This
10 provides a procedural interface that creates the right underlying
11 Builder object based on the keyword arguments supplied and the types of
14 The goal is for this external interface to be simple enough that the
15 vast majority of users can create new Builders as necessary to support
16 building new types of files in their configurations, without having to
17 dive any deeper into this subsystem.
24 # Permission is hereby granted, free of charge, to any person obtaining
25 # a copy of this software and associated documentation files (the
26 # "Software"), to deal in the Software without restriction, including
27 # without limitation the rights to use, copy, modify, merge, publish,
28 # distribute, sublicense, and/or sell copies of the Software, and to
29 # permit persons to whom the Software is furnished to do so, subject to
30 # the following conditions:
32 # The above copyright notice and this permission notice shall be included
33 # in all copies or substantial portions of the Software.
35 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
36 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
37 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
38 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
39 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
40 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
41 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
44 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
50 from SCons.Debug import logInstanceCreation
51 from SCons.Errors import InternalError, UserError
62 class DictCmdGenerator(SCons.Util.Selector):
63 """This is a callable class that can be used as a
64 command generator function. It holds on to a dictionary
65 mapping file suffixes to Actions. It uses that dictionary
66 to return the proper action based on the file suffix of
69 def src_suffixes(self):
72 def add_action(self, suffix, action):
73 """Add a suffix-action pair to the mapping.
77 def __call__(self, target, source, env, for_signature):
79 for src in map(str, source):
80 my_ext = SCons.Util.splitext(src)[1]
81 if ext and my_ext != ext:
82 raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" % (repr(map(str, target)), src, ext, my_ext))
86 raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source))))
89 ret = SCons.Util.Selector.__call__(self, env, source)
91 raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e[0], e[1], e[2]))
93 raise UserError("While building `%s': Don't know how to build a file with suffix `%s'." % (repr(map(str, target)), ext))
96 class CallableSelector(SCons.Util.Selector):
97 """A callable dictionary that will, in turn, call the value it
99 def __call__(self, env, source):
100 value = SCons.Util.Selector.__call__(self, env, source)
102 value = value(env, source)
105 class DictEmitter(SCons.Util.Selector):
106 """A callable dictionary that maps file suffixes to emitters.
107 When called, it finds the right emitter in its dictionary for the
108 suffix of the first source file, and calls that emitter to get the
109 right lists of targets and sources to return. If there's no emitter
110 for the suffix in its dictionary, the original target and source are
113 def __call__(self, target, source, env):
114 emitter = SCons.Util.Selector.__call__(self, env, source)
116 target, source = emitter(target, source, env)
117 return (target, source)
120 """A factory for builder objects."""
122 if kw.has_key('generator'):
123 if kw.has_key('action'):
124 raise UserError, "You must not specify both an action and a generator."
125 kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
127 elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
128 composite = DictCmdGenerator(kw['action'])
129 kw['action'] = SCons.Action.CommandGenerator(composite)
130 kw['src_suffix'] = composite.src_suffixes()
132 if kw.has_key('emitter'):
133 emitter = kw['emitter']
134 if SCons.Util.is_String(emitter):
135 # This allows users to pass in an Environment
136 # variable reference (like "$FOO") as an emitter.
137 # We will look in that Environment variable for
138 # a callable to use as the actual emitter.
139 var = SCons.Util.get_environment_var(emitter)
141 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter
142 kw['emitter'] = EmitterProxy(var)
143 elif SCons.Util.is_Dict(emitter):
144 kw['emitter'] = DictEmitter(emitter)
146 if kw.has_key('src_builder'):
147 ret = apply(MultiStepBuilder, (), kw)
149 ret = apply(BuilderBase, (), kw)
151 if not composite is None:
152 ret = CompositeBuilder(ret, composite)
156 def _init_nodes(builder, env, overrides, tlist, slist):
157 """Initialize lists of target and source nodes with all of
158 the proper Builder information.
161 # First, figure out if there are any errors in the way the targets
165 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
168 t_contents = t.builder.action.get_contents(tlist, slist, t.env)
169 contents = t.builder.action.get_contents(tlist, slist, env)
171 if t_contents == contents:
172 SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning,
173 "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s"%(str(t), t.builder.action.strfunction(tlist, slist, t.env)))
176 raise UserError, "Two environments with different actions were specified for the same target: %s"%str(t)
178 elif t.overrides != overrides:
179 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
181 elif builder.scanner and t.target_scanner and builder.scanner != t.target_scanner:
182 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
185 if t.builder != builder:
186 if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
187 raise UserError, "Two different target sets have a target in common: %s"%str(t)
189 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))
190 elif t.sources != slist:
191 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
193 # The targets are fine, so find or make the appropriate Executor to
194 # build this particular list of targets from this particular list of
199 executor = tlist[0].get_executor(create = 0)
200 except AttributeError:
203 executor.add_sources(slist)
205 executor = SCons.Executor.Executor(builder,
206 tlist[0].generate_build_env(env),
211 # Now set up the relevant information in the target Nodes themselves.
213 t.overrides = overrides
214 t.cwd = SCons.Node.FS.default_fs.getcwd()
215 t.builder_set(builder)
218 t.set_executor(executor)
220 t.target_scanner = builder.scanner
221 if not t.source_scanner:
222 t.source_scanner = env.get_scanner(t.scanner_key())
224 # Last, add scanners from the Environment to the source Nodes.
226 if not s.source_scanner:
227 s.source_scanner = env.get_scanner(s.scanner_key())
230 """This is a callable class that can act as a
231 Builder emitter. It holds on to a string that
232 is a key into an Environment dictionary, and will
233 look there at actual build time to see if it holds
234 a callable. If so, we will call that as the actual
236 def __init__(self, var):
237 self.var = SCons.Util.to_String(var)
239 def __call__(self, target, source, env):
242 # Recursively substitute the variable.
243 # We can't use env.subst() because it deals only
244 # in strings. Maybe we should change that?
245 while SCons.Util.is_String(emitter) and \
246 env.has_key(emitter):
247 emitter = env[emitter]
248 if not callable(emitter):
249 return (target, source)
251 return emitter(target, source, env)
253 def __cmp__(self, other):
254 return cmp(self.var, other.var)
257 """Base class for Builders, objects that create output
258 nodes (files) from input nodes (files).
261 def __init__(self, action = None,
265 node_factory = SCons.Node.FS.default_fs.File,
266 target_factory = None,
267 source_factory = None,
273 if __debug__: logInstanceCreation(self, 'BuilderBase')
274 self.action = SCons.Action.Action(action)
276 if SCons.Util.is_Dict(prefix):
277 prefix = CallableSelector(prefix)
279 if SCons.Util.is_Dict(suffix):
280 suffix = CallableSelector(suffix)
283 self.overrides = overrides
285 self.set_src_suffix(src_suffix)
287 self.target_factory = target_factory or node_factory
288 self.source_factory = source_factory or node_factory
289 self.scanner = scanner
291 self.emitter = emitter
293 def __nonzero__(self):
294 raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
296 def get_name(self, env):
297 """Attempts to get the name of the Builder.
299 Look at the BUILDERS variable of env, expecting it to be a
300 dictionary containing this Builder, and return the key of the
304 index = env['BUILDERS'].values().index(self)
305 return env['BUILDERS'].keys()[index]
306 except (AttributeError, KeyError, ValueError):
307 return str(self.__class__)
309 def __cmp__(self, other):
310 return cmp(self.__dict__, other.__dict__)
312 def splitext(self, path):
313 return SCons.Util.splitext(path)
315 def _create_nodes(self, env, overrides, target = None, source = None):
316 """Create and return lists of target and source nodes.
318 def _adjustixes(files, pre, suf):
322 if not SCons.Util.is_List(files):
326 if SCons.Util.is_String(f):
327 f = SCons.Util.adjustixes(f, pre, suf)
331 env = env.Override(overrides)
333 src_suf = self.get_src_suffix(env)
335 source = _adjustixes(source, None, src_suf)
336 slist = env.arg2nodes(source, self.source_factory)
338 pre = self.get_prefix(env, slist)
339 suf = self.get_suffix(env, slist)
343 t_from_s = slist[0].target_from_source
344 except AttributeError:
345 raise UserError("Do not know how to create a target from source `%s'" % slist[0])
346 tlist = [ t_from_s(pre, suf, self.splitext) ]
348 target = _adjustixes(target, pre, suf)
349 tlist = env.arg2nodes(target, self.target_factory)
352 # The emitter is going to do str(node), but because we're
353 # being called *from* a builder invocation, the new targets
354 # don't yet have a builder set on them and will look like
355 # source files. Fool the emitter's str() calls by setting
356 # up a temporary builder on the new targets.
359 if not t.is_derived():
361 new_targets.append(t)
363 target, source = self.emitter(target=tlist, source=slist, env=env)
365 # Now delete the temporary builders that we attached to any
366 # new targets, so that _init_nodes() doesn't do weird stuff
367 # to them because it thinks they already have builders.
368 for t in new_targets:
369 if t.builder is self:
370 # Only delete the temporary builder if the emitter
371 # didn't change it on us.
374 # Have to call arg2nodes yet again, since it is legal for
375 # emitters to spit out strings as well as Node instances.
376 slist = env.arg2nodes(source, self.source_factory)
377 tlist = env.arg2nodes(target, self.target_factory)
381 def __call__(self, env, target = None, source = _null, **overrides):
385 tlist, slist = self._create_nodes(env, overrides, target, source)
388 _init_nodes(self, env, overrides, tlist, slist)
391 _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
395 def adjust_suffix(self, suff):
396 if suff and not suff[0] in [ '.', '_', '$' ]:
400 def get_prefix(self, env, sources=[]):
403 prefix = prefix(env, sources)
404 return env.subst(prefix)
406 def get_suffix(self, env, sources=[]):
409 suffix = suffix(env, sources)
411 suffix = self.adjust_suffix(suffix)
412 return env.subst(suffix)
414 def src_suffixes(self, env):
415 return map(lambda x, s=self, e=env: e.subst(s.adjust_suffix(x)),
418 def set_src_suffix(self, src_suffix):
421 elif not SCons.Util.is_List(src_suffix):
422 src_suffix = [ src_suffix ]
423 self.src_suffix = src_suffix
425 def get_src_suffix(self, env):
426 """Get the first src_suffix in the list of src_suffixes."""
427 ret = self.src_suffixes(env)
432 def targets(self, node):
433 """Return the list of targets for this builder instance.
435 For most normal builders, this is just the supplied node.
439 def add_emitter(self, suffix, emitter):
440 """Add a suffix-emitter mapping to this Builder.
442 This assumes that emitter has been initialized with an
443 appropriate dictionary type, and will throw a TypeError if
444 not, so the caller is responsible for knowing that this is an
445 appropriate method to call for the Builder in question.
447 self.emitter[suffix] = emitter
449 class ListBuilder(SCons.Util.Proxy):
450 """A Proxy to support building an array of targets (for example,
451 foo.o and foo.h from foo.y) from a single Action execution.
454 def __init__(self, builder, env, tlist):
455 if __debug__: logInstanceCreation(self)
456 SCons.Util.Proxy.__init__(self, builder)
457 self.builder = builder
458 self.scanner = builder.scanner
461 self.multi = builder.multi
463 def targets(self, node):
464 """Return the list of targets for this builder instance.
468 def __cmp__(self, other):
469 return cmp(self.__dict__, other.__dict__)
471 def get_name(self, env):
472 """Attempts to get the name of the Builder."""
474 return "ListBuilder(%s)" % self.builder.get_name(env)
476 class MultiStepBuilder(BuilderBase):
477 """This is a builder subclass that can build targets in
478 multiple steps. The src_builder parameter to the constructor
479 accepts a builder that is called to build sources supplied to
480 this builder. The targets of that first build then become
481 the sources of this builder.
483 If this builder has a src_suffix supplied, then the src_builder
484 builder is NOT invoked if the suffix of a source file matches
487 def __init__(self, src_builder,
492 node_factory = SCons.Node.FS.default_fs.File,
493 target_factory = None,
494 source_factory = None,
497 if __debug__: logInstanceCreation(self)
498 BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
499 node_factory, target_factory, source_factory,
501 if not SCons.Util.is_List(src_builder):
502 src_builder = [ src_builder ]
503 self.src_builder = src_builder
505 self.cached_src_suffixes = {} # source suffixes keyed on id(env)
507 def __call__(self, env, target = None, source = _null, **kw):
512 slist = env.arg2nodes(source, self.source_factory)
516 sdict = self.sdict[id(env)]
519 self.sdict[id(env)] = sdict
520 for bld in self.src_builder:
521 if SCons.Util.is_String(bld):
523 bld = env['BUILDERS'][bld]
526 for suf in bld.src_suffixes(env):
529 src_suffixes = self.src_suffixes(env)
532 base, ext = self.splitext(str(snode))
533 if sdict.has_key(ext):
534 tgt = apply(sdict[ext], (env, None, snode), kw)
535 # Only supply the builder with sources it is capable
537 if SCons.Util.is_List(tgt):
538 tgt = filter(lambda x, self=self, suf=src_suffixes:
539 self.splitext(SCons.Util.to_String(x))[1] in suf,
541 if not SCons.Util.is_List(tgt):
542 final_sources.append(tgt)
544 final_sources.extend(tgt)
546 final_sources.append(snode)
548 return apply(BuilderBase.__call__,
549 (self, env, target, final_sources), kw)
551 def get_src_builders(self, env):
552 """Return all the src_builders for this Builder.
554 This is essentially a recursive descent of the src_builder "tree."
557 for bld in self.src_builder:
558 if SCons.Util.is_String(bld):
559 # All Environments should have a BUILDERS
560 # variable, so no need to check for it.
562 bld = env['BUILDERS'][bld]
568 def src_suffixes(self, env):
569 """Return a list of the src_suffix attributes for all
570 src_builders of this Builder.
573 return self.cached_src_suffixes[id(env)]
575 suffixes = BuilderBase.src_suffixes(self, env)
576 for builder in self.get_src_builders(env):
577 suffixes.extend(builder.src_suffixes(env))
578 self.cached_src_suffixes[id(env)] = suffixes
581 class CompositeBuilder(SCons.Util.Proxy):
582 """A Builder Proxy whose main purpose is to always have
583 a DictCmdGenerator as its action, and to provide access
584 to the DictCmdGenerator's add_action() method.
587 def __init__(self, builder, cmdgen):
588 if __debug__: logInstanceCreation(self)
589 SCons.Util.Proxy.__init__(self, builder)
591 # cmdgen should always be an instance of DictCmdGenerator.
593 self.builder = builder
595 def add_action(self, suffix, action):
596 self.cmdgen.add_action(suffix, action)
597 self.set_src_suffix(self.cmdgen.src_suffixes())
599 def __cmp__(self, other):
600 return cmp(self.__dict__, other.__dict__)