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 DictEmitter(UserDict.UserDict):
114 """A callable dictionary that maps file suffixes to emitters.
115 When called, it finds the right emitter in its dictionary for the
116 suffix of the first source file, and calls that emitter to get the
117 right lists of targets and sources to return. If there's no emitter
118 for the suffix in its dictionary, the original target and source are
121 def __call__(self, target, source, env):
122 ext = SCons.Util.splitext(str(source[0]))[1]
127 # Before raising the user error, try to perform Environment
128 # substitution on the keys of emitter_dict.
130 for (k,v) in self.items():
134 emitter = s_dict[ext]
138 target, source = emitter(target, source, env)
139 return (target, source)
142 """A factory for builder objects."""
144 if kw.has_key('generator'):
145 if kw.has_key('action'):
146 raise UserError, "You must not specify both an action and a generator."
147 kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
149 elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
150 composite = DictCmdGenerator(kw['action'])
151 kw['action'] = SCons.Action.CommandGenerator(composite)
152 kw['src_suffix'] = composite.src_suffixes()
154 if kw.has_key('emitter'):
155 emitter = kw['emitter']
156 if SCons.Util.is_String(emitter):
157 # This allows users to pass in an Environment
158 # variable reference (like "$FOO") as an emitter.
159 # We will look in that Environment variable for
160 # a callable to use as the actual emitter.
161 var = SCons.Util.get_environment_var(emitter)
163 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter
164 kw['emitter'] = EmitterProxy(var)
165 elif SCons.Util.is_Dict(emitter):
166 kw['emitter'] = DictEmitter(emitter)
168 if kw.has_key('src_builder'):
169 ret = apply(MultiStepBuilder, (), kw)
171 ret = apply(BuilderBase, (), kw)
174 ret = CompositeBuilder(ret, composite)
178 def _init_nodes(builder, env, overrides, tlist, slist):
179 """Initialize lists of target and source nodes with all of
180 the proper Builder information.
183 # First, figure out if there are any errors in the way the targets
187 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
190 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
191 elif t.overrides != overrides:
192 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
193 elif builder.scanner and builder.scanner != t.target_scanner:
194 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
197 if t.builder != builder:
198 if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
199 raise UserError, "Two different target sets have a target in common: %s"%str(t)
201 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))
202 elif t.sources != slist:
203 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
205 # The targets are fine, so find or make the appropriate Executor to
206 # build this particular list of targets from this particular list of
211 executor = tlist[0].get_executor(create = 0)
212 except AttributeError:
215 executor.add_sources(slist)
217 executor = SCons.Executor.Executor(builder,
218 tlist[0].generate_build_env(env),
223 # Now set up the relevant information in the target Nodes themselves.
225 t.overrides = overrides
226 t.cwd = SCons.Node.FS.default_fs.getcwd()
227 t.builder_set(builder)
230 t.set_executor(executor)
232 t.target_scanner = builder.scanner
234 # Last, add scanners from the Environment to the source Nodes.
236 src_key = s.scanner_key() # the file suffix
237 scanner = env.get_scanner(src_key)
239 s.source_scanner = scanner
242 """This is a callable class that can act as a
243 Builder emitter. It holds on to a string that
244 is a key into an Environment dictionary, and will
245 look there at actual build time to see if it holds
246 a callable. If so, we will call that as the actual
248 def __init__(self, var):
249 self.var = SCons.Util.to_String(var)
251 def __call__(self, target, source, env):
254 # Recursively substitute the variable.
255 # We can't use env.subst() because it deals only
256 # in strings. Maybe we should change that?
257 while SCons.Util.is_String(emitter) and \
258 env.has_key(emitter):
259 emitter = env[emitter]
260 if not callable(emitter):
261 return (target, source)
263 return emitter(target, source, env)
265 def __cmp__(self, other):
266 return cmp(self.var, other.var)
269 """Base class for Builders, objects that create output
270 nodes (files) from input nodes (files).
273 def __init__(self, action = None,
277 node_factory = SCons.Node.FS.default_fs.File,
278 target_factory = None,
279 source_factory = None,
285 self.action = SCons.Action.Action(action)
290 self.overrides = overrides
292 self.set_src_suffix(src_suffix)
294 self.target_factory = target_factory or node_factory
295 self.source_factory = source_factory or node_factory
296 self.scanner = scanner
298 self.emitter = emitter
300 def __nonzero__(self):
301 raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
303 def get_name(self, env):
304 """Attempts to get the name of the Builder.
306 Look at the BUILDERS variable of env, expecting it to be a
307 dictionary containing this Builder, and return the key of the
311 index = env['BUILDERS'].values().index(self)
312 return env['BUILDERS'].keys()[index]
313 except (AttributeError, KeyError, ValueError):
314 return str(self.__class__)
316 def __cmp__(self, other):
317 return cmp(self.__dict__, other.__dict__)
319 def splitext(self, path):
320 return SCons.Util.splitext(path)
322 def _create_nodes(self, env, overrides, target = None, source = None):
323 """Create and return lists of target and source nodes.
325 def adjustixes(files, pre, suf, self=self):
329 if not SCons.Util.is_List(files):
333 if SCons.Util.is_String(f):
335 path, fn = os.path.split(os.path.normpath(f))
336 if fn[:len(pre)] != pre:
337 f = os.path.join(path, pre + fn)
338 # Only append a suffix if the file does not have one.
339 if suf and not self.splitext(f)[1]:
340 if f[-len(suf):] != suf:
345 env = env.Override(overrides)
347 pre = self.get_prefix(env)
348 suf = self.get_suffix(env)
349 src_suf = self.get_src_suffix(env)
351 source = adjustixes(source, None, src_suf)
352 slist = SCons.Node.arg2nodes(source, self.source_factory)
356 t_from_s = slist[0].target_from_source
357 except AttributeError:
358 raise UserError("Do not know how to create a target from source `%s'" % slist[0])
359 tlist = [ t_from_s(pre, suf, self.splitext) ]
361 target = adjustixes(target, pre, suf)
362 tlist = SCons.Node.arg2nodes(target, self.target_factory)
365 # The emitter is going to do str(node), but because we're
366 # being called *from* a builder invocation, the new targets
367 # don't yet have a builder set on them and will look like
368 # source files. Fool the emitter's str() calls by setting
369 # up a temporary builder on the new targets.
372 if not t.is_derived():
374 new_targets.append(t)
376 target, source = self.emitter(target=tlist, source=slist, env=env)
378 # Now delete the temporary builders that we attached to any
379 # new targets, so that _init_nodes() doesn't do weird stuff
380 # to them because it thinks they already have builders.
381 for t in new_targets:
382 if t.builder is self:
383 # Only delete the temporary builder if the emitter
384 # didn't change it on us.
387 # Have to call arg2nodes yet again, since it is legal for
388 # emitters to spit out strings as well as Node instances.
389 slist = SCons.Node.arg2nodes(source, self.source_factory)
390 tlist = SCons.Node.arg2nodes(target, self.target_factory)
394 def __call__(self, env, target = None, source = _null, **overrides):
398 tlist, slist = self._create_nodes(env, overrides, target, source)
401 _init_nodes(self, env, overrides, tlist, slist)
404 _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
408 def adjust_suffix(self, suff):
409 if suff and not suff[0] in [ '.', '$' ]:
413 def get_prefix(self, env):
417 return env.subst(prefix)
419 def get_suffix(self, env):
424 suffix = self.adjust_suffix(suffix)
425 return env.subst(suffix)
427 def src_suffixes(self, env):
428 return map(lambda x, s=self, e=env: e.subst(s.adjust_suffix(x)),
431 def set_src_suffix(self, src_suffix):
434 elif not SCons.Util.is_List(src_suffix):
435 src_suffix = [ src_suffix ]
436 self.src_suffix = src_suffix
438 def get_src_suffix(self, env):
439 """Get the first src_suffix in the list of src_suffixes."""
440 ret = self.src_suffixes(env)
445 def targets(self, node):
446 """Return the list of targets for this builder instance.
448 For most normal builders, this is just the supplied node.
452 def add_emitter(self, suffix, emitter):
453 """Add a suffix-emitter mapping to this Builder.
455 This assumes that emitter has been initialized with an
456 appropriate dictionary type, and will throw a TypeError if
457 not, so the caller is responsible for knowing that this is an
458 appropriate method to call for the Builder in question.
460 self.emitter[suffix] = emitter
462 class ListBuilder(SCons.Util.Proxy):
463 """A Proxy to support building an array of targets (for example,
464 foo.o and foo.h from foo.y) from a single Action execution.
467 def __init__(self, builder, env, tlist):
468 SCons.Util.Proxy.__init__(self, builder)
469 self.builder = builder
470 self.scanner = builder.scanner
473 self.multi = builder.multi
475 def targets(self, node):
476 """Return the list of targets for this builder instance.
480 def __cmp__(self, other):
481 return cmp(self.__dict__, other.__dict__)
483 def get_name(self, env):
484 """Attempts to get the name of the Builder."""
486 return "ListBuilder(%s)" % self.builder.get_name(env)
488 class MultiStepBuilder(BuilderBase):
489 """This is a builder subclass that can build targets in
490 multiple steps. The src_builder parameter to the constructor
491 accepts a builder that is called to build sources supplied to
492 this builder. The targets of that first build then become
493 the sources of this builder.
495 If this builder has a src_suffix supplied, then the src_builder
496 builder is NOT invoked if the suffix of a source file matches
499 def __init__(self, src_builder,
504 node_factory = SCons.Node.FS.default_fs.File,
505 target_factory = None,
506 source_factory = None,
509 BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
510 node_factory, target_factory, source_factory,
512 if not SCons.Util.is_List(src_builder):
513 src_builder = [ src_builder ]
514 self.src_builder = src_builder
516 self.cached_src_suffixes = {} # source suffixes keyed on id(env)
518 def __call__(self, env, target = None, source = _null, **kw):
523 slist = SCons.Node.arg2nodes(source, self.source_factory)
527 sdict = self.sdict[id(env)]
530 self.sdict[id(env)] = sdict
531 for bld in self.src_builder:
532 if SCons.Util.is_String(bld):
534 bld = env['BUILDERS'][bld]
537 for suf in bld.src_suffixes(env):
540 src_suffixes = self.src_suffixes(env)
543 path, ext = self.splitext(snode.get_abspath())
544 if sdict.has_key(ext):
546 tgt = apply(src_bld, (env, path, snode), kw)
547 # Only supply the builder with sources it is capable
549 if SCons.Util.is_List(tgt):
550 tgt = filter(lambda x, self=self, suf=src_suffixes:
551 self.splitext(SCons.Util.to_String(x))[1] in suf,
553 if not SCons.Util.is_List(tgt):
554 final_sources.append(tgt)
556 final_sources.extend(tgt)
558 final_sources.append(snode)
560 return apply(BuilderBase.__call__,
561 (self, env, target, final_sources), kw)
563 def get_src_builders(self, env):
564 """Return all the src_builders for this Builder.
566 This is essentially a recursive descent of the src_builder "tree."
569 for bld in self.src_builder:
570 if SCons.Util.is_String(bld):
571 # All Environments should have a BUILDERS
572 # variable, so no need to check for it.
574 bld = env['BUILDERS'][bld]
580 def src_suffixes(self, env):
581 """Return a list of the src_suffix attributes for all
582 src_builders of this Builder.
585 return self.cached_src_suffixes[id(env)]
587 suffixes = BuilderBase.src_suffixes(self, env)
588 for builder in self.get_src_builders(env):
589 suffixes.extend(builder.src_suffixes(env))
590 self.cached_src_suffixes[id(env)] = suffixes
593 class CompositeBuilder(SCons.Util.Proxy):
594 """A Builder Proxy whose main purpose is to always have
595 a DictCmdGenerator as its action, and to provide access
596 to the DictCmdGenerator's add_action() method.
599 def __init__(self, builder, cmdgen):
600 SCons.Util.Proxy.__init__(self, builder)
602 # cmdgen should always be an instance of DictCmdGenerator.
604 self.builder = builder
606 def add_action(self, suffix, action):
607 self.cmdgen.add_action(suffix, action)
608 self.set_src_suffix(self.cmdgen.src_suffixes())
610 def __cmp__(self, other):
611 return cmp(self.__dict__, other.__dict__)