Fill in missing default Builder names. (Kevin Quick)
[scons.git] / src / engine / SCons / Environment.py
index f8ff6c35f49b1a2a7c4be978e27a356f31796bbe..af602a5c1b8349c15a466c175f49174eeb61ed3d 100644 (file)
@@ -39,12 +39,11 @@ import copy
 import os
 import os.path
 import string
-import re
-import shutil
 from UserDict import UserDict
 
 import SCons.Action
 import SCons.Builder
+from SCons.Debug import logInstanceCreation
 import SCons.Defaults
 import SCons.Errors
 import SCons.Node
@@ -52,6 +51,7 @@ import SCons.Node.Alias
 import SCons.Node.FS
 import SCons.Node.Python
 import SCons.Platform
+import SCons.SConsign
 import SCons.Sig
 import SCons.Sig.MD5
 import SCons.Sig.TimeStamp
@@ -64,7 +64,6 @@ class _Null:
 
 _null = _Null
 
-DefaultTargets = None
 CleanTargets = {}
 CalculatorArgs = {}
 
@@ -87,7 +86,8 @@ def installString(target, source, env):
 
 installAction = SCons.Action.Action(installFunc, installString)
 
-InstallBuilder = SCons.Builder.Builder(action=installAction)
+InstallBuilder = SCons.Builder.Builder(action=installAction,
+                                       name='InstallBuilder')
 
 def alias_builder(env, target, source):
     pass
@@ -95,7 +95,8 @@ def alias_builder(env, target, source):
 AliasBuilder = SCons.Builder.Builder(action = alias_builder,
                                      target_factory = SCons.Node.Alias.default_ans.Alias,
                                      source_factory = SCons.Node.FS.default_fs.Entry,
-                                     multi = 1)
+                                     multi = 1,
+                                     name='AliasBuilder')
 
 def our_deepcopy(x):
    """deepcopy lists and dictionaries, and just copy the reference
@@ -106,16 +107,38 @@ def our_deepcopy(x):
            copy[key] = our_deepcopy(x[key])
    elif SCons.Util.is_List(x):
        copy = map(our_deepcopy, x)
+       try:
+           copy = x.__class__(copy)
+       except AttributeError:
+           pass
    else:
        copy = x
    return copy
 
-def apply_tools(env, tools):
+def apply_tools(env, tools, toolpath):
     if tools:
+        # Filter out null tools from the list.
+        tools = filter(None, tools)
         for tool in tools:
             if SCons.Util.is_String(tool):
-                tool = SCons.Tool.Tool(tool)
-            tool(env)
+                env.Tool(tool, toolpath)
+            else:
+                tool(env)
+
+# These names are controlled by SCons; users should never set or override
+# them.  This warning can optionally be turned off, but scons will still
+# ignore the illegal variable names even if it's off.
+reserved_construction_var_names = \
+    ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']
+
+def copy_non_reserved_keywords(dict):
+    result = our_deepcopy(dict)
+    for k in result.keys():
+        if k in reserved_construction_var_names:
+            SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
+                                "Ignoring attempt to set reserved variable `%s'" % k)
+            del result[k]
+    return result
 
 class BuilderWrapper:
     """Wrapper class that associates an environment with a Builder at
@@ -208,13 +231,16 @@ class Base:
     def __init__(self,
                  platform=None,
                  tools=None,
+                 toolpath=[],
                  options=None,
                  **kw):
+        if __debug__: logInstanceCreation(self)
         self.fs = SCons.Node.FS.default_fs
         self.ans = SCons.Node.Alias.default_ans
         self.lookup_list = SCons.Node.arg2nodes_lookups
         self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
 
+        self._dict['__env__'] = self
         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
 
         if platform is None:
@@ -239,7 +265,7 @@ class Base:
             tools = self._dict.get('TOOLS', None)
             if tools is None:
                 tools = ['default']
-        apply_tools(self, tools)
+        apply_tools(self, tools, toolpath)
 
         # Reapply the passed in variables after calling the tools,
         # since they should overide anything set by the tools:
@@ -252,13 +278,21 @@ class Base:
             options.Update(self)
 
     def __cmp__(self, other):
-       return cmp(self._dict, other._dict)
+        # Since an Environment now has an '__env__' construction variable
+        # that refers to itself, delete that variable to avoid infinite
+        # loops when comparing the underlying dictionaries in some Python
+        # versions (*cough* 1.5.2 *cough*)...
+        sdict = self._dict.copy()
+        del sdict['__env__']
+        odict = other._dict.copy()
+        del odict['__env__']
+        return cmp(sdict, odict)
 
     def __getitem__(self, key):
         return self._dict[key]
 
     def __setitem__(self, key, value):
-        if key in ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
+        if key in reserved_construction_var_names:
             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
                                 "Ignoring attempt to set reserved variable `%s'" % key)
         elif key == 'BUILDERS':
