Add options to investigate object creation and memory consumption.
[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__
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 import os.path
47 import UserDict
48
49 import SCons.Action
50 from SCons.Debug import logInstanceCreation
51 from SCons.Errors import InternalError, UserError
52 import SCons.Executor
53 import SCons.Node.FS
54 import SCons.Util
55 import SCons.Warnings
56
57 class _Null:
58     pass
59
60 _null = _Null
61
62 class DictCmdGenerator(SCons.Util.Selector):
63     """This is a callable class that can be used as a
64     command generator function.  It holds on to a dictionary
65     mapping file suffixes to Actions.  It uses that dictionary
66     to return the proper action based on the file suffix of
67     the source file."""
68
69     def src_suffixes(self):
70         return self.keys()
71
72     def add_action(self, suffix, action):
73         """Add a suffix-action pair to the mapping.
74         """
75         self[suffix] = action
76
77     def __call__(self, target, source, env, for_signature):
78         ext = None
79         for src in map(str, source):
80             my_ext = SCons.Util.splitext(src)[1]
81             if ext and my_ext != ext:
82                 raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" % (repr(map(str, target)), src, ext, my_ext))
83             ext = my_ext
84
85         if not ext:
86             raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source))))
87
88         try:
89             ret = SCons.Util.Selector.__call__(self, env, source)
90         except KeyError, e:
91             raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e[0], e[1], e[2]))
92         if ret is None:
93             raise UserError("While building `%s': Don't know how to build a file with suffix `%s'." % (repr(map(str, target)), ext))
94         return ret
95
96 class CallableSelector(SCons.Util.Selector):
97     """A callable dictionary that will, in turn, call the value it
98     finds if it can."""
99     def __call__(self, env, source):
100         value = SCons.Util.Selector.__call__(self, env, source)
101         if callable(value):
102             value = value(env, source)
103         return value
104
105 class DictEmitter(SCons.Util.Selector):
106     """A callable dictionary that maps file suffixes to emitters.
107     When called, it finds the right emitter in its dictionary for the
108     suffix of the first source file, and calls that emitter to get the
109     right lists of targets and sources to return.  If there's no emitter
110     for the suffix in its dictionary, the original target and source are
111     returned.
112     """
113     def __call__(self, target, source, env):
114         emitter = SCons.Util.Selector.__call__(self, env, source)
115         if emitter:
116             target, source = emitter(target, source, env)
117         return (target, source)
118
119 def Builder(**kw):
120     """A factory for builder objects."""
121     composite = None
122     if kw.has_key('generator'):
123         if kw.has_key('action'):
124             raise UserError, "You must not specify both an action and a generator."
125         kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
126         del kw['generator']
127     elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
128         composite = DictCmdGenerator(kw['action'])
129         kw['action'] = SCons.Action.CommandGenerator(composite)
130         kw['src_suffix'] = composite.src_suffixes()
131
132     if kw.has_key('emitter'):
133         emitter = kw['emitter']
134         if SCons.Util.is_String(emitter):
135             # This allows users to pass in an Environment
136             # variable reference (like "$FOO") as an emitter.
137             # We will look in that Environment variable for
138             # a callable to use as the actual emitter.
139             var = SCons.Util.get_environment_var(emitter)
140             if not var:
141                 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter
142             kw['emitter'] = EmitterProxy(var)
143         elif SCons.Util.is_Dict(emitter):
144             kw['emitter'] = DictEmitter(emitter)
145
146     if kw.has_key('src_builder'):
147         ret = apply(MultiStepBuilder, (), kw)
148     else:
149         ret = apply(BuilderBase, (), kw)
150
151     if not composite is None:
152         ret = CompositeBuilder(ret, composite)
153
154     return ret
155
156 def _init_nodes(builder, env, overrides, tlist, slist):
157     """Initialize lists of target and source nodes with all of
158     the proper Builder information.
159     """
160
161     # First, figure out if there are any errors in the way the targets
162     # were specified.
163     for t in tlist:
164         if t.side_effect:
165             raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
166         if t.has_builder():
167             if t.env != env:
168                 t_contents = t.builder.action.get_contents(tlist, slist, t.env)
169                 contents = t.builder.action.get_contents(tlist, slist, env)
170
171                 if t_contents == contents:
172                     SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning,
173                                         "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s"%(str(t), t.builder.action.strfunction(tlist, slist, t.env)))
174
175                 else:
176                     raise UserError, "Two environments with different actions were specified for the same target: %s"%str(t)
177
178             elif t.overrides != overrides:
179                 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
180
181             elif builder.scanner and t.target_scanner and builder.scanner != t.target_scanner:
182                 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
183
184             if builder.multi:
185                 if t.builder != builder:
186                     if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
187                         raise UserError, "Two different target sets have a target in common: %s"%str(t)
188                     else:
189                         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))
190             elif t.sources != slist:
191                 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
192
193     # The targets are fine, so find or make the appropriate Executor to
194     # build this particular list of targets from this particular list of
195     # sources.
196     executor = None
197     if builder.multi:
198         try:
199             executor = tlist[0].get_executor(create = 0)
200         except AttributeError:
201             pass
202         else:
203             executor.add_sources(slist)
204     if executor is None:
205         executor = SCons.Executor.Executor(builder,
206                                            tlist[0].generate_build_env(env),
207                                            overrides,
208                                            tlist,
209                                            slist)
210
211     # Now set up the relevant information in the target Nodes themselves.
212     for t in tlist:
213         t.overrides = overrides
214         t.cwd = SCons.Node.FS.default_fs.getcwd()
215         t.builder_set(builder)
216         t.env_set(env)
217         t.add_source(slist)
218         t.set_executor(executor)
219         if builder.scanner:
220             t.target_scanner = builder.scanner
221         if not t.source_scanner:
222             t.source_scanner = env.get_scanner(t.scanner_key())
223
224     # Last, add scanners from the Environment to the source Nodes.
225     for s in slist:
226         if not s.source_scanner:
227             s.source_scanner = env.get_scanner(s.scanner_key())
228
229 class EmitterProxy:
230     """This is a callable class that can act as a
231     Builder emitter.  It holds on to a string that
232     is a key into an Environment dictionary, and will
233     look there at actual build time to see if it holds
234     a callable.  If so, we will call that as the actual
235     emitter."""
236     def __init__(self, var):
237         self.var = SCons.Util.to_String(var)
238
239     def __call__(self, target, source, env):
240         emitter = self.var
241
242         # Recursively substitute the variable.
243         # We can't use env.subst() because it deals only
244         # in strings.  Maybe we should change that?
245         while SCons.Util.is_String(emitter) and \
246               env.has_key(emitter):
247             emitter = env[emitter]
248         if not callable(emitter):
249             return (target, source)
250
251         return emitter(target, source, env)
252
253     def __cmp__(self, other):
254         return cmp(self.var, other.var)
255
256 class BuilderBase:
257     """Base class for Builders, objects that create output
258     nodes (files) from input nodes (files).
259     """
260
261     def __init__(self,  action = None,
262                         prefix = '',
263                         suffix = '',
264                         src_suffix = '',
265                         node_factory = SCons.Node.FS.default_fs.File,
266                         target_factory = None,
267                         source_factory = None,
268                         scanner = None,
269                         emitter = None,
270                         multi = 0,
271                         env = None,
272                         overrides = {}):
273         if __debug__: logInstanceCreation(self, 'BuilderBase')
274         self.action = SCons.Action.Action(action)
275         self.multi = multi
276         if SCons.Util.is_Dict(prefix):
277             prefix = CallableSelector(prefix)
278         self.prefix = prefix
279         if SCons.Util.is_Dict(suffix):
280             suffix = CallableSelector(suffix)
281         self.suffix = suffix
282         self.env = env
283         self.overrides = overrides
284
285         self.set_src_suffix(src_suffix)
286
287         self.target_factory = target_factory or node_factory
288         self.source_factory = source_factory or node_factory
289         self.scanner = scanner
290
291         self.emitter = emitter
292
293     def __nonzero__(self):
294         raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
295
296     def get_name(self, env):
297         """Attempts to get the name of the Builder.
298
299         Look at the BUILDERS variable of env, expecting it to be a
300         dictionary containing this Builder, and return the key of the
301         dictionary."""
302
303         try:
304             index = env['BUILDERS'].values().index(self)
305             return env['BUILDERS'].keys()[index]
306         except (AttributeError, KeyError, ValueError):
307             return str(self.__class__)
308
309     def __cmp__(self, other):
310         return cmp(self.__dict__, other.__dict__)
311
312     def splitext(self, path):
313         return SCons.Util.splitext(path)
314
315     def _create_nodes(self, env, overrides, target = None, source = None):
316         """Create and return lists of target and source nodes.
317         """
318         def _adjustixes(files, pre, suf):
319             if not files:
320                 return []
321             result = []
322             if not SCons.Util.is_List(files):
323                 files = [files]
324
325             for f in files:
326                 if SCons.Util.is_String(f):
327                     f = SCons.Util.adjustixes(f, pre, suf)
328                 result.append(f)
329             return result
330
331         env = env.Override(overrides)
332
333         src_suf = self.get_src_suffix(env)
334
335         source = _adjustixes(source, None, src_suf)
336         slist = env.arg2nodes(source, self.source_factory)
337
338         pre = self.get_prefix(env, slist)
339         suf = self.get_suffix(env, slist)
340
341         if target is None:
342             try:
343                 t_from_s = slist[0].target_from_source
344             except AttributeError:
345                 raise UserError("Do not know how to create a target from source `%s'" % slist[0])
346             tlist = [ t_from_s(pre, suf, self.splitext) ]
347         else:
348             target = _adjustixes(target, pre, suf)
349             tlist = env.arg2nodes(target, self.target_factory)
350
351         if self.emitter:
352             # The emitter is going to do str(node), but because we're
353             # being called *from* a builder invocation, the new targets
354             # don't yet have a builder set on them and will look like
355             # source files.  Fool the emitter's str() calls by setting
356             # up a temporary builder on the new targets.
357             new_targets = []
358             for t in tlist:
359                 if not t.is_derived():
360                     t.builder = self
361                     new_targets.append(t)
362
363             target, source = self.emitter(target=tlist, source=slist, env=env)
364
365             # Now delete the temporary builders that we attached to any
366             # new targets, so that _init_nodes() doesn't do weird stuff
367             # to them because it thinks they already have builders.
368             for t in new_targets:
369                 if t.builder is self:
370                     # Only delete the temporary builder if the emitter
371                     # didn't change it on us.
372                     t.builder = None
373
374             # Have to call arg2nodes yet again, since it is legal for
375             # emitters to spit out strings as well as Node instances.
376             slist = env.arg2nodes(source, self.source_factory)
377             tlist = env.arg2nodes(target, self.target_factory)
378
379         return tlist, slist
380
381     def __call__(self, env, target = None, source = _null, **overrides):
382         if source is _null:
383             source = target
384             target = None
385         tlist, slist = self._create_nodes(env, overrides, target, source)
386
387         if len(tlist) == 1:
388             _init_nodes(self, env, overrides, tlist, slist)
389             tlist = tlist[0]
390         else:
391             _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
392
393         return tlist
394
395     def adjust_suffix(self, suff):
396         if suff and not suff[0] in [ '.', '_', '$' ]:
397             return '.' + suff
398         return suff
399
400     def get_prefix(self, env, sources=[]):
401         prefix = self.prefix
402         if callable(prefix):
403             prefix = prefix(env, sources)
404         return env.subst(prefix)
405
406     def get_suffix(self, env, sources=[]):
407         suffix = self.suffix
408         if callable(suffix):
409             suffix = suffix(env, sources)
410         else:
411             suffix = self.adjust_suffix(suffix)
412         return env.subst(suffix)
413
414     def src_suffixes(self, env):
415         return map(lambda x, s=self, e=env: e.subst(s.adjust_suffix(x)),
416                    self.src_suffix)
417
418     def set_src_suffix(self, src_suffix):
419         if not src_suffix:
420             src_suffix = []
421         elif not SCons.Util.is_List(src_suffix):
422             src_suffix = [ src_suffix ]
423         self.src_suffix = src_suffix
424
425     def get_src_suffix(self, env):
426         """Get the first src_suffix in the list of src_suffixes."""
427         ret = self.src_suffixes(env)
428         if not ret:
429             return ''
430         return ret[0]
431
432     def targets(self, node):
433         """Return the list of targets for this builder instance.
434
435         For most normal builders, this is just the supplied node.
436         """
437         return [ node ]
438
439     def add_emitter(self, suffix, emitter):
440         """Add a suffix-emitter mapping to this Builder.
441
442         This assumes that emitter has been initialized with an
443         appropriate dictionary type, and will throw a TypeError if
444         not, so the caller is responsible for knowing that this is an
445         appropriate method to call for the Builder in question.
446         """
447         self.emitter[suffix] = emitter
448
449 class ListBuilder(SCons.Util.Proxy):
450     """A Proxy to support building an array of targets (for example,
451     foo.o and foo.h from foo.y) from a single Action execution.
452     """
453
454     def __init__(self, builder, env, tlist):
455         if __debug__: logInstanceCreation(self)
456         SCons.Util.Proxy.__init__(self, builder)
457         self.builder = builder
458         self.scanner = builder.scanner
459         self.env = env
460         self.tlist = tlist
461         self.multi = builder.multi
462
463     def targets(self, node):
464         """Return the list of targets for this builder instance.
465         """
466         return self.tlist
467
468     def __cmp__(self, other):
469         return cmp(self.__dict__, other.__dict__)
470
471     def get_name(self, env):
472         """Attempts to get the name of the Builder."""
473
474         return "ListBuilder(%s)" % self.builder.get_name(env)
475
476 class MultiStepBuilder(BuilderBase):
477     """This is a builder subclass that can build targets in
478     multiple steps.  The src_builder parameter to the constructor
479     accepts a builder that is called to build sources supplied to
480     this builder.  The targets of that first build then become
481     the sources of this builder.
482
483     If this builder has a src_suffix supplied, then the src_builder
484     builder is NOT invoked if the suffix of a source file matches
485     src_suffix.
486     """
487     def __init__(self,  src_builder,
488                         action = None,
489                         prefix = '',
490                         suffix = '',
491                         src_suffix = '',
492                         node_factory = SCons.Node.FS.default_fs.File,
493                         target_factory = None,
494                         source_factory = None,
495                         scanner=None,
496                         emitter=None):
497         if __debug__: logInstanceCreation(self)
498         BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
499                              node_factory, target_factory, source_factory,
500                              scanner, emitter)
501         if not SCons.Util.is_List(src_builder):
502             src_builder = [ src_builder ]
503         self.src_builder = src_builder
504         self.sdict = {}
505         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
506
507     def __call__(self, env, target = None, source = _null, **kw):
508         if source is _null:
509             source = target
510             target = None
511
512         slist = env.arg2nodes(source, self.source_factory)
513         final_sources = []
514
515         try:
516             sdict = self.sdict[id(env)]
517         except KeyError:
518             sdict = {}
519             self.sdict[id(env)] = sdict
520             for bld in self.src_builder:
521                 if SCons.Util.is_String(bld):
522                     try:
523                         bld = env['BUILDERS'][bld]
524                     except KeyError:
525                         continue
526                 for suf in bld.src_suffixes(env):
527                     sdict[suf] = bld
528
529         src_suffixes = self.src_suffixes(env)
530
531         for snode in slist:
532             base, ext = self.splitext(str(snode))
533             if sdict.has_key(ext):
534                 tgt = apply(sdict[ext], (env, None, snode), kw)
535                 # Only supply the builder with sources it is capable
536                 # of building.
537                 if SCons.Util.is_List(tgt):
538                     tgt = filter(lambda x, self=self, suf=src_suffixes:
539                                  self.splitext(SCons.Util.to_String(x))[1] in suf,
540                                  tgt)
541                 if not SCons.Util.is_List(tgt):
542                     final_sources.append(tgt)
543                 else:
544                     final_sources.extend(tgt)
545             else:
546                 final_sources.append(snode)
547
548         return apply(BuilderBase.__call__,
549                      (self, env, target, final_sources), kw)
550
551     def get_src_builders(self, env):
552         """Return all the src_builders for this Builder.
553
554         This is essentially a recursive descent of the src_builder "tree."
555         """
556         ret = []
557         for bld in self.src_builder:
558             if SCons.Util.is_String(bld):
559                 # All Environments should have a BUILDERS
560                 # variable, so no need to check for it.
561                 try:
562                     bld = env['BUILDERS'][bld]
563                 except KeyError:
564                     continue
565             ret.append(bld)
566         return ret
567
568     def src_suffixes(self, env):
569         """Return a list of the src_suffix attributes for all
570         src_builders of this Builder.
571         """
572         try:
573             return self.cached_src_suffixes[id(env)]
574         except KeyError:
575             suffixes = BuilderBase.src_suffixes(self, env)
576             for builder in self.get_src_builders(env):
577                 suffixes.extend(builder.src_suffixes(env))
578             self.cached_src_suffixes[id(env)] = suffixes
579             return suffixes
580
581 class CompositeBuilder(SCons.Util.Proxy):
582     """A Builder Proxy whose main purpose is to always have
583     a DictCmdGenerator as its action, and to provide access
584     to the DictCmdGenerator's add_action() method.
585     """
586
587     def __init__(self, builder, cmdgen):
588         if __debug__: logInstanceCreation(self)
589         SCons.Util.Proxy.__init__(self, builder)
590
591         # cmdgen should always be an instance of DictCmdGenerator.
592         self.cmdgen = cmdgen
593         self.builder = builder
594
595     def add_action(self, suffix, action):
596         self.cmdgen.add_action(suffix, action)
597         self.set_src_suffix(self.cmdgen.src_suffixes())
598
599     def __cmp__(self, other):
600         return cmp(self.__dict__, other.__dict__)