8 # Copyright (c) 2001, 2002 Steven Knight
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
37 from SCons.Errors import UserError
44 class DictCmdGenerator:
45 """This is a callable class that can be used as a
46 command generator function. It holds on to a dictionary
47 mapping file suffixes to Actions. It uses that dictionary
48 to return the proper action based on the file suffix of
51 def __init__(self, action_dict):
52 self.action_dict = action_dict
54 def src_suffixes(self):
55 return self.action_dict.keys()
57 def __call__(self, source, target, env, **kw):
59 for src in map(str, source):
60 my_ext = os.path.splitext(src)[1]
61 if ext and my_ext != ext:
62 raise UserError("Cannot build multiple sources with different extensions.")
66 raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
68 # XXX Do we need to perform Environment substitution
69 # on the keys of action_dict before looking it up?
70 return self.action_dict[ext]
72 raise UserError("Don't know how to build a file with suffix %s." % ext)
75 """A factory for builder objects."""
76 if kw.has_key('generator'):
77 if kw.has_key('action'):
78 raise UserError, "You must not specify both an action and a generator."
79 kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
81 elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
82 action_dict = kw['action']
83 kw['action'] = SCons.Action.CommandGenerator(DictCmdGenerator(action_dict))
84 kw['src_suffix'] = action_dict.keys()
86 if kw.has_key('emitter') and \
87 SCons.Util.is_String(kw['emitter']):
88 # This allows users to pass in an Environment
89 # variable reference (like "$FOO") as an emitter.
90 # We will look in that Environment variable for
91 # a callable to use as the actual emitter.
92 var = SCons.Util.get_environment_var(kw['emitter'])
94 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
95 kw['emitter'] = EmitterProxy(var)
97 if kw.has_key('src_builder'):
98 return apply(MultiStepBuilder, (), kw)
100 return apply(BuilderBase, (), kw)
102 def _init_nodes(builder, env, args, tlist, slist):
103 """Initialize lists of target and source nodes with all of
104 the proper Builder information.
108 src_key = s.scanner_key() # the file suffix
109 scanner = env.get_scanner(src_key)
111 s.source_scanner = scanner
114 if t.builder is not None:
116 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
117 elif t.build_args != args:
118 raise UserError, "Two different sets of build arguments were specified for the same target: %s"%str(t)
119 elif builder.scanner and builder.scanner != t.target_scanner:
120 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
123 if t.builder != builder:
124 if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
125 raise UserError, "Two different target sets have a target in common: %s"%str(t)
127 raise UserError, "Two different builders (%s and %s) were specified for the same target: %s"%(t.builder.name, builder.name, str(t))
128 elif t.sources != slist:
129 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
132 t.cwd = SCons.Node.FS.default_fs.getcwd()
133 t.builder_set(builder)
137 t.target_scanner = builder.scanner
139 class _callable_adaptor:
140 """When crteating a Builder, you can pass a string OR
141 a callable in for prefix, suffix, or src_suffix.
142 src_suffix even takes a list!
144 If a string or list is passed, we use this class to
145 adapt it to a callable."""
146 def __init__(self, static):
149 def __call__(self, **kw):
152 def __cmp__(self, other):
153 if isinstance(other, _callable_adaptor):
154 return cmp(self.static, other.static)
157 def _adjust_suffix(suff):
158 if suff and not suff[0] in [ '.', '$' ]:
163 """This is a callable class that can act as a
164 Builder emitter. It holds on to a string that
165 is a key into an Environment dictionary, and will
166 look there at actual build time to see if it holds
167 a callable. If so, we will call that as the actual
169 def __init__(self, var):
170 self.var = SCons.Util.to_String(var)
172 def __call__(self, target, source, env, **kw):
175 # Recursively substitue the variable.
176 # We can't use env.subst() because it deals only
177 # in strings. Maybe we should change that?
178 while SCons.Util.is_String(emitter) and \
179 env.has_key(emitter):
180 emitter = env[emitter]
181 if not callable(emitter):
182 return (target, source)
183 args = { 'target':target,
187 return apply(emitter, (), args)
190 """Base class for Builders, objects that create output
191 nodes (files) from input nodes (files).
194 def __init__(self, name = None,
199 node_factory = SCons.Node.FS.default_fs.File,
200 target_factory = None,
201 source_factory = None,
206 raise UserError, "You must specify a name for the builder."
208 self.action = SCons.Action.Action(action)
214 self.prefix = _callable_adaptor(str(prefix))
219 self.suffix = _callable_adaptor(str(suffix))
221 if callable(src_suffix):
222 self.src_suffix = src_suffix
223 elif SCons.Util.is_String(src_suffix):
224 self.src_suffix = _callable_adaptor([ str(src_suffix) ])
226 self.src_suffix = _callable_adaptor(src_suffix)
228 self.target_factory = target_factory or node_factory
229 self.source_factory = source_factory or node_factory
230 self.scanner = scanner
232 self.emitter = emitter
234 def __cmp__(self, other):
235 return cmp(self.__dict__, other.__dict__)
237 def _create_nodes(self, env, args, target = None, source = None):
238 """Create and return lists of target and source nodes.
240 def adjustixes(files, pre, suf):
243 #if not SCons.Util.is_List(files):
245 files = SCons.Util.argmunge(files)
248 if SCons.Util.is_String(f):
249 if pre and f[:len(pre)] != pre:
250 path, fn = os.path.split(os.path.normpath(f))
251 f = os.path.join(path, pre + fn)
252 # Only append a suffix if the file does not have one.
253 if suf and not os.path.splitext(f)[1]:
254 if f[-len(suf):] != suf:
259 pre = self.get_prefix(env, args)
260 suf = self.get_suffix(env, args)
261 src_suf = self.get_src_suffix(env, args)
263 # pass the targets and sources to the emitter as strings
264 # rather than nodes since str(node) doesn't work
265 # properly from any directory other than the top directory,
266 # and emitters are called "in" the SConscript directory:
267 tlist = adjustixes(target, pre, suf)
268 slist = adjustixes(source, None, src_suf)
270 emit_args = { 'target' : tlist,
273 emit_args.update(args)
274 target, source = apply(self.emitter, (), emit_args)
276 tlist = SCons.Node.arg2nodes(adjustixes(target, pre, suf),
278 slist = SCons.Node.arg2nodes(adjustixes(source, None, src_suf),
283 def __call__(self, env, target = None, source = None, **kw):
284 tlist, slist = self._create_nodes(env, kw, target, source)
287 _init_nodes(self, env, kw, tlist, slist)
290 _init_nodes(ListBuilder(self, env, tlist), env, kw, tlist, slist)
295 def execute(self, **kw):
296 """Execute a builder's action to create an output object.
298 return apply(self.action.execute, (), kw)
300 def get_raw_contents(self, **kw):
301 """Fetch the "contents" of the builder's action.
303 return apply(self.action.get_raw_contents, (), kw)
305 def get_contents(self, **kw):
306 """Fetch the "contents" of the builder's action
307 (for signature calculation).
309 return apply(self.action.get_contents, (), kw)
311 def src_suffixes(self, env, args):
312 return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
313 apply(self.src_suffix, (), args))
315 def get_src_suffix(self, env, args):
316 """Get the first src_suffix in the list of src_suffixes."""
317 ret = self.src_suffixes(env, args)
323 def get_suffix(self, env, args):
324 return env.subst(_adjust_suffix(apply(self.suffix, (), args)))
326 def get_prefix(self, env, args):
327 return env.subst(apply(self.prefix, (), args))
329 def targets(self, node):
330 """Return the list of targets for this builder instance.
332 For most normal builders, this is just the supplied node.
337 """This is technically not a Builder object, but a wrapper
338 around another Builder object. This is designed to look
339 like a Builder object, though, for purposes of building an
340 array of targets from a single Action execution.
343 def __init__(self, builder, env, tlist):
344 self.builder = builder
345 self.scanner = builder.scanner
348 self.multi = builder.multi
349 self.name = "ListBuilder(%s)"%builder.name
351 def execute(self, **kw):
352 if hasattr(self, 'status'):
355 # unlink all targets and make all directories
356 # before building anything
358 kw['target'] = self.tlist
359 self.status = apply(self.builder.execute, (), kw)
361 if not t is kw['target']:
365 def get_raw_contents(self, **kw):
366 return apply(self.builder.get_raw_contents, (), kw)
368 def get_contents(self, **kw):
369 return apply(self.builder.get_contents, (), kw)
371 def src_suffixes(self, env, args):
372 return self.builder.src_suffixes(env, args)
374 def targets(self, node):
375 """Return the list of targets for this builder instance.
379 def __cmp__(self, other):
380 return cmp(self.__dict__, other.__dict__)
382 class MultiStepBuilder(BuilderBase):
383 """This is a builder subclass that can build targets in
384 multiple steps. The src_builder parameter to the constructor
385 accepts a builder that is called to build sources supplied to
386 this builder. The targets of that first build then become
387 the sources of this builder.
389 If this builder has a src_suffix supplied, then the src_builder
390 builder is NOT invoked if the suffix of a source file matches
393 def __init__(self, src_builder,
399 node_factory = SCons.Node.FS.default_fs.File,
400 target_factory = None,
401 source_factory = None,
404 BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
405 node_factory, target_factory, source_factory,
407 if not SCons.Util.is_List(src_builder):
408 src_builder = [ src_builder ]
409 self.src_builder = src_builder
412 def __call__(self, env, target = None, source = None, **kw):
413 slist = SCons.Node.arg2nodes(source, self.source_factory)
418 sdict = self.sdict[r]
421 self.sdict[r] = sdict
422 for bld in self.src_builder:
423 for suf in bld.src_suffixes(env, kw):
427 path, ext = os.path.splitext(snode.abspath)
428 if sdict.has_key(ext):
431 dictArgs = copy.copy(kw)
432 dictArgs['target'] = [path]
433 dictArgs['source'] = snode
434 dictArgs['env'] = env
435 tgt = apply(src_bld, (), dictArgs)
436 if not SCons.Util.is_List(tgt):
439 # Only supply the builder with sources it is capable
441 tgt = filter(lambda x,
442 suf=self.src_suffixes(env, kw):
443 os.path.splitext(SCons.Util.to_String(x))[1] in \
445 final_sources.extend(tgt)
447 final_sources.append(snode)
449 dictKwArgs['target'] = target
450 dictKwArgs['source'] = final_sources
451 return apply(BuilderBase.__call__,
452 (self, env), dictKwArgs)
454 def src_suffixes(self, env, args):
455 return BuilderBase.src_suffixes(self, env, args) + \
456 reduce(lambda x, y: x + y,
457 map(lambda b, e=env, args=args: b.src_suffixes(e, args),