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