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