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