Avoid infinite recursion when comparing Environments, better sys.version use in src...
[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 import re
43 import shutil
44 from UserDict import UserDict
45
46 import SCons.Action
47 import SCons.Builder
48 from SCons.Debug import logInstanceCreation
49 import SCons.Defaults
50 import SCons.Errors
51 import SCons.Node
52 import SCons.Node.Alias
53 import SCons.Node.FS
54 import SCons.Node.Python
55 import SCons.Platform
56 import SCons.Sig
57 import SCons.Sig.MD5
58 import SCons.Sig.TimeStamp
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 'Install file: "%s" as "%s"' % (source[0], target[0])
87
88 installAction = SCons.Action.Action(installFunc, installString)
89
90 InstallBuilder = SCons.Builder.Builder(action=installAction)
91
92 def alias_builder(env, target, source):
93     pass
94
95 AliasBuilder = SCons.Builder.Builder(action = alias_builder,
96                                      target_factory = SCons.Node.Alias.default_ans.Alias,
97                                      source_factory = SCons.Node.FS.default_fs.Entry,
98                                      multi = 1)
99
100 def our_deepcopy(x):
101    """deepcopy lists and dictionaries, and just copy the reference
102    for everything else."""
103    if SCons.Util.is_Dict(x):
104        copy = {}
105        for key in x.keys():
106            copy[key] = our_deepcopy(x[key])
107    elif SCons.Util.is_List(x):
108        copy = map(our_deepcopy, x)
109        try:
110            copy = x.__class__(copy)
111        except AttributeError:
112            pass
113    else:
114        copy = x
115    return copy
116
117 def apply_tools(env, tools, toolpath):
118     if tools:
119         for tool in tools:
120             if SCons.Util.is_String(tool):
121                 env.Tool(tool, toolpath)
122             else:
123                 tool(env)
124
125 class BuilderWrapper:
126     """Wrapper class that associates an environment with a Builder at
127     instantiation."""
128     def __init__(self, env, builder):
129         self.env = env
130         self.builder = builder
131
132     def __call__(self, *args, **kw):
133         return apply(self.builder, (self.env,) + args, kw)
134
135     # This allows a Builder to be executed directly
136     # through the Environment to which it's attached.
137     # In practice, we shouldn't need this, because
138     # builders actually get executed through a Node.
139     # But we do have a unit test for this, and can't
140     # yet rule out that it would be useful in the
141     # future, so leave it for now.
142     def execute(self, **kw):
143         kw['env'] = self.env
144         apply(self.builder.execute, (), kw)
145
146 class BuilderDict(UserDict):
147     """This is a dictionary-like class used by an Environment to hold
148     the Builders.  We need to do this because every time someone changes
149     the Builders in the Environment's BUILDERS dictionary, we must
150     update the Environment's attributes."""
151     def __init__(self, dict, env):
152         # Set self.env before calling the superclass initialization,
153         # because it will end up calling our other methods, which will
154         # need to point the values in this dictionary to self.env.
155         self.env = env
156         UserDict.__init__(self, dict)
157
158     def __setitem__(self, item, val):
159         UserDict.__setitem__(self, item, val)
160         try:
161             self.setenvattr(item, val)
162         except AttributeError:
163             # Have to catch this because sometimes __setitem__ gets
164             # called out of __init__, when we don't have an env
165             # attribute yet, nor do we want one!
166             pass
167
168     def setenvattr(self, item, val):
169         """Set the corresponding environment attribute for this Builder.
170
171         If the value is already a BuilderWrapper, we pull the builder
172         out of it and make another one, so that making a copy of an
173         existing BuilderDict is guaranteed separate wrappers for each
174         Builder + Environment pair."""
175         try:
176             builder = val.builder
177         except AttributeError:
178             builder = val
179         setattr(self.env, item, BuilderWrapper(self.env, builder))
180
181     def __delitem__(self, item):
182         UserDict.__delitem__(self, item)
183         delattr(self.env, item)
184
185     def update(self, dict):
186         for i, v in dict.items():
187             self.__setitem__(i, v)
188
189 class Base:
190     """Base class for construction Environments.  These are
191     the primary objects used to communicate dependency and
192     construction information to the build engine.
193
194     Keyword arguments supplied when the construction Environment
195     is created are construction variables used to initialize the
196     Environment.
197     """
198
199     #######################################################################
200     # This is THE class for interacting with the SCons build engine,
201     # and it contains a lot of stuff, so we're going to try to keep this
202     # a little organized by grouping the methods.
203     #######################################################################
204
205     #######################################################################
206     # Methods that make an Environment act like a dictionary.  These have
207     # the expected standard names for Python mapping objects.  Note that
208     # we don't actually make an Environment a subclass of UserDict for
209     # performance reasons.  Note also that we only supply methods for
210     # dictionary functionality that we actually need and use.
211     #######################################################################
212
213     def __init__(self,
214                  platform=None,
215                  tools=None,
216                  toolpath=[],
217                  options=None,
218                  **kw):
219         if __debug__: logInstanceCreation(self)
220         self.fs = SCons.Node.FS.default_fs
221         self.ans = SCons.Node.Alias.default_ans
222         self.lookup_list = SCons.Node.arg2nodes_lookups
223         self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
224
225         self._dict['__env__'] = self
226         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
227
228         if platform is None:
229             platform = self._dict.get('PLATFORM', None)
230             if platform is None:
231                 platform = SCons.Platform.Platform()
232         if SCons.Util.is_String(platform):
233             platform = SCons.Platform.Platform(platform)
234         self._dict['PLATFORM'] = str(platform)
235         platform(self)
236
237         # Apply the passed-in variables before calling the tools,
238         # because they may use some of them:
239         apply(self.Replace, (), kw)
240         
241         # Update the environment with the customizable options
242         # before calling the tools, since they may use some of the options: 
243         if options:
244             options.Update(self)
245
246         if tools is None:
247             tools = self._dict.get('TOOLS', None)
248             if tools is None:
249                 tools = ['default']
250         apply_tools(self, tools, toolpath)
251
252         # Reapply the passed in variables after calling the tools,
253         # since they should overide anything set by the tools:
254         apply(self.Replace, (), kw)
255
256         # Update the environment with the customizable options
257         # after calling the tools, since they should override anything
258         # set by the tools:
259         if options:
260             options.Update(self)
261
262     def __cmp__(self, other):
263         # Since an Environment now has an '__env__' construction variable
264         # that refers to itself, delete that variable to avoid infinite
265         # loops when comparing the underlying dictionaries in some Python
266         # versions (*cough* 1.5.2 *cough*)...
267         sdict = self._dict.copy()
268         del sdict['__env__']
269         odict = other._dict.copy()
270         del odict['__env__']
271         return cmp(sdict, odict)
272
273     def __getitem__(self, key):
274         return self._dict[key]
275
276     def __setitem__(self, key, value):
277         if key in ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
278             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
279                                 "Ignoring attempt to set reserved variable `%s'" % key)
280         elif key == 'BUILDERS':
281             try:
282                 bd = self._dict[key]
283                 for k in bd.keys():
284                     del bd[k]
285             except KeyError:
286                 self._dict[key] = BuilderDict(kwbd, self)
287             self._dict[key].update(value)
288         else:
289             if not SCons.Util.is_valid_construction_var(key):
290                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
291             self._dict[key] = value
292
293     def __delitem__(self, key):
294         del self._dict[key]
295
296     def items(self):
297         "Emulates the items() method of dictionaries."""
298         return self._dict.items()
299
300     def has_key(self, key):
301         return self._dict.has_key(key)
302
303     def get(self, key, default=None):
304         "Emulates the get() method of dictionaries."""
305         return self._dict.get(key, default)
306
307     #######################################################################
308     # Utility methods that are primarily for internal use by SCons.
309     # These begin with lower-case letters.  Note that the subst() method
310     # is actually already out of the closet and used by people.
311     #######################################################################
312
313     def arg2nodes(self, args, node_factory=_null, lookup_list=_null):
314         if node_factory is _null:
315             node_factory = self.fs.File
316         if lookup_list is _null:
317             lookup_list = self.lookup_list
318
319         if not args:
320             return []
321
322         if not SCons.Util.is_List(args):
323             args = [args]
324
325         nodes = []
326         for v in args:
327             if SCons.Util.is_String(v):
328                 n = None
329                 for l in lookup_list:
330                     n = l(v)
331                     if not n is None:
332                         break
333                 if not n is None:
334                     if SCons.Util.is_String(n):
335                         n = self.subst(n, raw=1)
336                         if node_factory:
337                             n = node_factory(n)
338                     nodes.append(n)
339                 elif node_factory:
340                     v = self.subst(v, raw=1)
341                     nodes.append(node_factory(v))
342             else:
343                 nodes.append(v)
344     
345         return nodes
346
347     def get_calculator(self):
348         try:
349             return self._calculator
350         except AttributeError:
351             try:
352                 module = self._calc_module
353                 c = apply(SCons.Sig.Calculator, (module,), CalculatorArgs)
354             except AttributeError:
355                 # Note that we're calling get_calculator() here, so the
356                 # DefaultEnvironment() must have a _calc_module attribute
357                 # to avoid infinite recursion.
358                 c = SCons.Defaults.DefaultEnvironment().get_calculator()
359             self._calculator = c
360             return c
361
362     def get_builder(self, name):
363         """Fetch the builder with the specified name from the environment.
364         """
365         try:
366             return self._dict['BUILDERS'][name]
367         except KeyError:
368             return None
369
370     def get_scanner(self, skey):
371         """Find the appropriate scanner given a key (usually a file suffix).
372         Does a linear search. Could be sped up by creating a dictionary if
373         this proves too slow.
374         """
375         if self._dict['SCANNERS']:
376             for scanner in self._dict['SCANNERS']:
377                 if skey in scanner.skeys:
378                     return scanner
379         return None
380
381     def subst(self, string, raw=0, target=None, source=None, dict=None, conv=None):
382         """Recursively interpolates construction variables from the
383         Environment into the specified string, returning the expanded
384         result.  Construction variables are specified by a $ prefix
385         in the string and begin with an initial underscore or
386         alphabetic character followed by any number of underscores
387         or alphanumeric characters.  The construction variable names
388         may be surrounded by curly braces to separate the name from
389         trailing characters.
390         """
391         return SCons.Util.scons_subst(string, self, raw, target, source, dict, conv)
392
393     def subst_kw(self, kw, raw=0, target=None, source=None, dict=None):
394         nkw = {}
395         for k, v in kw.items():
396             k = self.subst(k, raw, target, source, dict)
397             if SCons.Util.is_String(v):
398                 v = self.subst(v, raw, target, source, dict)
399             nkw[k] = v
400         return nkw
401
402     def subst_list(self, string, raw=0, target=None, source=None, dict=None, conv=None):
403         """Calls through to SCons.Util.scons_subst_list().  See
404         the documentation for that function."""
405         return SCons.Util.scons_subst_list(string, self, raw, target, source, dict, conv)
406
407
408     def subst_path(self, path):
409         """Substitute a path list, turning EntryProxies into Nodes
410         and leaving Nodes (and other objects) as-is."""
411
412         if not SCons.Util.is_List(path):
413             path = [path]
414
415         def s(obj):
416             """This is the "string conversion" routine that we have our
417             substitutions use to return Nodes, not strings.  This relies
418             on the fact that an EntryProxy object has a get() method that
419             returns the underlying Node that it wraps, which is a bit of
420             architectural dependence that we might need to break or modify
421             in the future in response to additional requirements."""
422             try:
423                 get = obj.get
424             except AttributeError:
425                 pass
426             else:
427                 obj = get()
428             return obj
429
430         r = []
431         for p in path:
432             if SCons.Util.is_String(p):
433                 p = self.subst(p, conv=s)
434                 if SCons.Util.is_List(p):
435                     p = p[0]
436             else:
437                 p = s(p)
438             r.append(p)
439         return r
440
441     def _update(self, dict):
442         """Update an environment's values directly, bypassing the normal
443         checks that occur when users try to set items.
444         """
445         self._dict.update(dict)
446
447     def use_build_signature(self):
448         try:
449             return self._build_signature
450         except AttributeError:
451             b = SCons.Defaults.DefaultEnvironment()._build_signature
452             self._build_signature = b
453             return b
454
455     #######################################################################
456     # Public methods for manipulating an Environment.  These begin with
457     # upper-case letters.  The essential characteristic of methods in
458     # this section is that they do *not* have corresponding same-named
459     # global functions.  For example, a stand-alone Append() function
460     # makes no sense, because Append() is all about appending values to
461     # an Environment's construction variables.
462     #######################################################################
463
464     def Append(self, **kw):
465         """Append values to existing construction variables
466         in an Environment.
467         """
468         kw = our_deepcopy(kw)
469         for key, val in kw.items():
470             # It would be easier on the eyes to write this using
471             # "continue" statements whenever we finish processing an item,
472             # but Python 1.5.2 apparently doesn't let you use "continue"
473             # within try:-except: blocks, so we have to nest our code.
474             try:
475                 orig = self._dict[key]
476             except KeyError:
477                 # No existing variable in the environment, so just set
478                 # it to the new value.
479                 self._dict[key] = val
480             else:
481                 try:
482                     # Most straightforward:  just try to add them
483                     # together.  This will work in most cases, when the
484                     # original and new values are of compatible types.
485                     self._dict[key] = orig + val
486                 except TypeError:
487                     try:
488                         # Try to update a dictionary value with another.
489                         # If orig isn't a dictionary, it won't have an
490                         # update() method; if val isn't a dictionary,
491                         # it won't have a keys() method.  Either way,
492                         # it's an AttributeError.
493                         orig.update(val)
494                     except AttributeError:
495                         try:
496                             # Check if the original is a list.
497                             add_to_orig = orig.append
498                         except AttributeError:
499                             # The original isn't a list, but the new
500                             # value is (by process of elimination),
501                             # so insert the original in the new value
502                             # (if there's one to insert) and replace
503                             # the variable with it.
504                             if orig:
505                                 val.insert(0, orig)
506                             self._dict[key] = val
507                         else:
508                             # The original is a list, so append the new
509                             # value to it (if there's a value to append).
510                             if val:
511                                 add_to_orig(val)
512
513     def AppendENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
514         """Append path elements to the path 'name' in the 'ENV'
515         dictionary for this environment.  Will only add any particular
516         path once, and will normpath and normcase all paths to help
517         assure this.  This can also handle the case where the env
518         variable is a list instead of a string.
519         """
520
521         orig = ''
522         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
523             orig = self._dict[envname][name]
524
525         nv = SCons.Util.AppendPath(orig, newpath, sep)
526             
527         if not self._dict.has_key(envname):
528             self._dict[envname] = {}
529
530         self._dict[envname][name] = nv
531
532     def AppendUnique(self, **kw):
533         """Append values to existing construction variables
534         in an Environment, if they're not already there.
535         """
536         kw = our_deepcopy(kw)
537         for key, val in kw.items():
538             if not self._dict.has_key(key):
539                 self._dict[key] = val
540             elif SCons.Util.is_Dict(self._dict[key]) and \
541                  SCons.Util.is_Dict(val):
542                 self._dict[key].update(val)
543             elif SCons.Util.is_List(val):
544                 dk = self._dict[key]
545                 if not SCons.Util.is_List(dk):
546                     dk = [dk]
547                 val = filter(lambda x, dk=dk: x not in dk, val)
548                 self._dict[key] = dk + val
549             else:
550                 dk = self._dict[key]
551                 if SCons.Util.is_List(dk):
552                     if not val in dk:
553                         self._dict[key] = dk + val
554                 else:
555                     self._dict[key] = self._dict[key] + val
556
557     def Copy(self, tools=None, toolpath=[], **kw):
558         """Return a copy of a construction Environment.  The
559         copy is like a Python "deep copy"--that is, independent
560         copies are made recursively of each objects--except that
561         a reference is copied when an object is not deep-copyable
562         (like a function).  There are no references to any mutable
563         objects in the original Environment.
564         """
565         clone = copy.copy(self)
566         clone._dict = our_deepcopy(self._dict)
567         clone['__env__'] = clone
568         try:
569             cbd = clone._dict['BUILDERS']
570             clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
571         except KeyError:
572             pass
573         
574         apply_tools(clone, tools, toolpath)
575
576         # Apply passed-in variables after the new tools.
577         new = {}
578         for key, value in kw.items():
579             new[key] = SCons.Util.scons_subst_once(value, self, key)
580         apply(clone.Replace, (), new)
581         return clone
582
583     def Detect(self, progs):
584         """Return the first available program in progs.
585         """
586         if not SCons.Util.is_List(progs):
587             progs = [ progs ]
588         for prog in progs:
589             path = self.WhereIs(prog)
590             if path: return prog
591         return None
592
593     def Dictionary(self, *args):
594         if not args:
595             return self._dict
596         dlist = map(lambda x, s=self: s._dict[x], args)
597         if len(dlist) == 1:
598             dlist = dlist[0]
599         return dlist
600
601     def FindIxes(self, paths, prefix, suffix):
602         """
603         Search a list of paths for something that matches the prefix and suffix.
604
605         paths - the list of paths or nodes.
606         prefix - construction variable for the prefix.
607         suffix - construction variable for the suffix.
608         """
609
610         suffix = self.subst('$'+suffix)
611         prefix = self.subst('$'+prefix)
612
613         for path in paths:
614             dir,name = os.path.split(str(path))
615             if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix: 
616                 return path
617
618     def Override(self, overrides):
619         """
620         Produce a modified environment whose variables
621         are overriden by the overrides dictionaries.
622
623         overrides - a dictionary that will override
624         the variables of this environment.
625
626         This function is much more efficient than Copy()
627         or creating a new Environment because it doesn't do
628         a deep copy of the dictionary, and doesn't do a copy
629         at all if there are no overrides.
630         """
631
632         if overrides:
633             env = copy.copy(self)
634             env._dict = copy.copy(self._dict)
635             env['__env__'] = env
636             new = {}
637             for key, value in overrides.items():
638                 new[key] = SCons.Util.scons_subst_once(value, self, key)
639             env._dict.update(new)
640             return env
641         else:
642             return self
643
644     def ParseConfig(self, command, function=None):
645         """
646         Use the specified function to parse the output of the command
647         in order to modify the current environment. The 'command'
648         can be a string or a list of strings representing a command and
649         it's arguments. 'Function' is an optional argument that takes
650         the environment and the output of the command. If no function is
651         specified, the output will be treated as the output of a typical
652         'X-config' command (i.e. gtk-config) and used to set the CPPPATH,
653         LIBPATH, LIBS, and CCFLAGS variables.
654         """
655
656         # the default parse function
657         def parse_conf(env, output):
658             dict = {
659                 'CPPPATH' : [],
660                 'LIBPATH' : [],
661                 'LIBS'    : [],
662                 'CCFLAGS' : [],
663             }
664             static_libs = []
665     
666             params = string.split(output)
667             for arg in params:
668                 switch = arg[0:1]
669                 opt = arg[1:2]
670                 if switch == '-':
671                     if opt == 'L':
672                         dict['LIBPATH'].append(arg[2:])
673                     elif opt == 'l':
674                         dict['LIBS'].append(arg[2:])
675                     elif opt == 'I':
676                         dict['CPPPATH'].append(arg[2:])
677                     else:
678                         dict['CCFLAGS'].append(arg)
679                 else:
680                     static_libs.append(arg)
681             apply(env.Append, (), dict)
682             return static_libs
683     
684         if function is None:
685             function = parse_conf
686         if type(command) is type([]):
687             command = string.join(command)
688         command = self.subst(command)
689         return function(self, os.popen(command).read())
690
691     def Platform(self, platform):
692         platform = self.subst(platform)
693         return SCons.Platform.Platform(platform)(self)
694
695     def Prepend(self, **kw):
696         """Prepend values to existing construction variables
697         in an Environment.
698         """
699         kw = our_deepcopy(kw)
700         for key, val in kw.items():
701             # It would be easier on the eyes to write this using
702             # "continue" statements whenever we finish processing an item,
703             # but Python 1.5.2 apparently doesn't let you use "continue"
704             # within try:-except: blocks, so we have to nest our code.
705             try:
706                 orig = self._dict[key]
707             except KeyError:
708                 # No existing variable in the environment, so just set
709                 # it to the new value.
710                 self._dict[key] = val
711             else:
712                 try:
713                     # Most straightforward:  just try to add them
714                     # together.  This will work in most cases, when the
715                     # original and new values are of compatible types.
716                     self._dict[key] = val + orig
717                 except TypeError:
718                     try:
719                         # Try to update a dictionary value with another.
720                         # If orig isn't a dictionary, it won't have an
721                         # update() method; if val isn't a dictionary,
722                         # it won't have a keys() method.  Either way,
723                         # it's an AttributeError.
724                         orig.update(val)
725                     except AttributeError:
726                         try:
727                             # Check if the added value is a list.
728                             add_to_val = val.append
729                         except AttributeError:
730                             # The added value isn't a list, but the
731                             # original is (by process of elimination),
732                             # so insert the the new value in the original
733                             # (if there's one to insert).
734                             if val:
735                                 orig.insert(0, val)
736                         else:
737                             # The added value is a list, so append
738                             # the original to it (if there's a value
739                             # to append).
740                             if orig:
741                                 add_to_val(orig)
742                             self._dict[key] = val
743
744     def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
745         """Prepend path elements to the path 'name' in the 'ENV'
746         dictionary for this environment.  Will only add any particular
747         path once, and will normpath and normcase all paths to help
748         assure this.  This can also handle the case where the env
749         variable is a list instead of a string.
750         """
751
752         orig = ''
753         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
754             orig = self._dict[envname][name]
755
756         nv = SCons.Util.PrependPath(orig, newpath, sep)
757             
758         if not self._dict.has_key(envname):
759             self._dict[envname] = {}
760
761         self._dict[envname][name] = nv
762
763     def PrependUnique(self, **kw):
764         """Append values to existing construction variables
765         in an Environment, if they're not already there.
766         """
767         kw = our_deepcopy(kw)
768         for key, val in kw.items():
769             if not self._dict.has_key(key):
770                 self._dict[key] = val
771             elif SCons.Util.is_Dict(self._dict[key]) and \
772                  SCons.Util.is_Dict(val):
773                 self._dict[key].update(val)
774             elif SCons.Util.is_List(val):
775                 dk = self._dict[key]
776                 if not SCons.Util.is_List(dk):
777                     dk = [dk]
778                 val = filter(lambda x, dk=dk: x not in dk, val)
779                 self._dict[key] = val + dk
780             else:
781                 dk = self._dict[key]
782                 if SCons.Util.is_List(dk):
783                     if not val in dk:
784                         self._dict[key] = val + dk
785                 else:
786                     self._dict[key] = val + dk
787
788     def Replace(self, **kw):
789         """Replace existing construction variables in an Environment
790         with new construction variables and/or values.
791         """
792         try:
793             kwbd = our_deepcopy(kw['BUILDERS'])
794             del kw['BUILDERS']
795             self.__setitem__('BUILDERS', kwbd)
796         except KeyError:
797             pass
798         self._dict.update(our_deepcopy(kw))
799
800     def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
801         """
802         Replace old_prefix with new_prefix and old_suffix with new_suffix.
803
804         env - Environment used to interpolate variables.
805         path - the path that will be modified.
806         old_prefix - construction variable for the old prefix.
807         old_suffix - construction variable for the old suffix.
808         new_prefix - construction variable for the new prefix.
809         new_suffix - construction variable for the new suffix.
810         """
811         old_prefix = self.subst('$'+old_prefix)
812         old_suffix = self.subst('$'+old_suffix)
813
814         new_prefix = self.subst('$'+new_prefix)
815         new_suffix = self.subst('$'+new_suffix)
816
817         dir,name = os.path.split(str(path))
818         if name[:len(old_prefix)] == old_prefix:
819             name = name[len(old_prefix):]
820         if name[-len(old_suffix):] == old_suffix:
821             name = name[:-len(old_suffix)]
822         return os.path.join(dir, new_prefix+name+new_suffix)
823
824     def Tool(self, tool, toolpath=[]):
825         tool = self.subst(tool)
826         return SCons.Tool.Tool(tool, map(self.subst, toolpath))(self)
827
828     def WhereIs(self, prog, path=None, pathext=None):
829         """Find prog in the path.  
830         """
831         if path is None:
832             try:
833                 path = self['ENV']['PATH']
834             except KeyError:
835                 pass
836         elif SCons.Util.is_String(path):
837             path = self.subst(path)
838         if pathext is None:
839             try:
840                 pathext = self['ENV']['PATHEXT']
841             except KeyError:
842                 pass
843         elif SCons.Util.is_String(pathext):
844             pathext = self.subst(pathext)
845         path = SCons.Util.WhereIs(prog, path, pathext)
846         if path: return path
847         return None
848
849     #######################################################################
850     # Public methods for doing real "SCons stuff" (manipulating
851     # dependencies, setting attributes on targets, etc.).  These begin
852     # with upper-case letters.  The essential characteristic of methods
853     # in this section is that they all *should* have corresponding
854     # same-named global functions.
855     #######################################################################
856
857     def Action(self, *args, **kw):
858         nargs = self.subst(args)
859         nkw = self.subst_kw(kw)
860         return apply(SCons.Action.Action, nargs, nkw)
861
862     def AddPreAction(self, files, action):
863         nodes = self.arg2nodes(files, self.fs.Entry)
864         action = SCons.Action.Action(action)
865         for n in nodes:
866             n.add_pre_action(action)
867         return nodes
868     
869     def AddPostAction(self, files, action):
870         nodes = self.arg2nodes(files, self.fs.Entry)
871         action = SCons.Action.Action(action)
872         for n in nodes:
873             n.add_post_action(action)
874         return nodes
875
876     def Alias(self, target, *source, **kw):
877         if not SCons.Util.is_List(target):
878             target = [target]
879         tlist = []
880         for t in target:
881             if not isinstance(t, SCons.Node.Alias.Alias):
882                 t = self.arg2nodes(self.subst(t), self.ans.Alias)[0]
883             tlist.append(t)
884         try:
885             s = kw['source']
886         except KeyError:
887             try:
888                 s = source[0]
889             except IndexError:
890                 s = None
891         if s:
892             if not SCons.Util.is_List(s):
893                 s = [s]
894             s = filter(None, s)
895             s = self.arg2nodes(s, self.fs.Entry)
896             for t in tlist:
897                 AliasBuilder(self, t, s)
898         if len(tlist) == 1:
899             tlist = tlist[0]
900         return tlist
901
902     def AlwaysBuild(self, *targets):
903         tlist = []
904         for t in targets:
905             tlist.extend(self.arg2nodes(t, self.fs.File))
906
907         for t in tlist:
908             t.set_always_build()
909
910         if len(tlist) == 1:
911             tlist = tlist[0]
912         return tlist
913
914     def BuildDir(self, build_dir, src_dir, duplicate=1):
915         build_dir = self.arg2nodes(build_dir, self.fs.Dir)[0]
916         src_dir = self.arg2nodes(src_dir, self.fs.Dir)[0]
917         self.fs.BuildDir(build_dir, src_dir, duplicate)
918
919     def Builder(self, **kw):
920         nkw = self.subst_kw(kw)
921         return apply(SCons.Builder.Builder, [], nkw)
922
923     def CacheDir(self, path):
924         self.fs.CacheDir(self.subst(path))
925
926     def Clean(self, target, files):
927         global CleanTargets
928
929         if not isinstance(target, SCons.Node.Node):
930             target = self.subst(target)
931             target = self.fs.Entry(target, create=1)
932     
933         if not SCons.Util.is_List(files):
934             files = [files]
935     
936         nodes = []
937         for f in files:
938             if isinstance(f, SCons.Node.Node):
939                 nodes.append(f)
940             else:
941                 nodes.extend(self.arg2nodes(f, self.fs.Entry))
942     
943         try:
944             CleanTargets[target].extend(nodes)
945         except KeyError:
946             CleanTargets[target] = nodes
947
948     def Configure(self, *args, **kw):
949         nargs = [self]
950         if args:
951             nargs = nargs + self.subst_list(args)[0]
952         nkw = self.subst_kw(kw)
953         try:
954             nkw['custom_tests'] = self.subst_kw(nkw['custom_tests'])
955         except KeyError:
956             pass
957         return apply(SCons.SConf.SConf, nargs, nkw)
958
959     def Command(self, target, source, action, **kw):
960         """Builds the supplied target files from the supplied
961         source files using the supplied action.  Action may
962         be any type that the Builder constructor will accept
963         for an action."""
964         nkw = self.subst_kw(kw)
965         nkw['action'] = action
966         nkw['source_factory'] = self.fs.Entry
967         bld = apply(SCons.Builder.Builder, (), nkw)
968         return bld(self, target, source)
969
970     def Depends(self, target, dependency):
971         """Explicity specify that 'target's depend on 'dependency'."""
972         tlist = self.arg2nodes(target, self.fs.Entry)
973         dlist = self.arg2nodes(dependency, self.fs.Entry)
974         for t in tlist:
975             t.add_dependency(dlist)
976
977         if len(tlist) == 1:
978             tlist = tlist[0]
979         return tlist
980
981     def Dir(self, name, *args, **kw):
982         """
983         """
984         return apply(self.fs.Dir, (self.subst(name),) + args, kw)
985
986     def Environment(self, **kw):
987         return apply(SCons.Environment.Environment, [], self.subst_kw(kw))
988
989     def Execute(self, action, *args, **kw):
990         """Directly execute an action through an Environment
991         """
992         action = apply(self.Action, (action,) + args, kw)
993         return action([], [], self)
994
995     def File(self, name, *args, **kw):
996         """
997         """
998         return apply(self.fs.File, (self.subst(name),) + args, kw)
999
1000     def FindFile(self, file, dirs):
1001         file = self.subst(file)
1002         nodes = self.arg2nodes(dirs, self.fs.Dir)
1003         return SCons.Node.FS.find_file(file, nodes, self.fs.File)
1004
1005     def GetBuildPath(self, files):
1006         ret = map(str, self.arg2nodes(files, self.fs.Entry))
1007         if len(ret) == 1:
1008             return ret[0]
1009         return ret
1010
1011     def Ignore(self, target, dependency):
1012         """Ignore a dependency."""
1013         tlist = self.arg2nodes(target, self.fs.Entry)
1014         dlist = self.arg2nodes(dependency, self.fs.Entry)
1015         for t in tlist:
1016             t.add_ignore(dlist)
1017
1018         if len(tlist) == 1:
1019             tlist = tlist[0]
1020         return tlist
1021
1022     def Install(self, dir, source):
1023         """Install specified files in the given directory."""
1024         try:
1025             dnodes = self.arg2nodes(dir, self.fs.Dir)
1026         except TypeError:
1027             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)
1028         try:
1029             sources = self.arg2nodes(source, self.fs.File)
1030         except TypeError:
1031             if SCons.Util.is_List(source):
1032                 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))
1033             else:
1034                 raise SCons.Errors.UserError, "Source `%s' of Install() is not a file.  Install() source must be one or more files." % str(source)
1035         tgt = []
1036         for dnode in dnodes:
1037             for src in sources:
1038                 target = self.fs.File(src.name, dnode)
1039                 tgt.append(InstallBuilder(self, target, src))
1040         if len(tgt) == 1:
1041             tgt = tgt[0]
1042         return tgt
1043
1044     def InstallAs(self, target, source):
1045         """Install sources as targets."""
1046         sources = self.arg2nodes(source, self.fs.File)
1047         targets = self.arg2nodes(target, self.fs.File)
1048         ret = []
1049         for src, tgt in map(lambda x, y: (x, y), sources, targets):
1050             ret.append(InstallBuilder(self, tgt, src))
1051         if len(ret) == 1:
1052             ret = ret[0]
1053         return ret
1054
1055     def Literal(self, string):
1056         return SCons.Util.Literal(string)
1057
1058     def Local(self, *targets):
1059         ret = []
1060         for targ in targets:
1061             if isinstance(targ, SCons.Node.Node):
1062                 targ.set_local()
1063                 ret.append(targ)
1064             else:
1065                 for t in self.arg2nodes(targ, self.fs.Entry):
1066                    t.set_local()
1067                    ret.append(t)
1068         return ret
1069
1070     def Precious(self, *targets):
1071         tlist = []
1072         for t in targets:
1073             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1074
1075         for t in tlist:
1076             t.set_precious()
1077
1078         if len(tlist) == 1:
1079             tlist = tlist[0]
1080         return tlist
1081
1082     def Repository(self, *dirs, **kw):
1083         dirs = self.arg2nodes(list(dirs), self.fs.Dir)
1084         apply(self.fs.Repository, dirs, kw)
1085
1086     def Scanner(self, *args, **kw):
1087         nargs = []
1088         for arg in args:
1089             if SCons.Util.is_String(arg):
1090                 arg = self.subst(arg)
1091             nargs.append(arg)
1092         nkw = self.subst_kw(kw)
1093         return apply(SCons.Scanner.Base, nargs, nkw)
1094
1095     def SConsignFile(self, name=".sconsign.dbm", dbm_module=None):
1096         name = self.subst(name)
1097         if not os.path.isabs(name):
1098             name = os.path.join(str(self.fs.SConstruct_dir), name)
1099         SCons.Sig.SConsignFile(name, dbm_module)
1100
1101     def SideEffect(self, side_effect, target):
1102         """Tell scons that side_effects are built as side 
1103         effects of building targets."""
1104         side_effects = self.arg2nodes(side_effect, self.fs.Entry)
1105         targets = self.arg2nodes(target, self.fs.Entry)
1106
1107         for side_effect in side_effects:
1108             # A builder of 1 means the node is supposed to appear
1109             # buildable without actually having a builder, so we allow
1110             # it to be a side effect as well.
1111             if side_effect.has_builder() and side_effect.builder != 1:
1112                 raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
1113             side_effect.add_source(targets)
1114             side_effect.side_effect = 1
1115             self.Precious(side_effect)
1116             for target in targets:
1117                 target.side_effects.append(side_effect)
1118         if len(side_effects) == 1:
1119             return side_effects[0]
1120         else:
1121             return side_effects
1122
1123     def SourceCode(self, entry, builder):
1124         """Arrange for a source code builder for (part of) a tree."""
1125         entries = self.arg2nodes(entry, self.fs.Entry)
1126         for entry in entries:
1127             entry.set_src_builder(builder)
1128         if len(entries) == 1:
1129             return entries[0]
1130         return entries
1131
1132     def SourceSignatures(self, type):
1133         type = self.subst(type)
1134         if type == 'MD5':
1135             import SCons.Sig.MD5
1136             self._calc_module = SCons.Sig.MD5
1137         elif type == 'timestamp':
1138             import SCons.Sig.TimeStamp
1139             self._calc_module = SCons.Sig.TimeStamp
1140         else:
1141             raise UserError, "Unknown source signature type '%s'"%type
1142
1143     def Split(self, arg):
1144         """This function converts a string or list into a list of strings
1145         or Nodes.  This makes things easier for users by allowing files to
1146         be specified as a white-space separated list to be split.
1147         The input rules are:
1148             - A single string containing names separated by spaces. These will be
1149               split apart at the spaces.
1150             - A single Node instance
1151             - A list containing either strings or Node instances. Any strings
1152               in the list are not split at spaces.
1153         In all cases, the function returns a list of Nodes and strings."""
1154         if SCons.Util.is_List(arg):
1155             return map(self.subst, arg)
1156         elif SCons.Util.is_String(arg):
1157             return string.split(self.subst(arg))
1158         else:
1159             return [self.subst(arg)]
1160
1161     def TargetSignatures(self, type):
1162         type = self.subst(type)
1163         if type == 'build':
1164             self._build_signature = 1
1165         elif type == 'content':
1166             self._build_signature = 0
1167         else:
1168             raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type
1169
1170     def Value(self, value):
1171         """
1172         """
1173         return SCons.Node.Python.Value(value)
1174
1175 # The entry point that will be used by the external world
1176 # to refer to a construction environment.  This allows the wrapper
1177 # interface to extend a construction environment for its own purposes
1178 # by subclassing SCons.Environment.Base and then assigning the
1179 # class to SCons.Environment.Environment.
1180
1181 Environment = Base