Really support strfunction for all Action subclasses by refactoring the interface...
[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 target Node (file) from source Nodes
7 (files), and how to create those dependencies for tracking.
8
9 The main entry point here is the Builder() factory method.  This provides
10 a procedural interface that creates the right underlying Builder object
11 based on the keyword arguments supplied and the types of the arguments.
12
13 The goal is for this external interface to be simple enough that the
14 vast majority of users can create new Builders as necessary to support
15 building new types of files in their configurations, without having to
16 dive any deeper into this subsystem.
17
18 The base class here is BuilderBase.  This is a concrete base class which
19 does, in fact, represent most Builder objects that we (or users) create.
20
21 There is (at present) one subclasses:
22
23     MultiStepBuilder
24
25         This is a Builder that knows how to "chain" Builders so that
26         users can specify a source file that requires multiple steps
27         to turn into a target file.  A canonical example is building a
28         program from yacc input file, which requires invoking a builder
29         to turn the .y into a .c, the .c into a .o, and the .o into an
30         executable program.
31
32 There is also two proxies that look like Builders:
33
34     CompositeBuilder
35
36         This proxies for a Builder with an action that is actually a
37         dictionary that knows how to map file suffixes to a specific
38         action.  This is so that we can invoke different actions
39         (compilers, compile options) for different flavors of source
40         files.
41
42     ListBuilder
43
44         This proxies for a Builder *invocation* where the target
45         is a list of files, not a single file.
46
47 Builders and their proxies have the following public interface methods
48 used by other modules:
49
50     __call__()
51         THE public interface.  Calling a Builder object (with the
52         use of internal helper methods) sets up the target and source
53         dependencies, appropriate mapping to a specific action, and the
54         environment manipulation necessary for overridden construction
55         variable.  This also takes care of warning about possible mistakes
56         in keyword arguments.
57
58     targets()
59         Returns the list of targets for a specific builder instance.
60
61     add_emitter()
62         Adds an emitter for a specific file suffix, used by some Tool
63         modules to specify that (for example) a yacc invocation on a .y
64         can create a .h *and* a .c file.
65
66     add_action()
67         Adds an action for a specific file suffix, heavily used by
68         Tool modules to add their specific action(s) for turning
69         a source file into an object file to the global static
70         and shared object file Builders.
71
72 There are the following methods for internal use within this module:
73
74     _execute()
75         The internal method that handles the heavily lifting when a
76         Builder is called.  This is used so that the __call__() methods
77         can set up warning about possible mistakes in keyword-argument
78         overrides, and *then* execute all of the steps necessary so that
79         the warnings only occur once.
80
81     get_name()
82         Returns the Builder's name within a specific Environment,
83         primarily used to try to return helpful information in error
84         messages.
85
86     adjust_suffix()
87     get_prefix()
88     get_suffix()
89     get_src_suffix()
90     set_src_suffix()
91         Miscellaneous stuff for handling the prefix and suffix
92         manipulation we use in turning source file names into target
93         file names.
94
95 """
96
97 #
98 # __COPYRIGHT__
99 #
100 # Permission is hereby granted, free of charge, to any person obtaining
101 # a copy of this software and associated documentation files (the
102 # "Software"), to deal in the Software without restriction, including
103 # without limitation the rights to use, copy, modify, merge, publish,
104 # distribute, sublicense, and/or sell copies of the Software, and to
105 # permit persons to whom the Software is furnished to do so, subject to
106 # the following conditions:
107 #
108 # The above copyright notice and this permission notice shall be included
109 # in all copies or substantial portions of the Software.
110 #
111 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
112 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
113 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
114 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
115 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
116 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
117 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
118 #
119
120 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
121
122 import os.path
123 import UserDict
124 import UserList
125
126 import SCons.Action
127 from SCons.Debug import logInstanceCreation
128 from SCons.Errors import InternalError, UserError
129 import SCons.Executor
130 import SCons.Node.FS
131 import SCons.Util
132 import SCons.Warnings
133
134 class _Null:
135     pass
136
137 _null = _Null
138
139 class DictCmdGenerator(SCons.Util.Selector):
140     """This is a callable class that can be used as a
141     command generator function.  It holds on to a dictionary
142     mapping file suffixes to Actions.  It uses that dictionary
143     to return the proper action based on the file suffix of
144     the source file."""
145
146     def src_suffixes(self):
147         return self.keys()
148
149     def add_action(self, suffix, action):
150         """Add a suffix-action pair to the mapping.
151         """
152         self[suffix] = action
153
154     def __call__(self, target, source, env, for_signature):
155         ext = None
156         for src in map(str, source):
157             my_ext = SCons.Util.splitext(src)[1]
158             if ext and my_ext != ext:
159                 raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" % (repr(map(str, target)), src, ext, my_ext))
160             ext = my_ext
161
162         if not ext:
163             raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source))))
164
165         try:
166             ret = SCons.Util.Selector.__call__(self, env, source)
167         except KeyError, e:
168             raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e[0], e[1], e[2]))
169         if ret is None:
170             raise UserError("While building `%s': Don't know how to build a file with suffix `%s'." % (repr(map(str, target)), ext))
171         return ret
172
173 class CallableSelector(SCons.Util.Selector):
174     """A callable dictionary that will, in turn, call the value it
175     finds if it can."""
176     def __call__(self, env, source):
177         value = SCons.Util.Selector.__call__(self, env, source)
178         if callable(value):
179             value = value(env, source)
180         return value
181
182 class DictEmitter(SCons.Util.Selector):
183     """A callable dictionary that maps file suffixes to emitters.
184     When called, it finds the right emitter in its dictionary for the
185     suffix of the first source file, and calls that emitter to get the
186     right lists of targets and sources to return.  If there's no emitter
187     for the suffix in its dictionary, the original target and source are
188     returned.
189     """
190     def __call__(self, target, source, env):
191         emitter = SCons.Util.Selector.__call__(self, env, source)
192         if emitter:
193             target, source = emitter(target, source, env)
194         return (target, source)
195
196 class ListEmitter(UserList.UserList):
197     """A callable list of emitters that calls each in sequence,
198     returning the result.
199     """
200     def __call__(self, target, source, env):
201         for e in self.data:
202             target, source = e(target, source, env)
203         return (target, source)
204
205 # These are a common errors when calling a Builder;
206 # they are similar to the 'target' and 'source' keyword args to builders,
207 # so we issue warnings when we see them.  The warnings can, of course,
208 # be disabled.
209 misleading_keywords = {
210     'targets'   : 'target',
211     'sources'   : 'source',
212 }
213
214 class OverrideWarner(UserDict.UserDict):
215     """A class for warning about keyword arguments that we use as
216     overrides in a Builder call.
217
218     This class exists to handle the fact that a single MultiStepBuilder
219     call can actually invoke multiple builders as a result of a single
220     user-level Builder call.  This class only emits the warnings once,
221     no matter how many Builders are invoked.
222     """
223     def __init__(self, dict):
224         UserDict.UserDict.__init__(self, dict)
225         self.already_warned = None
226     def warn(self):
227         if self.already_warned:
228             return
229         for k in self.keys():
230             try:
231                 alt = misleading_keywords[k]
232             except KeyError:
233                 pass
234             else:
235                 SCons.Warnings.warn(SCons.Warnings.MisleadingKeywordsWarning,
236                                     "Did you mean to use `%s' instead of `%s'?" % (alt, k))
237         self.already_warned = 1
238
239 def Builder(**kw):
240     """A factory for builder objects."""
241     composite = None
242     if kw.has_key('generator'):
243         if kw.has_key('action'):
244             raise UserError, "You must not specify both an action and a generator."
245         kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
246         del kw['generator']
247     elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
248         composite = DictCmdGenerator(kw['action'])
249         kw['action'] = SCons.Action.CommandGenerator(composite)
250         kw['src_suffix'] = composite.src_suffixes()
251
252     if kw.has_key('emitter'):
253         emitter = kw['emitter']
254         if SCons.Util.is_String(emitter):
255             # This allows users to pass in an Environment
256             # variable reference (like "$FOO") as an emitter.
257             # We will look in that Environment variable for
258             # a callable to use as the actual emitter.
259             var = SCons.Util.get_environment_var(emitter)
260             if not var:
261                 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter
262             kw['emitter'] = EmitterProxy(var)
263         elif SCons.Util.is_Dict(emitter):
264             kw['emitter'] = DictEmitter(emitter)
265         elif SCons.Util.is_List(emitter):
266             kw['emitter'] = ListEmitter(emitter)
267
268     if kw.has_key('src_builder'):
269         ret = apply(MultiStepBuilder, (), kw)
270     else:
271         ret = apply(BuilderBase, (), kw)
272
273     if not composite is None:
274         ret = CompositeBuilder(ret, composite)
275
276     return ret
277
278 def _init_nodes(builder, env, overrides, tlist, slist):
279     """Initialize lists of target and source nodes with all of
280     the proper Builder information.
281     """
282
283     # First, figure out if there are any errors in the way the targets
284     # were specified.
285     for t in tlist:
286         if t.side_effect:
287             raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
288         if t.has_builder():
289             if not t.env is env:
290                 t_contents = t.builder.action.get_contents(tlist, slist, t.env)
291                 contents = t.builder.action.get_contents(tlist, slist, env)
292
293                 if t_contents == contents:
294                     SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning,
295                                         "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)))
296
297                 else:
298                     raise UserError, "Two environments with different actions were specified for the same target: %s"%str(t)
299
300             elif t.overrides != overrides:
301                 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
302
303             elif builder.scanner and t.target_scanner and builder.scanner != t.target_scanner:
304                 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
305
306             if builder.multi:
307                 if t.builder != builder:
308                     if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
309                         raise UserError, "Two different target sets have a target in common: %s"%str(t)
310                     else:
311                         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))
312             elif t.sources != slist:
313                 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
314
315     # The targets are fine, so find or make the appropriate Executor to
316     # build this particular list of targets from this particular list of
317     # sources.
318     executor = None
319     if builder.multi:
320         try:
321             executor = tlist[0].get_executor(create = 0)
322         except AttributeError:
323             pass
324         else:
325             executor.add_sources(slist)
326     if executor is None:
327         executor = SCons.Executor.Executor(builder.action,
328                                            env or builder.env,
329                                            [builder.overrides, overrides],
330                                            tlist,
331                                            slist)
332
333     # Now set up the relevant information in the target Nodes themselves.
334     for t in tlist:
335         t.overrides = overrides
336         t.cwd = SCons.Node.FS.default_fs.getcwd()
337         t.builder_set(builder)
338         t.env_set(env)
339         t.add_source(slist)
340         t.set_executor(executor)
341         if builder.scanner:
342             t.target_scanner = builder.scanner
343         if not t.source_scanner:
344             t.source_scanner = env.get_scanner(t.scanner_key())
345
346     # Last, add scanners from the Environment to the source Nodes.
347     for s in slist:
348         if not s.source_scanner:
349             s.source_scanner = env.get_scanner(s.scanner_key())
350
351 class EmitterProxy:
352     """This is a callable class that can act as a
353     Builder emitter.  It holds on to a string that
354     is a key into an Environment dictionary, and will
355     look there at actual build time to see if it holds
356     a callable.  If so, we will call that as the actual
357     emitter."""
358     def __init__(self, var):
359         self.var = SCons.Util.to_String(var)
360
361     def __call__(self, target, source, env):
362         emitter = self.var
363
364         # Recursively substitute the variable.
365         # We can't use env.subst() because it deals only
366         # in strings.  Maybe we should change that?
367         while SCons.Util.is_String(emitter) and env.has_key(emitter):
368             emitter = env[emitter]
369         if callable(emitter):
370             target, source = emitter(target, source, env)
371         elif SCons.Util.is_List(emitter):
372             for e in emitter:
373                 target, source = e(target, source, env)
374
375         return (target, source)
376
377
378     def __cmp__(self, other):
379         return cmp(self.var, other.var)
380
381 class BuilderBase:
382     """Base class for Builders, objects that create output
383     nodes (files) from input nodes (files).
384     """
385
386     def __init__(self,  action = None,
387                         prefix = '',
388                         suffix = '',
389                         src_suffix = '',
390                         node_factory = SCons.Node.FS.default_fs.File,
391                         target_factory = None,
392                         source_factory = None,
393                         scanner = None,
394                         emitter = None,
395                         multi = 0,
396                         env = None,
397                         **overrides):
398         if __debug__: logInstanceCreation(self, 'BuilderBase')
399         self.action = SCons.Action.Action(action)
400         self.multi = multi
401         if SCons.Util.is_Dict(prefix):
402             prefix = CallableSelector(prefix)
403         self.prefix = prefix
404         if SCons.Util.is_Dict(suffix):
405             suffix = CallableSelector(suffix)
406         self.suffix = suffix
407         self.env = env
408         if overrides.has_key('overrides'):
409             SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
410                 "The \"overrides\" keyword to Builder() creation has been deprecated;\n" +\
411                 "\tspecify the items as keyword arguments to the Builder() call instead.")
412             overrides.update(overrides['overrides'])
413             del overrides['overrides']
414         self.overrides = overrides
415
416         self.set_src_suffix(src_suffix)
417
418         self.target_factory = target_factory or node_factory
419         self.source_factory = source_factory or node_factory
420         self.scanner = scanner
421
422         self.emitter = emitter
423
424     def __nonzero__(self):
425         raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
426
427     def get_name(self, env):
428         """Attempts to get the name of the Builder.
429
430         Look at the BUILDERS variable of env, expecting it to be a
431         dictionary containing this Builder, and return the key of the
432         dictionary."""
433
434         try:
435             index = env['BUILDERS'].values().index(self)
436             return env['BUILDERS'].keys()[index]
437         except (AttributeError, KeyError, ValueError):
438             return str(self.__class__)
439
440     def __cmp__(self, other):
441         return cmp(self.__dict__, other.__dict__)
442
443     def splitext(self, path):
444         return SCons.Util.splitext(path)
445
446     def _create_nodes(self, env, overwarn, target = None, source = None):
447         """Create and return lists of target and source nodes.
448         """
449         def _adjustixes(files, pre, suf):
450             if not files:
451                 return []
452             result = []
453             if not SCons.Util.is_List(files):
454                 files = [files]
455
456             for f in files:
457                 if SCons.Util.is_String(f):
458                     f = SCons.Util.adjustixes(f, pre, suf)
459                 result.append(f)
460             return result
461
462         overwarn.warn()
463
464         env = env.Override(overwarn.data)
465
466         src_suf = self.get_src_suffix(env)
467
468         source = _adjustixes(source, None, src_suf)
469         slist = env.arg2nodes(source, self.source_factory)
470
471         pre = self.get_prefix(env, slist)
472         suf = self.get_suffix(env, slist)
473
474         if target is None:
475             try:
476                 t_from_s = slist[0].target_from_source
477             except AttributeError:
478                 raise UserError("Do not know how to create a target from source `%s'" % slist[0])
479             tlist = [ t_from_s(pre, suf, self.splitext) ]
480         else:
481             target = _adjustixes(target, pre, suf)
482             tlist = env.arg2nodes(target, self.target_factory)
483
484         if self.emitter:
485             # The emitter is going to do str(node), but because we're
486             # being called *from* a builder invocation, the new targets
487             # don't yet have a builder set on them and will look like
488             # source files.  Fool the emitter's str() calls by setting
489             # up a temporary builder on the new targets.
490             new_targets = []
491             for t in tlist:
492                 if not t.is_derived():
493                     t.builder = self
494                     new_targets.append(t)
495
496             target, source = self.emitter(target=tlist, source=slist, env=env)
497
498             # Now delete the temporary builders that we attached to any
499             # new targets, so that _init_nodes() doesn't do weird stuff
500             # to them because it thinks they already have builders.
501             for t in new_targets:
502                 if t.builder is self:
503                     # Only delete the temporary builder if the emitter
504                     # didn't change it on us.
505                     t.builder = None
506
507             # Have to call arg2nodes yet again, since it is legal for
508             # emitters to spit out strings as well as Node instances.
509             slist = env.arg2nodes(source, self.source_factory)
510             tlist = env.arg2nodes(target, self.target_factory)
511
512         return tlist, slist
513
514     def _execute(self, env, target = None, source = _null, overwarn={}):
515         if source is _null:
516             source = target
517             target = None
518         tlist, slist = self._create_nodes(env, overwarn, target, source)
519
520         if len(tlist) == 1:
521             builder = self
522             result = tlist[0]
523         else:
524             builder = ListBuilder(self, env, tlist)
525             result = tlist
526         _init_nodes(builder, env, overwarn.data, tlist, slist)
527
528         return result
529
530     def __call__(self, env, target = None, source = _null, **kw):
531         return self._execute(env, target, source, OverrideWarner(kw))
532
533     def adjust_suffix(self, suff):
534         if suff and not suff[0] in [ '.', '_', '$' ]:
535             return '.' + suff
536         return suff
537
538     def get_prefix(self, env, sources=[]):
539         prefix = self.prefix
540         if callable(prefix):
541             prefix = prefix(env, sources)
542         return env.subst(prefix)
543
544     def get_suffix(self, env, sources=[]):
545         suffix = self.suffix
546         if callable(suffix):
547             suffix = suffix(env, sources)
548         else:
549             suffix = self.adjust_suffix(suffix)
550         return env.subst(suffix)
551
552     def src_suffixes(self, env):
553         return map(lambda x, s=self, e=env: e.subst(s.adjust_suffix(x)),
554                    self.src_suffix)
555
556     def set_src_suffix(self, src_suffix):
557         if not src_suffix:
558             src_suffix = []
559         elif not SCons.Util.is_List(src_suffix):
560             src_suffix = [ src_suffix ]
561         self.src_suffix = src_suffix
562
563     def get_src_suffix(self, env):
564         """Get the first src_suffix in the list of src_suffixes."""
565         ret = self.src_suffixes(env)
566         if not ret:
567             return ''
568         return ret[0]
569
570     def targets(self, node):
571         """Return the list of targets for this builder instance.
572
573         For most normal builders, this is just the supplied node.
574         """
575         return [ node ]
576
577     def add_emitter(self, suffix, emitter):
578         """Add a suffix-emitter mapping to this Builder.
579
580         This assumes that emitter has been initialized with an
581         appropriate dictionary type, and will throw a TypeError if
582         not, so the caller is responsible for knowing that this is an
583         appropriate method to call for the Builder in question.
584         """
585         self.emitter[suffix] = emitter
586
587 class ListBuilder(SCons.Util.Proxy):
588     """A Proxy to support building an array of targets (for example,
589     foo.o and foo.h from foo.y) from a single Action execution.
590     """
591
592     def __init__(self, builder, env, tlist):
593         if __debug__: logInstanceCreation(self)
594         SCons.Util.Proxy.__init__(self, builder)
595         self.builder = builder
596         self.scanner = builder.scanner
597         self.env = env
598         self.tlist = tlist
599         self.multi = builder.multi
600
601     def targets(self, node):
602         """Return the list of targets for this builder instance.
603         """
604         return self.tlist
605
606     def __cmp__(self, other):
607         return cmp(self.__dict__, other.__dict__)
608
609     def get_name(self, env):
610         """Attempts to get the name of the Builder."""
611
612         return "ListBuilder(%s)" % self.builder.get_name(env)
613
614 class MultiStepBuilder(BuilderBase):
615     """This is a builder subclass that can build targets in
616     multiple steps.  The src_builder parameter to the constructor
617     accepts a builder that is called to build sources supplied to
618     this builder.  The targets of that first build then become
619     the sources of this builder.
620
621     If this builder has a src_suffix supplied, then the src_builder
622     builder is NOT invoked if the suffix of a source file matches
623     src_suffix.
624     """
625     def __init__(self,  src_builder,
626                         action = None,
627                         prefix = '',
628                         suffix = '',
629                         src_suffix = '',
630                         node_factory = SCons.Node.FS.default_fs.File,
631                         target_factory = None,
632                         source_factory = None,
633                         scanner=None,
634                         emitter=None):
635         if __debug__: logInstanceCreation(self)
636         BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
637                              node_factory, target_factory, source_factory,
638                              scanner, emitter)
639         if not SCons.Util.is_List(src_builder):
640             src_builder = [ src_builder ]
641         self.src_builder = src_builder
642         self.sdict = {}
643         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
644
645     def _execute(self, env, target = None, source = _null, overwarn={}):
646         if source is _null:
647             source = target
648             target = None
649
650         slist = env.arg2nodes(source, self.source_factory)
651         final_sources = []
652
653         try:
654             sdict = self.sdict[id(env)]
655         except KeyError:
656             sdict = {}
657             self.sdict[id(env)] = sdict
658             for bld in self.src_builder:
659                 if SCons.Util.is_String(bld):
660                     try:
661                         bld = env['BUILDERS'][bld]
662                     except KeyError:
663                         continue
664                 for suf in bld.src_suffixes(env):
665                     sdict[suf] = bld
666
667         src_suffixes = self.src_suffixes(env)
668
669         for snode in slist:
670             try:
671                 get_suffix = snode.get_suffix
672             except AttributeError:
673                 ext = self.splitext(str(snode))
674             else:
675                 ext = get_suffix()
676             try:
677                 subsidiary_builder = sdict[ext]
678             except KeyError:
679                 final_sources.append(snode)
680             else:
681                 tgt = subsidiary_builder._execute(env, None, snode, overwarn)
682                 # Only supply the builder with sources it is capable
683                 # of building.
684                 if SCons.Util.is_List(tgt):
685                     tgt = filter(lambda x, self=self, suf=src_suffixes:
686                                  self.splitext(SCons.Util.to_String(x))[1] in suf,
687                                  tgt)
688                 if not SCons.Util.is_List(tgt):
689                     final_sources.append(tgt)
690                 else:
691                     final_sources.extend(tgt)
692
693         return BuilderBase._execute(self, env, target, final_sources, overwarn)
694
695     def get_src_builders(self, env):
696         """Return all the src_builders for this Builder.
697
698         This is essentially a recursive descent of the src_builder "tree."
699         """
700         ret = []
701         for bld in self.src_builder:
702             if SCons.Util.is_String(bld):
703                 # All Environments should have a BUILDERS
704                 # variable, so no need to check for it.
705                 try:
706                     bld = env['BUILDERS'][bld]
707                 except KeyError:
708                     continue
709             ret.append(bld)
710         return ret
711
712     def src_suffixes(self, env):
713         """Return a list of the src_suffix attributes for all
714         src_builders of this Builder.
715         """
716         try:
717             return self.cached_src_suffixes[id(env)]
718         except KeyError:
719             suffixes = BuilderBase.src_suffixes(self, env)
720             for builder in self.get_src_builders(env):
721                 suffixes.extend(builder.src_suffixes(env))
722             self.cached_src_suffixes[id(env)] = suffixes
723             return suffixes
724
725 class CompositeBuilder(SCons.Util.Proxy):
726     """A Builder Proxy whose main purpose is to always have
727     a DictCmdGenerator as its action, and to provide access
728     to the DictCmdGenerator's add_action() method.
729     """
730
731     def __init__(self, builder, cmdgen):
732         if __debug__: logInstanceCreation(self)
733         SCons.Util.Proxy.__init__(self, builder)
734
735         # cmdgen should always be an instance of DictCmdGenerator.
736         self.cmdgen = cmdgen
737         self.builder = builder
738
739     def add_action(self, suffix, action):
740         self.cmdgen.add_action(suffix, action)
741         self.set_src_suffix(self.cmdgen.src_suffixes())
742
743     def __cmp__(self, other):
744         return cmp(self.__dict__, other.__dict__)