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