@@ -269,6 +303,9 @@ class Base:
             except KeyError:
                 self._dict[key] = BuilderDict(kwbd, self)
             self._dict[key].update(value)
+        elif key == 'SCANNERS':
+            self._dict[key] = value
+            self.scanner_map_delete()
         else:
             if not SCons.Util.is_valid_construction_var(key):
                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
@@ -303,7 +340,9 @@ class Base:
         if not args:
             return []
 
-        if not SCons.Util.is_List(args):
+        if SCons.Util.is_List(args):
+            args = SCons.Util.flatten(args)
+        else:
             args = [args]
 
         nodes = []
@@ -319,10 +358,16 @@ class Base:
                         n = self.subst(n, raw=1)
                         if node_factory:
                             n = node_factory(n)
-                    nodes.append(n)
+                    if SCons.Util.is_List(n):
+                        nodes.extend(n)
+                    else:
+                        nodes.append(n)
                 elif node_factory:
-                    v = self.subst(v, raw=1)
-                    nodes.append(node_factory(v))
+                    v = node_factory(self.subst(v, raw=1))
+                    if SCons.Util.is_List(v):
+                        nodes.extend(v)
+                    else:
+                        nodes.append(v)
             else:
                 nodes.append(v)
     
@@ -353,52 +398,115 @@ class Base:
 
     def get_scanner(self, skey):
         """Find the appropriate scanner given a key (usually a file suffix).
-        Does a linear search. Could be sped up by creating a dictionary if
-        this proves too slow.
         """
-        if self._dict['SCANNERS']:
-            for scanner in self._dict['SCANNERS']:
-                if skey in scanner.skeys:
-                    return scanner
-        return None
+        try:
+            sm = self.scanner_map
+        except AttributeError:
+            try:
+                scanners = self._dict['SCANNERS']
+            except KeyError:
+                self.scanner_map = {}
+                return None
+            else:
+                self.scanner_map = sm = {}
+                # Reverse the scanner list so that, if multiple scanners
+                # claim they can scan the same suffix, earlier scanners
+                # in the list will overwrite later scanners, so that
+                # the result looks like a "first match" to the user.
+                if not SCons.Util.is_List(scanners):
+                    scanners = [scanners]
+                scanners.reverse()
+                for scanner in scanners:
+                    for k in scanner.get_skeys(self):
+                        sm[k] = scanner
+        try:
+            return sm[skey]
+        except KeyError:
+            return None
 
-    def subst(self, string, raw=0, target=None, source=None):
-       """Recursively interpolates construction variables from the
-       Environment into the specified string, returning the expanded
-       result.  Construction variables are specified by a $ prefix
-       in the string and begin with an initial underscore or
-       alphabetic character followed by any number of underscores
-       or alphanumeric characters.  The construction variable names
-       may be surrounded by curly braces to separate the name from
-       trailing characters.
-       """
-        if raw:
-            mode = SCons.Util.SUBST_RAW
-        else:
-            mode = SCons.Util.SUBST_CMD
-        return SCons.Util.scons_subst(string, self, mode, target, source)
-    
-    def subst_kw(self, kw, raw=0, target=None, source=None):
-        if raw:
-            mode = SCons.Util.SUBST_RAW
-        else:
-            mode = SCons.Util.SUBST_CMD
+    def scanner_map_delete(self, kw=None):
+        """Delete the cached scanner map (if we need to).
+        """
+        if not kw is None and not kw.has_key('SCANNERS'):
+            return
+        try:
+            del self.scanner_map
+        except AttributeError:
+            pass
+
+    def subst(self, string, raw=0, target=None, source=None, dict=None, conv=None):
+        """Recursively interpolates construction variables from the
+        Environment into the specified string, returning the expanded
+        result.  Construction variables are specified by a $ prefix
+        in the string and begin with an initial underscore or
+        alphabetic character followed by any number of underscores
+        or alphanumeric characters.  The construction variable names
+        may be surrounded by curly braces to separate the name from
+        trailing characters.
+        """
+        return SCons.Util.scons_subst(string, self, raw, target, source, dict, conv)
+
+    def subst_kw(self, kw, raw=0, target=None, source=None, dict=None):
         nkw = {}
         for k, v in kw.items():
-            k = SCons.Util.scons_subst(k, self, mode, target, source)
+            k = self.subst(k, raw, target, source, dict)
             if SCons.Util.is_String(v):
-                v = SCons.Util.scons_subst(v, self, mode, target, source)
+                v = self.subst(v, raw, target, source, dict)
             nkw[k] = v
         return nkw
-    
-    def subst_list(self, string, raw=0, target=None, source=None):
+
+    def subst_list(self, string, raw=0, target=None, source=None, dict=None, conv=None):
         """Calls through to SCons.Util.scons_subst_list().  See
         the documentation for that function."""
