Finish the Action refactoring by removing Builder.execute() methods and moving except...
[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.builder is not None:
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             target = map(lambda x, s=suf:
289                                 os.path.splitext(os.path.split(str(x))[1])[0] + s,
290                          source)
291         else:
292             target = adjustixes(target, pre, suf)
293
294         if self.emitter:
295             # pass the targets and sources to the emitter as strings
296             # rather than nodes since str(node) doesn't work
297             # properly from any directory other than the top directory,
298             # and emitters are called "in" the SConscript directory:
299             target, source = self.emitter(target=target, source=source, env=env)
300
301         slist = SCons.Node.arg2nodes(source, self.source_factory)
302         tlist = SCons.Node.arg2nodes(target, self.target_factory)
303
304         return tlist, slist
305
306     def __call__(self, env, target = None, source = _null, **overrides):
307         if source is _null:
308             source = target
309             target = None
310         tlist, slist = self._create_nodes(env, overrides, target, source)
311
312         if len(tlist) == 1:
313             _init_nodes(self, env, overrides, tlist, slist)
314             tlist = tlist[0]
315         else:
316             _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
317
318         return tlist
319
320     def get_actions(self):
321         return self.action.get_actions()
322
323     def get_raw_contents(self, target, source, env):
324         """Fetch the "contents" of the builder's action.
325         """
326         return self.action.get_raw_contents(target, source, env)
327
328     def get_contents(self, target, source, env):
329         """Fetch the "contents" of the builder's action
330         (for signature calculation).
331         """
332         return self.action.get_contents(target, source, env)
333
334     def src_suffixes(self, env):
335         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
336                    self.src_suffix)
337
338     def set_src_suffix(self, src_suffix):
339         if not src_suffix:
340             src_suffix = []
341         elif not SCons.Util.is_List(src_suffix):
342             src_suffix = [ src_suffix ]
343         self.src_suffix = src_suffix
344
345     def get_src_suffix(self, env):
346         """Get the first src_suffix in the list of src_suffixes."""
347         ret = self.src_suffixes(env)
348         if not ret:
349             return ''
350         return ret[0]
351
352     def get_suffix(self, env):
353         return env.subst(_adjust_suffix(self.suffix))
354
355     def get_prefix(self, env):
356         return env.subst(self.prefix)
357
358     def targets(self, node):
359         """Return the list of targets for this builder instance.
360
361         For most normal builders, this is just the supplied node.
362         """
363         return [ node ]
364
365 class ListBuilder(SCons.Util.Proxy):
366     """A Proxy to support building an array of targets (for example,
367     foo.o and foo.h from foo.y) from a single Action execution.
368     """
369
370     def __init__(self, builder, env, tlist):
371         SCons.Util.Proxy.__init__(self, builder)
372         self.builder = builder
373         self.scanner = builder.scanner
374         self.env = env
375         self.tlist = tlist
376         self.multi = builder.multi
377         self.name = "ListBuilder(%s)"%builder.name
378
379     def targets(self, node):
380         """Return the list of targets for this builder instance.
381         """
382         return self.tlist
383
384     def __cmp__(self, other):
385         return cmp(self.__dict__, other.__dict__)
386
387     def get_name(self, env):
388         """Attempts to get the name of the Builder.
389
390         If the Builder's name attribute is None, then we will look at
391         the BUILDERS variable of env, expecting it to be a dictionary
392         containing this Builder, and we will return the key of the
393         dictionary."""
394
395         return "ListBuilder(%s)" % self.builder.get_name(env)
396
397 class MultiStepBuilder(BuilderBase):
398     """This is a builder subclass that can build targets in
399     multiple steps.  The src_builder parameter to the constructor
400     accepts a builder that is called to build sources supplied to
401     this builder.  The targets of that first build then become
402     the sources of this builder.
403
404     If this builder has a src_suffix supplied, then the src_builder
405     builder is NOT invoked if the suffix of a source file matches
406     src_suffix.
407     """
408     def __init__(self,  src_builder,
409                         name = None,
410                         action = None,
411                         prefix = '',
412                         suffix = '',
413                         src_suffix = '',
414                         node_factory = SCons.Node.FS.default_fs.File,
415                         target_factory = None,
416                         source_factory = None,
417                         scanner=None,
418                         emitter=None):
419         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
420                              node_factory, target_factory, source_factory,
421                              scanner, emitter)
422         if not SCons.Util.is_List(src_builder):
423             src_builder = [ src_builder ]
424         self.src_builder = src_builder
425         self.sdict = {}
426         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
427
428     def __call__(self, env, target = None, source = None, **kw):
429         slist = SCons.Node.arg2nodes(source, self.source_factory)
430         final_sources = []
431
432         try:
433             sdict = self.sdict[id(env)]
434         except KeyError:
435             sdict = {}
436             self.sdict[id(env)] = sdict
437             for bld in self.src_builder:
438                 if SCons.Util.is_String(bld):
439                     try:
440                         bld = env['BUILDERS'][bld]
441                     except KeyError:
442                         continue
443                 for suf in bld.src_suffixes(env):
444                     sdict[suf] = bld
445
446         src_suffixes = self.src_suffixes(env)
447
448         for snode in slist:
449             path, ext = SCons.Util.splitext(snode.abspath)
450             if sdict.has_key(ext):
451                 src_bld = sdict[ext]
452                 tgt = apply(src_bld, (env, path, snode), kw)
453                 # Only supply the builder with sources it is capable
454                 # of building.
455                 if SCons.Util.is_List(tgt):
456                     tgt = filter(lambda x, suf=src_suffixes:
457                                  SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
458                                  tgt)
459                 if not SCons.Util.is_List(tgt):
460                     final_sources.append(tgt)
461                 else:
462                     final_sources.extend(tgt)
463             else:
464                 final_sources.append(snode)
465
466         return apply(BuilderBase.__call__,
467                      (self, env, target, final_sources), kw)
468
469     def get_src_builders(self, env):
470         """Return all the src_builders for this Builder.
471
472         This is essentially a recursive descent of the src_builder "tree."
473         """
474         ret = []
475         for bld in self.src_builder:
476             if SCons.Util.is_String(bld):
477                 # All Environments should have a BUILDERS
478                 # variable, so no need to check for it.
479                 try:
480                     bld = env['BUILDERS'][bld]
481                 except KeyError:
482                     continue
483             ret.append(bld)
484         return ret
485
486     def src_suffixes(self, env):
487         """Return a list of the src_suffix attributes for all
488         src_builders of this Builder.
489         """
490         try:
491             return self.cached_src_suffixes[id(env)]
492         except KeyError:
493             suffixes = BuilderBase.src_suffixes(self, env)
494             for builder in self.get_src_builders(env):
495                 suffixes.extend(builder.src_suffixes(env))
496             self.cached_src_suffixes[id(env)] = suffixes
497             return suffixes
498
499 class CompositeBuilder(SCons.Util.Proxy):
500     """A Builder Proxy whose main purpose is to always have
501     a DictCmdGenerator as its action, and to provide access
502     to the DictCmdGenerator's add_action() method.
503     """
504
505     def __init__(self, builder, cmdgen):
506         SCons.Util.Proxy.__init__(self, builder)
507
508         # cmdgen should always be an instance of DictCmdGenerator.
509         self.cmdgen = cmdgen
510         self.builder = builder
511
512     def add_action(self, suffix, action):
513         self.cmdgen.add_action(suffix, action)
514         self.set_src_suffix(self.cmdgen.src_suffixes())
515         
516     def __cmp__(self, other):
517         return cmp(self.__dict__, other.__dict__)