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('name'):
103 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
104 "The use of the 'name' parameter to Builder() is deprecated.")
105 if kw.has_key('generator'):
106 if kw.has_key('action'):
107 raise UserError, "You must not specify both an action and a generator."
108 kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
110 elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
111 composite = DictCmdGenerator(kw['action'])
112 kw['action'] = SCons.Action.CommandGenerator(composite)
113 kw['src_suffix'] = composite.src_suffixes()
115 if kw.has_key('emitter') and \
116 SCons.Util.is_String(kw['emitter']):
117 # This allows users to pass in an Environment
118 # variable reference (like "$FOO") as an emitter.
119 # We will look in that Environment variable for
120 # a callable to use as the actual emitter.
121 var = SCons.Util.get_environment_var(kw['emitter'])
123 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
124 kw['emitter'] = EmitterProxy(var)
126 if kw.has_key('src_builder'):
127 ret = apply(MultiStepBuilder, (), kw)
129 ret = apply(BuilderBase, (), kw)
132 ret = CompositeBuilder(ret, composite)
136 def _init_nodes(builder, env, overrides, tlist, slist):
137 """Initialize lists of target and source nodes with all of
138 the proper Builder information.
142 src_key = s.scanner_key() # the file suffix
143 scanner = env.get_scanner(src_key)
145 s.source_scanner = scanner
149 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
152 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
153 elif t.overrides != overrides:
154 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
155 elif builder.scanner and builder.scanner != t.target_scanner:
156 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
159 if t.builder != builder:
160 if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
161 raise UserError, "Two different target sets have a target in common: %s"%str(t)
163 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))
164 elif t.sources != slist:
165 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
167 t.overrides = overrides
168 t.cwd = SCons.Node.FS.default_fs.getcwd()
169 t.builder_set(builder)
173 t.target_scanner = builder.scanner
175 def _adjust_suffix(suff):
176 if suff and not suff[0] in [ '.', '$' ]:
181 """This is a callable class that can act as a
182 Builder emitter. It holds on to a string that
183 is a key into an Environment dictionary, and will
184 look there at actual build time to see if it holds
185 a callable. If so, we will call that as the actual
187 def __init__(self, var):
188 self.var = SCons.Util.to_String(var)
190 def __call__(self, target, source, env):
193 # Recursively substitute the variable.
194 # We can't use env.subst() because it deals only
195 # in strings. Maybe we should change that?
196 while SCons.Util.is_String(emitter) and \
197 env.has_key(emitter):
198 emitter = env[emitter]
199 if not callable(emitter):
200 return (target, source)
202 return emitter(target, source, env)
204 def __cmp__(self, other):
205 return cmp(self.var, other.var)
208 """Base class for Builders, objects that create output
209 nodes (files) from input nodes (files).
212 def __init__(self, name = None,
217 node_factory = SCons.Node.FS.default_fs.File,
218 target_factory = None,
219 source_factory = None,
224 self.action = SCons.Action.Action(action)
229 self.set_src_suffix(src_suffix)
231 self.target_factory = target_factory or node_factory
232 self.source_factory = source_factory or node_factory
233 self.scanner = scanner
235 self.emitter = emitter
237 def __nonzero__(self):
238 raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
240 def get_name(self, env):
241 """Attempts to get the name of the Builder.
243 If the Builder's name attribute is None, then we will look at
244 the BUILDERS variable of env, expecting it to be a dictionary
245 containing this Builder, and we will return the key of the
251 index = env['BUILDERS'].values().index(self)
252 return env['BUILDERS'].keys()[index]
253 except (AttributeError, KeyError, ValueError):
254 return str(self.__class__)
256 def __cmp__(self, other):
257 return cmp(self.__dict__, other.__dict__)
259 def _create_nodes(self, env, overrides, target = None, source = None):
260 """Create and return lists of target and source nodes.
262 def adjustixes(files, pre, suf):
266 if not SCons.Util.is_List(files):
270 if SCons.Util.is_String(f):
271 if pre and f[:len(pre)] != pre:
272 path, fn = os.path.split(os.path.normpath(f))
273 f = os.path.join(path, pre + fn)
274 # Only append a suffix if the file does not have one.
275 if suf and not SCons.Util.splitext(f)[1]:
276 if f[-len(suf):] != suf:
281 env = env.Override(overrides)
283 pre = self.get_prefix(env)
284 suf = self.get_suffix(env)
285 src_suf = self.get_src_suffix(env)
287 source = adjustixes(source, None, src_suf)
290 if isinstance(s, SCons.Node.Node):
291 s = os.path.split(str(s))[1]
292 target = [ pre + os.path.splitext(s)[0] + suf ]
294 target = adjustixes(target, pre, suf)
297 # pass the targets and sources to the emitter as strings
298 # rather than nodes since str(node) doesn't work
299 # properly from any directory other than the top directory,
300 # and emitters are called "in" the SConscript directory:
301 target, source = self.emitter(target=target, source=source, env=env)
303 slist = SCons.Node.arg2nodes(source, self.source_factory)
304 tlist = SCons.Node.arg2nodes(target, self.target_factory)
308 def __call__(self, env, target = None, source = _null, **overrides):
312 tlist, slist = self._create_nodes(env, overrides, target, source)
315 _init_nodes(self, env, overrides, tlist, slist)
318 _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
322 def get_actions(self):
323 return self.action.get_actions()
325 def get_raw_contents(self, target, source, env):
326 """Fetch the "contents" of the builder's action.
328 return self.action.get_raw_contents(target, source, env)
330 def get_contents(self, target, source, env):
331 """Fetch the "contents" of the builder's action
332 (for signature calculation).
334 return self.action.get_contents(target, source, env)
336 def src_suffixes(self, env):
337 return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
340 def set_src_suffix(self, src_suffix):
343 elif not SCons.Util.is_List(src_suffix):
344 src_suffix = [ src_suffix ]
345 self.src_suffix = src_suffix
347 def get_src_suffix(self, env):
348 """Get the first src_suffix in the list of src_suffixes."""
349 ret = self.src_suffixes(env)
354 def get_suffix(self, env):
355 return env.subst(_adjust_suffix(self.suffix))
357 def get_prefix(self, env):
358 return env.subst(self.prefix)
360 def targets(self, node):
361 """Return the list of targets for this builder instance.
363 For most normal builders, this is just the supplied node.
367 class ListBuilder(SCons.Util.Proxy):
368 """A Proxy to support building an array of targets (for example,
369 foo.o and foo.h from foo.y) from a single Action execution.
372 def __init__(self, builder, env, tlist):
373 SCons.Util.Proxy.__init__(self, builder)
374 self.builder = builder
375 self.scanner = builder.scanner
378 self.multi = builder.multi
379 self.name = "ListBuilder(%s)"%builder.name
381 def targets(self, node):
382 """Return the list of targets for this builder instance.
386 def __cmp__(self, other):
387 return cmp(self.__dict__, other.__dict__)
389 def get_name(self, env):
390 """Attempts to get the name of the Builder.
392 If the Builder's name attribute is None, then we will look at
393 the BUILDERS variable of env, expecting it to be a dictionary
394 containing this Builder, and we will return the key of the
397 return "ListBuilder(%s)" % self.builder.get_name(env)
399 class MultiStepBuilder(BuilderBase):
400 """This is a builder subclass that can build targets in
401 multiple steps. The src_builder parameter to the constructor
402 accepts a builder that is called to build sources supplied to
403 this builder. The targets of that first build then become
404 the sources of this builder.
406 If this builder has a src_suffix supplied, then the src_builder
407 builder is NOT invoked if the suffix of a source file matches
410 def __init__(self, src_builder,
416 node_factory = SCons.Node.FS.default_fs.File,
417 target_factory = None,
418 source_factory = None,
421 BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
422 node_factory, target_factory, source_factory,
424 if not SCons.Util.is_List(src_builder):
425 src_builder = [ src_builder ]
426 self.src_builder = src_builder
428 self.cached_src_suffixes = {} # source suffixes keyed on id(env)
430 def __call__(self, env, target = None, source = _null, **kw):
435 slist = SCons.Node.arg2nodes(source, self.source_factory)
439 sdict = self.sdict[id(env)]
442 self.sdict[id(env)] = sdict
443 for bld in self.src_builder:
444 if SCons.Util.is_String(bld):
446 bld = env['BUILDERS'][bld]
449 for suf in bld.src_suffixes(env):
452 src_suffixes = self.src_suffixes(env)
455 path, ext = SCons.Util.splitext(snode.abspath)
456 if sdict.has_key(ext):
458 tgt = apply(src_bld, (env, path, snode), kw)
459 # Only supply the builder with sources it is capable
461 if SCons.Util.is_List(tgt):
462 tgt = filter(lambda x, suf=src_suffixes:
463 SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
465 if not SCons.Util.is_List(tgt):
466 final_sources.append(tgt)
468 final_sources.extend(tgt)
470 final_sources.append(snode)
472 return apply(BuilderBase.__call__,
473 (self, env, target, final_sources), kw)
475 def get_src_builders(self, env):
476 """Return all the src_builders for this Builder.
478 This is essentially a recursive descent of the src_builder "tree."
481 for bld in self.src_builder:
482 if SCons.Util.is_String(bld):
483 # All Environments should have a BUILDERS
484 # variable, so no need to check for it.
486 bld = env['BUILDERS'][bld]
492 def src_suffixes(self, env):
493 """Return a list of the src_suffix attributes for all
494 src_builders of this Builder.
497 return self.cached_src_suffixes[id(env)]
499 suffixes = BuilderBase.src_suffixes(self, env)
500 for builder in self.get_src_builders(env):
501 suffixes.extend(builder.src_suffixes(env))
502 self.cached_src_suffixes[id(env)] = suffixes
505 class CompositeBuilder(SCons.Util.Proxy):
506 """A Builder Proxy whose main purpose is to always have
507 a DictCmdGenerator as its action, and to provide access
508 to the DictCmdGenerator's add_action() method.
511 def __init__(self, builder, cmdgen):
512 SCons.Util.Proxy.__init__(self, builder)
514 # cmdgen should always be an instance of DictCmdGenerator.
516 self.builder = builder
518 def add_action(self, suffix, action):
519 self.cmdgen.add_action(suffix, action)
520 self.set_src_suffix(self.cmdgen.src_suffixes())
522 def __cmp__(self, other):
523 return cmp(self.__dict__, other.__dict__)