-        if raw:
-            mode = SCons.Util.SUBST_RAW
-        else:
-            mode = SCons.Util.SUBST_CMD
-        return SCons.Util.scons_subst_list(string, self, mode, target, source)
+        return SCons.Util.scons_subst_list(string, self, raw, target, source, dict, conv)
+
+
+    def subst_path(self, path):
+        """Substitute a path list, turning EntryProxies into Nodes
+        and leaving Nodes (and other objects) as-is."""
+
+        if not SCons.Util.is_List(path):
+            path = [path]
+
+        def s(obj):
+            """This is the "string conversion" routine that we have our
+            substitutions use to return Nodes, not strings.  This relies
+            on the fact that an EntryProxy object has a get() method that
+            returns the underlying Node that it wraps, which is a bit of
+            architectural dependence that we might need to break or modify
+            in the future in response to additional requirements."""
+            try:
+                get = obj.get
+            except AttributeError:
+                pass
+            else:
+                obj = get()
+            return obj
+
+        r = []
+        for p in path:
+            if SCons.Util.is_String(p):
+                p = self.subst(p, conv=s)
+                if SCons.Util.is_List(p):
+                    if len(p) == 1:
+                        p = p[0]
+                    else:
+                        # We have an object plus a string, or multiple
+                        # objects that we need to smush together.  No choice
+                        # but to make them into a string.
+                        p = string.join(map(SCons.Util.to_String, p), '')
+            else:
+                p = s(p)
+            r.append(p)
+        return r
+
+    subst_target_source = subst
+
+    def _update(self, dict):
+        """Update an environment's values directly, bypassing the normal
+        checks that occur when users try to set items.
+        """
+        self._dict.update(dict)
 
     def use_build_signature(self):
         try:
@@ -421,21 +529,51 @@ class Base:
         """Append values to existing construction variables
         in an Environment.
         """
-        kw = our_deepcopy(kw)
-        for key in kw.keys():
-            if not self._dict.has_key(key):
-                self._dict[key] = kw[key]
-            elif SCons.Util.is_List(self._dict[key]) and not \
-                 SCons.Util.is_List(kw[key]):
-                self._dict[key] = self._dict[key] + [ kw[key] ]
-            elif SCons.Util.is_List(kw[key]) and not \
-                 SCons.Util.is_List(self._dict[key]):
-                self._dict[key] = [ self._dict[key] ] + kw[key]
-            elif SCons.Util.is_Dict(self._dict[key]) and \
-                 SCons.Util.is_Dict(kw[key]):
-                self._dict[key].update(kw[key])
+        kw = copy_non_reserved_keywords(kw)
+        for key, val in kw.items():
+            # It would be easier on the eyes to write this using
+            # "continue" statements whenever we finish processing an item,
+            # but Python 1.5.2 apparently doesn't let you use "continue"
+            # within try:-except: blocks, so we have to nest our code.
+            try:
+                orig = self._dict[key]
+            except KeyError:
+                # No existing variable in the environment, so just set
+                # it to the new value.
+                self._dict[key] = val
             else:
-                self._dict[key] = self._dict[key] + kw[key]
+                try:
+                    # Most straightforward:  just try to add them
+                    # together.  This will work in most cases, when the
+                    # original and new values are of compatible types.
+                    self._dict[key] = orig + val
+                except TypeError:
+                    try:
+                        # Try to update a dictionary value with another.
+                        # If orig isn't a dictionary, it won't have an
+                        # update() method; if val isn't a dictionary,
+                        # it won't have a keys() method.  Either way,
+                        # it's an AttributeError.
+                        orig.update(val)
+                    except AttributeError:
+                        try:
+                            # Check if the original is a list.
+                            add_to_orig = orig.append
+                        except AttributeError:
+                            # The original isn't a list, but the new
+                            # value is (by process of elimination),
+                            # so insert the original in the new value
+                            # (if there's one to insert) and replace
+                            # the variable with it.
+                            if orig:
+                                val.insert(0, orig)
+                            self._dict[key] = val
+                        else:
+                            # The original is a list, so append the new
+                            # value to it (if there's a value to append).
+                            if val:
+                                add_to_orig(val)
+        self.scanner_map_delete(kw)
 
     def AppendENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
         """Append path elements to the path 'name' in the 'ENV'
@@ -456,7 +594,33 @@ class Base:
 
         self._dict[envname][name] = nv
 
-    def Copy(self, tools=None, **kw):
+    def AppendUnique(self, **kw):
+        """Append values to existing construction variables
+        in an Environment, if they're not already there.
+        """
+        kw = copy_non_reserved_keywords(kw)
+        for key, val in kw.items():
+            if not self._dict.has_key(key):
+                self._dict[key] = val
+            elif SCons.Util.is_Dict(self._dict[key]) and \
+                 SCons.Util.is_Dict(val):
+                self._dict[key].update(val)
+            elif SCons.Util.is_List(val):
+                dk = self._dict[key]
+                if not SCons.Util.is_List(dk):
+                    dk = [dk]
+                val = filter(lambda x, dk=dk: x not in dk, val)
+                self._dict[key] = dk + val
+            else:
+                dk = self._dict[key]
+                if SCons.Util.is_List(dk):
+                    if not val in dk:
+                        self._dict[key] = dk + val
+                else:
+                    self._dict[key] = self._dict[key] + val
+        self.scanner_map_delete(kw)
+
+    def Copy(self, tools=None, toolpath=[], **kw):
         """Return a copy of a construction Environment.  The
         copy is like a Python "deep copy"--that is, independent
         copies are made recursively of each objects--except that
