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__"
49 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("Cannot build multiple sources with different extensions: %s, %s" % (ext, my_ext))
89 raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
91 # XXX Do we need to perform Environment substitution
92 # on the keys of action_dict before looking it up?
93 return self.action_dict[ext]
95 raise UserError("Don't know how to build a file with suffix %s." % ext)
96 def __cmp__(self, other):
97 return cmp(self.action_dict, other.action_dict)
100 """A factory for builder objects."""
102 if kw.has_key('generator'):
103 if kw.has_key('action'):
104 raise UserError, "You must not specify both an action and a generator."
105 kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
107 elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
108 composite = DictCmdGenerator(kw['action'])
109 kw['action'] = SCons.Action.CommandGenerator(composite)
110 kw['src_suffix'] = composite.src_suffixes()
112 if kw.has_key('emitter') and \
113 SCons.Util.is_String(kw['emitter']):
114 # This allows users to pass in an Environment
115 # variable reference (like "$FOO") as an emitter.
116 # We will look in that Environment variable for
117 # a callable to use as the actual emitter.
118 var = SCons.Util.get_environment_var(kw['emitter'])
120 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
121 kw['emitter'] = EmitterProxy(var)
123 if kw.has_key('src_builder'):
124 ret = apply(MultiStepBuilder, (), kw)
126 ret = apply(BuilderBase, (), kw)
129 ret = CompositeBuilder(ret, composite)
133 def _init_nodes(builder, env, overrides, tlist, slist):
134 """Initialize lists of target and source nodes with all of
135 the proper Builder information.
139 src_key = s.scanner_key() # the file suffix
140 scanner = env.get_scanner(src_key)
142 s.source_scanner = scanner
146 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
149 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
150 elif t.overrides != overrides:
151 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
152 elif builder.scanner and builder.scanner != t.target_scanner:
153 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
156 if t.builder != builder:
157 if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
158 raise UserError, "Two different target sets have a target in common: %s"%str(t)
160 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))
161 elif t.sources != slist:
162 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
164 t.overrides = overrides
165 t.cwd = SCons.Node.FS.default_fs.getcwd()
166 t.builder_set(builder)
170 t.target_scanner = builder.scanner
172 def _adjust_suffix(suff):
173 if suff and not suff[0] in [ '.', '$' ]:
178 """This is a callable class that can act as a
179 Builder emitter. It holds on to a string that
180 is a key into an Environment dictionary, and will
181 look there at actual build time to see if it holds
182 a callable. If so, we will call that as the actual
184 def __init__(self, var):
185 self.var = SCons.Util.to_String(var)
187 def __call__(self, target, source, env):
190 # Recursively substitute the variable.
191 # We can't use env.subst() because it deals only
192 # in strings. Maybe we should change that?
193 while SCons.Util.is_String(emitter) and \
194 env.has_key(emitter):
195 emitter = env[emitter]
196 if not callable(emitter):
197 return (target, source)
199 return emitter(target, source, env)
201 def __cmp__(self, other):
202 return cmp(self.var, other.var)
205 """Base class for Builders, objects that create output
206 nodes (files) from input nodes (files).
209 def __init__(self, action = None,
213 node_factory = SCons.Node.FS.default_fs.File,
214 target_factory = None,
215 source_factory = None,
221 self.action = SCons.Action.Action(action)
226 self.overrides = overrides
228 self.set_src_suffix(src_suffix)
230 self.target_factory = target_factory or node_factory
231 self.source_factory = source_factory or node_factory
232 self.scanner = scanner
234 self.emitter = emitter
236 def __nonzero__(self):
237 raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
239 def get_name(self, env):
240 """Attempts to get the name of the Builder.
242 Look at the BUILDERS variable of env, expecting it to be a
243 dictionary containing this Builder, and return the key of the
247 index = env['BUILDERS'].values().index(self)
248 return env['BUILDERS'].keys()[index]
249 except (AttributeError, KeyError, ValueError):
250 return str(self.__class__)
252 def __cmp__(self, other):
253 return cmp(self.__dict__, other.__dict__)
255 def _create_nodes(self, env, overrides, target = None, source = None):
256 """Create and return lists of target and source nodes.
258 def adjustixes(files, pre, suf):
262 if not SCons.Util.is_List(files):
266 if SCons.Util.is_String(f):
267 if pre and f[:len(pre)] != pre:
268 path, fn = os.path.split(os.path.normpath(f))
269 f = os.path.join(path, pre + fn)
270 # Only append a suffix if the file does not have one.
271 if suf and not SCons.Util.splitext(f)[1]:
272 if f[-len(suf):] != suf:
277 env = env.Override(overrides)
279 pre = self.get_prefix(env)
280 suf = self.get_suffix(env)
281 src_suf = self.get_src_suffix(env)
283 source = adjustixes(source, None, src_suf)
286 if isinstance(s, SCons.Node.Node):
287 s = os.path.split(str(s))[1]
288 target = [ pre + os.path.splitext(s)[0] + suf ]
290 target = adjustixes(target, pre, suf)
292 slist = SCons.Node.arg2nodes(source, self.source_factory)
293 tlist = SCons.Node.arg2nodes(target, self.target_factory)
296 # The emitter is going to do str(node), but because we're
297 # being called *from* a builder invocation, the new targets
298 # don't yet have a builder set on them and will look like
299 # source files. Fool the emitter's str() calls by setting
300 # up a temporary builder on the new targets.
303 if not t.is_derived():
305 new_targets.append(t)
307 target, source = self.emitter(target=tlist, source=slist, env=env)
309 # Now delete the temporary builders that we attached to the
310 # new targets, so that _init_nodes() doesn't do weird stuff
311 # to them because it thinks they already have builders.
312 for t in new_targets:
315 # Have to call arg2nodes yet again, since it is legal for
316 # emitters to spit out strings as well as Node instances.
317 slist = SCons.Node.arg2nodes(source, self.source_factory)
318 tlist = SCons.Node.arg2nodes(target, self.target_factory)
322 def __call__(self, env, target = None, source = _null, **overrides):
326 tlist, slist = self._create_nodes(env, overrides, target, source)
329 _init_nodes(self, env, overrides, tlist, slist)
332 _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
336 def get_actions(self):
337 return self.action.get_actions()
339 def get_raw_contents(self, target, source, env):
340 """Fetch the "contents" of the builder's action.
342 return self.action.get_raw_contents(target, source, env)
344 def get_contents(self, target, source, env):
345 """Fetch the "contents" of the builder's action
346 (for signature calculation).
348 return self.action.get_contents(target, source, env)
350 def src_suffixes(self, env):
351 return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
354 def set_src_suffix(self, src_suffix):
357 elif not SCons.Util.is_List(src_suffix):
358 src_suffix = [ src_suffix ]
359 self.src_suffix = src_suffix
361 def get_src_suffix(self, env):
362 """Get the first src_suffix in the list of src_suffixes."""
363 ret = self.src_suffixes(env)
368 def get_suffix(self, env):
369 return env.subst(_adjust_suffix(self.suffix))
371 def get_prefix(self, env):
372 return env.subst(self.prefix)
374 def targets(self, node):
375 """Return the list of targets for this builder instance.
377 For most normal builders, this is just the supplied node.
381 class ListBuilder(SCons.Util.Proxy):
382 """A Proxy to support building an array of targets (for example,
383 foo.o and foo.h from foo.y) from a single Action execution.
386 def __init__(self, builder, env, tlist):
387 SCons.Util.Proxy.__init__(self, builder)
388 self.builder = builder
389 self.scanner = builder.scanner
392 self.multi = builder.multi
394 def targets(self, node):
395 """Return the list of targets for this builder instance.
399 def __cmp__(self, other):
400 return cmp(self.__dict__, other.__dict__)
402 def get_name(self, env):
403 """Attempts to get the name of the Builder."""
405 return "ListBuilder(%s)" % self.builder.get_name(env)
407 class MultiStepBuilder(BuilderBase):
408 """This is a builder subclass that can build targets in
409 multiple steps. The src_builder parameter to the constructor
410 accepts a builder that is called to build sources supplied to
411 this builder. The targets of that first build then become
412 the sources of this builder.
414 If this builder has a src_suffix supplied, then the src_builder
415 builder is NOT invoked if the suffix of a source file matches
418 def __init__(self, src_builder,
423 node_factory = SCons.Node.FS.default_fs.File,
424 target_factory = None,
425 source_factory = None,
428 BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
429 node_factory, target_factory, source_factory,
431 if not SCons.Util.is_List(src_builder):
432 src_builder = [ src_builder ]
433 self.src_builder = src_builder
435 self.cached_src_suffixes = {} # source suffixes keyed on id(env)
437 def __call__(self, env, target = None, source = _null, **kw):
442 slist = SCons.Node.arg2nodes(source, self.source_factory)
446 sdict = self.sdict[id(env)]
449 self.sdict[id(env)] = sdict
450 for bld in self.src_builder:
451 if SCons.Util.is_String(bld):
453 bld = env['BUILDERS'][bld]
456 for suf in bld.src_suffixes(env):
459 src_suffixes = self.src_suffixes(env)
462 path, ext = SCons.Util.splitext(snode.abspath)
463 if sdict.has_key(ext):
465 tgt = apply(src_bld, (env, path, snode), kw)
466 # Only supply the builder with sources it is capable
468 if SCons.Util.is_List(tgt):
469 tgt = filter(lambda x, suf=src_suffixes:
470 SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
472 if not SCons.Util.is_List(tgt):
473 final_sources.append(tgt)
475 final_sources.extend(tgt)
477 final_sources.append(snode)
479 return apply(BuilderBase.__call__,
480 (self, env, target, final_sources), kw)
482 def get_src_builders(self, env):
483 """Return all the src_builders for this Builder.
485 This is essentially a recursive descent of the src_builder "tree."
488 for bld in self.src_builder:
489 if SCons.Util.is_String(bld):
490 # All Environments should have a BUILDERS
491 # variable, so no need to check for it.
493 bld = env['BUILDERS'][bld]
499 def src_suffixes(self, env):
500 """Return a list of the src_suffix attributes for all
501 src_builders of this Builder.
504 return self.cached_src_suffixes[id(env)]
506 suffixes = BuilderBase.src_suffixes(self, env)
507 for builder in self.get_src_builders(env):
508 suffixes.extend(builder.src_suffixes(env))
509 self.cached_src_suffixes[id(env)] = suffixes
512 class CompositeBuilder(SCons.Util.Proxy):
513 """A Builder Proxy whose main purpose is to always have
514 a DictCmdGenerator as its action, and to provide access
515 to the DictCmdGenerator's add_action() method.
518 def __init__(self, builder, cmdgen):
519 SCons.Util.Proxy.__init__(self, builder)
521 # cmdgen should always be an instance of DictCmdGenerator.
523 self.builder = builder
525 def add_action(self, suffix, action):
526 self.cmdgen.add_action(suffix, action)
527 self.set_src_suffix(self.cmdgen.src_suffixes())
529 def __cmp__(self, other):
530 return cmp(self.__dict__, other.__dict__)