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