@@ -466,16 +630,21 @@ class Base:
         """
         clone = copy.copy(self)
         clone._dict = our_deepcopy(self._dict)
+        clone['__env__'] = clone
         try:
             cbd = clone._dict['BUILDERS']
             clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
         except KeyError:
             pass
         
-        apply_tools(clone, tools)
+        apply_tools(clone, tools, toolpath)
 
         # Apply passed-in variables after the new tools.
-        apply(clone.Replace, (), kw)
+        kw = copy_non_reserved_keywords(kw)
+        new = {}
+        for key, value in kw.items():
+            new[key] = SCons.Util.scons_subst_once(value, self, key)
+        apply(clone.Replace, (), new)
         return clone
 
     def Detect(self, progs):
@@ -496,6 +665,24 @@ class Base:
            dlist = dlist[0]
        return dlist
 
+    def Dump(self, key = None):
+        """
+        Using the standard Python pretty printer, dump the contents of the
+        scons build environment to stdout.
+
+        If the key passed in is anything other than None, then that will
+        be used as an index into the build environment dictionary and
+        whatever is found there will be fed into the pretty printer. Note
+        that this key is case sensitive.
+        """
+        import pprint
+        pp = pprint.PrettyPrinter(indent=2)
+        if key:
+            dict = self.Dictionary(key)
+        else:
+            dict = self.Dictionary()
+        return pp.pformat(dict)
+
     def FindIxes(self, paths, prefix, suffix):
         """
         Search a list of paths for something that matches the prefix and suffix.
@@ -505,8 +692,8 @@ class Base:
         suffix - construction variable for the suffix.
         """
 
-        suffix = self.subst('$%s'%suffix)
-        prefix = self.subst('$%s'%prefix)
+        suffix = self.subst('$'+suffix)
+        prefix = self.subst('$'+prefix)
 
         for path in paths:
             dir,name = os.path.split(str(path))
@@ -530,7 +717,12 @@ class Base:
         if overrides:
             env = copy.copy(self)
             env._dict = copy.copy(self._dict)
-            env._dict.update(overrides)
+            env['__env__'] = env
+            overrides = copy_non_reserved_keywords(overrides)
+            new = {}
+            for key, value in overrides.items():
+                new[key] = SCons.Util.scons_subst_once(value, self, key)
+            env._dict.update(new)
             return env
         else:
             return self
@@ -538,46 +730,62 @@ class Base:
     def ParseConfig(self, command, function=None):
         """
         Use the specified function to parse the output of the command
-        in order to modify the current environment. The 'command'
-        can be a string or a list of strings representing a command and
+        in order to modify the current environment. The 'command' can
+        be a string or a list of strings representing a command and
         it's arguments. 'Function' is an optional argument that takes
         the environment and the output of the command. If no function is
         specified, the output will be treated as the output of a typical
