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.
22 # Copyright (c) 2001, 2002 Steven Knight
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__"
51 from SCons.Errors import UserError
64 class DictCmdGenerator:
65 """This is a callable class that can be used as a
66 command generator function. It holds on to a dictionary
67 mapping file suffixes to Actions. It uses that dictionary
68 to return the proper action based on the file suffix of
71 def __init__(self, action_dict):
72 self.action_dict = action_dict
74 def src_suffixes(self):
75 return self.action_dict.keys()
77 def add_action(self, suffix, action):
78 """Add a suffix-action pair to the mapping.
80 self.action_dict[suffix] = action
82 def __call__(self, source, target, env, **kw):
84 for src in map(str, source):
85 my_ext = SCons.Util.splitext(src)[1]
86 if ext and my_ext != ext:
87 raise UserError("Cannot build multiple sources with different extensions: %s, %s" % (ext, my_ext))
91 raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
93 # XXX Do we need to perform Environment substitution
94 # on the keys of action_dict before looking it up?
95 return self.action_dict[ext]
97 raise UserError("Don't know how to build a file with suffix %s." % ext)
98 def __cmp__(self, other):
99 return cmp(self.action_dict, other.action_dict)
102 """A factory for builder objects."""
104 if kw.has_key('name'):
105 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
106 "The use of the 'name' parameter to Builder() is deprecated.")
107 if kw.has_key('generator'):
108 if kw.has_key('action'):
109 raise UserError, "You must not specify both an action and a generator."
110 kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
112 elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
113 composite = DictCmdGenerator(kw['action'])
114 kw['action'] = SCons.Action.CommandGenerator(composite)
115 kw['src_suffix'] = composite.src_suffixes()
117 if kw.has_key('emitter') and \
118 SCons.Util.is_String(kw['emitter']):
119 # This allows users to pass in an Environment
120 # variable reference (like "$FOO") as an emitter.
121 # We will look in that Environment variable for
122 # a callable to use as the actual emitter.
123 var = SCons.Util.get_environment_var(kw['emitter'])
125 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
126 kw['emitter'] = EmitterProxy(var)
128 if kw.has_key('src_builder'):
129 ret = apply(MultiStepBuilder, (), kw)
131 ret = apply(BuilderBase, (), kw)
134 ret = CompositeBuilder(ret, composite)
138 def _init_nodes(builder, env, args, tlist, slist):
139 """Initialize lists of target and source nodes with all of
140 the proper Builder information.
144 src_key = s.scanner_key() # the file suffix
145 scanner = env.get_scanner(src_key)
147 s.source_scanner = scanner
151 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
152 if t.builder is not None:
154 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
155 elif t.build_args != args:
156 raise UserError, "Two different sets of build arguments were specified for the same target: %s"%str(t)
157 elif builder.scanner and builder.scanner != t.target_scanner:
158 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
161 if t.builder != builder:
162 if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
163 raise UserError, "Two different target sets have a target in common: %s"%str(t)
165 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))
166 elif t.sources != slist:
167 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
170 t.cwd = SCons.Node.FS.default_fs.getcwd()
171 t.builder_set(builder)
175 t.target_scanner = builder.scanner
177 def _adjust_suffix(suff):
178 if suff and not suff[0] in [ '.', '$' ]:
183 """This is a callable class that can act as a
184 Builder emitter. It holds on to a string that
185 is a key into an Environment dictionary, and will
186 look there at actual build time to see if it holds
187 a callable. If so, we will call that as the actual
189 def __init__(self, var):
190 self.var = SCons.Util.to_String(var)
192 def __call__(self, target, source, env, **kw):
195 # Recursively substitute the variable.
196 # We can't use env.subst() because it deals only
197 # in strings. Maybe we should change that?
198 while SCons.Util.is_String(emitter) and \
199 env.has_key(emitter):
200 emitter = env[emitter]
201 if not callable(emitter):
202 return (target, source)
203 args = { 'target':target,
207 return apply(emitter, (), args)
209 def __cmp__(self, other):
210 return cmp(self.var, other.var)
213 """Base class for Builders, objects that create output
214 nodes (files) from input nodes (files).
217 def __init__(self, name = None,
222 node_factory = SCons.Node.FS.default_fs.File,
223 target_factory = None,
224 source_factory = None,
229 self.action = SCons.Action.Action(action)
234 self.set_src_suffix(src_suffix)
236 self.target_factory = target_factory or node_factory
237 self.source_factory = source_factory or node_factory
238 self.scanner = scanner
240 self.emitter = emitter
242 def get_name(self, env):
243 """Attempts to get the name of the Builder.
245 If the Builder's name attribute is None, then we will look at
246 the BUILDERS variable of env, expecting it to be a dictionary
247 containing this Builder, and we will return the key of the
253 index = env['BUILDERS'].values().index(self)
254 return env['BUILDERS'].keys()[index]
255 except (AttributeError, KeyError, ValueError):
256 return str(self.__class__)
258 def __cmp__(self, other):
259 return cmp(self.__dict__, other.__dict__)
261 def _create_nodes(self, env, args, target = None, source = None):
262 """Create and return lists of target and source nodes.
264 def adjustixes(files, pre, suf):
268 if not SCons.Util.is_List(files):
272 if SCons.Util.is_String(f):
273 if pre and f[:len(pre)] != pre:
274 path, fn = os.path.split(os.path.normpath(f))
275 f = os.path.join(path, pre + fn)
276 # Only append a suffix if the file does not have one.
277 if suf and not SCons.Util.splitext(f)[1]:
278 if f[-len(suf):] != suf:
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)
289 target = map(lambda x, s=suf:
290 os.path.splitext(os.path.split(str(x))[1])[0] + s,
293 target = adjustixes(target, pre, suf)
296 # pass the targets and sources to the emitter as strings
297 # rather than nodes since str(node) doesn't work
298 # properly from any directory other than the top directory,
299 # and emitters are called "in" the SConscript directory:
300 emit_args = { 'target' : target,
303 emit_args.update(args)
304 target, source = apply(self.emitter, (), emit_args)
306 slist = SCons.Node.arg2nodes(source, self.source_factory)
307 tlist = SCons.Node.arg2nodes(target, self.target_factory)
311 def __call__(self, env, target = None, source = _null, **kw):
315 tlist, slist = self._create_nodes(env, kw, target, source)
318 _init_nodes(self, env, kw, tlist, slist)
321 _init_nodes(ListBuilder(self, env, tlist), env, kw, tlist, slist)
325 def execute(self, **kw):
326 """Execute a builder's action to create an output object.
328 return apply(self.action.execute, (), kw)
330 def get_raw_contents(self, **kw):
331 """Fetch the "contents" of the builder's action.
333 return apply(self.action.get_raw_contents, (), kw)
335 def get_contents(self, **kw):
336 """Fetch the "contents" of the builder's action
337 (for signature calculation).
339 return apply(self.action.get_contents, (), kw)
341 def src_suffixes(self, env):
342 return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
345 def set_src_suffix(self, src_suffix):
348 elif not SCons.Util.is_List(src_suffix):
349 src_suffix = [ src_suffix ]
350 self.src_suffix = src_suffix
352 def get_src_suffix(self, env):
353 """Get the first src_suffix in the list of src_suffixes."""
354 ret = self.src_suffixes(env)
359 def get_suffix(self, env):
360 return env.subst(_adjust_suffix(self.suffix))
362 def get_prefix(self, env):
363 return env.subst(self.prefix)
365 def targets(self, node):
366 """Return the list of targets for this builder instance.
368 For most normal builders, this is just the supplied node.
372 class ListBuilder(SCons.Util.Proxy):
373 """A Proxy to support building an array of targets (for example,
374 foo.o and foo.h from foo.y) from a single Action execution.
377 def __init__(self, builder, env, tlist):
378 SCons.Util.Proxy.__init__(self, builder)
379 self.builder = builder
380 self.scanner = builder.scanner
383 self.multi = builder.multi
384 self.name = "ListBuilder(%s)"%builder.name
386 def execute(self, **kw):
387 if hasattr(self, 'status'):
390 # unlink all targets and make all directories
391 # before building anything
393 kw['target'] = self.tlist
394 self.status = apply(self.builder.execute, (), kw)
396 if not t is kw['target']:
400 def targets(self, node):
401 """Return the list of targets for this builder instance.
405 def __cmp__(self, other):
406 return cmp(self.__dict__, other.__dict__)
408 def get_name(self, env):
409 """Attempts to get the name of the Builder.
411 If the Builder's name attribute is None, then we will look at
412 the BUILDERS variable of env, expecting it to be a dictionary
413 containing this Builder, and we will return the key of the
416 return "ListBuilder(%s)" % self.builder.get_name(env)
418 class MultiStepBuilder(BuilderBase):
419 """This is a builder subclass that can build targets in
420 multiple steps. The src_builder parameter to the constructor
421 accepts a builder that is called to build sources supplied to
422 this builder. The targets of that first build then become
423 the sources of this builder.
425 If this builder has a src_suffix supplied, then the src_builder
426 builder is NOT invoked if the suffix of a source file matches
429 def __init__(self, src_builder,
435 node_factory = SCons.Node.FS.default_fs.File,
436 target_factory = None,
437 source_factory = None,
440 BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
441 node_factory, target_factory, source_factory,
443 if not SCons.Util.is_List(src_builder):
444 src_builder = [ src_builder ]
445 self.src_builder = src_builder
447 self.cached_src_suffixes = {} # source suffixes keyed on id(env)
449 def __call__(self, env, target = None, source = None, **kw):
450 slist = SCons.Node.arg2nodes(source, self.source_factory)
454 sdict = self.sdict[id(env)]
457 self.sdict[id(env)] = sdict
458 for bld in self.src_builder:
459 if SCons.Util.is_String(bld):
461 bld = env['BUILDERS'][bld]
464 for suf in bld.src_suffixes(env):
467 src_suffixes = self.src_suffixes(env)
469 path, ext = SCons.Util.splitext(snode.abspath)
470 if sdict.has_key(ext):
472 tgt = apply(src_bld, (env, path, snode), kw)
473 # Only supply the builder with sources it is capable
475 if SCons.Util.is_List(tgt):
476 tgt = filter(lambda x, suf=src_suffixes:
477 SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
479 if not SCons.Util.is_List(tgt):
480 final_sources.append(tgt)
482 final_sources.extend(tgt)
484 final_sources.append(snode)
486 return apply(BuilderBase.__call__,
487 (self, env, target, final_sources), kw)
489 def get_src_builders(self, env):
490 """Return all the src_builders for this Builder.
492 This is essentially a recursive descent of the src_builder "tree."
495 for bld in self.src_builder:
496 if SCons.Util.is_String(bld):
497 # All Environments should have a BUILDERS
498 # variable, so no need to check for it.
500 bld = env['BUILDERS'][bld]
506 def src_suffixes(self, env):
507 """Return a list of the src_suffix attributes for all
508 src_builders of this Builder.
511 return self.cached_src_suffixes[id(env)]
513 suffixes = BuilderBase.src_suffixes(self, env)
514 for builder in self.get_src_builders(env):
515 suffixes.extend(builder.src_suffixes(env))
516 self.cached_src_suffixes[id(env)] = suffixes
519 class CompositeBuilder(SCons.Util.Proxy):
520 """A Builder Proxy whose main purpose is to always have
521 a DictCmdGenerator as its action, and to provide access
522 to the DictCmdGenerator's add_action() method.
525 def __init__(self, builder, cmdgen):
526 SCons.Util.Proxy.__init__(self, builder)
528 # cmdgen should always be an instance of DictCmdGenerator.
530 self.builder = builder
532 def add_action(self, suffix, action):
533 self.cmdgen.add_action(suffix, action)
534 self.set_src_suffix(self.cmdgen.src_suffixes())
536 def __cmp__(self, other):
537 return cmp(self.__dict__, other.__dict__)