1"""SCons.Builder
2
3Builder object subsystem.
4
5A Builder object is a callable that encapsulates information about how
6to execute actions to create a target Node (file) from source Nodes
7(files), and how to create those dependencies for tracking.
8
9The main entry point here is the Builder() factory method.  This provides
10a procedural interface that creates the right underlying Builder object
11based on the keyword arguments supplied and the types of the arguments.
12
13The goal is for this external interface to be simple enough that the
14vast majority of users can create new Builders as necessary to support
15building new types of files in their configurations, without having to
16dive any deeper into this subsystem.
17
18The base class here is BuilderBase.  This is a concrete base class which
19does, in fact, represent the Builder objects that we (or users) create.
20
21There is also a proxy that looks like a Builder:
22
23    CompositeBuilder
24
25        This proxies for a Builder with an action that is actually a
26        dictionary that knows how to map file suffixes to a specific
27        action.  This is so that we can invoke different actions
28        (compilers, compile options) for different flavors of source
29        files.
30
31Builders and their proxies have the following public interface methods
32used by other modules:
33
34    __call__()
35        THE public interface.  Calling a Builder object (with the
36        use of internal helper methods) sets up the target and source
37        dependencies, appropriate mapping to a specific action, and the
38        environment manipulation necessary for overridden construction
39        variable.  This also takes care of warning about possible mistakes
40        in keyword arguments.
41
42    add_emitter()
43        Adds an emitter for a specific file suffix, used by some Tool
44        modules to specify that (for example) a yacc invocation on a .y
45        can create a .h *and* a .c file.
46
47    add_action()
48        Adds an action for a specific file suffix, heavily used by
49        Tool modules to add their specific action(s) for turning
50        a source file into an object file to the global static
51        and shared object file Builders.
52
53There are the following methods for internal use within this module:
54
55    _execute()
56        The internal method that handles the heavily lifting when a
57        Builder is called.  This is used so that the __call__() methods
58        can set up warning about possible mistakes in keyword-argument
59        overrides, and *then* execute all of the steps necessary so that
60        the warnings only occur once.
61
62    get_name()
63        Returns the Builder's name within a specific Environment,
64        primarily used to try to return helpful information in error
65        messages.
66
67    adjust_suffix()
68    get_prefix()
69    get_suffix()
70    get_src_suffix()
71    set_src_suffix()
72        Miscellaneous stuff for handling the prefix and suffix
73        manipulation we use in turning source file names into target
74        file names.
75
76"""
77
78#
79# Copyright (c) 2001 - 2016 The SCons Foundation
80#
81# Permission is hereby granted, free of charge, to any person obtaining
82# a copy of this software and associated documentation files (the
83# "Software"), to deal in the Software without restriction, including
84# without limitation the rights to use, copy, modify, merge, publish,
85# distribute, sublicense, and/or sell copies of the Software, and to
86# permit persons to whom the Software is furnished to do so, subject to
87# the following conditions:
88#
89# The above copyright notice and this permission notice shall be included
90# in all copies or substantial portions of the Software.
91#
92# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
93# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
94# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
95# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
96# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
97# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
98# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
99
100__revision__ = "src/engine/SCons/Builder.py rel_2.5.0:3543:937e55cd78f7 2016/04/09 11:29:54 bdbaddog"
101
102import collections
103
104import SCons.Action
105import SCons.Debug
106from SCons.Debug import logInstanceCreation
107from SCons.Errors import InternalError, UserError
108import SCons.Executor
109import SCons.Memoize
110import SCons.Util
111import SCons.Warnings
112
113class _Null(object):
114    pass
115
116_null = _Null
117
118def match_splitext(path, suffixes = []):
119    if suffixes:
120        matchsuf = [S for S in suffixes if path[-len(S):] == S]
121        if matchsuf:
122            suf = max([(len(_f),_f) for _f in matchsuf])[1]
123            return [path[:-len(suf)], path[-len(suf):]]
124    return SCons.Util.splitext(path)
125
126class DictCmdGenerator(SCons.Util.Selector):
127    """This is a callable class that can be used as a
128    command generator function.  It holds on to a dictionary
129    mapping file suffixes to Actions.  It uses that dictionary
130    to return the proper action based on the file suffix of
131    the source file."""
132
133    def __init__(self, dict=None, source_ext_match=1):
134        SCons.Util.Selector.__init__(self, dict)
135        self.source_ext_match = source_ext_match
136
137    def src_suffixes(self):
138        return list(self.keys())
139
140    def add_action(self, suffix, action):
141        """Add a suffix-action pair to the mapping.
142        """
143        self[suffix] = action
144
145    def __call__(self, target, source, env, for_signature):
146        if not source:
147            return []
148
149        if self.source_ext_match:
150            suffixes = self.src_suffixes()
151            ext = None
152            for src in map(str, source):
153                my_ext = match_splitext(src, suffixes)[1]
154                if ext and my_ext != ext:
155                    raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s"
156                             % (repr(list(map(str, target))), src, ext, my_ext))
157                ext = my_ext
158        else:
159            ext = match_splitext(str(source[0]), self.src_suffixes())[1]
160
161        if not ext:
162            #return ext
163            raise UserError("While building `%s': "
164                            "Cannot deduce file extension from source files: %s"
165                 % (repr(list(map(str, target))), repr(list(map(str, source)))))
166
167        try:
168            ret = SCons.Util.Selector.__call__(self, env, source, ext)
169        except KeyError, e:
170            raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e.args[0], e.args[1], e.args[2]))
171        if ret is None:
172            raise UserError("While building `%s' from `%s': Don't know how to build from a source file with suffix `%s'.  Expected a suffix in this list: %s." % \
173                            (repr(list(map(str, target))), repr(list(map(str, source))), ext, repr(list(self.keys()))))
174        return ret
175
176class CallableSelector(SCons.Util.Selector):
177    """A callable dictionary that will, in turn, call the value it
178    finds if it can."""
179    def __call__(self, env, source):
180        value = SCons.Util.Selector.__call__(self, env, source)
181        if callable(value):
182            value = value(env, source)
183        return value
184
185class DictEmitter(SCons.Util.Selector):
186    """A callable dictionary that maps file suffixes to emitters.
187    When called, it finds the right emitter in its dictionary for the
188    suffix of the first source file, and calls that emitter to get the
189    right lists of targets and sources to return.  If there's no emitter
190    for the suffix in its dictionary, the original target and source are
191    returned.
192    """
193    def __call__(self, target, source, env):
194        emitter = SCons.Util.Selector.__call__(self, env, source)
195        if emitter:
196            target, source = emitter(target, source, env)
197        return (target, source)
198
199class ListEmitter(collections.UserList):
200    """A callable list of emitters that calls each in sequence,
201    returning the result.
202    """
203    def __call__(self, target, source, env):
204        for e in self.data:
205            target, source = e(target, source, env)
206        return (target, source)
207
208# These are a common errors when calling a Builder;
209# they are similar to the 'target' and 'source' keyword args to builders,
210# so we issue warnings when we see them.  The warnings can, of course,
211# be disabled.
212misleading_keywords = {
213    'targets'   : 'target',
214    'sources'   : 'source',
215}
216
217class OverrideWarner(collections.UserDict):
218    """A class for warning about keyword arguments that we use as
219    overrides in a Builder call.
220
221    This class exists to handle the fact that a single Builder call
222    can actually invoke multiple builders.  This class only emits the
223    warnings once, no matter how many Builders are invoked.
224    """
225    def __init__(self, dict):
226        collections.UserDict.__init__(self, dict)
227        if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.OverrideWarner')
228        self.already_warned = None
229    def warn(self):
230        if self.already_warned:
231            return
232        for k in self.keys():
233            if k in misleading_keywords:
234                alt = misleading_keywords[k]
235                msg = "Did you mean to use `%s' instead of `%s'?" % (alt, k)
236                SCons.Warnings.warn(SCons.Warnings.MisleadingKeywordsWarning, msg)
237        self.already_warned = 1
238
239def Builder(**kw):
240    """A factory for builder objects."""
241    composite = None
242    if 'generator' in kw:
243        if 'action' in kw:
244            raise UserError("You must not specify both an action and a generator.")
245        kw['action'] = SCons.Action.CommandGeneratorAction(kw['generator'], {})
246        del kw['generator']
247    elif 'action' in kw:
248        source_ext_match = kw.get('source_ext_match', 1)
249        if 'source_ext_match' in kw:
250            del kw['source_ext_match']
251        if SCons.Util.is_Dict(kw['action']):
252            composite = DictCmdGenerator(kw['action'], source_ext_match)
253            kw['action'] = SCons.Action.CommandGeneratorAction(composite, {})
254            kw['src_suffix'] = composite.src_suffixes()
255        else:
256            kw['action'] = SCons.Action.Action(kw['action'])
257
258    if 'emitter' in kw:
259        emitter = kw['emitter']
260        if SCons.Util.is_String(emitter):
261            # This allows users to pass in an Environment
262            # variable reference (like "$FOO") as an emitter.
263            # We will look in that Environment variable for
264            # a callable to use as the actual emitter.
265            var = SCons.Util.get_environment_var(emitter)
266            if not var:
267                raise UserError("Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter)
268            kw['emitter'] = EmitterProxy(var)
269        elif SCons.Util.is_Dict(emitter):
270            kw['emitter'] = DictEmitter(emitter)
271        elif SCons.Util.is_List(emitter):
272            kw['emitter'] = ListEmitter(emitter)
273
274    result = BuilderBase(**kw)
275
276    if not composite is None:
277        result = CompositeBuilder(result, composite)
278
279    return result
280
281def _node_errors(builder, env, tlist, slist):
282    """Validate that the lists of target and source nodes are
283    legal for this builder and environment.  Raise errors or
284    issue warnings as appropriate.
285    """
286
287    # First, figure out if there are any errors in the way the targets
288    # were specified.
289    for t in tlist:
290        if t.side_effect:
291            raise UserError("Multiple ways to build the same target were specified for: %s" % t)
292        if t.has_explicit_builder():
293            if not t.env is None and not t.env is env:
294                action = t.builder.action
295                t_contents = t.builder.action.get_contents(tlist, slist, t.env)
296                contents = builder.action.get_contents(tlist, slist, env)
297
298                if t_contents == contents:
299                    msg = "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s" % (t, action.genstring(tlist, slist, t.env))
300                    SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning, msg)
301                else:
302                    msg = "Two environments with different actions were specified for the same target: %s\n(action 1: %s)\n(action 2: %s)" % (t,t_contents,contents)
303                    raise UserError(msg)
304            if builder.multi:
305                if t.builder != builder:
306                    msg = "Two different builders (%s and %s) were specified for the same target: %s" % (t.builder.get_name(env), builder.get_name(env), t)
307                    raise UserError(msg)
308                # TODO(batch):  list constructed each time!
309                if t.get_executor().get_all_targets() != tlist:
310                    msg = "Two different target lists have a target in common: %s  (from %s and from %s)" % (t, list(map(str, t.get_executor().get_all_targets())), list(map(str, tlist)))
311                    raise UserError(msg)
312            elif t.sources != slist:
313                msg = "Multiple ways to build the same target were specified for: %s  (from %s and from %s)" % (t, list(map(str, t.sources)), list(map(str, slist)))
314                raise UserError(msg)
315
316    if builder.single_source:
317        if len(slist) > 1:
318            raise UserError("More than one source given for single-source builder: targets=%s sources=%s" % (list(map(str,tlist)), list(map(str,slist))))
319
320class EmitterProxy(object):
321    """This is a callable class that can act as a
322    Builder emitter.  It holds on to a string that
323    is a key into an Environment dictionary, and will
324    look there at actual build time to see if it holds
325    a callable.  If so, we will call that as the actual
326    emitter."""
327    def __init__(self, var):
328        self.var = SCons.Util.to_String(var)
329
330    def __call__(self, target, source, env):
331        emitter = self.var
332
333        # Recursively substitute the variable.
334        # We can't use env.subst() because it deals only
335        # in strings.  Maybe we should change that?
336        while SCons.Util.is_String(emitter) and emitter in env:
337            emitter = env[emitter]
338        if callable(emitter):
339            target, source = emitter(target, source, env)
340        elif SCons.Util.is_List(emitter):
341            for e in emitter:
342                target, source = e(target, source, env)
343
344        return (target, source)
345
346
347    def __cmp__(self, other):
348        return cmp(self.var, other.var)
349
350class BuilderBase(object):
351    """Base class for Builders, objects that create output
352    nodes (files) from input nodes (files).
353    """
354
355    def __init__(self,  action = None,
356                        prefix = '',
357                        suffix = '',
358                        src_suffix = '',
359                        target_factory = None,
360                        source_factory = None,
361                        target_scanner = None,
362                        source_scanner = None,
363                        emitter = None,
364                        multi = 0,
365                        env = None,
366                        single_source = 0,
367                        name = None,
368                        chdir = _null,
369                        is_explicit = 1,
370                        src_builder = None,
371                        ensure_suffix = False,
372                        **overrides):
373        if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.BuilderBase')
374        self._memo = {}
375        self.action = action
376        self.multi = multi
377        if SCons.Util.is_Dict(prefix):
378            prefix = CallableSelector(prefix)
379        self.prefix = prefix
380        if SCons.Util.is_Dict(suffix):
381            suffix = CallableSelector(suffix)
382        self.env = env
383        self.single_source = single_source
384        if 'overrides' in overrides:
385            SCons.Warnings.warn(SCons.Warnings.DeprecatedBuilderKeywordsWarning,
386                "The \"overrides\" keyword to Builder() creation has been deprecated;\n" +\
387                "\tspecify the items as keyword arguments to the Builder() call instead.")
388            overrides.update(overrides['overrides'])
389            del overrides['overrides']
390        if 'scanner' in overrides:
391            SCons.Warnings.warn(SCons.Warnings.DeprecatedBuilderKeywordsWarning,
392                                "The \"scanner\" keyword to Builder() creation has been deprecated;\n"
393                                "\tuse: source_scanner or target_scanner as appropriate.")
394            del overrides['scanner']
395        self.overrides = overrides
396
397        self.set_suffix(suffix)
398        self.set_src_suffix(src_suffix)
399        self.ensure_suffix = ensure_suffix
400
401        self.target_factory = target_factory
402        self.source_factory = source_factory
403        self.target_scanner = target_scanner
404        self.source_scanner = source_scanner
405
406        self.emitter = emitter
407
408        # Optional Builder name should only be used for Builders
409        # that don't get attached to construction environments.
410        if name:
411            self.name = name
412        self.executor_kw = {}
413        if not chdir is _null:
414            self.executor_kw['chdir'] = chdir
415        self.is_explicit = is_explicit
416
417        if src_builder is None:
418            src_builder = []
419        elif not SCons.Util.is_List(src_builder):
420            src_builder = [ src_builder ]
421        self.src_builder = src_builder
422
423    def __nonzero__(self):
424        raise InternalError("Do not test for the Node.builder attribute directly; use Node.has_builder() instead")
425
426    def get_name(self, env):
427        """Attempts to get the name of the Builder.
428
429        Look at the BUILDERS variable of env, expecting it to be a
430        dictionary containing this Builder, and return the key of the
431        dictionary.  If there's no key, then return a directly-configured
432        name (if there is one) or the name of the class (by default)."""
433
434        try:
435            index = list(env['BUILDERS'].values()).index(self)
436            return list(env['BUILDERS'].keys())[index]
437        except (AttributeError, KeyError, TypeError, ValueError):
438            try:
439                return self.name
440            except AttributeError:
441                return str(self.__class__)
442
443    def __cmp__(self, other):
444        return cmp(self.__dict__, other.__dict__)
445
446    def splitext(self, path, env=None):
447        if not env:
448            env = self.env
449        if env:
450            suffixes = self.src_suffixes(env)
451        else:
452            suffixes = []
453        return match_splitext(path, suffixes)
454
455    def _adjustixes(self, files, pre, suf, ensure_suffix=False):
456        if not files:
457            return []
458        result = []
459        if not SCons.Util.is_List(files):
460            files = [files]
461
462        for f in files:
463            if SCons.Util.is_String(f):
464                f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix)
465            result.append(f)
466        return result
467
468    def _create_nodes(self, env, target = None, source = None):
469        """Create and return lists of target and source nodes.
470        """
471        src_suf = self.get_src_suffix(env)
472
473        target_factory = env.get_factory(self.target_factory)
474        source_factory = env.get_factory(self.source_factory)
475
476        source = self._adjustixes(source, None, src_suf)
477        slist = env.arg2nodes(source, source_factory)
478
479        pre = self.get_prefix(env, slist)
480        suf = self.get_suffix(env, slist)
481
482        if target is None:
483            try:
484                t_from_s = slist[0].target_from_source
485            except AttributeError:
486                raise UserError("Do not know how to create a target from source `%s'" % slist[0])
487            except IndexError:
488                tlist = []
489            else:
490                splitext = lambda S: self.splitext(S,env)
491                tlist = [ t_from_s(pre, suf, splitext) ]
492        else:
493            target = self._adjustixes(target, pre, suf, self.ensure_suffix)
494            tlist = env.arg2nodes(target, target_factory, target=target, source=source)
495
496        if self.emitter:
497            # The emitter is going to do str(node), but because we're
498            # being called *from* a builder invocation, the new targets
499            # don't yet have a builder set on them and will look like
500            # source files.  Fool the emitter's str() calls by setting
501            # up a temporary builder on the new targets.
502            new_targets = []
503            for t in tlist:
504                if not t.is_derived():
505                    t.builder_set(self)
506                    new_targets.append(t)
507
508            orig_tlist = tlist[:]
509            orig_slist = slist[:]
510
511            target, source = self.emitter(target=tlist, source=slist, env=env)
512
513            # Now delete the temporary builders that we attached to any
514            # new targets, so that _node_errors() doesn't do weird stuff
515            # to them because it thinks they already have builders.
516            for t in new_targets:
517                if t.builder is self:
518                    # Only delete the temporary builder if the emitter
519                    # didn't change it on us.
520                    t.builder_set(None)
521
522            # Have to call arg2nodes yet again, since it is legal for
523            # emitters to spit out strings as well as Node instances.
524            tlist = env.arg2nodes(target, target_factory,
525                                  target=orig_tlist, source=orig_slist)
526            slist = env.arg2nodes(source, source_factory,
527                                  target=orig_tlist, source=orig_slist)
528
529        return tlist, slist
530
531    def _execute(self, env, target, source, overwarn={}, executor_kw={}):
532        # We now assume that target and source are lists or None.
533        if self.src_builder:
534            source = self.src_builder_sources(env, source, overwarn)
535
536        if self.single_source and len(source) > 1 and target is None:
537            result = []
538            if target is None: target = [None]*len(source)
539            for tgt, src in zip(target, source):
540                if not tgt is None: tgt = [tgt]
541                if not src is None: src = [src]
542                result.extend(self._execute(env, tgt, src, overwarn))
543            return SCons.Node.NodeList(result)
544
545        overwarn.warn()
546
547        tlist, slist = self._create_nodes(env, target, source)
548
549        # Check for errors with the specified target/source lists.
550        _node_errors(self, env, tlist, slist)
551
552        # The targets are fine, so find or make the appropriate Executor to
553        # build this particular list of targets from this particular list of
554        # sources.
555
556        executor = None
557        key = None
558
559        if self.multi:
560            try:
561                executor = tlist[0].get_executor(create = 0)
562            except (AttributeError, IndexError):
563                pass
564            else:
565                executor.add_sources(slist)
566
567        if executor is None:
568            if not self.action:
569                fmt = "Builder %s must have an action to build %s."
570                raise UserError(fmt % (self.get_name(env or self.env),
571                                        list(map(str,tlist))))
572            key = self.action.batch_key(env or self.env, tlist, slist)
573            if key:
574                try:
575                    executor = SCons.Executor.GetBatchExecutor(key)
576                except KeyError:
577                    pass
578                else:
579                    executor.add_batch(tlist, slist)
580
581        if executor is None:
582            executor = SCons.Executor.Executor(self.action, env, [],
583                                               tlist, slist, executor_kw)
584            if key:
585                SCons.Executor.AddBatchExecutor(key, executor)
586
587        # Now set up the relevant information in the target Nodes themselves.
588        for t in tlist:
589            t.cwd = env.fs.getcwd()
590            t.builder_set(self)
591            t.env_set(env)
592            t.add_source(slist)
593            t.set_executor(executor)
594            t.set_explicit(self.is_explicit)
595
596        return SCons.Node.NodeList(tlist)
597
598    def __call__(self, env, target=None, source=None, chdir=_null, **kw):
599        # We now assume that target and source are lists or None.
600        # The caller (typically Environment.BuilderWrapper) is
601        # responsible for converting any scalar values to lists.
602        if chdir is _null:
603            ekw = self.executor_kw
604        else:
605            ekw = self.executor_kw.copy()
606            ekw['chdir'] = chdir
607        if kw:
608            if 'srcdir' in kw:
609                def prependDirIfRelative(f, srcdir=kw['srcdir']):
610                    import os.path
611                    if SCons.Util.is_String(f) and not os.path.isabs(f):
612                        f = os.path.join(srcdir, f)
613                    return f
614                if not SCons.Util.is_List(source):
615                    source = [source]
616                source = list(map(prependDirIfRelative, source))
617                del kw['srcdir']
618            if self.overrides:
619                env_kw = self.overrides.copy()
620                env_kw.update(kw)
621            else:
622                env_kw = kw
623        else:
624            env_kw = self.overrides
625        env = env.Override(env_kw)
626        return self._execute(env, target, source, OverrideWarner(kw), ekw)
627
628    def adjust_suffix(self, suff):
629        if suff and not suff[0] in [ '.', '_', '$' ]:
630            return '.' + suff
631        return suff
632
633    def get_prefix(self, env, sources=[]):
634        prefix = self.prefix
635        if callable(prefix):
636            prefix = prefix(env, sources)
637        return env.subst(prefix)
638
639    def set_suffix(self, suffix):
640        if not callable(suffix):
641            suffix = self.adjust_suffix(suffix)
642        self.suffix = suffix
643
644    def get_suffix(self, env, sources=[]):
645        suffix = self.suffix
646        if callable(suffix):
647            suffix = suffix(env, sources)
648        return env.subst(suffix)
649
650    def set_src_suffix(self, src_suffix):
651        if not src_suffix:
652            src_suffix = []
653        elif not SCons.Util.is_List(src_suffix):
654            src_suffix = [ src_suffix ]
655        self.src_suffix = [callable(suf) and suf or self.adjust_suffix(suf) for suf in src_suffix]
656
657    def get_src_suffix(self, env):
658        """Get the first src_suffix in the list of src_suffixes."""
659        ret = self.src_suffixes(env)
660        if not ret:
661            return ''
662        return ret[0]
663
664    def add_emitter(self, suffix, emitter):
665        """Add a suffix-emitter mapping to this Builder.
666
667        This assumes that emitter has been initialized with an
668        appropriate dictionary type, and will throw a TypeError if
669        not, so the caller is responsible for knowing that this is an
670        appropriate method to call for the Builder in question.
671        """
672        self.emitter[suffix] = emitter
673
674    def add_src_builder(self, builder):
675        """
676        Add a new Builder to the list of src_builders.
677
678        This requires wiping out cached values so that the computed
679        lists of source suffixes get re-calculated.
680        """
681        self._memo = {}
682        self.src_builder.append(builder)
683
684    def _get_sdict(self, env):
685        """
686        Returns a dictionary mapping all of the source suffixes of all
687        src_builders of this Builder to the underlying Builder that
688        should be called first.
689
690        This dictionary is used for each target specified, so we save a
691        lot of extra computation by memoizing it for each construction
692        environment.
693
694        Note that this is re-computed each time, not cached, because there
695        might be changes to one of our source Builders (or one of their
696        source Builders, and so on, and so on...) that we can't "see."
697
698        The underlying methods we call cache their computed values,
699        though, so we hope repeatedly aggregating them into a dictionary
700        like this won't be too big a hit.  We may need to look for a
701        better way to do this if performance data show this has turned
702        into a significant bottleneck.
703        """
704        sdict = {}
705        for bld in self.get_src_builders(env):
706            for suf in bld.src_suffixes(env):
707                sdict[suf] = bld
708        return sdict
709
710    def src_builder_sources(self, env, source, overwarn={}):
711        sdict = self._get_sdict(env)
712
713        src_suffixes = self.src_suffixes(env)
714
715        lengths = list(set(map(len, src_suffixes)))
716
717        def match_src_suffix(name, src_suffixes=src_suffixes, lengths=lengths):
718            node_suffixes = [name[-l:] for l in lengths]
719            for suf in src_suffixes:
720                if suf in node_suffixes:
721                    return suf
722            return None
723
724        result = []
725        for s in SCons.Util.flatten(source):
726            if SCons.Util.is_String(s):
727                match_suffix = match_src_suffix(env.subst(s))
728                if not match_suffix and not '.' in s:
729                    src_suf = self.get_src_suffix(env)
730                    s = self._adjustixes(s, None, src_suf)[0]
731            else:
732                match_suffix = match_src_suffix(s.name)
733            if match_suffix:
734                try:
735                    bld = sdict[match_suffix]
736                except KeyError:
737                    result.append(s)
738                else:
739                    tlist = bld._execute(env, None, [s], overwarn)
740                    # If the subsidiary Builder returned more than one
741                    # target, then filter out any sources that this
742                    # Builder isn't capable of building.
743                    if len(tlist) > 1:
744                        tlist = [t for t in tlist if match_src_suffix(t.name)]
745                    result.extend(tlist)
746            else:
747                result.append(s)
748
749        source_factory = env.get_factory(self.source_factory)
750
751        return env.arg2nodes(result, source_factory)
752
753    def _get_src_builders_key(self, env):
754        return id(env)
755
756    @SCons.Memoize.CountDictCall(_get_src_builders_key)
757    def get_src_builders(self, env):
758        """
759        Returns the list of source Builders for this Builder.
760
761        This exists mainly to look up Builders referenced as
762        strings in the 'BUILDER' variable of the construction
763        environment and cache the result.
764        """
765        memo_key = id(env)
766        try:
767            memo_dict = self._memo['get_src_builders']
768        except KeyError:
769            memo_dict = {}
770            self._memo['get_src_builders'] = memo_dict
771        else:
772            try:
773                return memo_dict[memo_key]
774            except KeyError:
775                pass
776
777        builders = []
778        for bld in self.src_builder:
779            if SCons.Util.is_String(bld):
780                try:
781                    bld = env['BUILDERS'][bld]
782                except KeyError:
783                    continue
784            builders.append(bld)
785
786        memo_dict[memo_key] = builders
787        return builders
788
789    def _subst_src_suffixes_key(self, env):
790        return id(env)
791
792    @SCons.Memoize.CountDictCall(_subst_src_suffixes_key)
793    def subst_src_suffixes(self, env):
794        """
795        The suffix list may contain construction variable expansions,
796        so we have to evaluate the individual strings.  To avoid doing
797        this over and over, we memoize the results for each construction
798        environment.
799        """
800        memo_key = id(env)
801        try:
802            memo_dict = self._memo['subst_src_suffixes']
803        except KeyError:
804            memo_dict = {}
805            self._memo['subst_src_suffixes'] = memo_dict
806        else:
807            try:
808                return memo_dict[memo_key]
809            except KeyError:
810                pass
811        suffixes = [env.subst(x) for x in self.src_suffix]
812        memo_dict[memo_key] = suffixes
813        return suffixes
814
815    def src_suffixes(self, env):
816        """
817        Returns the list of source suffixes for all src_builders of this
818        Builder.
819
820        This is essentially a recursive descent of the src_builder "tree."
821        (This value isn't cached because there may be changes in a
822        src_builder many levels deep that we can't see.)
823        """
824        sdict = {}
825        suffixes = self.subst_src_suffixes(env)
826        for s in suffixes:
827            sdict[s] = 1
828        for builder in self.get_src_builders(env):
829            for s in builder.src_suffixes(env):
830                if s not in sdict:
831                    sdict[s] = 1
832                    suffixes.append(s)
833        return suffixes
834
835class CompositeBuilder(SCons.Util.Proxy):
836    """A Builder Proxy whose main purpose is to always have
837    a DictCmdGenerator as its action, and to provide access
838    to the DictCmdGenerator's add_action() method.
839    """
840
841    def __init__(self, builder, cmdgen):
842        if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.CompositeBuilder')
843        SCons.Util.Proxy.__init__(self, builder)
844
845        # cmdgen should always be an instance of DictCmdGenerator.
846        self.cmdgen = cmdgen
847        self.builder = builder
848
849    __call__ = SCons.Util.Delegate('__call__')
850
851    def add_action(self, suffix, action):
852        self.cmdgen.add_action(suffix, action)
853        self.set_src_suffix(self.cmdgen.src_suffixes())
854
855def is_a_Builder(obj):
856    """"Returns True if the specified obj is one of our Builder classes.
857
858    The test is complicated a bit by the fact that CompositeBuilder
859    is a proxy, not a subclass of BuilderBase.
860    """
861    return (isinstance(obj, BuilderBase)
862            or isinstance(obj, CompositeBuilder)
863            or callable(obj))
864
865# Local Variables:
866# tab-width:4
867# indent-tabs-mode:nil
868# End:
869# vim: set expandtab tabstop=4 shiftwidth=4:
870