-        'X-config' command (i.e. gtk-config) and used to set the CPPPATH,
-        LIBPATH, LIBS, and CCFLAGS variables.
+        'X-config' command (i.e. gtk-config) and used to append to the
+        ASFLAGS, CCFLAGS, CPPFLAGS, CPPPATH, LIBPATH, LIBS, LINKFLAGS
+        and CCFLAGS variables.
         """
 
         # the default parse function
-        def parse_conf(env, output):
-            env_dict = env.Dictionary()
-            static_libs = []
-    
-            # setup all the dictionary options
-            if not env_dict.has_key('CPPPATH'):
-                env_dict['CPPPATH'] = []
-            if not env_dict.has_key('LIBPATH'):
-                env_dict['LIBPATH'] = []
-            if not env_dict.has_key('LIBS'):
-                env_dict['LIBS'] = []
-            if not env_dict.has_key('CCFLAGS') or env_dict['CCFLAGS'] == "":
-                env_dict['CCFLAGS'] = []
+        def parse_conf(env, output, fs=self.fs):
+            dict = {
+                'ASFLAGS'       : [],
+                'CCFLAGS'       : [],
+                'CPPFLAGS'      : [],
+                'CPPPATH'       : [],
+                'LIBPATH'       : [],
+                'LIBS'          : [],
+                'LINKFLAGS'     : [],
+            }
     
             params = string.split(output)
+            append_next_arg_to=''       # for multi-word args
             for arg in params:
-                switch = arg[0:1]
-                opt = arg[1:2]
-                if switch == '-':
-                    if opt == 'L':
-                        env_dict['LIBPATH'].append(arg[2:])
-                    elif opt == 'l':
-                        env_dict['LIBS'].append(arg[2:])
-                    elif opt == 'I':
-                        env_dict['CPPPATH'].append(arg[2:])
-                    else:
-                        env_dict['CCFLAGS'].append(arg)
+                if append_next_arg_to:
+                    dict[append_next_arg_to].append(arg)
+                    append_next_arg_to = ''
+                elif arg[0] != '-':
+                    dict['LIBS'].append(fs.File(arg))
+                elif arg[:2] == '-L':
+                    dict['LIBPATH'].append(arg[2:])
+                elif arg[:2] == '-l':
+                    dict['LIBS'].append(arg[2:])
+                elif arg[:2] == '-I':
+                    dict['CPPPATH'].append(arg[2:])
+                elif arg[:4] == '-Wa,':
+                    dict['ASFLAGS'].append(arg)
+                elif arg[:4] == '-Wl,':
+                    dict['LINKFLAGS'].append(arg)
+                elif arg[:4] == '-Wp,':
+                    dict['CPPFLAGS'].append(arg)
+                elif arg == '-framework':
+                    dict['LINKFLAGS'].append(arg)
+                    append_next_arg_to='LINKFLAGS'
+                elif arg == '-mno-cygwin':
+                    dict['CCFLAGS'].append(arg)
+                    dict['LINKFLAGS'].append(arg)
+                elif arg == '-mwindows':
+                    dict['LINKFLAGS'].append(arg)
+                elif arg == '-pthread':
+                    dict['CCFLAGS'].append(arg)
+                    dict['LINKFLAGS'].append(arg)
                 else:
-                    static_libs.append(arg)
-            return static_libs
+                    dict['CCFLAGS'].append(arg)
+            apply(env.Append, (), dict)
     
         if function is None:
             function = parse_conf
@@ -586,6 +794,32 @@ class Base:
         command = self.subst(command)
         return function(self, os.popen(command).read())
 
+    def ParseDepends(self, filename, must_exist=None):
+        """
+        Parse a mkdep-style file for explicit dependencies.  This is
+        completely abusable, and should be unnecessary in the "normal"
+        case of proper SCons configuration, but it may help make
+        the transition from a Make hierarchy easier for some people
+        to swallow.  It can also be genuinely useful when using a tool
+        that can write a .d file, but for which writing a scanner would
+        be too complicated.
+        """
+        try:
+            fp = open(filename, 'r')
+        except IOError:
+            if must_exist:
+                raise
+            return
+        for line in SCons.Util.LogicalLines(fp).readlines():
+            if line[0] == '#':
+                continue
+            try:
+                target, depends = string.split(line, ':', 1)
+            except:
+                pass
+            else:
+                self.Depends(string.split(target), string.split(depends))
+
     def Platform(self, platform):
         platform = self.subst(platform)
         return SCons.Platform.Platform(platform)(self)
@@ -594,21 +828,51 @@ class Base:
         """Prepend values to existing construction variables
         in an Environment.
         """
-        kw = our_deepcopy(kw)
-        for key in kw.keys():
-            if not self._dict.has_key(key):
-                self._dict[key] = kw[key]
-            elif SCons.Util.is_List(self._dict[key]) and not \
-                 SCons.Util.is_List(kw[key]):
-                self._dict[key] = [ kw[key] ] + self._dict[key]
-            elif SCons.Util.is_List(kw[key]) and not \
-                 SCons.Util.is_List(self._dict[key]):
-                self._dict[key] = kw[key] + [ self._dict[key] ]
-            elif SCons.Util.is_Dict(self._dict[key]) and \
-                 SCons.Util.is_Dict(kw[key]):
-                self._dict[key].update(kw[key])
+        kw = copy_non_reserved_keywords(kw)
+        for key, val in kw.items():
+            # It would be easier on the eyes to write this using
+            # "continue" statements whenever we finish processing an item,
+            # but Python 1.5.2 apparently doesn't let you use "continue"
+            # within try:-except: blocks, so we have to nest our code.
+            try:
+                orig = self._dict[key]
+            except KeyError:
+                # No existing variable in the environment, so just set
+                # it to the new value.
+                self._dict[key] = val
             else:
-                self._dict[key] = kw[key] + self._dict[key]
+                try:
+                    # Most straightforward:  just try to add them
+                    # together.  This will work in most cases, when the
+                    # original and new values are of compatible types.
+                    self._dict[key] = val + orig
+                except TypeError:
+                    try:
+                        # Try to update a dictionary value with another.
+                        # If orig isn't a dictionary, it won't have an
+                        # update() method; if val isn't a dictionary,
+                        # it won't have a keys() method.  Either way,
+                        # it's an AttributeError.
+                        orig.update(val)
+                    except AttributeError:
+                        try:
+                            # Check if the added value is a list.
+                            add_to_val = val.append
+                        except AttributeError:
+                            # The added value isn't a list, but the
+                            # original is (by process of elimination),
+                            # so insert the the new value in the original
+                            # (if there's one to insert).
+                            if val:
+                                orig.insert(0, val)
+                        else:
+                            # The added value is a list, so append
+                            # the original to it (if there's a value
+                            # to append).
+                            if orig:
+                                add_to_val(orig)
+                            self._dict[key] = val
+        self.scanner_map_delete(kw)
 
     def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
         """Prepend path elements to the path 'name' in the 'ENV'
