Enhance OverrideEnvironment, and rename the base class to an enhanced and maybe-even...
[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 string
42 from UserDict import UserDict
43
44 import SCons.Action
45 import SCons.Builder
46 from SCons.Debug import logInstanceCreation
47 import SCons.Defaults
48 import SCons.Errors
49 import SCons.Node
50 import SCons.Node.Alias
51 import SCons.Node.FS
52 import SCons.Node.Python
53 import SCons.Platform
54 import SCons.SConsign
55 import SCons.Sig
56 import SCons.Sig.MD5
57 import SCons.Sig.TimeStamp
58 import SCons.Tool
59 import SCons.Util
60 import SCons.Warnings
61
62 class _Null:
63     pass
64
65 _null = _Null
66
67 CleanTargets = {}
68 CalculatorArgs = {}
69
70 # Pull UserError into the global name space for the benefit of
71 # Environment().SourceSignatures(), which has some import statements
72 # which seem to mess up its ability to reference SCons directly.
73 UserError = SCons.Errors.UserError
74
75 def installFunc(target, source, env):
76     """Install a source file into a target using the function specified
77     as the INSTALL construction variable."""
78     try:
79         install = env['INSTALL']
80     except KeyError:
81         raise SCons.Errors.UserError('Missing INSTALL construction variable.')
82     return install(target[0].path, source[0].path, env)
83
84 def installString(target, source, env):
85     return 'Install file: "%s" as "%s"' % (source[0], target[0])
86
87 installAction = SCons.Action.Action(installFunc, installString)
88
89 InstallBuilder = SCons.Builder.Builder(action=installAction,
90                                        name='InstallBuilder')
91
92 def alias_builder(env, target, source):
93     pass
94
95 AliasBuilder = SCons.Builder.Builder(action = alias_builder,
96                                      target_factory = SCons.Node.Alias.default_ans.Alias,
97                                      source_factory = SCons.Node.FS.default_fs.Entry,
98                                      multi = 1,
99                                      is_explicit = None,
100                                      name='AliasBuilder')
101
102 def our_deepcopy(x):
103    """deepcopy lists and dictionaries, and just copy the reference
104    for everything else."""
105    if SCons.Util.is_Dict(x):
106        copy = {}
107        for key in x.keys():
108            copy[key] = our_deepcopy(x[key])
109    elif SCons.Util.is_List(x):
110        copy = map(our_deepcopy, x)
111        try:
112            copy = x.__class__(copy)
113        except AttributeError:
114            pass
115    else:
116        copy = x
117    return copy
118
119 def apply_tools(env, tools, toolpath):
120     if not tools:
121         return
122     # Filter out null tools from the list.
123     for tool in filter(None, tools):
124         if SCons.Util.is_List(tool) or type(tool)==type(()):
125             toolname = tool[0]
126             toolargs = tool[1] # should be a dict of kw args
127             tool = apply(env.Tool, (toolname, toolpath), toolargs)
128         else:
129             env.Tool(tool, toolpath)
130
131 # These names are controlled by SCons; users should never set or override
132 # them.  This warning can optionally be turned off, but scons will still
133 # ignore the illegal variable names even if it's off.
134 reserved_construction_var_names = \
135     ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']
136
137 def copy_non_reserved_keywords(dict):
138     result = our_deepcopy(dict)
139     for k in result.keys():
140         if k in reserved_construction_var_names:
141             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
142                                 "Ignoring attempt to set reserved variable `%s'" % k)
143             del result[k]
144     return result
145
146 class BuilderWrapper:
147     """Wrapper class that associates an environment with a Builder at
148     instantiation."""
149     def __init__(self, env, builder):
150         self.env = env
151         self.builder = builder
152
153     def __call__(self, target=None, source=_null, *args, **kw):
154         if source is _null:
155             source = target
156             target = None
157         if not target is None and not SCons.Util.is_List(target):
158             target = [target]
159         if not source is None and not SCons.Util.is_List(source):
160             source = [source]
161         return apply(self.builder, (self.env, target, source) + args, kw)
162
163     # This allows a Builder to be executed directly
164     # through the Environment to which it's attached.
165     # In practice, we shouldn't need this, because
166     # builders actually get executed through a Node.
167     # But we do have a unit test for this, and can't
168     # yet rule out that it would be useful in the
169     # future, so leave it for now.
170     def execute(self, **kw):
171         kw['env'] = self.env
172         apply(self.builder.execute, (), kw)
173
174 class BuilderDict(UserDict):
175     """This is a dictionary-like class used by an Environment to hold
176     the Builders.  We need to do this because every time someone changes
177     the Builders in the Environment's BUILDERS dictionary, we must
178     update the Environment's attributes."""
179     def __init__(self, dict, env):
180         # Set self.env before calling the superclass initialization,
181         # because it will end up calling our other methods, which will
182         # need to point the values in this dictionary to self.env.
183         self.env = env
184         UserDict.__init__(self, dict)
185
186     def __setitem__(self, item, val):
187         UserDict.__setitem__(self, item, val)
188         try:
189             self.setenvattr(item, val)
190         except AttributeError:
191             # Have to catch this because sometimes __setitem__ gets
192             # called out of __init__, when we don't have an env
193             # attribute yet, nor do we want one!
194             pass
195
196     def setenvattr(self, item, val):
197         """Set the corresponding environment attribute for this Builder.
198
199         If the value is already a BuilderWrapper, we pull the builder
200         out of it and make another one, so that making a copy of an
201         existing BuilderDict is guaranteed separate wrappers for each
202         Builder + Environment pair."""
203         try:
204             builder = val.builder
205         except AttributeError:
206             builder = val
207         setattr(self.env, item, BuilderWrapper(self.env, builder))
208
209     def __delitem__(self, item):
210         UserDict.__delitem__(self, item)
211         delattr(self.env, item)
212
213     def update(self, dict):
214         for i, v in dict.items():
215             self.__setitem__(i, v)
216
217 class SubstitutionEnvironment:
218     """Base class for different flavors of construction environments.
219
220     This class contains a minimal set of methods that handle contruction
221     variable expansion and conversion of strings to Nodes, which may or
222     may not be actually useful as a stand-alone class.  Which methods
223     ended up in this class is pretty arbitrary right now.  They're
224     basically the ones which we've empirically determined are common to
225     the different construction environment subclasses, and most of the
226     others that use or touch the underlying dictionary of construction
227     variables.
228
229     Eventually, this class should contain all the methods that we
230     determine are necessary for a "minimal" interface to the build engine.
231     A full "native Python" SCons environment has gotten pretty heavyweight
232     with all of the methods and Tools and construction variables we've
233     jammed in there, so it would be nice to have a lighter weight
234     alternative for interfaces that don't need all of the bells and
235     whistles.  (At some point, we'll also probably rename this class
236     "Base," since that more reflects what we want this class to become,
237     but because we've released comments that tell people to subclass
238     Environment.Base to create their own flavors of construction
239     environment, we'll save that for a future refactoring when this
240     class actually becomes useful.)
241     """
242     def __init__(self, **kw):
243         """Initialization of an underlying SubstitutionEnvironment class.
244         """
245         if __debug__: logInstanceCreation(self)
246         self.fs = SCons.Node.FS.default_fs
247         self.ans = SCons.Node.Alias.default_ans
248         self.lookup_list = SCons.Node.arg2nodes_lookups
249         self._dict = kw.copy()
250         self._dict['__env__'] = self
251
252     def __cmp__(self, other):
253         # Since an Environment now has an '__env__' construction variable
254         # that refers to itself, delete that variable to avoid infinite
255         # loops when comparing the underlying dictionaries in some Python
256         # versions (*cough* 1.5.2 *cough*)...
257         sdict = self._dict.copy()
258         del sdict['__env__']
259         odict = other._dict.copy()
260         del odict['__env__']
261         return cmp(sdict, odict)
262
263     def __delitem__(self, key):
264         del self._dict[key]
265
266     def __getitem__(self, key):
267         return self._dict[key]
268
269     def __setitem__(self, key, value):
270         if key in reserved_construction_var_names:
271             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
272                                 "Ignoring attempt to set reserved variable `%s'" % key)
273         elif key == 'BUILDERS':
274             try:
275                 bd = self._dict[key]
276                 for k in bd.keys():
277                     del bd[k]
278             except KeyError:
279                 self._dict[key] = BuilderDict(kwbd, self)
280             self._dict[key].update(value)
281         elif key == 'SCANNERS':
282             self._dict[key] = value
283             self.scanner_map_delete()
284         else:
285             if not SCons.Util.is_valid_construction_var(key):
286                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
287             self._dict[key] = value
288
289     def get(self, key, default=None):
290         "Emulates the get() method of dictionaries."""
291         return self._dict.get(key, default)
292
293     def has_key(self, key):
294         return self._dict.has_key(key)
295
296     def items(self):
297         "Emulates the items() method of dictionaries."""
298         result = self._dict.items()
299         result = filter(lambda t: t[0] != '__env__', result)
300         return result
301
302     def arg2nodes(self, args, node_factory=_null, lookup_list=_null):
303         if node_factory is _null:
304             node_factory = self.fs.File
305         if lookup_list is _null:
306             lookup_list = self.lookup_list
307
308         if not args:
309             return []
310
311         if SCons.Util.is_List(args):
312             args = SCons.Util.flatten(args)
313         else:
314             args = [args]
315
316         nodes = []
317         for v in args:
318             if SCons.Util.is_String(v):
319                 n = None
320                 for l in lookup_list:
321                     n = l(v)
322                     if not n is None:
323                         break
324                 if not n is None:
325                     if SCons.Util.is_String(n):
326                         n = self.subst(n, raw=1)
327                         if node_factory:
328                             n = node_factory(n)
329                     if SCons.Util.is_List(n):
330                         nodes.extend(n)
331                     else:
332                         nodes.append(n)
333                 elif node_factory:
334                     v = node_factory(self.subst(v, raw=1))
335                     if SCons.Util.is_List(v):
336                         nodes.extend(v)
337                     else:
338                         nodes.append(v)
339             else:
340                 nodes.append(v)
341     
342         return nodes
343
344     def gvars(self):
345         return self._dict
346
347     def lvars(self):
348         return {}
349
350     def subst(self, string, raw=0, target=None, source=None, conv=None):
351         """Recursively interpolates construction variables from the
352         Environment into the specified string, returning the expanded
353         result.  Construction variables are specified by a $ prefix
354         in the string and begin with an initial underscore or
355         alphabetic character followed by any number of underscores
356         or alphanumeric characters.  The construction variable names
357         may be surrounded by curly braces to separate the name from
358         trailing characters.
359         """
360         gvars = self.gvars()
361         lvars = self.lvars()
362         return SCons.Util.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
363
364     def subst_kw(self, kw, raw=0, target=None, source=None):
365         nkw = {}
366         for k, v in kw.items():
367             k = self.subst(k, raw, target, source)
368             if SCons.Util.is_String(v):
369                 v = self.subst(v, raw, target, source)
370             nkw[k] = v
371         return nkw
372
373     def subst_list(self, string, raw=0, target=None, source=None, conv=None):
374         """Calls through to SCons.Util.scons_subst_list().  See
375         the documentation for that function."""
376         gvars = self.gvars()
377         lvars = self.lvars()
378         return SCons.Util.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
379
380     def subst_path(self, path):
381         """Substitute a path list, turning EntryProxies into Nodes
382         and leaving Nodes (and other objects) as-is."""
383
384         if not SCons.Util.is_List(path):
385             path = [path]
386
387         def s(obj):
388             """This is the "string conversion" routine that we have our
389             substitutions use to return Nodes, not strings.  This relies
390             on the fact that an EntryProxy object has a get() method that
391             returns the underlying Node that it wraps, which is a bit of
392             architectural dependence that we might need to break or modify
393             in the future in response to additional requirements."""
394             try:
395                 get = obj.get
396             except AttributeError:
397                 pass
398             else:
399                 obj = get()
400             return obj
401
402         r = []
403         for p in path:
404             if SCons.Util.is_String(p):
405                 p = self.subst(p, conv=s)
406                 if SCons.Util.is_List(p):
407                     if len(p) == 1:
408                         p = p[0]
409                     else:
410                         # We have an object plus a string, or multiple
411                         # objects that we need to smush together.  No choice
412                         # but to make them into a string.
413                         p = string.join(map(SCons.Util.to_String, p), '')
414             else:
415                 p = s(p)
416             r.append(p)
417         return r
418
419     subst_target_source = subst
420
421     def Override(self, overrides):
422         """
423         Produce a modified environment whose variables are overriden by
424         the overrides dictionaries.  "overrides" is a dictionary that
425         will override the variables of this environment.
426
427         This function is much more efficient than Copy() or creating
428         a new Environment because it doesn't copy the construction
429         environment dictionary, it just wraps the underlying construction
430         environment, and doesn't even create a wrapper object if there
431         are no overrides.
432         """
433         if overrides:
434             o = copy_non_reserved_keywords(overrides)
435             overrides = {}
436             for key, value in o.items():
437                 overrides[key] = SCons.Util.scons_subst_once(value, self, key)
438         if overrides:
439             env = OverrideEnvironment(self, overrides)
440             return env
441         else:
442             return self
443
444 class Base(SubstitutionEnvironment):
445     """Base class for "real" construction Environments.  These are the
446     primary objects used to communicate dependency and construction
447     information to the build engine.
448
449     Keyword arguments supplied when the construction Environment
450     is created are construction variables used to initialize the
451     Environment.
452     """
453
454     #######################################################################
455     # This is THE class for interacting with the SCons build engine,
456     # and it contains a lot of stuff, so we're going to try to keep this
457     # a little organized by grouping the methods.
458     #######################################################################
459
460     #######################################################################
461     # Methods that make an Environment act like a dictionary.  These have
462     # the expected standard names for Python mapping objects.  Note that
463     # we don't actually make an Environment a subclass of UserDict for
464     # performance reasons.  Note also that we only supply methods for
465     # dictionary functionality that we actually need and use.
466     #######################################################################
467
468     def __init__(self,
469                  platform=None,
470                  tools=None,
471                  toolpath=[],
472                  options=None,
473                  **kw):
474         """
475         Initialization of a basic SCons construction environment,
476         including setting up special construction variables like BUILDER,
477         PLATFORM, etc., and searching for and applying available Tools.
478
479         Note that we do *not* call the underlying base class
480         (SubsitutionEnvironment) initialization, because we need to
481         initialize things in a very specific order that doesn't work
482         with the much simpler base class initialization.
483         """
484         if __debug__: logInstanceCreation(self)
485         self.fs = SCons.Node.FS.default_fs
486         self.ans = SCons.Node.Alias.default_ans
487         self.lookup_list = SCons.Node.arg2nodes_lookups
488         self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
489
490         self._dict['__env__'] = self
491         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
492
493         if platform is None:
494             platform = self._dict.get('PLATFORM', None)
495             if platform is None:
496                 platform = SCons.Platform.Platform()
497         if SCons.Util.is_String(platform):
498             platform = SCons.Platform.Platform(platform)
499         self._dict['PLATFORM'] = str(platform)
500         platform(self)
501
502         # Apply the passed-in variables before calling the tools,
503         # because they may use some of them:
504         apply(self.Replace, (), kw)
505         
506         # Update the environment with the customizable options
507         # before calling the tools, since they may use some of the options: 
508         if options:
509             options.Update(self)
510
511         if tools is None:
512             tools = self._dict.get('TOOLS', None)
513             if tools is None:
514                 tools = ['default']
515         apply_tools(self, tools, toolpath)
516
517         # Reapply the passed in variables after calling the tools,
518         # since they should overide anything set by the tools:
519         apply(self.Replace, (), kw)
520
521         # Update the environment with the customizable options
522         # after calling the tools, since they should override anything
523         # set by the tools:
524         if options:
525             options.Update(self)
526
527     #######################################################################
528     # Utility methods that are primarily for internal use by SCons.
529     # These begin with lower-case letters.
530     #######################################################################
531
532     def get_calculator(self):
533         try:
534             return self._calculator
535         except AttributeError:
536             try:
537                 module = self._calc_module
538                 c = apply(SCons.Sig.Calculator, (module,), CalculatorArgs)
539             except AttributeError:
540                 # Note that we're calling get_calculator() here, so the
541                 # DefaultEnvironment() must have a _calc_module attribute
542                 # to avoid infinite recursion.
543                 c = SCons.Defaults.DefaultEnvironment().get_calculator()
544             self._calculator = c
545             return c
546
547     def get_builder(self, name):
548         """Fetch the builder with the specified name from the environment.
549         """
550         try:
551             return self._dict['BUILDERS'][name]
552         except KeyError:
553             return None
554
555     def get_scanner(self, skey):
556         """Find the appropriate scanner given a key (usually a file suffix).
557         """
558         try:
559             sm = self.scanner_map
560         except AttributeError:
561             try:
562                 scanners = self._dict['SCANNERS']
563             except KeyError:
564                 self.scanner_map = {}
565                 return None
566             else:
567                 self.scanner_map = sm = {}
568                 # Reverse the scanner list so that, if multiple scanners
569                 # claim they can scan the same suffix, earlier scanners
570                 # in the list will overwrite later scanners, so that
571                 # the result looks like a "first match" to the user.
572                 if not SCons.Util.is_List(scanners):
573                     scanners = [scanners]
574                 scanners.reverse()
575                 for scanner in scanners:
576                     for k in scanner.get_skeys(self):
577                         sm[k] = scanner
578         try:
579             return sm[skey]
580         except KeyError:
581             return None
582
583     def scanner_map_delete(self, kw=None):
584         """Delete the cached scanner map (if we need to).
585         """
586         if not kw is None and not kw.has_key('SCANNERS'):
587             return
588         try:
589             del self.scanner_map
590         except AttributeError:
591             pass
592
593     def _update(self, dict):
594         """Update an environment's values directly, bypassing the normal
595         checks that occur when users try to set items.
596         """
597         self._dict.update(dict)
598
599     def use_build_signature(self):
600         try:
601             return self._build_signature
602         except AttributeError:
603             b = SCons.Defaults.DefaultEnvironment()._build_signature
604             self._build_signature = b
605             return b
606
607     #######################################################################
608     # Public methods for manipulating an Environment.  These begin with
609     # upper-case letters.  The essential characteristic of methods in
610     # this section is that they do *not* have corresponding same-named
611     # global functions.  For example, a stand-alone Append() function
612     # makes no sense, because Append() is all about appending values to
613     # an Environment's construction variables.
614     #######################################################################
615
616     def Append(self, **kw):
617         """Append values to existing construction variables
618         in an Environment.
619         """
620         kw = copy_non_reserved_keywords(kw)
621         for key, val in kw.items():
622             # It would be easier on the eyes to write this using
623             # "continue" statements whenever we finish processing an item,
624             # but Python 1.5.2 apparently doesn't let you use "continue"
625             # within try:-except: blocks, so we have to nest our code.
626             try:
627                 orig = self._dict[key]
628             except KeyError:
629                 # No existing variable in the environment, so just set
630                 # it to the new value.
631                 self._dict[key] = val
632             else:
633                 try:
634                     # Most straightforward:  just try to add them
635                     # together.  This will work in most cases, when the
636                     # original and new values are of compatible types.
637                     self._dict[key] = orig + val
638                 except TypeError:
639                     try:
640                         # Try to update a dictionary value with another.
641                         # If orig isn't a dictionary, it won't have an
642                         # update() method; if val isn't a dictionary,
643                         # it won't have a keys() method.  Either way,
644                         # it's an AttributeError.
645                         orig.update(val)
646                     except AttributeError:
647                         try:
648                             # Check if the original is a list.
649                             add_to_orig = orig.append
650                         except AttributeError:
651                             # The original isn't a list, but the new
652                             # value is (by process of elimination),
653                             # so insert the original in the new value
654                             # (if there's one to insert) and replace
655                             # the variable with it.
656                             if orig:
657                                 val.insert(0, orig)
658                             self._dict[key] = val
659                         else:
660                             # The original is a list, so append the new
661                             # value to it (if there's a value to append).
662                             if val:
663                                 add_to_orig(val)
664         self.scanner_map_delete(kw)
665
666     def AppendENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
667         """Append path elements to the path 'name' in the 'ENV'
668         dictionary for this environment.  Will only add any particular
669         path once, and will normpath and normcase all paths to help
670         assure this.  This can also handle the case where the env
671         variable is a list instead of a string.
672         """
673
674         orig = ''
675         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
676             orig = self._dict[envname][name]
677
678         nv = SCons.Util.AppendPath(orig, newpath, sep)
679             
680         if not self._dict.has_key(envname):
681             self._dict[envname] = {}
682
683         self._dict[envname][name] = nv
684
685     def AppendUnique(self, **kw):
686         """Append values to existing construction variables
687         in an Environment, if they're not already there.
688         """
689         kw = copy_non_reserved_keywords(kw)
690         for key, val in kw.items():
691             if not self._dict.has_key(key):
692                 self._dict[key] = val
693             elif SCons.Util.is_Dict(self._dict[key]) and \
694                  SCons.Util.is_Dict(val):
695                 self._dict[key].update(val)
696             elif SCons.Util.is_List(val):
697                 dk = self._dict[key]
698                 if not SCons.Util.is_List(dk):
699                     dk = [dk]
700                 val = filter(lambda x, dk=dk: x not in dk, val)
701                 self._dict[key] = dk + val
702             else:
703                 dk = self._dict[key]
704                 if SCons.Util.is_List(dk):
705                     if not val in dk:
706                         self._dict[key] = dk + val
707                 else:
708                     self._dict[key] = self._dict[key] + val
709         self.scanner_map_delete(kw)
710
711     def Copy(self, tools=[], toolpath=[], **kw):
712         """Return a copy of a construction Environment.  The
713         copy is like a Python "deep copy"--that is, independent
714         copies are made recursively of each objects--except that
715         a reference is copied when an object is not deep-copyable
716         (like a function).  There are no references to any mutable
717         objects in the original Environment.
718         """
719         clone = copy.copy(self)
720         clone._dict = our_deepcopy(self._dict)
721         clone['__env__'] = clone
722         try:
723             cbd = clone._dict['BUILDERS']
724             clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
725         except KeyError:
726             pass
727         
728         apply_tools(clone, tools, toolpath)
729
730         # Apply passed-in variables after the new tools.
731         kw = copy_non_reserved_keywords(kw)
732         new = {}
733         for key, value in kw.items():
734             new[key] = SCons.Util.scons_subst_once(value, self, key)
735         apply(clone.Replace, (), new)
736         if __debug__: logInstanceCreation(self, 'EnvironmentCopy')
737         return clone
738
739     def Detect(self, progs):
740         """Return the first available program in progs.
741         """
742         if not SCons.Util.is_List(progs):
743             progs = [ progs ]
744         for prog in progs:
745             path = self.WhereIs(prog)
746             if path: return prog
747         return None
748
749     def Dictionary(self, *args):
750         if not args:
751             return self._dict
752         dlist = map(lambda x, s=self: s._dict[x], args)
753         if len(dlist) == 1:
754             dlist = dlist[0]
755         return dlist
756
757     def Dump(self, key = None):
758         """
759         Using the standard Python pretty printer, dump the contents of the
760         scons build environment to stdout.
761
762         If the key passed in is anything other than None, then that will
763         be used as an index into the build environment dictionary and
764         whatever is found there will be fed into the pretty printer. Note
765         that this key is case sensitive.
766         """
767         import pprint
768         pp = pprint.PrettyPrinter(indent=2)
769         if key:
770             dict = self.Dictionary(key)
771         else:
772             dict = self.Dictionary()
773         return pp.pformat(dict)
774
775     def FindIxes(self, paths, prefix, suffix):
776         """
777         Search a list of paths for something that matches the prefix and suffix.
778
779         paths - the list of paths or nodes.
780         prefix - construction variable for the prefix.
781         suffix - construction variable for the suffix.
782         """
783
784         suffix = self.subst('$'+suffix)
785         prefix = self.subst('$'+prefix)
786
787         for path in paths:
788             dir,name = os.path.split(str(path))
789             if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix: 
790                 return path
791
792     def ParseConfig(self, command, function=None):
793         """
794         Use the specified function to parse the output of the command
795         in order to modify the current environment. The 'command' can
796         be a string or a list of strings representing a command and
797         it's arguments. 'Function' is an optional argument that takes
798         the environment and the output of the command. If no function is
799         specified, the output will be treated as the output of a typical
800         'X-config' command (i.e. gtk-config) and used to append to the
801         ASFLAGS, CCFLAGS, CPPFLAGS, CPPPATH, LIBPATH, LIBS, LINKFLAGS
802         and CCFLAGS variables.
803         """
804
805         # the default parse function
806         def parse_conf(env, output, fs=self.fs):
807             dict = {
808                 'ASFLAGS'       : [],
809                 'CCFLAGS'       : [],
810                 'CPPFLAGS'      : [],
811                 'CPPPATH'       : [],
812                 'LIBPATH'       : [],
813                 'LIBS'          : [],
814                 'LINKFLAGS'     : [],
815             }
816     
817             params = string.split(output)
818             append_next_arg_to=''       # for multi-word args
819             for arg in params:
820                 if append_next_arg_to:
821                     dict[append_next_arg_to].append(arg)
822                     append_next_arg_to = ''
823                 elif arg[0] != '-':
824                     dict['LIBS'].append(fs.File(arg))
825                 elif arg[:2] == '-L':
826                     if arg[2:]:
827                         dict['LIBPATH'].append(arg[2:])
828                     else:
829                         append_next_arg_to = 'LIBPATH'
830                 elif arg[:2] == '-l':
831                     if arg[2:]:
832                         dict['LIBS'].append(arg[2:])
833                     else:
834                         append_next_arg_to = 'LIBS'
835                 elif arg[:2] == '-I':
836                     if arg[2:]:
837                         dict['CPPPATH'].append(arg[2:])
838                     else:
839                         append_next_arg_to = 'CPPPATH'
840                 elif arg[:4] == '-Wa,':
841                     dict['ASFLAGS'].append(arg)
842                 elif arg[:4] == '-Wl,':
843                     dict['LINKFLAGS'].append(arg)
844                 elif arg[:4] == '-Wp,':
845                     dict['CPPFLAGS'].append(arg)
846                 elif arg == '-framework':
847                     dict['LINKFLAGS'].append(arg)
848                     append_next_arg_to='LINKFLAGS'
849                 elif arg == '-mno-cygwin':
850                     dict['CCFLAGS'].append(arg)
851                     dict['LINKFLAGS'].append(arg)
852                 elif arg == '-mwindows':
853                     dict['LINKFLAGS'].append(arg)
854                 elif arg == '-pthread':
855                     dict['CCFLAGS'].append(arg)
856                     dict['LINKFLAGS'].append(arg)
857                 else:
858                     dict['CCFLAGS'].append(arg)
859             apply(env.Append, (), dict)
860     
861         if function is None:
862             function = parse_conf
863         if type(command) is type([]):
864             command = string.join(command)
865         command = self.subst(command)
866         return function(self, os.popen(command).read())
867
868     def ParseDepends(self, filename, must_exist=None):
869         """
870         Parse a mkdep-style file for explicit dependencies.  This is
871         completely abusable, and should be unnecessary in the "normal"
872         case of proper SCons configuration, but it may help make
873         the transition from a Make hierarchy easier for some people
874         to swallow.  It can also be genuinely useful when using a tool
875         that can write a .d file, but for which writing a scanner would
876         be too complicated.
877         """
878         try:
879             fp = open(filename, 'r')
880         except IOError:
881             if must_exist:
882                 raise
883             return
884         for line in SCons.Util.LogicalLines(fp).readlines():
885             if line[0] == '#':
886                 continue
887             try:
888                 target, depends = string.split(line, ':', 1)
889             except (AttributeError, TypeError, ValueError):
890                 # Python 1.5.2 throws TypeError if line isn't a string,
891                 # Python 2.x throws AttributeError because it tries
892                 # to call line.splite().  Either can throw ValueError
893                 # if the line doesn't split into two or more elements.
894                 pass
895             else:
896                 self.Depends(string.split(target), string.split(depends))
897
898     def Platform(self, platform):
899         platform = self.subst(platform)
900         return SCons.Platform.Platform(platform)(self)
901
902     def Prepend(self, **kw):
903         """Prepend values to existing construction variables
904         in an Environment.
905         """
906         kw = copy_non_reserved_keywords(kw)
907         for key, val in kw.items():
908             # It would be easier on the eyes to write this using
909             # "continue" statements whenever we finish processing an item,
910             # but Python 1.5.2 apparently doesn't let you use "continue"
911             # within try:-except: blocks, so we have to nest our code.
912             try:
913                 orig = self._dict[key]
914             except KeyError:
915                 # No existing variable in the environment, so just set
916                 # it to the new value.
917                 self._dict[key] = val
918             else:
919                 try:
920                     # Most straightforward:  just try to add them
921                     # together.  This will work in most cases, when the
922                     # original and new values are of compatible types.
923                     self._dict[key] = val + orig
924                 except TypeError:
925                     try:
926                         # Try to update a dictionary value with another.
927                         # If orig isn't a dictionary, it won't have an
928                         # update() method; if val isn't a dictionary,
929                         # it won't have a keys() method.  Either way,
930                         # it's an AttributeError.
931                         orig.update(val)
932                     except AttributeError:
933                         try:
934                             # Check if the added value is a list.
935                             add_to_val = val.append
936                         except AttributeError:
937                             # The added value isn't a list, but the
938                             # original is (by process of elimination),
939                             # so insert the the new value in the original
940                             # (if there's one to insert).
941                             if val:
942                                 orig.insert(0, val)
943                         else:
944                             # The added value is a list, so append
945                             # the original to it (if there's a value
946                             # to append).
947                             if orig:
948                                 add_to_val(orig)
949                             self._dict[key] = val
950         self.scanner_map_delete(kw)
951
952     def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
953         """Prepend path elements to the path 'name' in the 'ENV'
954         dictionary for this environment.  Will only add any particular
955         path once, and will normpath and normcase all paths to help
956         assure this.  This can also handle the case where the env
957         variable is a list instead of a string.
958         """
959
960         orig = ''
961         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
962             orig = self._dict[envname][name]
963
964         nv = SCons.Util.PrependPath(orig, newpath, sep)
965             
966         if not self._dict.has_key(envname):
967             self._dict[envname] = {}
968
969         self._dict[envname][name] = nv
970
971     def PrependUnique(self, **kw):
972         """Append values to existing construction variables
973         in an Environment, if they're not already there.
974         """
975         kw = copy_non_reserved_keywords(kw)
976         for key, val in kw.items():
977             if not self._dict.has_key(key):
978                 self._dict[key] = val
979             elif SCons.Util.is_Dict(self._dict[key]) and \
980                  SCons.Util.is_Dict(val):
981                 self._dict[key].update(val)
982             elif SCons.Util.is_List(val):
983                 dk = self._dict[key]
984                 if not SCons.Util.is_List(dk):
985                     dk = [dk]
986                 val = filter(lambda x, dk=dk: x not in dk, val)
987                 self._dict[key] = val + dk
988             else:
989                 dk = self._dict[key]
990                 if SCons.Util.is_List(dk):
991                     if not val in dk:
992                         self._dict[key] = val + dk
993                 else:
994                     self._dict[key] = val + dk
995         self.scanner_map_delete(kw)
996
997     def Replace(self, **kw):
998         """Replace existing construction variables in an Environment
999         with new construction variables and/or values.
1000         """
1001         try:
1002             kwbd = our_deepcopy(kw['BUILDERS'])
1003             del kw['BUILDERS']
1004             self.__setitem__('BUILDERS', kwbd)
1005         except KeyError:
1006             pass
1007         kw = copy_non_reserved_keywords(kw)
1008         self._dict.update(our_deepcopy(kw))
1009         self.scanner_map_delete(kw)
1010
1011     def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
1012         """
1013         Replace old_prefix with new_prefix and old_suffix with new_suffix.
1014
1015         env - Environment used to interpolate variables.
1016         path - the path that will be modified.
1017         old_prefix - construction variable for the old prefix.
1018         old_suffix - construction variable for the old suffix.
1019         new_prefix - construction variable for the new prefix.
1020         new_suffix - construction variable for the new suffix.
1021         """
1022         old_prefix = self.subst('$'+old_prefix)
1023         old_suffix = self.subst('$'+old_suffix)
1024
1025         new_prefix = self.subst('$'+new_prefix)
1026         new_suffix = self.subst('$'+new_suffix)
1027
1028         dir,name = os.path.split(str(path))
1029         if name[:len(old_prefix)] == old_prefix:
1030             name = name[len(old_prefix):]
1031         if name[-len(old_suffix):] == old_suffix:
1032             name = name[:-len(old_suffix)]
1033         return os.path.join(dir, new_prefix+name+new_suffix)
1034
1035     def SetDefault(self, **kw):
1036         for k in kw.keys():
1037             if self._dict.has_key(k):
1038                 del kw[k]
1039         apply(self.Replace, (), kw)
1040
1041     def Tool(self, tool, toolpath=[], **kw):
1042         if SCons.Util.is_String(tool):
1043             tool = self.subst(tool)
1044             toolpath = map(self.subst, toolpath)
1045             tool = apply(SCons.Tool.Tool, (tool, toolpath), kw)
1046         tool(self)
1047
1048     def WhereIs(self, prog, path=None, pathext=None, reject=[]):
1049         """Find prog in the path.  
1050         """
1051         if path is None:
1052             try:
1053                 path = self['ENV']['PATH']
1054             except KeyError:
1055                 pass
1056         elif SCons.Util.is_String(path):
1057             path = self.subst(path)
1058         if pathext is None:
1059             try:
1060                 pathext = self['ENV']['PATHEXT']
1061             except KeyError:
1062                 pass
1063         elif SCons.Util.is_String(pathext):
1064             pathext = self.subst(pathext)
1065         path = SCons.Util.WhereIs(prog, path, pathext, reject)
1066         if path: return path
1067         return None
1068
1069     #######################################################################
1070     # Public methods for doing real "SCons stuff" (manipulating
1071     # dependencies, setting attributes on targets, etc.).  These begin
1072     # with upper-case letters.  The essential characteristic of methods
1073     # in this section is that they all *should* have corresponding
1074     # same-named global functions.
1075     #######################################################################
1076
1077     def Action(self, *args, **kw):
1078         nargs = self.subst(args)
1079         nkw = self.subst_kw(kw)
1080         return apply(SCons.Action.Action, nargs, nkw)
1081
1082     def AddPreAction(self, files, action):
1083         nodes = self.arg2nodes(files, self.fs.Entry)
1084         action = SCons.Action.Action(action)
1085         for n in nodes:
1086             n.add_pre_action(action)
1087         return nodes
1088     
1089     def AddPostAction(self, files, action):
1090         nodes = self.arg2nodes(files, self.fs.Entry)
1091         action = SCons.Action.Action(action)
1092         for n in nodes:
1093             n.add_post_action(action)
1094         return nodes
1095
1096     def Alias(self, target, source=[], action=None, **kw):
1097         tlist = self.arg2nodes(target, self.ans.Alias)
1098         if not SCons.Util.is_List(source):
1099             source = [source]
1100         source = filter(None, source)
1101
1102         if not action:
1103             if not source:
1104                 # There are no source files and no action, so just
1105                 # return a target list of classic Alias Nodes, without
1106                 # any builder.  The externally visible effect is that
1107                 # this will make the wrapping Script.BuildTask class
1108                 # say that there's "Nothing to be done" for this Alias,
1109                 # instead of that it's "up to date."
1110                 return tlist
1111
1112             # No action, but there are sources.  Re-call all the target
1113             # builders to add the sources to each target.
1114             result = []
1115             for t in tlist:
1116                 bld = t.get_builder(AliasBuilder)
1117                 result.extend(bld(self, t, source))
1118             return result
1119
1120         action = SCons.Action.Action(action)
1121         nkw = self.subst_kw(kw)
1122         nkw['source_factory'] = self.fs.Entry
1123         nkw['multi'] = 1
1124         nkw['action'] = action
1125         bld = apply(SCons.Builder.Builder, (), nkw)
1126
1127         # Apply the Builder separately to each target so that the Aliases
1128         # stay separate.  If we did one "normal" Builder call with the
1129         # whole target list, then all of the target Aliases would be
1130         # associated under a single Executor.
1131         result = []
1132         for t in tlist:
1133             # Calling the convert() method will cause a new Executor to be
1134             # created from scratch, so we have to explicitly initialize
1135             # it with the target's existing sources, plus our new ones,
1136             # so nothing gets lost.
1137             b = t.get_builder()
1138             if b is None or b is AliasBuilder:
1139                 b = bld
1140             else:
1141                 nkw['action'] = b.action + action
1142                 b = apply(SCons.Builder.Builder, (), nkw)
1143             t.convert()
1144             result.extend(b(self, t, t.sources + source))
1145         return result
1146
1147     def AlwaysBuild(self, *targets):
1148         tlist = []
1149         for t in targets:
1150             tlist.extend(self.arg2nodes(t, self.fs.File))
1151         for t in tlist:
1152             t.set_always_build()
1153         return tlist
1154
1155     def BuildDir(self, build_dir, src_dir, duplicate=1):
1156         build_dir = self.arg2nodes(build_dir, self.fs.Dir)[0]
1157         src_dir = self.arg2nodes(src_dir, self.fs.Dir)[0]
1158         self.fs.BuildDir(build_dir, src_dir, duplicate)
1159
1160     def Builder(self, **kw):
1161         nkw = self.subst_kw(kw)
1162         return apply(SCons.Builder.Builder, [], nkw)
1163
1164     def CacheDir(self, path):
1165         self.fs.CacheDir(self.subst(path))
1166
1167     def Clean(self, targets, files):
1168         global CleanTargets
1169         tlist = self.arg2nodes(targets, self.fs.Entry)
1170         flist = self.arg2nodes(files, self.fs.Entry)
1171         for t in tlist:
1172             try:
1173                 CleanTargets[t].extend(flist)
1174             except KeyError:
1175                 CleanTargets[t] = flist
1176
1177     def Configure(self, *args, **kw):
1178         nargs = [self]
1179         if args:
1180             nargs = nargs + self.subst_list(args)[0]
1181         nkw = self.subst_kw(kw)
1182         nkw['called_from_env_method'] = 1
1183         try:
1184             nkw['custom_tests'] = self.subst_kw(nkw['custom_tests'])
1185         except KeyError:
1186             pass
1187         return apply(SCons.SConf.SConf, nargs, nkw)
1188
1189     def Command(self, target, source, action, **kw):
1190         """Builds the supplied target files from the supplied
1191         source files using the supplied action.  Action may
1192         be any type that the Builder constructor will accept
1193         for an action."""
1194         bld = SCons.Builder.Builder(action = action,
1195                                     source_factory = self.fs.Entry)
1196         return apply(bld, (self, target, source), kw)
1197
1198     def Depends(self, target, dependency):
1199         """Explicity specify that 'target's depend on 'dependency'."""
1200         tlist = self.arg2nodes(target, self.fs.Entry)
1201         dlist = self.arg2nodes(dependency, self.fs.Entry)
1202         for t in tlist:
1203             t.add_dependency(dlist)
1204         return tlist
1205
1206     def Dir(self, name, *args, **kw):
1207         """
1208         """
1209         return apply(self.fs.Dir, (self.subst(name),) + args, kw)
1210
1211     def Environment(self, **kw):
1212         return apply(SCons.Environment.Environment, [], self.subst_kw(kw))
1213
1214     def Execute(self, action, *args, **kw):
1215         """Directly execute an action through an Environment
1216         """
1217         action = apply(self.Action, (action,) + args, kw)
1218         return action([], [], self)
1219
1220     def File(self, name, *args, **kw):
1221         """
1222         """
1223         return apply(self.fs.File, (self.subst(name),) + args, kw)
1224
1225     def FindFile(self, file, dirs):
1226         file = self.subst(file)
1227         nodes = self.arg2nodes(dirs, self.fs.Dir)
1228         return SCons.Node.FS.find_file(file, nodes, self.fs.File)
1229
1230     def Flatten(self, sequence):
1231         return SCons.Util.flatten(sequence)
1232
1233     def GetBuildPath(self, files):
1234         result = map(str, self.arg2nodes(files, self.fs.Entry))
1235         if SCons.Util.is_List(files):
1236             return result
1237         else:
1238             return result[0]
1239
1240     def Ignore(self, target, dependency):
1241         """Ignore a dependency."""
1242         tlist = self.arg2nodes(target, self.fs.Entry)
1243         dlist = self.arg2nodes(dependency, self.fs.Entry)
1244         for t in tlist:
1245             t.add_ignore(dlist)
1246         return tlist
1247
1248     def Install(self, dir, source):
1249         """Install specified files in the given directory."""
1250         try:
1251             dnodes = self.arg2nodes(dir, self.fs.Dir)
1252         except TypeError:
1253             raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?" % str(dir)
1254         try:
1255             sources = self.arg2nodes(source, self.fs.File)
1256         except TypeError:
1257             if SCons.Util.is_List(source):
1258                 raise SCons.Errors.UserError, "Source `%s' of Install() contains one or more non-files.  Install() source must be one or more files." % repr(map(str, source))
1259             else:
1260                 raise SCons.Errors.UserError, "Source `%s' of Install() is not a file.  Install() source must be one or more files." % str(source)
1261         tgt = []
1262         for dnode in dnodes:
1263             for src in sources:
1264                 target = self.fs.File(src.name, dnode)
1265                 tgt.extend(InstallBuilder(self, target, src))
1266         return tgt
1267
1268     def InstallAs(self, target, source):
1269         """Install sources as targets."""
1270         sources = self.arg2nodes(source, self.fs.File)
1271         targets = self.arg2nodes(target, self.fs.File)
1272         result = []
1273         for src, tgt in map(lambda x, y: (x, y), sources, targets):
1274             result.extend(InstallBuilder(self, tgt, src))
1275         return result
1276
1277     def Literal(self, string):
1278         return SCons.Util.Literal(string)
1279
1280     def Local(self, *targets):
1281         ret = []
1282         for targ in targets:
1283             if isinstance(targ, SCons.Node.Node):
1284                 targ.set_local()
1285                 ret.append(targ)
1286             else:
1287                 for t in self.arg2nodes(targ, self.fs.Entry):
1288                    t.set_local()
1289                    ret.append(t)
1290         return ret
1291
1292     def Precious(self, *targets):
1293         tlist = []
1294         for t in targets:
1295             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1296         for t in tlist:
1297             t.set_precious()
1298         return tlist
1299
1300     def Repository(self, *dirs, **kw):
1301         dirs = self.arg2nodes(list(dirs), self.fs.Dir)
1302         apply(self.fs.Repository, dirs, kw)
1303
1304     def Scanner(self, *args, **kw):
1305         nargs = []
1306         for arg in args:
1307             if SCons.Util.is_String(arg):
1308                 arg = self.subst(arg)
1309             nargs.append(arg)
1310         nkw = self.subst_kw(kw)
1311         return apply(SCons.Scanner.Scanner, nargs, nkw)
1312
1313     def SConsignFile(self, name=".sconsign", dbm_module=None):
1314         name = self.subst(name)
1315         if not os.path.isabs(name):
1316             name = os.path.join(str(self.fs.SConstruct_dir), name)
1317         SCons.SConsign.File(name, dbm_module)
1318
1319     def SideEffect(self, side_effect, target):
1320         """Tell scons that side_effects are built as side 
1321         effects of building targets."""
1322         side_effects = self.arg2nodes(side_effect, self.fs.Entry)
1323         targets = self.arg2nodes(target, self.fs.Entry)
1324
1325         for side_effect in side_effects:
1326             if side_effect.multiple_side_effect_has_builder():
1327                 raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
1328             side_effect.add_source(targets)
1329             side_effect.side_effect = 1
1330             self.Precious(side_effect)
1331             for target in targets:
1332                 target.side_effects.append(side_effect)
1333         return side_effects
1334
1335     def SourceCode(self, entry, builder):
1336         """Arrange for a source code builder for (part of) a tree."""
1337         entries = self.arg2nodes(entry, self.fs.Entry)
1338         for entry in entries:
1339             entry.set_src_builder(builder)
1340         return entries
1341
1342     def SourceSignatures(self, type):
1343         type = self.subst(type)
1344         if type == 'MD5':
1345             import SCons.Sig.MD5
1346             self._calc_module = SCons.Sig.MD5
1347         elif type == 'timestamp':
1348             import SCons.Sig.TimeStamp
1349             self._calc_module = SCons.Sig.TimeStamp
1350         else:
1351             raise UserError, "Unknown source signature type '%s'"%type
1352
1353     def Split(self, arg):
1354         """This function converts a string or list into a list of strings
1355         or Nodes.  This makes things easier for users by allowing files to
1356         be specified as a white-space separated list to be split.
1357         The input rules are:
1358             - A single string containing names separated by spaces. These will be
1359               split apart at the spaces.
1360             - A single Node instance
1361             - A list containing either strings or Node instances. Any strings
1362               in the list are not split at spaces.
1363         In all cases, the function returns a list of Nodes and strings."""
1364         if SCons.Util.is_List(arg):
1365             return map(self.subst, arg)
1366         elif SCons.Util.is_String(arg):
1367             return string.split(self.subst(arg))
1368         else:
1369             return [self.subst(arg)]
1370
1371     def TargetSignatures(self, type):
1372         type = self.subst(type)
1373         if type == 'build':
1374             self._build_signature = 1
1375         elif type == 'content':
1376             self._build_signature = 0
1377         else:
1378             raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type
1379
1380     def Value(self, value):
1381         """
1382         """
1383         return SCons.Node.Python.Value(value)
1384
1385 class OverrideEnvironment(SubstitutionEnvironment):
1386     """A proxy that overrides variables in a wrapped construction
1387     environment by returning values from an overrides dictionary in
1388     preference to values from the underlying subject environment.
1389
1390     This is a lightweight (I hope) proxy that passes through most use of
1391     attributes to the underlying Environment.Base class, but has just
1392     enough additional methods defined to act like a real construction
1393     environment with overridden values.  It can wrap either a Base
1394     construction environment, or another OverrideEnvironment, which
1395     can in turn nest arbitrary OverrideEnvironments...
1396
1397     Note that we do *not* call the underlying base class
1398     (SubsitutionEnvironment) initialization, because we get most of those
1399     from proxying the attributes of the subject construction environment.
1400     But because we subclass SubstitutionEnvironment, this class also
1401     has inherited arg2nodes() and subst*() methods; those methods can't
1402     be proxied because they need *this* object's methods to fetch the
1403     values from the overrides dictionary.
1404     """
1405     def __init__(self, subject, overrides={}):
1406         if __debug__: logInstanceCreation(self, 'OverrideEnvironment')
1407         self.__dict__['__subject'] = subject
1408         self.__dict__['overrides'] = overrides
1409         self.__dict__['overrides']['__env__'] = self
1410
1411     # Methods that make this class act like a proxy.
1412     def __getattr__(self, name):
1413         return getattr(self.__dict__['__subject'], name)
1414     def __setattr__(self, name, value):
1415         return setattr(self.__dict__['__subject'], name, value)
1416
1417     # Methods that make this class act like a dictionary.
1418     def __getitem__(self, key):
1419         try:
1420             return self.__dict__['overrides'][key]
1421         except KeyError:
1422             return self.__dict__['__subject'].__getitem__(key)
1423     def __setitem__(self, key, value):
1424         if not SCons.Util.is_valid_construction_var(key):
1425             raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
1426         self.__dict__['overrides'][key] = value
1427     def __delitem__(self, key):
1428         try:
1429             del self.__dict__['overrides'][key]
1430         except KeyError:
1431             pass
1432         return self.__dict__['__subject'].__delitem__(key)
1433     def get(self, key, default=None):
1434         """Emulates the get() method of dictionaries."""
1435         try:
1436             return self.__dict__['overrides'][key]
1437         except KeyError:
1438             return self.__dict__['__subject'].get(key, default)
1439     def has_key(self, key):
1440         try:
1441             self.__dict__['overrides'][key]
1442             return 1
1443         except KeyError:
1444             return self.__dict__['__subject'].has_key(key)
1445     def items(self):
1446         """Emulates the items() method of dictionaries."""
1447         return self.Dictionary().items()
1448
1449     # Overridden private construction environment methods.
1450     def _update(self, dict):
1451         """Update an environment's values directly, bypassing the normal
1452         checks that occur when users try to set items.
1453         """
1454         self.__dict__['overrides'].update(dict)
1455
1456     def gvars(self):
1457         return self.__dict__['__subject'].gvars()
1458
1459     def lvars(self):
1460         lvars = self.__dict__['__subject'].lvars()
1461         lvars.update(self.__dict__['overrides'])
1462         return lvars
1463
1464     # Overridden public construction environment methods.
1465     def Replace(self, **kw):
1466         kw = copy_non_reserved_keywords(kw)
1467         self.__dict__['overrides'].update(our_deepcopy(kw))
1468
1469 # The entry point that will be used by the external world
1470 # to refer to a construction environment.  This allows the wrapper
1471 # interface to extend a construction environment for its own purposes
1472 # by subclassing SCons.Environment.Base and then assigning the
1473 # class to SCons.Environment.Environment.
1474
1475 Environment = Base
1476
1477 # An entry point for returning a proxy subclass instance that overrides
1478 # the subst*() methods so they don't actually perform construction
1479 # variable substitution.  This is specifically intended to be the shim
1480 # layer in between global function calls (which don't want construction
1481 # variable substitution) and the DefaultEnvironment() (which would
1482 # substitute variables if left to its own devices)."""
1483 #
1484 # We have to wrap this in a function that allows us to delay definition of
1485 # the class until it's necessary, so that when it subclasses Environment
1486 # it will pick up whatever Environment subclass the wrapper interface
1487 # might have assigned to SCons.Environment.Environment.
1488
1489 def NoSubstitutionProxy(subject):
1490     class _NoSubstitutionProxy(Environment):
1491         def __init__(self, subject):
1492             self.__dict__['__subject'] = subject
1493         def __getattr__(self, name):
1494             return getattr(self.__dict__['__subject'], name)
1495         def __setattr__(self, name, value):
1496             return setattr(self.__dict__['__subject'], name, value)
1497         def raw_to_mode(self, dict):
1498             try:
1499                 raw = dict['raw']
1500             except KeyError:
1501                 pass
1502             else:
1503                 del dict['raw']
1504                 dict['mode'] = raw
1505         def subst(self, string, *args, **kwargs):
1506             return string
1507         def subst_kw(self, kw, *args, **kwargs):
1508             return kw
1509         def subst_list(self, string, *args, **kwargs):
1510             nargs = (string, self,) + args
1511             nkw = kwargs.copy()
1512             nkw['gvars'] = {}
1513             self.raw_to_mode(nkw)
1514             return apply(SCons.Util.scons_subst_list, nargs, nkw)
1515         def subst_target_source(self, string, *args, **kwargs):
1516             nargs = (string, self,) + args
1517             nkw = kwargs.copy()
1518             nkw['gvars'] = {}
1519             self.raw_to_mode(nkw)
1520             return apply(SCons.Util.scons_subst, nargs, nkw)
1521     return _NoSubstitutionProxy(subject)