9c91c0112f19b43bc8b8fe5c200200d4a312622b
[scons.git] / src / engine / SCons / Builder.py
1 """SCons.Builder
2
3 Builder object subsystem.
4
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.
8
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
12 the arguments.
13
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.
18
19 """
20
21 #
22 # Copyright (c) 2001, 2002 Steven Knight
23 #
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:
31 #
32 # The above copyright notice and this permission notice shall be included
33 # in all copies or substantial portions of the Software.
34 #
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.
42 #
43
44 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
45
46
47
48 import os.path
49 import string
50 import copy
51 from SCons.Errors import UserError
52
53 import SCons.Action
54 import SCons.Node
55 import SCons.Node.FS
56 import SCons.Util
57 import SCons.Warnings
58
59 class _Null:
60     pass
61
62 _null = _Null
63
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
69     the source file."""
70     
71     def __init__(self, action_dict):
72         self.action_dict = action_dict
73
74     def src_suffixes(self):
75         return self.action_dict.keys()
76
77     def add_action(self, suffix, action):
78         """Add a suffix-action pair to the mapping.
79         """
80         self.action_dict[suffix] = action
81
82     def __call__(self, target, source, env, for_signature):
83         ext = None
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))
88             ext = my_ext
89
90         if ext is None:
91             raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
92         try:
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]
96         except KeyError:
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)
100
101 def Builder(**kw):
102     """A factory for builder objects."""
103     composite = None
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'])
111         del 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()
116
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'])
124         if not var:
125             raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
126         kw['emitter'] = EmitterProxy(var)
127
128     if kw.has_key('src_builder'):
129         ret = apply(MultiStepBuilder, (), kw)
130     else:
131         ret = apply(BuilderBase, (), kw)
132
133     if composite:
134         ret = CompositeBuilder(ret, composite)
135
136     return ret
137
138 def _init_nodes(builder, env, overrides, tlist, slist):
139     """Initialize lists of target and source nodes with all of
140     the proper Builder information.
141     """
142
143     for s in slist:
144         src_key = s.scanner_key()        # the file suffix
145         scanner = env.get_scanner(src_key)
146         if scanner:
147             s.source_scanner = scanner
148
149     for t in tlist:
150         if t.side_effect:
151             raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
152         if t.has_builder():
153             if t.env != env:
154                 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
155             elif t.overrides != overrides:
156                 raise UserError, "Two different sets of overrides 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)
159
160             if builder.multi:
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)
164                     else:
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)
168
169         t.overrides = overrides
170         t.cwd = SCons.Node.FS.default_fs.getcwd()
171         t.builder_set(builder)
172         t.env_set(env)
173         t.add_source(slist)
174         if builder.scanner:
175             t.target_scanner = builder.scanner
176
177 def _adjust_suffix(suff):
178     if suff and not suff[0] in [ '.', '$' ]:
179         return '.' + suff
180     return suff
181
182 class EmitterProxy:
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
188     emitter."""
189     def __init__(self, var):
190         self.var = SCons.Util.to_String(var)
191
192     def __call__(self, target, source, env):
193         emitter = self.var
194
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
204         return emitter(target, source, env)
205
206     def __cmp__(self, other):
207         return cmp(self.var, other.var)
208
209 class BuilderBase:
210     """Base class for Builders, objects that create output
211     nodes (files) from input nodes (files).
212     """
213
214     def __init__(self,  name = None,
215                         action = None,
216                         prefix = '',
217                         suffix = '',
218                         src_suffix = '',
219                         node_factory = SCons.Node.FS.default_fs.File,
220                         target_factory = None,
221                         source_factory = None,
222                         scanner = None,
223                         emitter = None,
224                         multi = 0):
225         self.name = name
226         self.action = SCons.Action.Action(action)
227         self.multi = multi
228         self.prefix = prefix
229         self.suffix = suffix
230
231         self.set_src_suffix(src_suffix)
232
233         self.target_factory = target_factory or node_factory
234         self.source_factory = source_factory or node_factory
235         self.scanner = scanner
236
237         self.emitter = emitter
238
239     def get_name(self, env):
240         """Attempts to get the name of the Builder.
241
242         If the Builder's name attribute is None, then we will look at
243         the BUILDERS variable of env, expecting it to be a dictionary
244         containing this Builder, and we will return the key of the
245         dictionary."""
246
247         if self.name:
248             return self.name
249         try:
250             index = env['BUILDERS'].values().index(self)
251             return env['BUILDERS'].keys()[index]
252         except (AttributeError, KeyError, ValueError):
253             return str(self.__class__)
254
255     def __cmp__(self, other):
256         return cmp(self.__dict__, other.__dict__)
257
258     def _create_nodes(self, env, overrides, target = None, source = None):
259         """Create and return lists of target and source nodes.
260         """
261         def adjustixes(files, pre, suf):
262             if not files:
263                 return []
264             ret = []
265             if not SCons.Util.is_List(files):
266                 files = [files]
267
268             for f in files:
269                 if SCons.Util.is_String(f):
270                     if pre and f[:len(pre)] != pre:
271                         path, fn = os.path.split(os.path.normpath(f))
272                         f = os.path.join(path, pre + fn)
273                     # Only append a suffix if the file does not have one.
274                     if suf and not SCons.Util.splitext(f)[1]:
275                         if f[-len(suf):] != suf:
276                             f = f + suf
277                 ret.append(f)
278             return ret
279
280         env = env.Override(overrides)
281
282         pre = self.get_prefix(env)
283         suf = self.get_suffix(env)
284         src_suf = self.get_src_suffix(env)
285
286         source = adjustixes(source, None, src_suf)
287         if target is None:
288             s = source[0]
289             if isinstance(s, SCons.Node.Node):
290                 s = os.path.split(str(s))[1]
291             target = [ os.path.splitext(s)[0] + suf ]
292         else:
293             target = adjustixes(target, pre, suf)
294
295         if self.emitter:
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             target, source = self.emitter(target=target, source=source, env=env)
301
302         slist = SCons.Node.arg2nodes(source, self.source_factory)
303         tlist = SCons.Node.arg2nodes(target, self.target_factory)
304
305         return tlist, slist
306
307     def __call__(self, env, target = None, source = _null, **overrides):
308         if source is _null:
309             source = target
310             target = None
311         tlist, slist = self._create_nodes(env, overrides, target, source)
312
313         if len(tlist) == 1:
314             _init_nodes(self, env, overrides, tlist, slist)
315             tlist = tlist[0]
316         else:
317             _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
318
319         return tlist
320
321     def get_actions(self):
322         return self.action.get_actions()
323
324     def get_raw_contents(self, target, source, env):
325         """Fetch the "contents" of the builder's action.
326         """
327         return self.action.get_raw_contents(target, source, env)
328
329     def get_contents(self, target, source, env):
330         """Fetch the "contents" of the builder's action
331         (for signature calculation).
332         """
333         return self.action.get_contents(target, source, env)
334
335     def src_suffixes(self, env):
336         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
337                    self.src_suffix)
338
339     def set_src_suffix(self, src_suffix):
340         if not src_suffix:
341             src_suffix = []
342         elif not SCons.Util.is_List(src_suffix):
343             src_suffix = [ src_suffix ]
344         self.src_suffix = src_suffix
345
346     def get_src_suffix(self, env):
347         """Get the first src_suffix in the list of src_suffixes."""
348         ret = self.src_suffixes(env)
349         if not ret:
350             return ''
351         return ret[0]
352
353     def get_suffix(self, env):
354         return env.subst(_adjust_suffix(self.suffix))
355
356     def get_prefix(self, env):
357         return env.subst(self.prefix)
358
359     def targets(self, node):
360         """Return the list of targets for this builder instance.
361
362         For most normal builders, this is just the supplied node.
363         """
364         return [ node ]
365
366 class ListBuilder(SCons.Util.Proxy):
367     """A Proxy to support building an array of targets (for example,
368     foo.o and foo.h from foo.y) from a single Action execution.
369     """
370
371     def __init__(self, builder, env, tlist):
372         SCons.Util.Proxy.__init__(self, builder)
373         self.builder = builder
374         self.scanner = builder.scanner
375         self.env = env
376         self.tlist = tlist
377         self.multi = builder.multi
378         self.name = "ListBuilder(%s)"%builder.name
379
380     def targets(self, node):
381         """Return the list of targets for this builder instance.
382         """
383         return self.tlist
384
385     def __cmp__(self, other):
386         return cmp(self.__dict__, other.__dict__)
387
388     def get_name(self, env):
389         """Attempts to get the name of the Builder.
390
391         If the Builder's name attribute is None, then we will look at
392         the BUILDERS variable of env, expecting it to be a dictionary
393         containing this Builder, and we will return the key of the
394         dictionary."""
395
396         return "ListBuilder(%s)" % self.builder.get_name(env)
397
398 class MultiStepBuilder(BuilderBase):
399     """This is a builder subclass that can build targets in
400     multiple steps.  The src_builder parameter to the constructor
401     accepts a builder that is called to build sources supplied to
402     this builder.  The targets of that first build then become
403     the sources of this builder.
404
405     If this builder has a src_suffix supplied, then the src_builder
406     builder is NOT invoked if the suffix of a source file matches
407     src_suffix.
408     """
409     def __init__(self,  src_builder,
410                         name = None,
411                         action = None,
412                         prefix = '',
413                         suffix = '',
414                         src_suffix = '',
415                         node_factory = SCons.Node.FS.default_fs.File,
416                         target_factory = None,
417                         source_factory = None,
418                         scanner=None,
419                         emitter=None):
420         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
421                              node_factory, target_factory, source_factory,
422                              scanner, emitter)
423         if not SCons.Util.is_List(src_builder):
424             src_builder = [ src_builder ]
425         self.src_builder = src_builder
426         self.sdict = {}
427         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
428
429     def __call__(self, env, target = None, source = _null, **kw):
430         if source is _null:
431             source = target
432             target = None
433
434         slist = SCons.Node.arg2nodes(source, self.source_factory)
435         final_sources = []
436
437         try:
438             sdict = self.sdict[id(env)]
439         except KeyError:
440             sdict = {}
441             self.sdict[id(env)] = sdict
442             for bld in self.src_builder:
443                 if SCons.Util.is_String(bld):
444                     try:
445                         bld = env['BUILDERS'][bld]
446                     except KeyError:
447                         continue
448                 for suf in bld.src_suffixes(env):
449                     sdict[suf] = bld
450
451         src_suffixes = self.src_suffixes(env)
452
453         for snode in slist:
454             path, ext = SCons.Util.splitext(snode.abspath)
455             if sdict.has_key(ext):
456                 src_bld = sdict[ext]
457                 tgt = apply(src_bld, (env, path, snode), kw)
458                 # Only supply the builder with sources it is capable
459                 # of building.
460                 if SCons.Util.is_List(tgt):
461                     tgt = filter(lambda x, suf=src_suffixes:
462                                  SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
463                                  tgt)
464                 if not SCons.Util.is_List(tgt):
465                     final_sources.append(tgt)
466                 else:
467                     final_sources.extend(tgt)
468             else:
469                 final_sources.append(snode)
470
471         return apply(BuilderBase.__call__,
472                      (self, env, target, final_sources), kw)
473
474     def get_src_builders(self, env):
475         """Return all the src_builders for this Builder.
476
477         This is essentially a recursive descent of the src_builder "tree."
478         """
479         ret = []
480         for bld in self.src_builder:
481             if SCons.Util.is_String(bld):
482                 # All Environments should have a BUILDERS
483                 # variable, so no need to check for it.
484                 try:
485                     bld = env['BUILDERS'][bld]
486                 except KeyError:
487                     continue
488             ret.append(bld)
489         return ret
490
491     def src_suffixes(self, env):
492         """Return a list of the src_suffix attributes for all
493         src_builders of this Builder.
494         """
495         try:
496             return self.cached_src_suffixes[id(env)]
497         except KeyError:
498             suffixes = BuilderBase.src_suffixes(self, env)
499             for builder in self.get_src_builders(env):
500                 suffixes.extend(builder.src_suffixes(env))
501             self.cached_src_suffixes[id(env)] = suffixes
502             return suffixes
503
504 class CompositeBuilder(SCons.Util.Proxy):
505     """A Builder Proxy whose main purpose is to always have
506     a DictCmdGenerator as its action, and to provide access
507     to the DictCmdGenerator's add_action() method.
508     """
509
510     def __init__(self, builder, cmdgen):
511         SCons.Util.Proxy.__init__(self, builder)
512
513         # cmdgen should always be an instance of DictCmdGenerator.
514         self.cmdgen = cmdgen
515         self.builder = builder
516
517     def add_action(self, suffix, action):
518         self.cmdgen.add_action(suffix, action)
519         self.set_src_suffix(self.cmdgen.src_suffixes())
520         
521     def __cmp__(self, other):
522         return cmp(self.__dict__, other.__dict__)