@@ -629,6 +893,32 @@ class Base:
 
         self._dict[envname][name] = nv
 
+    def PrependUnique(self, **kw):
+        """Append values to existing construction variables
+        in an Environment, if they're not already there.
+        """
+        kw = copy_non_reserved_keywords(kw)
+        for key, val in kw.items():
+            if not self._dict.has_key(key):
+                self._dict[key] = val
+            elif SCons.Util.is_Dict(self._dict[key]) and \
+                 SCons.Util.is_Dict(val):
+                self._dict[key].update(val)
+            elif SCons.Util.is_List(val):
+                dk = self._dict[key]
+                if not SCons.Util.is_List(dk):
+                    dk = [dk]
+                val = filter(lambda x, dk=dk: x not in dk, val)
+                self._dict[key] = val + dk
+            else:
+                dk = self._dict[key]
+                if SCons.Util.is_List(dk):
+                    if not val in dk:
+                        self._dict[key] = val + dk
+                else:
+                    self._dict[key] = val + dk
+        self.scanner_map_delete(kw)
+
     def Replace(self, **kw):
         """Replace existing construction variables in an Environment
         with new construction variables and/or values.
@@ -639,7 +929,9 @@ class Base:
             self.__setitem__('BUILDERS', kwbd)
         except KeyError:
             pass
+        kw = copy_non_reserved_keywords(kw)
         self._dict.update(our_deepcopy(kw))
+        self.scanner_map_delete(kw)
 
     def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
         """
