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.Errors import InternalError, UserError
62 class DictCmdGenerator:
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 __init__(self, action_dict):
70 self.action_dict = action_dict
72 def src_suffixes(self):
73 return self.action_dict.keys()
75 def add_action(self, suffix, action):
76 """Add a suffix-action pair to the mapping.
78 self.action_dict[suffix] = action
80 def __call__(self, target, source, env, for_signature):
82 for src in map(str, source):
83 my_ext = SCons.Util.splitext(src)[1]
84 if ext and my_ext != ext:
85 raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" % (repr(map(str, target)), src, ext, my_ext))
89 raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source))))
91 return self.action_dict[ext]
93 # Before raising the user error, try to perform Environment
94 # substitution on the keys of action_dict.
96 for (k,v) in self.action_dict.items():
98 if s_dict.has_key(s_k):
99 # XXX Note that we do only raise errors, when variables
100 # point to the same suffix. If one suffix is a
101 # literal and a variable suffix contains this literal
102 # we don't raise an error (cause the literal 'wins')
103 raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (s_dict[s_k][0], k, s_k))
106 return s_dict[ext][1]
108 raise UserError("While building `%s': Don't know how to build a file with suffix %s." % (repr(map(str, target)), repr(ext)))
110 def __cmp__(self, other):
111 return cmp(self.action_dict, other.action_dict)
113 class Selector(UserDict.UserDict):
114 """A callable dictionary that maps file suffixes to dictionary
116 def __call__(self, env, source):
117 ext = SCons.Util.splitext(str(source[0]))[1]
121 # Try to perform Environment substitution on the keys of
122 # emitter_dict before giving up.
124 for (k,v) in self.items():
135 class DictEmitter(Selector):
136 """A callable dictionary that maps file suffixes to emitters.
137 When called, it finds the right emitter in its dictionary for the
138 suffix of the first source file, and calls that emitter to get the
139 right lists of targets and sources to return. If there's no emitter
140 for the suffix in its dictionary, the original target and source are
143 def __call__(self, target, source, env):
144 emitter = Selector.__call__(self, env, source)
146 target, source = emitter(target, source, env)
147 return (target, source)
150 """A factory for builder objects."""
152 if kw.has_key('generator'):
153 if kw.has_key('action'):
154 raise UserError, "You must not specify both an action and a generator."
155 kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
157 elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
158 composite = DictCmdGenerator(kw['action'])
159 kw['action'] = SCons.Action.CommandGenerator(composite)
160 kw['src_suffix'] = composite.src_suffixes()
162 if kw.has_key('emitter'):
163 emitter = kw['emitter']
164 if SCons.Util.is_String(emitter):
165 # This allows users to pass in an Environment
166 # variable reference (like "$FOO") as an emitter.
167 # We will look in that Environment variable for
168 # a callable to use as the actual emitter.
169 var = SCons.Util.get_environment_var(emitter)
171 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter
172 kw['emitter'] = EmitterProxy(var)
173 elif SCons.Util.is_Dict(emitter):
174 kw['emitter'] = DictEmitter(emitter)
176 if kw.has_key('src_builder'):
177 ret = apply(MultiStepBuilder, (), kw)
179 ret = apply(BuilderBase, (), kw)
182 ret = CompositeBuilder(ret, composite)
186 def _init_nodes(builder, env, overrides, tlist, slist):
187 """Initialize lists of target and source nodes with all of
188 the proper Builder information.
191 # First, figure out if there are any errors in the way the targets
195 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
198 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
199 elif t.overrides != overrides:
200 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
201 elif builder.scanner and builder.scanner != t.target_scanner:
202 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
205 if t.builder != builder:
206 if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
207 raise UserError, "Two different target sets have a target in common: %s"%str(t)
209 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))
210 elif t.sources != slist:
211 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
213 # The targets are fine, so find or make the appropriate Executor to
214 # build this particular list of targets from this particular list of
219 executor = tlist[0].get_executor(create = 0)
220 except AttributeError:
223 executor.add_sources(slist)
225 executor = SCons.Executor.Executor(builder,
226 tlist[0].generate_build_env(env),
231 # Now set up the relevant information in the target Nodes themselves.
233 t.overrides = overrides
234 t.cwd = SCons.Node.FS.default_fs.getcwd()
235 t.builder_set(builder)
238 t.set_executor(executor)
240 t.target_scanner = builder.scanner
242 # Last, add scanners from the Environment to the source Nodes.
244 src_key = s.scanner_key() # the file suffix
245 scanner = env.get_scanner(src_key)
247 s.source_scanner = scanner
250 """This is a callable class that can act as a
251 Builder emitter. It holds on to a string that
252 is a key into an Environment dictionary, and will
253 look there at actual build time to see if it holds
254 a callable. If so, we will call that as the actual
256 def __init__(self, var):
257 self.var = SCons.Util.to_String(var)
259 def __call__(self, target, source, env):
262 # Recursively substitute the variable.
263 # We can't use env.subst() because it deals only
264 # in strings. Maybe we should change that?
265 while SCons.Util.is_String(emitter) and \
266 env.has_key(emitter):
267 emitter = env[emitter]
268 if not callable(emitter):
269 return (target, source)
271 return emitter(target, source, env)
273 def __cmp__(self, other):
274 return cmp(self.var, other.var)
277 """Base class for Builders, objects that create output
278 nodes (files) from input nodes (files).
281 def __init__(self, action = None,
285 node_factory = SCons.Node.FS.default_fs.File,
286 target_factory = None,
287 source_factory = None,
293 self.action = SCons.Action.Action(action)
295 if SCons.Util.is_Dict(prefix):
296 prefix = Selector(prefix)
298 if SCons.Util.is_Dict(suffix):
299 suffix = Selector(suffix)
302 self.overrides = overrides
304 self.set_src_suffix(src_suffix)
306 self.target_factory = target_factory or node_factory
307 self.source_factory = source_factory or node_factory
308 self.scanner = scanner
310 self.emitter = emitter
312 def __nonzero__(self):
313 raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
315 def get_name(self, env):
316 """Attempts to get the name of the Builder.
318 Look at the BUILDERS variable of env, expecting it to be a
319 dictionary containing this Builder, and return the key of the
323 index = env['BUILDERS'].values().index(self)
324 return env['BUILDERS'].keys()[index]
325 except (AttributeError, KeyError, ValueError):
326 return str(self.__class__)
328 def __cmp__(self, other):
329 return cmp(self.__dict__, other.__dict__)
331 def splitext(self, path):
332 return SCons.Util.splitext(path)
334 def _create_nodes(self, env, overrides, target = None, source = None):
335 """Create and return lists of target and source nodes.
337 def adjustixes(files, pre, suf, self=self):
341 if not SCons.Util.is_List(files):
345 if SCons.Util.is_String(f):
347 path, fn = os.path.split(os.path.normpath(f))
348 if fn[:len(pre)] != pre:
349 f = os.path.join(path, pre + fn)
350 # Only append a suffix if the file does not have one.
351 if suf and not self.splitext(f)[1]:
352 if f[-len(suf):] != suf:
357 env = env.Override(overrides)
359 src_suf = self.get_src_suffix(env)
361 source = adjustixes(source, None, src_suf)
362 slist = SCons.Node.arg2nodes(source, self.source_factory)
364 pre = self.get_prefix(env, slist)
365 suf = self.get_suffix(env, slist)
369 t_from_s = slist[0].target_from_source
370 except AttributeError:
371 raise UserError("Do not know how to create a target from source `%s'" % slist[0])
372 tlist = [ t_from_s(pre, suf, self.splitext) ]
374 target = adjustixes(target, pre, suf)
375 tlist = SCons.Node.arg2nodes(target, self.target_factory)
378 # The emitter is going to do str(node), but because we're
379 # being called *from* a builder invocation, the new targets
380 # don't yet have a builder set on them and will look like
381 # source files. Fool the emitter's str() calls by setting
382 # up a temporary builder on the new targets.
385 if not t.is_derived():
387 new_targets.append(t)
389 target, source = self.emitter(target=tlist, source=slist, env=env)
391 # Now delete the temporary builders that we attached to any
392 # new targets, so that _init_nodes() doesn't do weird stuff
393 # to them because it thinks they already have builders.
394 for t in new_targets:
395 if t.builder is self:
396 # Only delete the temporary builder if the emitter
397 # didn't change it on us.
400 # Have to call arg2nodes yet again, since it is legal for
401 # emitters to spit out strings as well as Node instances.
402 slist = SCons.Node.arg2nodes(source, self.source_factory)
403 tlist = SCons.Node.arg2nodes(target, self.target_factory)
407 def __call__(self, env, target = None, source = _null, **overrides):
411 tlist, slist = self._create_nodes(env, overrides, target, source)
414 _init_nodes(self, env, overrides, tlist, slist)
417 _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
421 def adjust_suffix(self, suff):
422 if suff and not suff[0] in [ '.', '$' ]:
426 def get_prefix(self, env, sources=[]):
429 prefix = prefix(env, sources)
430 return env.subst(prefix)
432 def get_suffix(self, env, sources=[]):
435 suffix = suffix(env, sources)
437 suffix = self.adjust_suffix(suffix)
438 return env.subst(suffix)
440 def src_suffixes(self, env):
441 return map(lambda x, s=self, e=env: e.subst(s.adjust_suffix(x)),
444 def set_src_suffix(self, src_suffix):
447 elif not SCons.Util.is_List(src_suffix):
448 src_suffix = [ src_suffix ]
449 self.src_suffix = src_suffix
451 def get_src_suffix(self, env):
452 """Get the first src_suffix in the list of src_suffixes."""
453 ret = self.src_suffixes(env)
458 def targets(self, node):
459 """Return the list of targets for this builder instance.
461 For most normal builders, this is just the supplied node.
465 def add_emitter(self, suffix, emitter):
466 """Add a suffix-emitter mapping to this Builder.
468 This assumes that emitter has been initialized with an
469 appropriate dictionary type, and will throw a TypeError if
470 not, so the caller is responsible for knowing that this is an
471 appropriate method to call for the Builder in question.
473 self.emitter[suffix] = emitter
475 class ListBuilder(SCons.Util.Proxy):
476 """A Proxy to support building an array of targets (for example,
477 foo.o and foo.h from foo.y) from a single Action execution.
480 def __init__(self, builder, env, tlist):
481 SCons.Util.Proxy.__init__(self, builder)
482 self.builder = builder
483 self.scanner = builder.scanner
486 self.multi = builder.multi
488 def targets(self, node):
489 """Return the list of targets for this builder instance.
493 def __cmp__(self, other):
494 return cmp(self.__dict__, other.__dict__)
496 def get_name(self, env):
497 """Attempts to get the name of the Builder."""
499 return "ListBuilder(%s)" % self.builder.get_name(env)
501 class MultiStepBuilder(BuilderBase):
502 """This is a builder subclass that can build targets in
503 multiple steps. The src_builder parameter to the constructor
504 accepts a builder that is called to build sources supplied to
505 this builder. The targets of that first build then become
506 the sources of this builder.
508 If this builder has a src_suffix supplied, then the src_builder
509 builder is NOT invoked if the suffix of a source file matches
512 def __init__(self, src_builder,
517 node_factory = SCons.Node.FS.default_fs.File,
518 target_factory = None,
519 source_factory = None,
522 BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
523 node_factory, target_factory, source_factory,
525 if not SCons.Util.is_List(src_builder):
526 src_builder = [ src_builder ]
527 self.src_builder = src_builder
529 self.cached_src_suffixes = {} # source suffixes keyed on id(env)
531 def __call__(self, env, target = None, source = _null, **kw):
536 slist = SCons.Node.arg2nodes(source, self.source_factory)
540 sdict = self.sdict[id(env)]
543 self.sdict[id(env)] = sdict
544 for bld in self.src_builder:
545 if SCons.Util.is_String(bld):
547 bld = env['BUILDERS'][bld]
550 for suf in bld.src_suffixes(env):
553 src_suffixes = self.src_suffixes(env)
556 path, ext = self.splitext(snode.get_abspath())
557 if sdict.has_key(ext):
559 tgt = apply(src_bld, (env, path, snode), kw)
560 # Only supply the builder with sources it is capable
562 if SCons.Util.is_List(tgt):
563 tgt = filter(lambda x, self=self, suf=src_suffixes:
564 self.splitext(SCons.Util.to_String(x))[1] in suf,
566 if not SCons.Util.is_List(tgt):
567 final_sources.append(tgt)
569 final_sources.extend(tgt)
571 final_sources.append(snode)
573 return apply(BuilderBase.__call__,
574 (self, env, target, final_sources), kw)
576 def get_src_builders(self, env):
577 """Return all the src_builders for this Builder.
579 This is essentially a recursive descent of the src_builder "tree."
582 for bld in self.src_builder:
583 if SCons.Util.is_String(bld):
584 # All Environments should have a BUILDERS
585 # variable, so no need to check for it.
587 bld = env['BUILDERS'][bld]
593 def src_suffixes(self, env):
594 """Return a list of the src_suffix attributes for all
595 src_builders of this Builder.
598 return self.cached_src_suffixes[id(env)]
600 suffixes = BuilderBase.src_suffixes(self, env)
601 for builder in self.get_src_builders(env):
602 suffixes.extend(builder.src_suffixes(env))
603 self.cached_src_suffixes[id(env)] = suffixes
606 class CompositeBuilder(SCons.Util.Proxy):
607 """A Builder Proxy whose main purpose is to always have
608 a DictCmdGenerator as its action, and to provide access
609 to the DictCmdGenerator's add_action() method.
612 def __init__(self, builder, cmdgen):
613 SCons.Util.Proxy.__init__(self, builder)
615 # cmdgen should always be an instance of DictCmdGenerator.
617 self.builder = builder
619 def add_action(self, suffix, action):
620 self.cmdgen.add_action(suffix, action)
621 self.set_src_suffix(self.cmdgen.src_suffixes())
623 def __cmp__(self, other):
624 return cmp(self.__dict__, other.__dict__)