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