@@ -652,11 +944,11 @@ class Base:
         new_prefix - construction variable for the new prefix.
         new_suffix - construction variable for the new suffix.
         """
-        old_prefix = self.subst('$%s'%old_prefix)
-        old_suffix = self.subst('$%s'%old_suffix)
+        old_prefix = self.subst('$'+old_prefix)
+        old_suffix = self.subst('$'+old_suffix)
 
-        new_prefix = self.subst('$%s'%new_prefix)
-        new_suffix = self.subst('$%s'%new_suffix)
+        new_prefix = self.subst('$'+new_prefix)
+        new_suffix = self.subst('$'+new_suffix)
 
         dir,name = os.path.split(str(path))
         if name[:len(old_prefix)] == old_prefix:
@@ -665,11 +957,17 @@ class Base:
             name = name[:-len(old_suffix)]
         return os.path.join(dir, new_prefix+name+new_suffix)
 
-    def Tool(self, tool):
+    def SetDefault(self, **kw):
+        for k in kw.keys():
+            if self._dict.has_key(k):
+                del kw[k]
+        apply(self.Replace, (), kw)
+
+    def Tool(self, tool, toolpath=[]):
         tool = self.subst(tool)
-        return SCons.Tool.Tool(tool)(self)
+        return SCons.Tool.Tool(tool, map(self.subst, toolpath))(self)
 
-    def WhereIs(self, prog, path=None, pathext=None):
+    def WhereIs(self, prog, path=None, pathext=None, reject=[]):
         """Find prog in the path.  
         """
         if path is None:
@@ -686,7 +984,7 @@ class Base:
                 pass
         elif SCons.Util.is_String(pathext):
             pathext = self.subst(pathext)
-        path = SCons.Util.WhereIs(prog, path, pathext)
+        path = SCons.Util.WhereIs(prog, path, pathext, reject)
         if path: return path
         return None
 
@@ -699,7 +997,7 @@ class Base:
     #######################################################################
 
     def Action(self, *args, **kw):
-        nargs = self.subst_list(args)
+        nargs = self.subst(args)
         nkw = self.subst_kw(kw)
         return apply(SCons.Action.Action, nargs, nkw)
 
@@ -739,20 +1037,14 @@ class Base:
             s = self.arg2nodes(s, self.fs.Entry)
             for t in tlist:
                 AliasBuilder(self, t, s)
-        if len(tlist) == 1:
-            tlist = tlist[0]
         return tlist
 
     def AlwaysBuild(self, *targets):
         tlist = []
         for t in targets:
             tlist.extend(self.arg2nodes(t, self.fs.File))
-
         for t in tlist:
             t.set_always_build()
-
-        if len(tlist) == 1:
-            tlist = tlist[0]
         return tlist
 
     def BuildDir(self, build_dir, src_dir, duplicate=1):
@@ -767,69 +1059,45 @@ class Base:
     def CacheDir(self, path):
         self.fs.CacheDir(self.subst(path))
 
-    def Clean(self, target, files):
+    def Clean(self, targets, files):
         global CleanTargets
-
-        if not isinstance(target, SCons.Node.Node):
-            target = self.subst(target)
-            target = self.fs.Entry(target, create=1)
-    
-        if not SCons.Util.is_List(files):
-            files = [files]
-    
-        nodes = []
-        for f in files:
-            if isinstance(f, SCons.Node.Node):
-                nodes.append(f)
-            else:
-                nodes.extend(self.arg2nodes(f, self.fs.Entry))
-    
-        try:
-            CleanTargets[target].extend(nodes)
-        except KeyError:
-            CleanTargets[target] = nodes
+        tlist = self.arg2nodes(targets, self.fs.Entry)
+        flist = self.arg2nodes(files, self.fs.Entry)
+        for t in tlist:
+            try:
+                CleanTargets[t].extend(flist)
+            except KeyError:
+                CleanTargets[t] = flist
 
     def Configure(self, *args, **kw):
         nargs = [self]
         if args:
             nargs = nargs + self.subst_list(args)[0]
         nkw = self.subst_kw(kw)
+        nkw['called_from_env_method'] = 1
         try:
             nkw['custom_tests'] = self.subst_kw(nkw['custom_tests'])
         except KeyError:
             pass
         return apply(SCons.SConf.SConf, nargs, nkw)
 
-    def Command(self, target, source, action):
+    def Command(self, target, source, action, **kw):
         """Builds the supplied target files from the supplied
         source files using the supplied action.  Action may
         be any type that the Builder constructor will accept
         for an action."""
-        bld = SCons.Builder.Builder(action=action,
-                                    source_factory=self.fs.Entry)
+        nkw = self.subst_kw(kw)
+        nkw['action'] = action
+        nkw['source_factory'] = self.fs.Entry
+        bld = apply(SCons.Builder.Builder, (), nkw)
         return bld(self, target, source)
 
-    def Default(self, *targets):
-        global DefaultTargets
-        if DefaultTargets is None:
-            DefaultTargets = []
-        for t in targets:
-            if t is None:
-                DefaultTargets = []
-            elif isinstance(t, SCons.Node.Node):
-                DefaultTargets.append(t)
-            else:
-                DefaultTargets.extend(self.arg2nodes(t, self.fs.Entry))
-
     def Depends(self, target, dependency):
         """Explicity specify that 'target's depend on 'dependency'."""
         tlist = self.arg2nodes(target, self.fs.Entry)
         dlist = self.arg2nodes(dependency, self.fs.Entry)
         for t in tlist:
             t.add_dependency(dlist)
-
-        if len(tlist) == 1:
-            tlist = tlist[0]
         return tlist
 
     def Dir(self, name, *args, **kw):
@@ -840,6 +1108,12 @@ class Base:
     def Environment(self, **kw):
         return apply(SCons.Environment.Environment, [], self.subst_kw(kw))
 
+    def Execute(self, action, *args, **kw):
+        """Directly execute an action through an Environment
+        """
+        action = apply(self.Action, (action,) + args, kw)
+        return action([], [], self)
+
     def File(self, name, *args, **kw):
         """
         """
@@ -850,11 +1124,15 @@ class Base:
         nodes = self.arg2nodes(dirs, self.fs.Dir)
         return SCons.Node.FS.find_file(file, nodes, self.fs.File)
 
+    def Flatten(self, sequence):
+        return SCons.Util.flatten(sequence)
+
     def GetBuildPath(self, files):
-        ret = map(str, self.arg2nodes(files, self.fs.Entry))
-        if len(ret) == 1:
-            return ret[0]
-        return ret
+        result = map(str, self.arg2nodes(files, self.fs.Entry))
+        if SCons.Util.is_List(files):
+            return result
+        else:
+            return result[0]
 
     def Ignore(self, target, dependency):
         """Ignore a dependency."""
@@ -862,9 +1140,6 @@ class Base:
         dlist = self.arg2nodes(dependency, self.fs.Entry)
         for t in tlist:
             t.add_ignore(dlist)
-
-        if len(tlist) == 1:
-            tlist = tlist[0]
         return tlist
 
     def Install(self, dir, source):
@@ -884,21 +1159,17 @@ class Base:
         for dnode in dnodes:
             for src in sources:
                 target = self.fs.File(src.name, dnode)
-                tgt.append(InstallBuilder(self, target, src))
-        if len(tgt) == 1:
-            tgt = tgt[0]
+                tgt.extend(InstallBuilder(self, target, src))
         return tgt
 
     def InstallAs(self, target, source):
         """Install sources as targets."""
         sources = self.arg2nodes(source, self.fs.File)
         targets = self.arg2nodes(target, self.fs.File)
-        ret = []
+        result = []
         for src, tgt in map(lambda x, y: (x, y), sources, targets):
-            ret.append(InstallBuilder(self, tgt, src))
-        if len(ret) == 1:
-            ret = ret[0]
-        return ret
+            result.extend(InstallBuilder(self, tgt, src))
+        return result
 
     def Literal(self, string):
         return SCons.Util.Literal(string)
@@ -919,12 +1190,8 @@ class Base:
         tlist = []
         for t in targets:
             tlist.extend(self.arg2nodes(t, self.fs.Entry))
-
         for t in tlist:
             t.set_precious()
-
-        if len(tlist) == 1:
-            tlist = tlist[0]
         return tlist
 
     def Repository(self, *dirs, **kw):
@@ -938,13 +1205,13 @@ class Base:
                 arg = self.subst(arg)
             nargs.append(arg)
         nkw = self.subst_kw(kw)
-        return apply(SCons.Scanner.Base, nargs, nkw)
+        return apply(SCons.Scanner.Scanner, nargs, nkw)
 
-    def SConsignFile(self, name=".sconsign.dbm"):
+    def SConsignFile(self, name=".sconsign", dbm_module=None):
         name = self.subst(name)
         if not os.path.isabs(name):
             name = os.path.join(str(self.fs.SConstruct_dir), name)
-        SCons.Sig.SConsignFile(name)
+        SCons.SConsign.File(name, dbm_module)
 
     def SideEffect(self, side_effect, target):
         """Tell scons that side_effects are built as side 
