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
59 class DictCmdGenerator:
60 """This is a callable class that can be used as a
61 command generator function. It holds on to a dictionary
62 mapping file suffixes to Actions. It uses that dictionary
63 to return the proper action based on the file suffix of
66 def __init__(self, action_dict):
67 self.action_dict = action_dict
69 def src_suffixes(self):
70 return self.action_dict.keys()
72 def add_action(self, suffix, action):
73 """Add a suffix-action pair to the mapping.
75 self.action_dict[suffix] = action
77 def __call__(self, source, target, env, **kw):
79 for src in map(str, source):
80 my_ext = os.path.splitext(src)[1]
81 if ext and my_ext != ext:
82 raise UserError("Cannot build multiple sources with different extensions.")
86 raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
88 # XXX Do we need to perform Environment substitution
89 # on the keys of action_dict before looking it up?
90 return self.action_dict[ext]
92 raise UserError("Don't know how to build a file with suffix %s." % ext)
93 def __cmp__(self, other):
94 return cmp(self.action_dict, other.action_dict)
97 """A factory for builder objects."""
99 if kw.has_key('name'):
100 SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
101 "The use of the 'name' parameter to Builder() is deprecated.")
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, args, 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
145 if t.builder is not None:
147 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
148 elif t.build_args != args:
149 raise UserError, "Two different sets of build arguments were specified for the same target: %s"%str(t)
150 elif builder.scanner and builder.scanner != t.target_scanner:
151 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
154 if t.builder != builder:
155 if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
156 raise UserError, "Two different target sets have a target in common: %s"%str(t)
158 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))
159 elif t.sources != slist:
160 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
163 t.cwd = SCons.Node.FS.default_fs.getcwd()
164 t.builder_set(builder)
168 t.target_scanner = builder.scanner
170 def _adjust_suffix(suff):
171 if suff and not suff[0] in [ '.', '$' ]:
176 """This is a callable class that can act as a
177 Builder emitter. It holds on to a string that
178 is a key into an Environment dictionary, and will
179 look there at actual build time to see if it holds
180 a callable. If so, we will call that as the actual
182 def __init__(self, var):
183 self.var = SCons.Util.to_String(var)
185 def __call__(self, target, source, env, **kw):
188 # Recursively substitute the variable.
189 # We can't use env.subst() because it deals only
190 # in strings. Maybe we should change that?
191 while SCons.Util.is_String(emitter) and \
192 env.has_key(emitter):
193 emitter = env[emitter]
194 if not callable(emitter):
195 return (target, source)
196 args = { 'target':target,
200 return apply(emitter, (), args)
202 def __cmp__(self, other):
203 return cmp(self.var, other.var)
206 """Base class for Builders, objects that create output
207 nodes (files) from input nodes (files).
210 def __init__(self, name = None,
215 node_factory = SCons.Node.FS.default_fs.File,
216 target_factory = None,
217 source_factory = None,
222 self.action = SCons.Action.Action(action)
227 self.set_src_suffix(src_suffix)
229 self.target_factory = target_factory or node_factory
230 self.source_factory = source_factory or node_factory
231 self.scanner = scanner
233 self.emitter = emitter
235 def get_name(self, env):
236 """Attempts to get the name of the Builder.
238 If the Builder's name attribute is None, then we will look at
239 the BUILDERS variable of env, expecting it to be a dictionary
240 containing this Builder, and we will return the key of the
246 index = env['BUILDERS'].values().index(self)
247 return env['BUILDERS'].keys()[index]
248 except (AttributeError, KeyError, ValueError):
249 return str(self.__class__)
251 def __cmp__(self, other):
252 return cmp(self.__dict__, other.__dict__)
254 def _create_nodes(self, env, args, target = None, source = None):
255 """Create and return lists of target and source nodes.
257 def adjustixes(files, pre, suf):
261 if not SCons.Util.is_List(files):
265 if SCons.Util.is_String(f):
266 if pre and f[:len(pre)] != pre:
267 path, fn = os.path.split(os.path.normpath(f))
268 f = os.path.join(path, pre + fn)
269 # Only append a suffix if the file does not have one.
270 if suf and not os.path.splitext(f)[1]:
271 if f[-len(suf):] != suf:
276 pre = self.get_prefix(env)
277 suf = self.get_suffix(env)
278 src_suf = self.get_src_suffix(env)
280 # pass the targets and sources to the emitter as strings
281 # rather than nodes since str(node) doesn't work
282 # properly from any directory other than the top directory,
283 # and emitters are called "in" the SConscript directory:
284 tlist = adjustixes(target, pre, suf)
285 slist = adjustixes(source, None, src_suf)
287 emit_args = { 'target' : tlist,
290 emit_args.update(args)
291 target, source = apply(self.emitter, (), emit_args)
293 tlist = SCons.Node.arg2nodes(adjustixes(target, pre, suf),
295 slist = SCons.Node.arg2nodes(adjustixes(source, None, src_suf),
300 def __call__(self, env, target = None, source = None, **kw):
301 tlist, slist = self._create_nodes(env, kw, target, source)
304 _init_nodes(self, env, kw, tlist, slist)
307 _init_nodes(ListBuilder(self, env, tlist), env, kw, tlist, slist)
311 def execute(self, **kw):
312 """Execute a builder's action to create an output object.
314 return apply(self.action.execute, (), kw)
316 def get_raw_contents(self, **kw):
317 """Fetch the "contents" of the builder's action.
319 return apply(self.action.get_raw_contents, (), kw)
321 def get_contents(self, **kw):
322 """Fetch the "contents" of the builder's action
323 (for signature calculation).
325 return apply(self.action.get_contents, (), kw)
327 def src_suffixes(self, env):
328 return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
331 def set_src_suffix(self, src_suffix):
334 elif not SCons.Util.is_List(src_suffix):
335 src_suffix = [ src_suffix ]
336 self.src_suffix = src_suffix
338 def get_src_suffix(self, env):
339 """Get the first src_suffix in the list of src_suffixes."""
340 ret = self.src_suffixes(env)
345 def get_suffix(self, env):
346 return env.subst(_adjust_suffix(self.suffix))
348 def get_prefix(self, env):
349 return env.subst(self.prefix)
351 def targets(self, node):
352 """Return the list of targets for this builder instance.
354 For most normal builders, this is just the supplied node.
358 class ListBuilder(SCons.Util.Proxy):
359 """A Proxy to support building an array of targets (for example,
360 foo.o and foo.h from foo.y) from a single Action execution.
363 def __init__(self, builder, env, tlist):
364 SCons.Util.Proxy.__init__(self, builder)
365 self.builder = builder
366 self.scanner = builder.scanner
369 self.multi = builder.multi
370 self.name = "ListBuilder(%s)"%builder.name
372 def execute(self, **kw):
373 if hasattr(self, 'status'):
376 # unlink all targets and make all directories
377 # before building anything
379 kw['target'] = self.tlist
380 self.status = apply(self.builder.execute, (), kw)
382 if not t is kw['target']:
386 def targets(self, node):
387 """Return the list of targets for this builder instance.
391 def __cmp__(self, other):
392 return cmp(self.__dict__, other.__dict__)
394 def get_name(self, env):
395 """Attempts to get the name of the Builder.
397 If the Builder's name attribute is None, then we will look at
398 the BUILDERS variable of env, expecting it to be a dictionary
399 containing this Builder, and we will return the key of the
402 return "ListBuilder(%s)" % self.builder.get_name(env)
404 class MultiStepBuilder(BuilderBase):
405 """This is a builder subclass that can build targets in
406 multiple steps. The src_builder parameter to the constructor
407 accepts a builder that is called to build sources supplied to
408 this builder. The targets of that first build then become
409 the sources of this builder.
411 If this builder has a src_suffix supplied, then the src_builder
412 builder is NOT invoked if the suffix of a source file matches
415 def __init__(self, src_builder,
421 node_factory = SCons.Node.FS.default_fs.File,
422 target_factory = None,
423 source_factory = None,
426 BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
427 node_factory, target_factory, source_factory,
429 if not SCons.Util.is_List(src_builder):
430 src_builder = [ src_builder ]
431 self.src_builder = src_builder
434 def __call__(self, env, target = None, source = None, **kw):
435 slist = SCons.Node.arg2nodes(source, self.source_factory)
440 sdict = self.sdict[r]
443 self.sdict[r] = sdict
444 for bld in self.src_builder:
445 if SCons.Util.is_String(bld):
447 bld = env['BUILDERS'][bld]
450 for suf in bld.src_suffixes(env):
454 path, ext = os.path.splitext(snode.abspath)
455 if sdict.has_key(ext):
458 dictArgs = copy.copy(kw)
459 dictArgs['target'] = [ path + src_bld.get_suffix(env) ]
460 dictArgs['source'] = snode
461 dictArgs['env'] = env
462 tgt = apply(src_bld, (), dictArgs)
463 if not SCons.Util.is_List(tgt):
466 # Only supply the builder with sources it is capable
468 tgt = filter(lambda x,
469 suf=self.src_suffixes(env):
470 os.path.splitext(SCons.Util.to_String(x))[1] in \
472 final_sources.extend(tgt)
474 final_sources.append(snode)
476 dictKwArgs['target'] = target
477 dictKwArgs['source'] = final_sources
478 return apply(BuilderBase.__call__,
479 (self, env), dictKwArgs)
481 def get_src_builders(self, env):
482 """Return all the src_builders for this Builder.
484 This is essentially a recursive descent of the src_builder "tree."
487 for bld in self.src_builder:
488 if SCons.Util.is_String(bld):
489 # All Environments should have a BUILDERS
490 # variable, so no need to check for it.
492 bld = env['BUILDERS'][bld]
498 def src_suffixes(self, env):
499 """Return a list of the src_suffix attributes for all
500 src_builders of this Builder.
502 suffixes = BuilderBase.src_suffixes(self, env)
503 for builder in self.get_src_builders(env):
504 suffixes.extend(builder.src_suffixes(env))
507 class CompositeBuilder(SCons.Util.Proxy):
508 """A Builder Proxy whose main purpose is to always have
509 a DictCmdGenerator as its action, and to provide access
510 to the DictCmdGenerator's add_action() method.
513 def __init__(self, builder, cmdgen):
514 SCons.Util.Proxy.__init__(self, builder)
516 # cmdgen should always be an instance of DictCmdGenerator.
518 self.builder = builder
520 def add_action(self, suffix, action):
521 self.cmdgen.add_action(suffix, action)
522 self.set_src_suffix(self.cmdgen.src_suffixes())
524 def __cmp__(self, other):
525 return cmp(self.__dict__, other.__dict__)