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