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"""Base class for construction Environments.
25
26These are the primary objects used to communicate dependency and
27construction information to the build engine.
28
29Keyword arguments supplied when the construction Environment is created
30are construction variables used to initialize the Environment.
31"""
32
33import copy
34import os
35import sys
36import re
37import shlex
38from collections import UserDict
39
40import SCons.Action
41import SCons.Builder
42import SCons.Debug
43from SCons.Debug import logInstanceCreation
44import SCons.Defaults
45from SCons.Errors import UserError, BuildError
46import SCons.Memoize
47import SCons.Node
48import SCons.Node.Alias
49import SCons.Node.FS
50import SCons.Node.Python
51import SCons.Platform
52import SCons.SConf
53import SCons.SConsign
54import SCons.Subst
55import SCons.Tool
56import SCons.Warnings
57from SCons.Util import (
58    AppendPath,
59    CLVar,
60    LogicalLines,
61    MethodWrapper,
62    PrependPath,
63    Split,
64    WhereIs,
65    flatten,
66    is_Dict,
67    is_List,
68    is_Sequence,
69    is_String,
70    is_Tuple,
71    semi_deepcopy,
72    semi_deepcopy_dict,
73    to_String_for_subst,
74    uniquer_hashables,
75)
76
77class _Null:
78    pass
79
80_null = _Null
81
82_warn_copy_deprecated = True
83_warn_source_signatures_deprecated = True
84_warn_target_signatures_deprecated = True
85
86CleanTargets = {}
87CalculatorArgs = {}
88
89def alias_builder(env, target, source):
90    pass
91
92AliasBuilder = SCons.Builder.Builder(
93    action=alias_builder,
94    target_factory=SCons.Node.Alias.default_ans.Alias,
95    source_factory=SCons.Node.FS.Entry,
96    multi=True,
97    is_explicit=None,
98    name='AliasBuilder',
99)
100
101def apply_tools(env, tools, toolpath):
102    # Store the toolpath in the Environment.
103    # This is expected to work even if no tools are given, so do this first.
104    if toolpath is not None:
105        env['toolpath'] = toolpath
106    if not tools:
107        return
108
109    # Filter out null tools from the list.
110    for tool in [_f for _f in tools if _f]:
111        if is_List(tool) or is_Tuple(tool):
112            # toolargs should be a dict of kw args
113            toolname, toolargs, *rest = tool
114            _ = env.Tool(toolname, **toolargs)
115        else:
116            _ = env.Tool(tool)
117
118# These names are (or will be) controlled by SCons; users should never
119# set or override them.  The warning can optionally be turned off,
120# but scons will still ignore the illegal variable names even if it's off.
121reserved_construction_var_names = [
122    'CHANGED_SOURCES',
123    'CHANGED_TARGETS',
124    'SOURCE',
125    'SOURCES',
126    'TARGET',
127    'TARGETS',
128    'UNCHANGED_SOURCES',
129    'UNCHANGED_TARGETS',
130]
131
132future_reserved_construction_var_names = [
133    #'HOST_OS',
134    #'HOST_ARCH',
135    #'HOST_CPU',
136]
137
138def copy_non_reserved_keywords(dict):
139    result = semi_deepcopy(dict)
140    for k in result.copy().keys():
141        if k in reserved_construction_var_names:
142            msg = "Ignoring attempt to set reserved variable `$%s'"
143            SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % k)
144            del result[k]
145    return result
146
147def _set_reserved(env, key, value):
148    msg = "Ignoring attempt to set reserved variable `$%s'"
149    SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % key)
150
151def _set_future_reserved(env, key, value):
152    env._dict[key] = value
153    msg = "`$%s' will be reserved in a future release and setting it will become ignored"
154    SCons.Warnings.warn(SCons.Warnings.FutureReservedVariableWarning, msg % key)
155
156def _set_BUILDERS(env, key, value):
157    try:
158        bd = env._dict[key]
159        for k in bd.copy().keys():
160            del bd[k]
161    except KeyError:
162        bd = BuilderDict(bd, env)
163        env._dict[key] = bd
164    for k, v in value.items():
165        if not SCons.Builder.is_a_Builder(v):
166            raise UserError('%s is not a Builder.' % repr(v))
167    bd.update(value)
168
169def _del_SCANNERS(env, key):
170    del env._dict[key]
171    env.scanner_map_delete()
172
173def _set_SCANNERS(env, key, value):
174    env._dict[key] = value
175    env.scanner_map_delete()
176
177def _delete_duplicates(l, keep_last):
178    """Delete duplicates from a sequence, keeping the first or last."""
179    seen=set()
180    result=[]
181    if keep_last:           # reverse in & out, then keep first
182        l.reverse()
183    for i in l:
184        try:
185            if i not in seen:
186                result.append(i)
187                seen.add(i)
188        except TypeError:
189            # probably unhashable.  Just keep it.
190            result.append(i)
191    if keep_last:
192        result.reverse()
193    return result
194
195
196
197# The following is partly based on code in a comment added by Peter
198# Shannon at the following page (there called the "transplant" class):
199#
200# ASPN : Python Cookbook : Dynamically added methods to a class
201# https://code.activestate.com/recipes/81732/
202#
203# We had independently been using the idiom as BuilderWrapper, but
204# factoring out the common parts into this base class, and making
205# BuilderWrapper a subclass that overrides __call__() to enforce specific
206# Builder calling conventions, simplified some of our higher-layer code.
207#
208# Note: MethodWrapper moved to SCons.Util as it was needed there
209# and otherwise we had a circular import problem.
210
211class BuilderWrapper(MethodWrapper):
212    """
213    A MethodWrapper subclass that that associates an environment with
214    a Builder.
215
216    This mainly exists to wrap the __call__() function so that all calls
217    to Builders can have their argument lists massaged in the same way
218    (treat a lone argument as the source, treat two arguments as target
219    then source, make sure both target and source are lists) without
220    having to have cut-and-paste code to do it.
221
222    As a bit of obsessive backwards compatibility, we also intercept
223    attempts to get or set the "env" or "builder" attributes, which were
224    the names we used before we put the common functionality into the
225    MethodWrapper base class.  We'll keep this around for a while in case
226    people shipped Tool modules that reached into the wrapper (like the
227    Tool/qt.py module does, or did).  There shouldn't be a lot attribute
228    fetching or setting on these, so a little extra work shouldn't hurt.
229    """
230    def __call__(self, target=None, source=_null, *args, **kw):
231        if source is _null:
232            source = target
233            target = None
234        if target is not None and not is_List(target):
235            target = [target]
236        if source is not None and not is_List(source):
237            source = [source]
238        return super().__call__(target, source, *args, **kw)
239
240    def __repr__(self):
241        return '<BuilderWrapper %s>' % repr(self.name)
242
243    def __str__(self):
244        return self.__repr__()
245
246    def __getattr__(self, name):
247        if name == 'env':
248            return self.object
249        elif name == 'builder':
250            return self.method
251        else:
252            raise AttributeError(name)
253
254    def __setattr__(self, name, value):
255        if name == 'env':
256            self.object = value
257        elif name == 'builder':
258            self.method = value
259        else:
260            self.__dict__[name] = value
261
262    # This allows a Builder to be executed directly
263    # through the Environment to which it's attached.
264    # In practice, we shouldn't need this, because
265    # builders actually get executed through a Node.
266    # But we do have a unit test for this, and can't
267    # yet rule out that it would be useful in the
268    # future, so leave it for now.
269    #def execute(self, **kw):
270    #    kw['env'] = self.env
271    #    self.builder.execute(**kw)
272
273class BuilderDict(UserDict):
274    """This is a dictionary-like class used by an Environment to hold
275    the Builders.  We need to do this because every time someone changes
276    the Builders in the Environment's BUILDERS dictionary, we must
277    update the Environment's attributes."""
278    def __init__(self, dict, env):
279        # Set self.env before calling the superclass initialization,
280        # because it will end up calling our other methods, which will
281        # need to point the values in this dictionary to self.env.
282        self.env = env
283        UserDict.__init__(self, dict)
284
285    def __semi_deepcopy__(self):
286        # These cannot be copied since they would both modify the same builder object, and indeed
287        # just copying would modify the original builder
288        raise TypeError( 'cannot semi_deepcopy a BuilderDict' )
289
290    def __setitem__(self, item, val):
291        try:
292            method = getattr(self.env, item).method
293        except AttributeError:
294            pass
295        else:
296            self.env.RemoveMethod(method)
297        UserDict.__setitem__(self, item, val)
298        BuilderWrapper(self.env, val, item)
299
300    def __delitem__(self, item):
301        UserDict.__delitem__(self, item)
302        delattr(self.env, item)
303
304    def update(self, dict):
305        for i, v in dict.items():
306            self.__setitem__(i, v)
307
308
309
310_is_valid_var = re.compile(r'[_a-zA-Z]\w*$')
311
312def is_valid_construction_var(varstr):
313    """Return if the specified string is a legitimate construction
314    variable.
315    """
316    return _is_valid_var.match(varstr)
317
318
319
320class SubstitutionEnvironment:
321    """Base class for different flavors of construction environments.
322
323    This class contains a minimal set of methods that handle construction
324    variable expansion and conversion of strings to Nodes, which may or
325    may not be actually useful as a stand-alone class.  Which methods
326    ended up in this class is pretty arbitrary right now.  They're
327    basically the ones which we've empirically determined are common to
328    the different construction environment subclasses, and most of the
329    others that use or touch the underlying dictionary of construction
330    variables.
331
332    Eventually, this class should contain all the methods that we
333    determine are necessary for a "minimal" interface to the build engine.
334    A full "native Python" SCons environment has gotten pretty heavyweight
335    with all of the methods and Tools and construction variables we've
336    jammed in there, so it would be nice to have a lighter weight
337    alternative for interfaces that don't need all of the bells and
338    whistles.  (At some point, we'll also probably rename this class
339    "Base," since that more reflects what we want this class to become,
340    but because we've released comments that tell people to subclass
341    Environment.Base to create their own flavors of construction
342    environment, we'll save that for a future refactoring when this
343    class actually becomes useful.)
344    """
345
346    def __init__(self, **kw):
347        """Initialization of an underlying SubstitutionEnvironment class.
348        """
349        if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.SubstitutionEnvironment')
350        self.fs = SCons.Node.FS.get_default_fs()
351        self.ans = SCons.Node.Alias.default_ans
352        self.lookup_list = SCons.Node.arg2nodes_lookups
353        self._dict = kw.copy()
354        self._init_special()
355        self.added_methods = []
356        #self._memo = {}
357
358    def _init_special(self):
359        """Initial the dispatch tables for special handling of
360        special construction variables."""
361        self._special_del = {}
362        self._special_del['SCANNERS'] = _del_SCANNERS
363
364        self._special_set = {}
365        for key in reserved_construction_var_names:
366            self._special_set[key] = _set_reserved
367        for key in future_reserved_construction_var_names:
368            self._special_set[key] = _set_future_reserved
369        self._special_set['BUILDERS'] = _set_BUILDERS
370        self._special_set['SCANNERS'] = _set_SCANNERS
371
372        # Freeze the keys of self._special_set in a list for use by
373        # methods that need to check.
374        self._special_set_keys = list(self._special_set.keys())
375
376    def __eq__(self, other):
377        return self._dict == other._dict
378
379    def __delitem__(self, key):
380        special = self._special_del.get(key)
381        if special:
382            special(self, key)
383        else:
384            del self._dict[key]
385
386    def __getitem__(self, key):
387        return self._dict[key]
388
389    def __setitem__(self, key, value):
390        # This is heavily used.  This implementation is the best we have
391        # according to the timings in bench/env.__setitem__.py.
392        #
393        # The "key in self._special_set_keys" test here seems to perform
394        # pretty well for the number of keys we have.  A hard-coded
395        # list worked a little better in Python 2.5, but that has the
396        # disadvantage of maybe getting out of sync if we ever add more
397        # variable names.
398        # So right now it seems like a good trade-off, but feel free to
399        # revisit this with bench/env.__setitem__.py as needed (and
400        # as newer versions of Python come out).
401        if key in self._special_set_keys:
402            self._special_set[key](self, key, value)
403        else:
404            # If we already have the entry, then it's obviously a valid
405            # key and we don't need to check.  If we do check, using a
406            # global, pre-compiled regular expression directly is more
407            # efficient than calling another function or a method.
408            if key not in self._dict and not _is_valid_var.match(key):
409                raise UserError("Illegal construction variable `%s'" % key)
410            self._dict[key] = value
411
412    def get(self, key, default=None):
413        """Emulates the get() method of dictionaries."""
414        return self._dict.get(key, default)
415
416    def __contains__(self, key):
417        return key in self._dict
418
419    def keys(self):
420        """Emulates the keys() method of dictionaries."""
421        return self._dict.keys()
422
423    def values(self):
424        """Emulates the values() method of dictionaries."""
425        return self._dict.values()
426
427    def items(self):
428        """Emulates the items() method of dictionaries."""
429        return self._dict.items()
430
431    def setdefault(self, key, default=None):
432        """Emulates the setdefault() method of dictionaries."""
433        return self._dict.setdefault(key, default)
434
435    def arg2nodes(self, args, node_factory=_null, lookup_list=_null, **kw):
436        if node_factory is _null:
437            node_factory = self.fs.File
438        if lookup_list is _null:
439            lookup_list = self.lookup_list
440
441        if not args:
442            return []
443
444        args = flatten(args)
445
446        nodes = []
447        for v in args:
448            if is_String(v):
449                n = None
450                for l in lookup_list:
451                    n = l(v)
452                    if n is not None:
453                        break
454                if n is not None:
455                    if is_String(n):
456                        # n = self.subst(n, raw=1, **kw)
457                        kw['raw'] = 1
458                        n = self.subst(n, **kw)
459                        if node_factory:
460                            n = node_factory(n)
461                    if is_List(n):
462                        nodes.extend(n)
463                    else:
464                        nodes.append(n)
465                elif node_factory:
466                    # v = node_factory(self.subst(v, raw=1, **kw))
467                    kw['raw'] = 1
468                    v = node_factory(self.subst(v, **kw))
469                    if is_List(v):
470                        nodes.extend(v)
471                    else:
472                        nodes.append(v)
473            else:
474                nodes.append(v)
475
476        return nodes
477
478    def gvars(self):
479        return self._dict
480
481    def lvars(self):
482        return {}
483
484    def subst(self, string, raw=0, target=None, source=None, conv=None, executor=None):
485        """Recursively interpolates construction variables from the
486        Environment into the specified string, returning the expanded
487        result.  Construction variables are specified by a $ prefix
488        in the string and begin with an initial underscore or
489        alphabetic character followed by any number of underscores
490        or alphanumeric characters.  The construction variable names
491        may be surrounded by curly braces to separate the name from
492        trailing characters.
493        """
494        gvars = self.gvars()
495        lvars = self.lvars()
496        lvars['__env__'] = self
497        if executor:
498            lvars.update(executor.get_lvars())
499        return SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
500
501    def subst_kw(self, kw, raw=0, target=None, source=None):
502        nkw = {}
503        for k, v in kw.items():
504            k = self.subst(k, raw, target, source)
505            if is_String(v):
506                v = self.subst(v, raw, target, source)
507            nkw[k] = v
508        return nkw
509
510    def subst_list(self, string, raw=0, target=None, source=None, conv=None, executor=None):
511        """Calls through to SCons.Subst.scons_subst_list().  See
512        the documentation for that function."""
513        gvars = self.gvars()
514        lvars = self.lvars()
515        lvars['__env__'] = self
516        if executor:
517            lvars.update(executor.get_lvars())
518        return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
519
520    def subst_path(self, path, target=None, source=None):
521        """Substitute a path list, turning EntryProxies into Nodes
522        and leaving Nodes (and other objects) as-is."""
523
524        if not is_List(path):
525            path = [path]
526
527        def s(obj):
528            """This is the "string conversion" routine that we have our
529            substitutions use to return Nodes, not strings.  This relies
530            on the fact that an EntryProxy object has a get() method that
531            returns the underlying Node that it wraps, which is a bit of
532            architectural dependence that we might need to break or modify
533            in the future in response to additional requirements."""
534            try:
535                get = obj.get
536            except AttributeError:
537                obj = to_String_for_subst(obj)
538            else:
539                obj = get()
540            return obj
541
542        r = []
543        for p in path:
544            if is_String(p):
545                p = self.subst(p, target=target, source=source, conv=s)
546                if is_List(p):
547                    if len(p) == 1:
548                        p = p[0]
549                    else:
550                        # We have an object plus a string, or multiple
551                        # objects that we need to smush together.  No choice
552                        # but to make them into a string.
553                        p = ''.join(map(to_String_for_subst, p))
554            else:
555                p = s(p)
556            r.append(p)
557        return r
558
559    subst_target_source = subst
560
561    def backtick(self, command):
562        import subprocess
563        # common arguments
564        kw = { 'stdin' : 'devnull',
565               'stdout' : subprocess.PIPE,
566               'stderr' : subprocess.PIPE,
567               'universal_newlines' : True,
568             }
569        # if the command is a list, assume it's been quoted
570        # othewise force a shell
571        if not is_List(command): kw['shell'] = True
572        # run constructed command
573        p = SCons.Action._subproc(self, command, **kw)
574        out,err = p.communicate()
575        status = p.wait()
576        if err:
577            sys.stderr.write("" + err)
578        if status:
579            raise OSError("'%s' exited %d" % (command, status))
580        return out
581
582    def AddMethod(self, function, name=None):
583        """
584        Adds the specified function as a method of this construction
585        environment with the specified name.  If the name is omitted,
586        the default name is the name of the function itself.
587        """
588        method = MethodWrapper(self, function, name)
589        self.added_methods.append(method)
590
591    def RemoveMethod(self, function):
592        """
593        Removes the specified function's MethodWrapper from the
594        added_methods list, so we don't re-bind it when making a clone.
595        """
596        self.added_methods = [dm for dm in self.added_methods if dm.method is not function]
597
598    def Override(self, overrides):
599        """
600        Produce a modified environment whose variables are overridden by
601        the overrides dictionaries.  "overrides" is a dictionary that
602        will override the variables of this environment.
603
604        This function is much more efficient than Clone() or creating
605        a new Environment because it doesn't copy the construction
606        environment dictionary, it just wraps the underlying construction
607        environment, and doesn't even create a wrapper object if there
608        are no overrides.
609        """
610        if not overrides: return self
611        o = copy_non_reserved_keywords(overrides)
612        if not o: return self
613        overrides = {}
614        merges = None
615        for key, value in o.items():
616            if key == 'parse_flags':
617                merges = value
618            else:
619                overrides[key] = SCons.Subst.scons_subst_once(value, self, key)
620        env = OverrideEnvironment(self, overrides)
621        if merges:
622            env.MergeFlags(merges)
623        return env
624
625    def ParseFlags(self, *flags):
626        """Return a dict of parsed flags.
627
628        Parse ``flags`` and return a dict with the flags distributed into
629        the appropriate construction variable names.  The flags are treated
630        as a typical set of command-line flags for a GNU-like toolchain,
631        such as might have been generated by one of the {foo}-config scripts,
632        and used to populate the entries based on knowledge embedded in
633        this method - the choices are not expected to be portable to other
634        toolchains.
635
636        If one of the ``flags`` strings begins with a bang (exclamation mark),
637        it is assumed to be a command and the rest of the string is executed;
638        the result of that evaluation is then added to the dict.
639        """
640        dict = {
641            'ASFLAGS'       : CLVar(''),
642            'CFLAGS'        : CLVar(''),
643            'CCFLAGS'       : CLVar(''),
644            'CXXFLAGS'      : CLVar(''),
645            'CPPDEFINES'    : [],
646            'CPPFLAGS'      : CLVar(''),
647            'CPPPATH'       : [],
648            'FRAMEWORKPATH' : CLVar(''),
649            'FRAMEWORKS'    : CLVar(''),
650            'LIBPATH'       : [],
651            'LIBS'          : [],
652            'LINKFLAGS'     : CLVar(''),
653            'RPATH'         : [],
654        }
655
656        def do_parse(arg):
657            # if arg is a sequence, recurse with each element
658            if not arg:
659                return
660
661            if not is_String(arg):
662                for t in arg: do_parse(t)
663                return
664
665            # if arg is a command, execute it
666            if arg[0] == '!':
667                arg = self.backtick(arg[1:])
668
669            # utility function to deal with -D option
670            def append_define(name, dict = dict):
671                t = name.split('=')
672                if len(t) == 1:
673                    dict['CPPDEFINES'].append(name)
674                else:
675                    dict['CPPDEFINES'].append([t[0], '='.join(t[1:])])
676
677            # Loop through the flags and add them to the appropriate option.
678            # This tries to strike a balance between checking for all possible
679            # flags and keeping the logic to a finite size, so it doesn't
680            # check for some that don't occur often.  It particular, if the
681            # flag is not known to occur in a config script and there's a way
682            # of passing the flag to the right place (by wrapping it in a -W
683            # flag, for example) we don't check for it.  Note that most
684            # preprocessor options are not handled, since unhandled options
685            # are placed in CCFLAGS, so unless the preprocessor is invoked
686            # separately, these flags will still get to the preprocessor.
687            # Other options not currently handled:
688            #  -iqoutedir      (preprocessor search path)
689            #  -u symbol       (linker undefined symbol)
690            #  -s              (linker strip files)
691            #  -static*        (linker static binding)
692            #  -shared*        (linker dynamic binding)
693            #  -symbolic       (linker global binding)
694            #  -R dir          (deprecated linker rpath)
695            # IBM compilers may also accept -qframeworkdir=foo
696
697            params = shlex.split(arg)
698            append_next_arg_to = None   # for multi-word args
699            for arg in params:
700                if append_next_arg_to:
701                    if append_next_arg_to == 'CPPDEFINES':
702                        append_define(arg)
703                    elif append_next_arg_to == '-include':
704                        t = ('-include', self.fs.File(arg))
705                        dict['CCFLAGS'].append(t)
706                    elif append_next_arg_to == '-imacros':
707                        t = ('-imacros', self.fs.File(arg))
708                        dict['CCFLAGS'].append(t)
709                    elif append_next_arg_to == '-isysroot':
710                        t = ('-isysroot', arg)
711                        dict['CCFLAGS'].append(t)
712                        dict['LINKFLAGS'].append(t)
713                    elif append_next_arg_to == '-isystem':
714                        t = ('-isystem', arg)
715                        dict['CCFLAGS'].append(t)
716                    elif append_next_arg_to == '-iquote':
717                        t = ('-iquote', arg)
718                        dict['CCFLAGS'].append(t)
719                    elif append_next_arg_to == '-idirafter':
720                        t = ('-idirafter', arg)
721                        dict['CCFLAGS'].append(t)
722                    elif append_next_arg_to == '-arch':
723                        t = ('-arch', arg)
724                        dict['CCFLAGS'].append(t)
725                        dict['LINKFLAGS'].append(t)
726                    elif append_next_arg_to == '--param':
727                        t = ('--param', arg)
728                        dict['CCFLAGS'].append(t)
729                    else:
730                        dict[append_next_arg_to].append(arg)
731                    append_next_arg_to = None
732                elif not arg[0] in ['-', '+']:
733                    dict['LIBS'].append(self.fs.File(arg))
734                elif arg == '-dylib_file':
735                    dict['LINKFLAGS'].append(arg)
736                    append_next_arg_to = 'LINKFLAGS'
737                elif arg[:2] == '-L':
738                    if arg[2:]:
739                        dict['LIBPATH'].append(arg[2:])
740                    else:
741                        append_next_arg_to = 'LIBPATH'
742                elif arg[:2] == '-l':
743                    if arg[2:]:
744                        dict['LIBS'].append(arg[2:])
745                    else:
746                        append_next_arg_to = 'LIBS'
747                elif arg[:2] == '-I':
748                    if arg[2:]:
749                        dict['CPPPATH'].append(arg[2:])
750                    else:
751                        append_next_arg_to = 'CPPPATH'
752                elif arg[:4] == '-Wa,':
753                    dict['ASFLAGS'].append(arg[4:])
754                    dict['CCFLAGS'].append(arg)
755                elif arg[:4] == '-Wl,':
756                    if arg[:11] == '-Wl,-rpath=':
757                        dict['RPATH'].append(arg[11:])
758                    elif arg[:7] == '-Wl,-R,':
759                        dict['RPATH'].append(arg[7:])
760                    elif arg[:6] == '-Wl,-R':
761                        dict['RPATH'].append(arg[6:])
762                    else:
763                        dict['LINKFLAGS'].append(arg)
764                elif arg[:4] == '-Wp,':
765                    dict['CPPFLAGS'].append(arg)
766                elif arg[:2] == '-D':
767                    if arg[2:]:
768                        append_define(arg[2:])
769                    else:
770                        append_next_arg_to = 'CPPDEFINES'
771                elif arg == '-framework':
772                    append_next_arg_to = 'FRAMEWORKS'
773                elif arg[:14] == '-frameworkdir=':
774                    dict['FRAMEWORKPATH'].append(arg[14:])
775                elif arg[:2] == '-F':
776                    if arg[2:]:
777                        dict['FRAMEWORKPATH'].append(arg[2:])
778                    else:
779                        append_next_arg_to = 'FRAMEWORKPATH'
780                elif arg in [
781                    '-mno-cygwin',
782                    '-pthread',
783                    '-openmp',
784                    '-fmerge-all-constants',
785                    '-fopenmp',
786                ]:
787                    dict['CCFLAGS'].append(arg)
788                    dict['LINKFLAGS'].append(arg)
789                elif arg == '-mwindows':
790                    dict['LINKFLAGS'].append(arg)
791                elif arg[:5] == '-std=':
792                    if '++' in arg[5:]:
793                        key='CXXFLAGS'
794                    else:
795                        key='CFLAGS'
796                    dict[key].append(arg)
797                elif arg[0] == '+':
798                    dict['CCFLAGS'].append(arg)
799                    dict['LINKFLAGS'].append(arg)
800                elif arg in [
801                    '-include',
802                    '-imacros',
803                    '-isysroot',
804                    '-isystem',
805                    '-iquote',
806                    '-idirafter',
807                    '-arch',
808                    '--param',
809                ]:
810                    append_next_arg_to = arg
811                else:
812                    dict['CCFLAGS'].append(arg)
813
814        for arg in flags:
815            do_parse(arg)
816        return dict
817
818    def MergeFlags(self, args, unique=True):
819        """Merge flags into construction variables.
820
821        Merges the flags from ``args`` into this construction environent.
822        If ``args`` is not a dict, it is first converted to a dictionary with
823        flags distributed into appropriate construction variables.
824        See :meth:`ParseFlags`.
825
826        Args:
827            args: flags to merge
828            unique: merge flags rather than appending (default: True)
829
830        """
831        if not is_Dict(args):
832            args = self.ParseFlags(args)
833
834        if not unique:
835            self.Append(**args)
836            return
837
838        for key, value in args.items():
839            if not value:
840                continue
841            value = Split(value)
842            try:
843                orig = self[key]
844            except KeyError:
845                orig = value
846            else:
847                if not orig:
848                    orig = value
849                elif value:
850                    # Add orig and value.  The logic here was lifted from
851                    # part of env.Append() (see there for a lot of comments
852                    # about the order in which things are tried) and is
853                    # used mainly to handle coercion of strings to CLVar to
854                    # "do the right thing" given (e.g.) an original CCFLAGS
855                    # string variable like '-pipe -Wall'.
856                    try:
857                        orig = orig + value
858                    except (KeyError, TypeError):
859                        try:
860                            add_to_orig = orig.append
861                        except AttributeError:
862                            value.insert(0, orig)
863                            orig = value
864                        else:
865                            add_to_orig(value)
866            t = []
867            if key[-4:] == 'PATH':
868                ### keep left-most occurence
869                for v in orig:
870                    if v not in t:
871                        t.append(v)
872            else:
873                ### keep right-most occurence
874                for v in orig[::-1]:
875                    if v not in t:
876                        t.insert(0, v)
877            self[key] = t
878
879
880def default_decide_source(dependency, target, prev_ni, repo_node=None):
881    f = SCons.Defaults.DefaultEnvironment().decide_source
882    return f(dependency, target, prev_ni, repo_node)
883
884
885def default_decide_target(dependency, target, prev_ni, repo_node=None):
886    f = SCons.Defaults.DefaultEnvironment().decide_target
887    return f(dependency, target, prev_ni, repo_node)
888
889
890def default_copy_from_cache(env, src, dst):
891    return SCons.CacheDir.CacheDir.copy_from_cache(env, src, dst)
892
893
894def default_copy_to_cache(env, src, dst):
895    return SCons.CacheDir.CacheDir.copy_to_cache(env, src, dst)
896
897
898class Base(SubstitutionEnvironment):
899    """Base class for "real" construction Environments.
900
901    These are the primary objects used to communicate dependency
902    and construction information to the build engine.
903
904    Keyword arguments supplied when the construction Environment
905    is created are construction variables used to initialize the
906    Environment.
907    """
908
909    #######################################################################
910    # This is THE class for interacting with the SCons build engine,
911    # and it contains a lot of stuff, so we're going to try to keep this
912    # a little organized by grouping the methods.
913    #######################################################################
914
915    #######################################################################
916    # Methods that make an Environment act like a dictionary.  These have
917    # the expected standard names for Python mapping objects.  Note that
918    # we don't actually make an Environment a subclass of UserDict for
919    # performance reasons.  Note also that we only supply methods for
920    # dictionary functionality that we actually need and use.
921    #######################################################################
922
923    def __init__(
924        self,
925        platform=None,
926        tools=None,
927        toolpath=None,
928        variables=None,
929        parse_flags=None,
930        **kw
931    ):
932        """Initialization of a basic SCons construction environment.
933
934        Sets up special construction variables like BUILDER,
935        PLATFORM, etc., and searches for and applies available Tools.
936
937        Note that we do *not* call the underlying base class
938        (SubsitutionEnvironment) initialization, because we need to
939        initialize things in a very specific order that doesn't work
940        with the much simpler base class initialization.
941        """
942        if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.Base')
943        self._memo = {}
944        self.fs = SCons.Node.FS.get_default_fs()
945        self.ans = SCons.Node.Alias.default_ans
946        self.lookup_list = SCons.Node.arg2nodes_lookups
947        self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment)
948        self._init_special()
949        self.added_methods = []
950
951        # We don't use AddMethod, or define these as methods in this
952        # class, because we *don't* want these functions to be bound
953        # methods.  They need to operate independently so that the
954        # settings will work properly regardless of whether a given
955        # target ends up being built with a Base environment or an
956        # OverrideEnvironment or what have you.
957        self.decide_target = default_decide_target
958        self.decide_source = default_decide_source
959
960        self.cache_timestamp_newer = False
961
962        self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
963
964        if platform is None:
965            platform = self._dict.get('PLATFORM', None)
966            if platform is None:
967                platform = SCons.Platform.Platform()
968        if is_String(platform):
969            platform = SCons.Platform.Platform(platform)
970        self._dict['PLATFORM'] = str(platform)
971        platform(self)
972
973        self._dict['HOST_OS']      = self._dict.get('HOST_OS',None)
974        self._dict['HOST_ARCH']    = self._dict.get('HOST_ARCH',None)
975
976        # Now set defaults for TARGET_{OS|ARCH}
977        self._dict['TARGET_OS']      = self._dict.get('TARGET_OS',None)
978        self._dict['TARGET_ARCH']    = self._dict.get('TARGET_ARCH',None)
979
980
981        # Apply the passed-in and customizable variables to the
982        # environment before calling the tools, because they may use
983        # some of them during initialization.
984        if 'options' in kw:
985            # Backwards compatibility:  they may stll be using the
986            # old "options" keyword.
987            variables = kw['options']
988            del kw['options']
989        self.Replace(**kw)
990        keys = list(kw.keys())
991        if variables:
992            keys = keys + list(variables.keys())
993            variables.Update(self)
994
995        save = {}
996        for k in keys:
997            try:
998                save[k] = self._dict[k]
999            except KeyError:
1000                # No value may have been set if they tried to pass in a
1001                # reserved variable name like TARGETS.
1002                pass
1003
1004        SCons.Tool.Initializers(self)
1005
1006        if tools is None:
1007            tools = self._dict.get('TOOLS', None)
1008            if tools is None:
1009                tools = ['default']
1010        apply_tools(self, tools, toolpath)
1011
1012        # Now restore the passed-in and customized variables
1013        # to the environment, since the values the user set explicitly
1014        # should override any values set by the tools.
1015        for key, val in save.items():
1016            self._dict[key] = val
1017
1018        # Finally, apply any flags to be merged in
1019        if parse_flags:
1020            self.MergeFlags(parse_flags)
1021
1022    #######################################################################
1023    # Utility methods that are primarily for internal use by SCons.
1024    # These begin with lower-case letters.
1025    #######################################################################
1026
1027    def get_builder(self, name):
1028        """Fetch the builder with the specified name from the environment.
1029        """
1030        try:
1031            return self._dict['BUILDERS'][name]
1032        except KeyError:
1033            return None
1034
1035    def validate_CacheDir_class(self, custom_class=None):
1036        """Validate the passed custom CacheDir class, or if no args are passed,
1037        validate the custom CacheDir class from the environment.
1038        """
1039
1040        if custom_class is None:
1041            custom_class = self.get("CACHEDIR_CLASS", SCons.CacheDir.CacheDir)
1042        if not issubclass(custom_class, SCons.CacheDir.CacheDir):
1043            raise UserError("Custom CACHEDIR_CLASS %s not derived from CacheDir" % str(custom_class))
1044        return custom_class
1045
1046    def get_CacheDir(self):
1047        try:
1048            path = self._CacheDir_path
1049        except AttributeError:
1050            path = SCons.Defaults.DefaultEnvironment()._CacheDir_path
1051
1052        cachedir_class = self.validate_CacheDir_class()
1053        try:
1054            if (path == self._last_CacheDir_path
1055                    # this checks if the cachedir class type has changed from what the
1056                    # instantiated cache dir type is. If the are exactly the same we
1057                    # can just keep using the existing one, otherwise the user is requesting
1058                    # something new, so we will re-instantiate below.
1059                    and type(self._last_CacheDir) is cachedir_class):
1060                return self._last_CacheDir
1061        except AttributeError:
1062            pass
1063
1064        cd = cachedir_class(path)
1065        self._last_CacheDir_path = path
1066        self._last_CacheDir = cd
1067        return cd
1068
1069    def get_factory(self, factory, default='File'):
1070        """Return a factory function for creating Nodes for this
1071        construction environment.
1072        """
1073        name = default
1074        try:
1075            is_node = issubclass(factory, SCons.Node.FS.Base)
1076        except TypeError:
1077            # The specified factory isn't a Node itself--it's
1078            # most likely None, or possibly a callable.
1079            pass
1080        else:
1081            if is_node:
1082                # The specified factory is a Node (sub)class.  Try to
1083                # return the FS method that corresponds to the Node's
1084                # name--that is, we return self.fs.Dir if they want a Dir,
1085                # self.fs.File for a File, etc.
1086                try: name = factory.__name__
1087                except AttributeError: pass
1088                else: factory = None
1089        if not factory:
1090            # They passed us None, or we picked up a name from a specified
1091            # class, so return the FS method.  (Note that we *don't*
1092            # use our own self.{Dir,File} methods because that would
1093            # cause env.subst() to be called twice on the file name,
1094            # interfering with files that have $$ in them.)
1095            factory = getattr(self.fs, name)
1096        return factory
1097
1098    @SCons.Memoize.CountMethodCall
1099    def _gsm(self):
1100        try:
1101            return self._memo['_gsm']
1102        except KeyError:
1103            pass
1104
1105        result = {}
1106
1107        try:
1108            scanners = self._dict['SCANNERS']
1109        except KeyError:
1110            pass
1111        else:
1112            # Reverse the scanner list so that, if multiple scanners
1113            # claim they can scan the same suffix, earlier scanners
1114            # in the list will overwrite later scanners, so that
1115            # the result looks like a "first match" to the user.
1116            if not is_List(scanners):
1117                scanners = [scanners]
1118            else:
1119                scanners = scanners[:] # copy so reverse() doesn't mod original
1120            scanners.reverse()
1121            for scanner in scanners:
1122                for k in scanner.get_skeys(self):
1123                    if k and self['PLATFORM'] == 'win32':
1124                        k = k.lower()
1125                    result[k] = scanner
1126
1127        self._memo['_gsm'] = result
1128
1129        return result
1130
1131    def get_scanner(self, skey):
1132        """Find the appropriate scanner given a key (usually a file suffix).
1133        """
1134        if skey and self['PLATFORM'] == 'win32':
1135            skey = skey.lower()
1136        return self._gsm().get(skey)
1137
1138    def scanner_map_delete(self, kw=None):
1139        """Delete the cached scanner map (if we need to).
1140        """
1141        try:
1142            del self._memo['_gsm']
1143        except KeyError:
1144            pass
1145
1146    def _update(self, other):
1147        """Private method to update an environment's consvar dict directly.
1148
1149        Bypasses the normal checks that occur when users try to set items.
1150        """
1151        self._dict.update(other)
1152
1153    def _update_onlynew(self, other):
1154        """Private method to add new items to an environment's consvar dict.
1155
1156        Only adds items from `other` whose keys do not already appear in
1157        the existing dict; values from `other` are not used for replacement.
1158        Bypasses the normal checks that occur when users try to set items.
1159        """
1160        for k, v in other.items():
1161            if k not in self._dict:
1162                self._dict[k] = v
1163
1164
1165    def get_src_sig_type(self):
1166        try:
1167            return self.src_sig_type
1168        except AttributeError:
1169            t = SCons.Defaults.DefaultEnvironment().src_sig_type
1170            self.src_sig_type = t
1171            return t
1172
1173    def get_tgt_sig_type(self):
1174        try:
1175            return self.tgt_sig_type
1176        except AttributeError:
1177            t = SCons.Defaults.DefaultEnvironment().tgt_sig_type
1178            self.tgt_sig_type = t
1179            return t
1180
1181    #######################################################################
1182    # Public methods for manipulating an Environment.  These begin with
1183    # upper-case letters.  The essential characteristic of methods in
1184    # this section is that they do *not* have corresponding same-named
1185    # global functions.  For example, a stand-alone Append() function
1186    # makes no sense, because Append() is all about appending values to
1187    # an Environment's construction variables.
1188    #######################################################################
1189
1190    def Append(self, **kw):
1191        """Append values to construction variables in an Environment.
1192
1193        The variable is created if it is not already present.
1194        """
1195
1196        kw = copy_non_reserved_keywords(kw)
1197        for key, val in kw.items():
1198            try:
1199                if key == 'CPPDEFINES' and is_String(self._dict[key]):
1200                    self._dict[key] = [self._dict[key]]
1201                orig = self._dict[key]
1202            except KeyError:
1203                # No existing var in the environment, so set to the new value.
1204                if key == 'CPPDEFINES' and is_String(val):
1205                    self._dict[key] = [val]
1206                else:
1207                    self._dict[key] = val
1208                continue
1209
1210            try:
1211                # Check if the original looks like a dict: has .update?
1212                update_dict = orig.update
1213            except AttributeError:
1214                try:
1215                    # Just try to add them together.  This will work
1216                    # in most cases, when the original and new values
1217                    # are compatible types.
1218                    self._dict[key] = orig + val
1219                except (KeyError, TypeError):
1220                    try:
1221                        # Check if the original is a list: has .append?
1222                        add_to_orig = orig.append
1223                    except AttributeError:
1224                        # The original isn't a list, but the new
1225                        # value is (by process of elimination),
1226                        # so insert the original in the new value
1227                        # (if there's one to insert) and replace
1228                        # the variable with it.
1229                        if orig:
1230                            val.insert(0, orig)
1231                        self._dict[key] = val
1232                    else:
1233                        # The original is a list, so append the new
1234                        # value to it (if there's a value to append).
1235                        if val:
1236                            add_to_orig(val)
1237                continue
1238
1239            # The original looks like a dictionary, so update it
1240            # based on what we think the value looks like.
1241            # We can't just try adding the value because
1242            # dictionaries don't have __add__() methods, and
1243            # things like UserList will incorrectly coerce the
1244            # original dict to a list (which we don't want).
1245            if is_List(val):
1246                if key == 'CPPDEFINES':
1247                    tmp = []
1248                    for (k, v) in orig.items():
1249                        if v is not None:
1250                            tmp.append((k, v))
1251                        else:
1252                            tmp.append((k,))
1253                    orig = tmp
1254                    orig += val
1255                    self._dict[key] = orig
1256                else:
1257                    for v in val:
1258                        orig[v] = None
1259            else:
1260                try:
1261                    update_dict(val)
1262                except (AttributeError, TypeError, ValueError):
1263                    if is_Dict(val):
1264                        for k, v in val.items():
1265                            orig[k] = v
1266                    else:
1267                        orig[val] = None
1268
1269        self.scanner_map_delete(kw)
1270
1271    def _canonicalize(self, path):
1272        """Allow Dirs and strings beginning with # for top-relative.
1273
1274        Note this uses the current env's fs (in self).
1275        """
1276        if not is_String(path):  # typically a Dir
1277            path = str(path)
1278        if path and path[0] == '#':
1279            path = str(self.fs.Dir(path))
1280        return path
1281
1282    def AppendENVPath(self, name, newpath, envname = 'ENV',
1283                      sep = os.pathsep, delete_existing=0):
1284        """Append path elements to the path 'name' in the 'ENV'
1285        dictionary for this environment.  Will only add any particular
1286        path once, and will normpath and normcase all paths to help
1287        assure this.  This can also handle the case where the env
1288        variable is a list instead of a string.
1289
1290        If delete_existing is 0, a newpath which is already in the path
1291        will not be moved to the end (it will be left where it is).
1292        """
1293
1294        orig = ''
1295        if envname in self._dict and name in self._dict[envname]:
1296            orig = self._dict[envname][name]
1297
1298        nv = AppendPath(orig, newpath, sep, delete_existing, canonicalize=self._canonicalize)
1299
1300        if envname not in self._dict:
1301            self._dict[envname] = {}
1302
1303        self._dict[envname][name] = nv
1304
1305    def AppendUnique(self, delete_existing=0, **kw):
1306        """Append values to existing construction variables
1307        in an Environment, if they're not already there.
1308        If delete_existing is 1, removes existing values first, so
1309        values move to end.
1310        """
1311        kw = copy_non_reserved_keywords(kw)
1312        for key, val in kw.items():
1313            if is_List(val):
1314                val = _delete_duplicates(val, delete_existing)
1315            if key not in self._dict or self._dict[key] in ('', None):
1316                self._dict[key] = val
1317            elif is_Dict(self._dict[key]) and is_Dict(val):
1318                self._dict[key].update(val)
1319            elif is_List(val):
1320                dk = self._dict[key]
1321                if key == 'CPPDEFINES':
1322                    tmp = []
1323                    for i in val:
1324                        if is_List(i):
1325                            if len(i) >= 2:
1326                                tmp.append((i[0], i[1]))
1327                            else:
1328                                tmp.append((i[0],))
1329                        elif is_Tuple(i):
1330                            tmp.append(i)
1331                        else:
1332                            tmp.append((i,))
1333                    val = tmp
1334                    # Construct a list of (key, value) tuples.
1335                    if is_Dict(dk):
1336                        tmp = []
1337                        for (k, v) in dk.items():
1338                            if v is not None:
1339                                tmp.append((k, v))
1340                            else:
1341                                tmp.append((k,))
1342                        dk = tmp
1343                    elif is_String(dk):
1344                        dk = [(dk,)]
1345                    else:
1346                        tmp = []
1347                        for i in dk:
1348                            if is_List(i):
1349                                if len(i) >= 2:
1350                                    tmp.append((i[0], i[1]))
1351                                else:
1352                                    tmp.append((i[0],))
1353                            elif is_Tuple(i):
1354                                tmp.append(i)
1355                            else:
1356                                tmp.append((i,))
1357                        dk = tmp
1358                else:
1359                    if not is_List(dk):
1360                        dk = [dk]
1361                if delete_existing:
1362                    dk = [x for x in dk if x not in val]
1363                else:
1364                    val = [x for x in val if x not in dk]
1365                self._dict[key] = dk + val
1366            else:
1367                dk = self._dict[key]
1368                if is_List(dk):
1369                    if key == 'CPPDEFINES':
1370                        tmp = []
1371                        for i in dk:
1372                            if is_List(i):
1373                                if len(i) >= 2:
1374                                    tmp.append((i[0], i[1]))
1375                                else:
1376                                    tmp.append((i[0],))
1377                            elif is_Tuple(i):
1378                                tmp.append(i)
1379                            else:
1380                                tmp.append((i,))
1381                        dk = tmp
1382                        # Construct a list of (key, value) tuples.
1383                        if is_Dict(val):
1384                            tmp = []
1385                            for (k, v) in val.items():
1386                                if v is not None:
1387                                    tmp.append((k, v))
1388                                else:
1389                                    tmp.append((k,))
1390                            val = tmp
1391                        elif is_String(val):
1392                            val = [(val,)]
1393                        if delete_existing:
1394                            dk = list(filter(lambda x, val=val: x not in val, dk))
1395                            self._dict[key] = dk + val
1396                        else:
1397                            dk = [x for x in dk if x not in val]
1398                            self._dict[key] = dk + val
1399                    else:
1400                        # By elimination, val is not a list.  Since dk is a
1401                        # list, wrap val in a list first.
1402                        if delete_existing:
1403                            dk = list(filter(lambda x, val=val: x not in val, dk))
1404                            self._dict[key] = dk + [val]
1405                        else:
1406                            if val not in dk:
1407                                self._dict[key] = dk + [val]
1408                else:
1409                    if key == 'CPPDEFINES':
1410                        if is_String(dk):
1411                            dk = [dk]
1412                        elif is_Dict(dk):
1413                            tmp = []
1414                            for (k, v) in dk.items():
1415                                if v is not None:
1416                                    tmp.append((k, v))
1417                                else:
1418                                    tmp.append((k,))
1419                            dk = tmp
1420                        if is_String(val):
1421                            if val in dk:
1422                                val = []
1423                            else:
1424                                val = [val]
1425                        elif is_Dict(val):
1426                            tmp = []
1427                            for i,j in val.items():
1428                                if j is not None:
1429                                    tmp.append((i,j))
1430                                else:
1431                                    tmp.append(i)
1432                            val = tmp
1433                    if delete_existing:
1434                        dk = [x for x in dk if x not in val]
1435                    self._dict[key] = dk + val
1436        self.scanner_map_delete(kw)
1437
1438    def Clone(self, tools=[], toolpath=None, parse_flags = None, **kw):
1439        """Return a copy of a construction Environment.
1440
1441        The copy is like a Python "deep copy"--that is, independent
1442        copies are made recursively of each objects--except that
1443        a reference is copied when an object is not deep-copyable
1444        (like a function).  There are no references to any mutable
1445        objects in the original Environment.
1446        """
1447
1448        builders = self._dict.get('BUILDERS', {})
1449
1450        clone = copy.copy(self)
1451        # BUILDERS is not safe to do a simple copy
1452        clone._dict = semi_deepcopy_dict(self._dict, ['BUILDERS'])
1453        clone._dict['BUILDERS'] = BuilderDict(builders, clone)
1454
1455        # Check the methods added via AddMethod() and re-bind them to
1456        # the cloned environment.  Only do this if the attribute hasn't
1457        # been overwritten by the user explicitly and still points to
1458        # the added method.
1459        clone.added_methods = []
1460        for mw in self.added_methods:
1461            if mw == getattr(self, mw.name):
1462                clone.added_methods.append(mw.clone(clone))
1463
1464        clone._memo = {}
1465
1466        # Apply passed-in variables before the tools
1467        # so the tools can use the new variables
1468        kw = copy_non_reserved_keywords(kw)
1469        new = {}
1470        for key, value in kw.items():
1471            new[key] = SCons.Subst.scons_subst_once(value, self, key)
1472        clone.Replace(**new)
1473
1474        apply_tools(clone, tools, toolpath)
1475
1476        # apply them again in case the tools overwrote them
1477        clone.Replace(**new)
1478
1479        # Finally, apply any flags to be merged in
1480        if parse_flags:
1481            clone.MergeFlags(parse_flags)
1482
1483        if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.EnvironmentClone')
1484        return clone
1485
1486    def _changed_build(self, dependency, target, prev_ni, repo_node=None):
1487        if dependency.changed_state(target, prev_ni, repo_node):
1488            return 1
1489        return self.decide_source(dependency, target, prev_ni, repo_node)
1490
1491    def _changed_content(self, dependency, target, prev_ni, repo_node=None):
1492        return dependency.changed_content(target, prev_ni, repo_node)
1493
1494    def _changed_source(self, dependency, target, prev_ni, repo_node=None):
1495        target_env = dependency.get_build_env()
1496        type = target_env.get_tgt_sig_type()
1497        if type == 'source':
1498            return target_env.decide_source(dependency, target, prev_ni, repo_node)
1499        else:
1500            return target_env.decide_target(dependency, target, prev_ni, repo_node)
1501
1502    def _changed_timestamp_then_content(self, dependency, target, prev_ni, repo_node=None):
1503        return dependency.changed_timestamp_then_content(target, prev_ni, repo_node)
1504
1505    def _changed_timestamp_newer(self, dependency, target, prev_ni, repo_node=None):
1506        return dependency.changed_timestamp_newer(target, prev_ni, repo_node)
1507
1508    def _changed_timestamp_match(self, dependency, target, prev_ni, repo_node=None):
1509        return dependency.changed_timestamp_match(target, prev_ni, repo_node)
1510
1511    def Decider(self, function):
1512        self.cache_timestamp_newer = False
1513        if function in ('MD5', 'content'):
1514            # TODO: Handle if user requests MD5 and not content with deprecation notice
1515            function = self._changed_content
1516        elif function in ('MD5-timestamp', 'content-timestamp'):
1517            function = self._changed_timestamp_then_content
1518        elif function in ('timestamp-newer', 'make'):
1519            function = self._changed_timestamp_newer
1520            self.cache_timestamp_newer = True
1521        elif function == 'timestamp-match':
1522            function = self._changed_timestamp_match
1523        elif not callable(function):
1524            raise UserError("Unknown Decider value %s" % repr(function))
1525
1526        # We don't use AddMethod because we don't want to turn the
1527        # function, which only expects three arguments, into a bound
1528        # method, which would add self as an initial, fourth argument.
1529        self.decide_target = function
1530        self.decide_source = function
1531
1532
1533    def Detect(self, progs):
1534        """Return the first available program from one or more possibilities.
1535
1536        Args:
1537            progs (str or list): one or more command names to check for
1538
1539        """
1540        if not is_List(progs):
1541            progs = [progs]
1542        for prog in progs:
1543            path = self.WhereIs(prog)
1544            if path: return prog
1545        return None
1546
1547
1548    def Dictionary(self, *args):
1549        r"""Return construction variables from an environment.
1550
1551        Args:
1552          \*args (optional): variable names to look up
1553
1554        Returns:
1555          If `args` omitted, the dictionary of all construction variables.
1556          If one arg, the corresponding value is returned.
1557          If more than one arg, a list of values is returned.
1558
1559        Raises:
1560          KeyError: if any of `args` is not in the construction environment.
1561
1562        """
1563        if not args:
1564            return self._dict
1565        dlist = [self._dict[x] for x in args]
1566        if len(dlist) == 1:
1567            dlist = dlist[0]
1568        return dlist
1569
1570
1571    def Dump(self, key=None, format='pretty'):
1572        """ Return construction variables serialized to a string.
1573
1574        Args:
1575          key (optional): if None, format the whole dict of variables.
1576            Else format the value of `key` (Default value = None)
1577          format (str, optional): specify the format to serialize to.
1578            `"pretty"` generates a pretty-printed string,
1579            `"json"` a JSON-formatted string.
1580            (Default value = `"pretty"`)
1581
1582        """
1583        if key:
1584            cvars = self.Dictionary(key)
1585        else:
1586            cvars = self.Dictionary()
1587
1588        fmt = format.lower()
1589
1590        if fmt == 'pretty':
1591            import pprint
1592            pp = pprint.PrettyPrinter(indent=2)
1593
1594            # TODO: pprint doesn't do a nice job on path-style values
1595            # if the paths contain spaces (i.e. Windows), because the
1596            # algorithm tries to break lines on spaces, while breaking
1597            # on the path-separator would be more "natural". Is there
1598            # a better way to format those?
1599            return pp.pformat(cvars)
1600
1601        elif fmt == 'json':
1602            import json
1603            def non_serializable(obj):
1604                return str(type(obj).__qualname__)
1605            return json.dumps(cvars, indent=4, default=non_serializable)
1606        else:
1607            raise ValueError("Unsupported serialization format: %s." % fmt)
1608
1609
1610    def FindIxes(self, paths, prefix, suffix):
1611        """Search a list of paths for something that matches the prefix and suffix.
1612
1613        Args:
1614          paths: the list of paths or nodes.
1615          prefix: construction variable for the prefix.
1616          suffix: construction variable for the suffix.
1617
1618        Returns: the matched path or None
1619
1620        """
1621
1622        suffix = self.subst('$'+suffix)
1623        prefix = self.subst('$'+prefix)
1624
1625        for path in paths:
1626            name = os.path.basename(str(path))
1627            if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix:
1628                return path
1629
1630    def ParseConfig(self, command, function=None, unique=True):
1631        """
1632        Use the specified function to parse the output of the command
1633        in order to modify the current environment.  The 'command' can
1634        be a string or a list of strings representing a command and
1635        its arguments.  'Function' is an optional argument that takes
1636        the environment, the output of the command, and the unique flag.
1637        If no function is specified, MergeFlags, which treats the output
1638        as the result of a typical 'X-config' command (i.e. gtk-config),
1639        will merge the output into the appropriate variables.
1640        """
1641        if function is None:
1642            def parse_conf(env, cmd, unique=unique):
1643                return env.MergeFlags(cmd, unique)
1644            function = parse_conf
1645        if is_List(command):
1646            command = ' '.join(command)
1647        command = self.subst(command)
1648        return function(self, self.backtick(command))
1649
1650    def ParseDepends(self, filename, must_exist=None, only_one=False):
1651        """
1652        Parse a mkdep-style file for explicit dependencies.  This is
1653        completely abusable, and should be unnecessary in the "normal"
1654        case of proper SCons configuration, but it may help make
1655        the transition from a Make hierarchy easier for some people
1656        to swallow.  It can also be genuinely useful when using a tool
1657        that can write a .d file, but for which writing a scanner would
1658        be too complicated.
1659        """
1660        filename = self.subst(filename)
1661        try:
1662            with open(filename, 'r') as fp:
1663                lines = LogicalLines(fp).readlines()
1664        except IOError:
1665            if must_exist:
1666                raise
1667            return
1668        lines = [l for l in lines if l[0] != '#']
1669        tdlist = []
1670        for line in lines:
1671            try:
1672                target, depends = line.split(':', 1)
1673            except (AttributeError, ValueError):
1674                # Throws AttributeError if line isn't a string.  Can throw
1675                # ValueError if line doesn't split into two or more elements.
1676                pass
1677            else:
1678                tdlist.append((target.split(), depends.split()))
1679        if only_one:
1680            targets = []
1681            for td in tdlist:
1682                targets.extend(td[0])
1683            if len(targets) > 1:
1684                raise UserError(
1685                            "More than one dependency target found in `%s':  %s"
1686                                            % (filename, targets))
1687        for target, depends in tdlist:
1688            self.Depends(target, depends)
1689
1690    def Platform(self, platform):
1691        platform = self.subst(platform)
1692        return SCons.Platform.Platform(platform)(self)
1693
1694    def Prepend(self, **kw):
1695        """Prepend values to construction variables in an Environment.
1696
1697        The variable is created if it is not already present.
1698        """
1699
1700        kw = copy_non_reserved_keywords(kw)
1701        for key, val in kw.items():
1702            try:
1703                orig = self._dict[key]
1704            except KeyError:
1705                # No existing var in the environment so set to the new value.
1706                self._dict[key] = val
1707                continue
1708
1709            try:
1710                # Check if the original looks like a dict: has .update?
1711                update_dict = orig.update
1712            except AttributeError:
1713                try:
1714                    # Just try to add them together.  This will work
1715                    # in most cases, when the original and new values
1716                    # are compatible types.
1717                    self._dict[key] = val + orig
1718                except (KeyError, TypeError):
1719                    try:
1720                        # Check if the added value is a list: has .append?
1721                        add_to_val = val.append
1722                    except AttributeError:
1723                        # The added value isn't a list, but the
1724                        # original is (by process of elimination),
1725                        # so insert the the new value in the original
1726                        # (if there's one to insert).
1727                        if val:
1728                            orig.insert(0, val)
1729                    else:
1730                        # The added value is a list, so append
1731                        # the original to it (if there's a value
1732                        # to append) and replace the original.
1733                        if orig:
1734                            add_to_val(orig)
1735                        self._dict[key] = val
1736                continue
1737
1738            # The original looks like a dictionary, so update it
1739            # based on what we think the value looks like.
1740            # We can't just try adding the value because
1741            # dictionaries don't have __add__() methods, and
1742            # things like UserList will incorrectly coerce the
1743            # original dict to a list (which we don't want).
1744            if is_List(val):
1745                for v in val:
1746                    orig[v] = None
1747            else:
1748                try:
1749                    update_dict(val)
1750                except (AttributeError, TypeError, ValueError):
1751                    if is_Dict(val):
1752                        for k, v in val.items():
1753                            orig[k] = v
1754                    else:
1755                        orig[val] = None
1756
1757        self.scanner_map_delete(kw)
1758
1759    def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep,
1760                       delete_existing=1):
1761        """Prepend path elements to the path 'name' in the 'ENV'
1762        dictionary for this environment.  Will only add any particular
1763        path once, and will normpath and normcase all paths to help
1764        assure this.  This can also handle the case where the env
1765        variable is a list instead of a string.
1766
1767        If delete_existing is 0, a newpath which is already in the path
1768        will not be moved to the front (it will be left where it is).
1769        """
1770
1771        orig = ''
1772        if envname in self._dict and name in self._dict[envname]:
1773            orig = self._dict[envname][name]
1774
1775        nv = PrependPath(orig, newpath, sep, delete_existing,
1776                                    canonicalize=self._canonicalize)
1777
1778        if envname not in self._dict:
1779            self._dict[envname] = {}
1780
1781        self._dict[envname][name] = nv
1782
1783    def PrependUnique(self, delete_existing=0, **kw):
1784        """Prepend values to existing construction variables
1785        in an Environment, if they're not already there.
1786        If delete_existing is 1, removes existing values first, so
1787        values move to front.
1788        """
1789        kw = copy_non_reserved_keywords(kw)
1790        for key, val in kw.items():
1791            if is_List(val):
1792                val = _delete_duplicates(val, not delete_existing)
1793            if key not in self._dict or self._dict[key] in ('', None):
1794                self._dict[key] = val
1795            elif is_Dict(self._dict[key]) and is_Dict(val):
1796                self._dict[key].update(val)
1797            elif is_List(val):
1798                dk = self._dict[key]
1799                if not is_List(dk):
1800                    dk = [dk]
1801                if delete_existing:
1802                    dk = [x for x in dk if x not in val]
1803                else:
1804                    val = [x for x in val if x not in dk]
1805                self._dict[key] = val + dk
1806            else:
1807                dk = self._dict[key]
1808                if is_List(dk):
1809                    # By elimination, val is not a list.  Since dk is a
1810                    # list, wrap val in a list first.
1811                    if delete_existing:
1812                        dk = [x for x in dk if x not in val]
1813                        self._dict[key] = [val] + dk
1814                    else:
1815                        if val not in dk:
1816                            self._dict[key] = [val] + dk
1817                else:
1818                    if delete_existing:
1819                        dk = [x for x in dk if x not in val]
1820                    self._dict[key] = val + dk
1821        self.scanner_map_delete(kw)
1822
1823    def Replace(self, **kw):
1824        """Replace existing construction variables in an Environment
1825        with new construction variables and/or values.
1826        """
1827        try:
1828            kwbd = kw['BUILDERS']
1829        except KeyError:
1830            pass
1831        else:
1832            kwbd = BuilderDict(kwbd,self)
1833            del kw['BUILDERS']
1834            self.__setitem__('BUILDERS', kwbd)
1835        kw = copy_non_reserved_keywords(kw)
1836        self._update(semi_deepcopy(kw))
1837        self.scanner_map_delete(kw)
1838
1839    def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
1840        """
1841        Replace old_prefix with new_prefix and old_suffix with new_suffix.
1842
1843        env - Environment used to interpolate variables.
1844        path - the path that will be modified.
1845        old_prefix - construction variable for the old prefix.
1846        old_suffix - construction variable for the old suffix.
1847        new_prefix - construction variable for the new prefix.
1848        new_suffix - construction variable for the new suffix.
1849        """
1850        old_prefix = self.subst('$'+old_prefix)
1851        old_suffix = self.subst('$'+old_suffix)
1852
1853        new_prefix = self.subst('$'+new_prefix)
1854        new_suffix = self.subst('$'+new_suffix)
1855
1856        dir,name = os.path.split(str(path))
1857        if name[:len(old_prefix)] == old_prefix:
1858            name = name[len(old_prefix):]
1859        if name[-len(old_suffix):] == old_suffix:
1860            name = name[:-len(old_suffix)]
1861        return os.path.join(dir, new_prefix+name+new_suffix)
1862
1863    def SetDefault(self, **kw):
1864        for k in list(kw.keys()):
1865            if k in self._dict:
1866                del kw[k]
1867        self.Replace(**kw)
1868
1869    def _find_toolpath_dir(self, tp):
1870        return self.fs.Dir(self.subst(tp)).srcnode().get_abspath()
1871
1872    def Tool(self, tool, toolpath=None, **kwargs) -> SCons.Tool.Tool:
1873        if is_String(tool):
1874            tool = self.subst(tool)
1875            if toolpath is None:
1876                toolpath = self.get('toolpath', [])
1877            toolpath = list(map(self._find_toolpath_dir, toolpath))
1878            tool = SCons.Tool.Tool(tool, toolpath, **kwargs)
1879        tool(self)
1880        return tool
1881
1882    def WhereIs(self, prog, path=None, pathext=None, reject=None):
1883        """Find prog in the path. """
1884        if not prog:  # nothing to search for, just give up
1885            return None
1886        if path is None:
1887            try:
1888                path = self['ENV']['PATH']
1889            except KeyError:
1890                pass
1891        elif is_String(path):
1892            path = self.subst(path)
1893        if pathext is None:
1894            try:
1895                pathext = self['ENV']['PATHEXT']
1896            except KeyError:
1897                pass
1898        elif is_String(pathext):
1899            pathext = self.subst(pathext)
1900        prog = CLVar(self.subst(prog))  # support "program --with-args"
1901        path = WhereIs(prog[0], path, pathext, reject)
1902        if path:
1903            return path
1904        return None
1905
1906    #######################################################################
1907    # Public methods for doing real "SCons stuff" (manipulating
1908    # dependencies, setting attributes on targets, etc.).  These begin
1909    # with upper-case letters.  The essential characteristic of methods
1910    # in this section is that they all *should* have corresponding
1911    # same-named global functions.
1912    #######################################################################
1913
1914    def Action(self, *args, **kw):
1915        def subst_string(a, self=self):
1916            if is_String(a):
1917                a = self.subst(a)
1918            return a
1919        nargs = list(map(subst_string, args))
1920        nkw = self.subst_kw(kw)
1921        return SCons.Action.Action(*nargs, **nkw)
1922
1923    def AddPreAction(self, files, action):
1924        nodes = self.arg2nodes(files, self.fs.Entry)
1925        action = SCons.Action.Action(action)
1926        uniq = {}
1927        for executor in [n.get_executor() for n in nodes]:
1928            uniq[executor] = 1
1929        for executor in uniq.keys():
1930            executor.add_pre_action(action)
1931        return nodes
1932
1933    def AddPostAction(self, files, action):
1934        nodes = self.arg2nodes(files, self.fs.Entry)
1935        action = SCons.Action.Action(action)
1936        uniq = {}
1937        for executor in [n.get_executor() for n in nodes]:
1938            uniq[executor] = 1
1939        for executor in uniq.keys():
1940            executor.add_post_action(action)
1941        return nodes
1942
1943    def Alias(self, target, source=[], action=None, **kw):
1944        tlist = self.arg2nodes(target, self.ans.Alias)
1945        if not is_List(source):
1946            source = [source]
1947        source = [_f for _f in source if _f]
1948
1949        if not action:
1950            if not source:
1951                # There are no source files and no action, so just
1952                # return a target list of classic Alias Nodes, without
1953                # any builder.  The externally visible effect is that
1954                # this will make the wrapping Script.BuildTask class
1955                # say that there's "Nothing to be done" for this Alias,
1956                # instead of that it's "up to date."
1957                return tlist
1958
1959            # No action, but there are sources.  Re-call all the target
1960            # builders to add the sources to each target.
1961            result = []
1962            for t in tlist:
1963                bld = t.get_builder(AliasBuilder)
1964                result.extend(bld(self, t, source))
1965            return result
1966
1967        nkw = self.subst_kw(kw)
1968        nkw.update({
1969            'action'            : SCons.Action.Action(action),
1970            'source_factory'    : self.fs.Entry,
1971            'multi'             : 1,
1972            'is_explicit'       : None,
1973        })
1974        bld = SCons.Builder.Builder(**nkw)
1975
1976        # Apply the Builder separately to each target so that the Aliases
1977        # stay separate.  If we did one "normal" Builder call with the
1978        # whole target list, then all of the target Aliases would be
1979        # associated under a single Executor.
1980        result = []
1981        for t in tlist:
1982            # Calling the convert() method will cause a new Executor to be
1983            # created from scratch, so we have to explicitly initialize
1984            # it with the target's existing sources, plus our new ones,
1985            # so nothing gets lost.
1986            b = t.get_builder()
1987            if b is None or b is AliasBuilder:
1988                b = bld
1989            else:
1990                nkw['action'] = b.action + action
1991                b = SCons.Builder.Builder(**nkw)
1992            t.convert()
1993            result.extend(b(self, t, t.sources + source))
1994        return result
1995
1996    def AlwaysBuild(self, *targets):
1997        tlist = []
1998        for t in targets:
1999            tlist.extend(self.arg2nodes(t, self.fs.Entry))
2000        for t in tlist:
2001            t.set_always_build()
2002        return tlist
2003
2004    def Builder(self, **kw):
2005        nkw = self.subst_kw(kw)
2006        return SCons.Builder.Builder(**nkw)
2007
2008    def CacheDir(self, path, custom_class=None):
2009        if path is not None:
2010            path = self.subst(path)
2011        self._CacheDir_path = path
2012
2013        if custom_class:
2014            self['CACHEDIR_CLASS'] = self.validate_CacheDir_class(custom_class)
2015
2016        if SCons.Action.execute_actions:
2017            # Only initialize the CacheDir if  -n/-no_exec was NOT specified.
2018            # Now initialized the CacheDir and prevent a race condition which can
2019            # happen when there's no existing cache dir and you are building with
2020            # multiple threads, but initializing it before the task walk starts
2021            self.get_CacheDir()
2022
2023    def Clean(self, targets, files):
2024        global CleanTargets
2025        tlist = self.arg2nodes(targets, self.fs.Entry)
2026        flist = self.arg2nodes(files, self.fs.Entry)
2027        for t in tlist:
2028            try:
2029                CleanTargets[t].extend(flist)
2030            except KeyError:
2031                CleanTargets[t] = flist
2032
2033    def Configure(self, *args, **kw):
2034        nargs = [self]
2035        if args:
2036            nargs = nargs + self.subst_list(args)[0]
2037        nkw = self.subst_kw(kw)
2038        nkw['_depth'] = kw.get('_depth', 0) + 1
2039        try:
2040            nkw['custom_tests'] = self.subst_kw(nkw['custom_tests'])
2041        except KeyError:
2042            pass
2043        return SCons.SConf.SConf(*nargs, **nkw)
2044
2045    def Command(self, target, source, action, **kw):
2046        """Builds the supplied target files from the supplied
2047        source files using the supplied action.  Action may
2048        be any type that the Builder constructor will accept
2049        for an action."""
2050        bkw = {
2051            'action': action,
2052            'target_factory': self.fs.Entry,
2053            'source_factory': self.fs.Entry,
2054        }
2055        # source scanner
2056        try:
2057            bkw['source_scanner'] = kw['source_scanner']
2058        except KeyError:
2059            pass
2060        else:
2061            del kw['source_scanner']
2062
2063        # target scanner
2064        try:
2065            bkw['target_scanner'] = kw['target_scanner']
2066        except KeyError:
2067            pass
2068        else:
2069            del kw['target_scanner']
2070
2071        # source factory
2072        try:
2073            bkw['source_factory'] = kw['source_factory']
2074        except KeyError:
2075            pass
2076        else:
2077            del kw['source_factory']
2078
2079        # target factory
2080        try:
2081            bkw['target_factory'] = kw['target_factory']
2082        except KeyError:
2083            pass
2084        else:
2085            del kw['target_factory']
2086
2087        bld = SCons.Builder.Builder(**bkw)
2088        return bld(self, target, source, **kw)
2089
2090    def Depends(self, target, dependency):
2091        """Explicity specify that 'target's depend on 'dependency'."""
2092        tlist = self.arg2nodes(target, self.fs.Entry)
2093        dlist = self.arg2nodes(dependency, self.fs.Entry)
2094        for t in tlist:
2095            t.add_dependency(dlist)
2096        return tlist
2097
2098    def Dir(self, name, *args, **kw):
2099        """
2100        """
2101        s = self.subst(name)
2102        if is_Sequence(s):
2103            result=[]
2104            for e in s:
2105                result.append(self.fs.Dir(e, *args, **kw))
2106            return result
2107        return self.fs.Dir(s, *args, **kw)
2108
2109    def PyPackageDir(self, modulename):
2110        s = self.subst(modulename)
2111        if is_Sequence(s):
2112            result=[]
2113            for e in s:
2114                result.append(self.fs.PyPackageDir(e))
2115            return result
2116        return self.fs.PyPackageDir(s)
2117
2118    def NoClean(self, *targets):
2119        """Tags a target so that it will not be cleaned by -c"""
2120        tlist = []
2121        for t in targets:
2122            tlist.extend(self.arg2nodes(t, self.fs.Entry))
2123        for t in tlist:
2124            t.set_noclean()
2125        return tlist
2126
2127    def NoCache(self, *targets):
2128        """Tags a target so that it will not be cached"""
2129        tlist = []
2130        for t in targets:
2131            tlist.extend(self.arg2nodes(t, self.fs.Entry))
2132        for t in tlist:
2133            t.set_nocache()
2134        return tlist
2135
2136    def Entry(self, name, *args, **kw):
2137        """
2138        """
2139        s = self.subst(name)
2140        if is_Sequence(s):
2141            result=[]
2142            for e in s:
2143                result.append(self.fs.Entry(e, *args, **kw))
2144            return result
2145        return self.fs.Entry(s, *args, **kw)
2146
2147    def Environment(self, **kw):
2148        return SCons.Environment.Environment(**self.subst_kw(kw))
2149
2150    def Execute(self, action, *args, **kw):
2151        """Directly execute an action through an Environment
2152        """
2153        action = self.Action(action, *args, **kw)
2154        result = action([], [], self)
2155        if isinstance(result, BuildError):
2156            errstr = result.errstr
2157            if result.filename:
2158                errstr = result.filename + ': ' + errstr
2159            sys.stderr.write("scons: *** %s\n" % errstr)
2160            return result.status
2161        else:
2162            return result
2163
2164    def File(self, name, *args, **kw):
2165        """
2166        """
2167        s = self.subst(name)
2168        if is_Sequence(s):
2169            result=[]
2170            for e in s:
2171                result.append(self.fs.File(e, *args, **kw))
2172            return result
2173        return self.fs.File(s, *args, **kw)
2174
2175    def FindFile(self, file, dirs):
2176        file = self.subst(file)
2177        nodes = self.arg2nodes(dirs, self.fs.Dir)
2178        return SCons.Node.FS.find_file(file, tuple(nodes))
2179
2180    def Flatten(self, sequence):
2181        return flatten(sequence)
2182
2183    def GetBuildPath(self, files):
2184        result = list(map(str, self.arg2nodes(files, self.fs.Entry)))
2185        if is_List(files):
2186            return result
2187        else:
2188            return result[0]
2189
2190    def Glob(self, pattern, ondisk=True, source=False, strings=False, exclude=None):
2191        return self.fs.Glob(self.subst(pattern), ondisk, source, strings, exclude)
2192
2193    def Ignore(self, target, dependency):
2194        """Ignore a dependency."""
2195        tlist = self.arg2nodes(target, self.fs.Entry)
2196        dlist = self.arg2nodes(dependency, self.fs.Entry)
2197        for t in tlist:
2198            t.add_ignore(dlist)
2199        return tlist
2200
2201    def Literal(self, string):
2202        return SCons.Subst.Literal(string)
2203
2204    def Local(self, *targets):
2205        ret = []
2206        for targ in targets:
2207            if isinstance(targ, SCons.Node.Node):
2208                targ.set_local()
2209                ret.append(targ)
2210            else:
2211                for t in self.arg2nodes(targ, self.fs.Entry):
2212                   t.set_local()
2213                   ret.append(t)
2214        return ret
2215
2216    def Precious(self, *targets):
2217        tlist = []
2218        for t in targets:
2219            tlist.extend(self.arg2nodes(t, self.fs.Entry))
2220        for t in tlist:
2221            t.set_precious()
2222        return tlist
2223
2224    def Pseudo(self, *targets):
2225        tlist = []
2226        for t in targets:
2227            tlist.extend(self.arg2nodes(t, self.fs.Entry))
2228        for t in tlist:
2229            t.set_pseudo()
2230        return tlist
2231
2232    def Repository(self, *dirs, **kw):
2233        dirs = self.arg2nodes(list(dirs), self.fs.Dir)
2234        self.fs.Repository(*dirs, **kw)
2235
2236    def Requires(self, target, prerequisite):
2237        """Specify that 'prerequisite' must be built before 'target',
2238        (but 'target' does not actually depend on 'prerequisite'
2239        and need not be rebuilt if it changes)."""
2240        tlist = self.arg2nodes(target, self.fs.Entry)
2241        plist = self.arg2nodes(prerequisite, self.fs.Entry)
2242        for t in tlist:
2243            t.add_prerequisite(plist)
2244        return tlist
2245
2246    def Scanner(self, *args, **kw):
2247        nargs = []
2248        for arg in args:
2249            if is_String(arg):
2250                arg = self.subst(arg)
2251            nargs.append(arg)
2252        nkw = self.subst_kw(kw)
2253        return SCons.Scanner.Base(*nargs, **nkw)
2254
2255    def SConsignFile(self, name=".sconsign", dbm_module=None):
2256        if name is not None:
2257            name = self.subst(name)
2258            if not os.path.isabs(name):
2259                name = os.path.join(str(self.fs.SConstruct_dir), name)
2260        if name:
2261            name = os.path.normpath(name)
2262            sconsign_dir = os.path.dirname(name)
2263            if sconsign_dir and not os.path.exists(sconsign_dir):
2264                self.Execute(SCons.Defaults.Mkdir(sconsign_dir))
2265        SCons.SConsign.File(name, dbm_module)
2266
2267    def SideEffect(self, side_effect, target):
2268        """Tell scons that side_effects are built as side
2269        effects of building targets."""
2270        side_effects = self.arg2nodes(side_effect, self.fs.Entry)
2271        targets = self.arg2nodes(target, self.fs.Entry)
2272
2273        added_side_effects = []
2274        for side_effect in side_effects:
2275            if side_effect.multiple_side_effect_has_builder():
2276                raise UserError("Multiple ways to build the same target were specified for: %s" % str(side_effect))
2277            side_effect.add_source(targets)
2278            side_effect.side_effect = 1
2279            self.Precious(side_effect)
2280            added = False
2281            for target in targets:
2282                if side_effect not in target.side_effects:
2283                    target.side_effects.append(side_effect)
2284                    added = True
2285            if added:
2286                added_side_effects.append(side_effect)
2287        return added_side_effects
2288
2289    def Split(self, arg):
2290        """This function converts a string or list into a list of strings
2291        or Nodes.  This makes things easier for users by allowing files to
2292        be specified as a white-space separated list to be split.
2293
2294        The input rules are:
2295            - A single string containing names separated by spaces. These will be
2296              split apart at the spaces.
2297            - A single Node instance
2298            - A list containing either strings or Node instances. Any strings
2299              in the list are not split at spaces.
2300
2301        In all cases, the function returns a list of Nodes and strings."""
2302
2303        if is_List(arg):
2304            return list(map(self.subst, arg))
2305        elif is_String(arg):
2306            return self.subst(arg).split()
2307        else:
2308            return [self.subst(arg)]
2309
2310    def Value(self, value, built_value=None, name=None):
2311        """
2312        """
2313        return SCons.Node.Python.ValueWithMemo(value, built_value, name)
2314
2315    def VariantDir(self, variant_dir, src_dir, duplicate=1):
2316        variant_dir = self.arg2nodes(variant_dir, self.fs.Dir)[0]
2317        src_dir = self.arg2nodes(src_dir, self.fs.Dir)[0]
2318        self.fs.VariantDir(variant_dir, src_dir, duplicate)
2319
2320    def FindSourceFiles(self, node='.'):
2321        """ returns a list of all source files.
2322        """
2323        node = self.arg2nodes(node, self.fs.Entry)[0]
2324
2325        sources = []
2326        def build_source(ss):
2327            for s in ss:
2328                if isinstance(s, SCons.Node.FS.Dir):
2329                    build_source(s.all_children())
2330                elif s.has_builder():
2331                    build_source(s.sources)
2332                elif isinstance(s.disambiguate(), SCons.Node.FS.File):
2333                    sources.append(s)
2334        build_source(node.all_children())
2335
2336        def final_source(node):
2337            while node != node.srcnode():
2338              node = node.srcnode()
2339            return node
2340        sources = list(map(final_source, sources))
2341        # remove duplicates
2342        return list(set(sources))
2343
2344    def FindInstalledFiles(self):
2345        """ returns the list of all targets of the Install and InstallAs Builder.
2346        """
2347        from SCons.Tool import install
2348        if install._UNIQUE_INSTALLED_FILES is None:
2349            install._UNIQUE_INSTALLED_FILES = uniquer_hashables(install._INSTALLED_FILES)
2350        return install._UNIQUE_INSTALLED_FILES
2351
2352
2353class OverrideEnvironment(Base):
2354    """A proxy that overrides variables in a wrapped construction
2355    environment by returning values from an overrides dictionary in
2356    preference to values from the underlying subject environment.
2357
2358    This is a lightweight (I hope) proxy that passes through most use of
2359    attributes to the underlying Environment.Base class, but has just
2360    enough additional methods defined to act like a real construction
2361    environment with overridden values.  It can wrap either a Base
2362    construction environment, or another OverrideEnvironment, which
2363    can in turn nest arbitrary OverrideEnvironments...
2364
2365    Note that we do *not* call the underlying base class
2366    (SubsitutionEnvironment) initialization, because we get most of those
2367    from proxying the attributes of the subject construction environment.
2368    But because we subclass SubstitutionEnvironment, this class also
2369    has inherited arg2nodes() and subst*() methods; those methods can't
2370    be proxied because they need *this* object's methods to fetch the
2371    values from the overrides dictionary.
2372    """
2373
2374    def __init__(self, subject, overrides=None):
2375        if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.OverrideEnvironment')
2376        self.__dict__['__subject'] = subject
2377        if overrides is None:
2378            self.__dict__['overrides'] = dict()
2379        else:
2380            self.__dict__['overrides'] = overrides
2381
2382    # Methods that make this class act like a proxy.
2383    def __getattr__(self, name):
2384        attr = getattr(self.__dict__['__subject'], name)
2385        # Here we check if attr is one of the Wrapper classes. For
2386        # example when a pseudo-builder is being called from an
2387        # OverrideEnvironment.
2388        #
2389        # These wrappers when they're constructed capture the
2390        # Environment they are being constructed with and so will not
2391        # have access to overrided values. So we rebuild them with the
2392        # OverrideEnvironment so they have access to overrided values.
2393        if isinstance(attr, MethodWrapper):
2394            return attr.clone(self)
2395        else:
2396            return attr
2397
2398    def __setattr__(self, name, value):
2399        setattr(self.__dict__['__subject'], name, value)
2400
2401    # Methods that make this class act like a dictionary.
2402    def __getitem__(self, key):
2403        try:
2404            return self.__dict__['overrides'][key]
2405        except KeyError:
2406            return self.__dict__['__subject'].__getitem__(key)
2407
2408    def __setitem__(self, key, value):
2409        if not is_valid_construction_var(key):
2410            raise UserError("Illegal construction variable `%s'" % key)
2411        self.__dict__['overrides'][key] = value
2412
2413    def __delitem__(self, key):
2414        try:
2415            del self.__dict__['overrides'][key]
2416        except KeyError:
2417            deleted = 0
2418        else:
2419            deleted = 1
2420        try:
2421            result = self.__dict__['__subject'].__delitem__(key)
2422        except KeyError:
2423            if not deleted:
2424                raise
2425            result = None
2426        return result
2427
2428    def get(self, key, default=None):
2429        """Emulates the get() method of dictionaries."""
2430        try:
2431            return self.__dict__['overrides'][key]
2432        except KeyError:
2433            return self.__dict__['__subject'].get(key, default)
2434
2435    def __contains__(self, key):
2436        if key in self.__dict__['overrides']:
2437            return True
2438        return key in self.__dict__['__subject']
2439
2440    def Dictionary(self, *args):
2441        d = self.__dict__['__subject'].Dictionary().copy()
2442        d.update(self.__dict__['overrides'])
2443        if not args:
2444            return d
2445        dlist = [d[x] for x in args]
2446        if len(dlist) == 1:
2447            dlist = dlist[0]
2448        return dlist
2449
2450    def items(self):
2451        """Emulates the items() method of dictionaries."""
2452        return self.Dictionary().items()
2453
2454    def keys(self):
2455        """Emulates the keys() method of dictionaries."""
2456        return self.Dictionary().keys()
2457
2458    def values(self):
2459        """Emulates the values() method of dictionaries."""
2460        return self.Dictionary().values()
2461
2462    def setdefault(self, key, default=None):
2463        """Emulates the setdefault() method of dictionaries."""
2464        try:
2465            return self.__getitem__(key)
2466        except KeyError:
2467            self.__dict__['overrides'][key] = default
2468            return default
2469
2470    # Overridden private construction environment methods.
2471    def _update(self, other):
2472        self.__dict__['overrides'].update(other)
2473
2474    def _update_onlynew(self, other):
2475        for k, v in other.items():
2476            if k not in self.__dict__['overrides']:
2477                self.__dict__['overrides'][k] = v
2478
2479    def gvars(self):
2480        return self.__dict__['__subject'].gvars()
2481
2482    def lvars(self):
2483        lvars = self.__dict__['__subject'].lvars()
2484        lvars.update(self.__dict__['overrides'])
2485        return lvars
2486
2487    # Overridden public construction environment methods.
2488    def Replace(self, **kw):
2489        kw = copy_non_reserved_keywords(kw)
2490        self.__dict__['overrides'].update(semi_deepcopy(kw))
2491
2492
2493# The entry point that will be used by the external world
2494# to refer to a construction environment.  This allows the wrapper
2495# interface to extend a construction environment for its own purposes
2496# by subclassing SCons.Environment.Base and then assigning the
2497# class to SCons.Environment.Environment.
2498
2499Environment = Base
2500
2501
2502def NoSubstitutionProxy(subject):
2503    """
2504    An entry point for returning a proxy subclass instance that overrides
2505    the subst*() methods so they don't actually perform construction
2506    variable substitution.  This is specifically intended to be the shim
2507    layer in between global function calls (which don't want construction
2508    variable substitution) and the DefaultEnvironment() (which would
2509    substitute variables if left to its own devices).
2510
2511    We have to wrap this in a function that allows us to delay definition of
2512    the class until it's necessary, so that when it subclasses Environment
2513    it will pick up whatever Environment subclass the wrapper interface
2514    might have assigned to SCons.Environment.Environment.
2515    """
2516    class _NoSubstitutionProxy(Environment):
2517        def __init__(self, subject):
2518            self.__dict__['__subject'] = subject
2519        def __getattr__(self, name):
2520            return getattr(self.__dict__['__subject'], name)
2521        def __setattr__(self, name, value):
2522            return setattr(self.__dict__['__subject'], name, value)
2523        def executor_to_lvars(self, kwdict):
2524            if 'executor' in kwdict:
2525                kwdict['lvars'] = kwdict['executor'].get_lvars()
2526                del kwdict['executor']
2527            else:
2528                kwdict['lvars'] = {}
2529        def raw_to_mode(self, dict):
2530            try:
2531                raw = dict['raw']
2532            except KeyError:
2533                pass
2534            else:
2535                del dict['raw']
2536                dict['mode'] = raw
2537        def subst(self, string, *args, **kwargs):
2538            return string
2539        def subst_kw(self, kw, *args, **kwargs):
2540            return kw
2541        def subst_list(self, string, *args, **kwargs):
2542            nargs = (string, self,) + args
2543            nkw = kwargs.copy()
2544            nkw['gvars'] = {}
2545            self.executor_to_lvars(nkw)
2546            self.raw_to_mode(nkw)
2547            return SCons.Subst.scons_subst_list(*nargs, **nkw)
2548        def subst_target_source(self, string, *args, **kwargs):
2549            nargs = (string, self,) + args
2550            nkw = kwargs.copy()
2551            nkw['gvars'] = {}
2552            self.executor_to_lvars(nkw)
2553            self.raw_to_mode(nkw)
2554            return SCons.Subst.scons_subst(*nargs, **nkw)
2555    return _NoSubstitutionProxy(subject)
2556
2557# Local Variables:
2558# tab-width:4
2559# indent-tabs-mode:nil
2560# End:
2561# vim: set expandtab tabstop=4 shiftwidth=4:
2562