@@ -953,28 +1220,20 @@ class Base:
         targets = self.arg2nodes(target, self.fs.Entry)
 
         for side_effect in side_effects:
-            # A builder of 1 means the node is supposed to appear
-            # buildable without actually having a builder, so we allow
-            # it to be a side effect as well.
-            if side_effect.has_builder() and side_effect.builder != 1:
+            if side_effect.multiple_side_effect_has_builder():
                 raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
             side_effect.add_source(targets)
             side_effect.side_effect = 1
             self.Precious(side_effect)
             for target in targets:
                 target.side_effects.append(side_effect)
-        if len(side_effects) == 1:
-            return side_effects[0]
-        else:
-            return side_effects
+        return side_effects
 
     def SourceCode(self, entry, builder):
         """Arrange for a source code builder for (part of) a tree."""
         entries = self.arg2nodes(entry, self.fs.Entry)
         for entry in entries:
             entry.set_src_builder(builder)
-        if len(entries) == 1:
-            return entries[0]
         return entries
 
     def SourceSignatures(self, type):
@@ -1027,3 +1286,49 @@ class Base:
 # class to SCons.Environment.Environment.
 
 Environment = Base
+
+# An entry point for returning a proxy subclass instance that overrides
+# the subst*() methods so they don't actually perform construction
+# variable substitution.  This is specifically intended to be the shim
+# layer in between global function calls (which don't want construction
+# variable substitution) and the DefaultEnvironment() (which would
+# substitute variables if left to its own devices)."""
+#
+# We have to wrap this in a function that allows us to delay definition of
+# the class until it's necessary, so that when it subclasses Environment
+# it will pick up whatever Environment subclass the wrapper interface
+# might have assigned to SCons.Environment.Environment.
+
+def NoSubstitutionProxy(subject):
+    class _NoSubstitutionProxy(Environment):
+        def __init__(self, subject):
+            self.__dict__['__subject'] = subject
+        def __getattr__(self, name):
+            return getattr(self.__dict__['__subject'], name)
+        def __setattr__(self, name, value):
+            return setattr(self.__dict__['__subject'], name, value)
+        def raw_to_mode(self, dict):
+            try:
+                raw = dict['raw']
+            except KeyError:
+                pass
+            else:
+                del dict['raw']
+                dict['mode'] = raw
+        def subst(self, string, *args, **kwargs):
+            return string
+        def subst_kw(self, kw, *args, **kwargs):
+            return kw
+        def subst_list(self, string, *args, **kwargs):
+            nargs = (string, self,) + args
+            nkw = kwargs.copy()
+            nkw['gvars'] = {}
+            self.raw_to_mode(nkw)
+            return apply(SCons.Util.scons_subst_list, nargs, nkw)
+        def subst_target_source(self, string, *args, **kwargs):
+            nargs = (string, self,) + args
+            nkw = kwargs.copy()
+            nkw['gvars'] = {}
+            self.raw_to_mode(nkw)
+            return apply(SCons.Util.scons_subst, nargs, nkw)
+    return _NoSubstitutionProxy(subject)