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