http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / Script / SConscript.py
index 45d05b9813cd1bb36023c07d0a9fa80f7a195916..c55d22038b3ebb3c514c5b4d6ee5c0884406b9a5 100644 (file)
@@ -1,12 +1,12 @@
 """SCons.Script.SConscript
 
-This module defines the Python API provided to SConscript and SConstruct 
+This module defines the Python API provided to SConscript and SConstruct
 files.
 
 """
 
 #
-# Copyright (c) 2001, 2002 Steven Knight
+# __COPYRIGHT__
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -27,171 +27,274 @@ files.
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #
+from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons
+import SCons.Action
 import SCons.Builder
 import SCons.Defaults
 import SCons.Environment
 import SCons.Errors
 import SCons.Node
+import SCons.Node.Alias
 import SCons.Node.FS
 import SCons.Platform
+import SCons.SConf
+import SCons.Script.Main
 import SCons.Tool
 import SCons.Util
-import SCons.Options
-import SCons
 
 import os
 import os.path
-import string
+import re
 import sys
+import traceback
+import UserList
+
+# The following variables used to live in this module.  Some
+# SConscript files out there may have referred to them directly as
+# SCons.Script.SConscript.*.  This is now supported by some special
+# handling towards the bottom of the SConscript.__init__.py module.
+#Arguments = {}
+#ArgList = []
+#BuildTargets = TargetList()
+#CommandLineTargets = []
+#DefaultTargets = []
+
+class SConscriptReturn(Exception):
+    pass
 
-def do_nothing(text): pass
-HelpFunction = do_nothing
-
-default_targets = []
-arguments = {}
 launch_dir = os.path.abspath(os.curdir)
 
+GlobalDict = None
+
 # global exports set by Export():
 global_exports = {}
 
 # chdir flag
-sconscript_chdir = 0
+sconscript_chdir = 1
+
+def get_calling_namespaces():
+    """Return the locals and globals for the function that called
+    into this module in the current call stack."""
+    try: 1/0
+    except ZeroDivisionError: 
+        # Don't start iterating with the current stack-frame to
+        # prevent creating reference cycles (f_back is safe).
+        frame = sys.exc_info()[2].tb_frame.f_back
 
-def SConscriptChdir(flag):
-    global sconscript_chdir
-    sconscript_chdir = flag
+    # Find the first frame that *isn't* from this file.  This means
+    # that we expect all of the SCons frames that implement an Export()
+    # or SConscript() call to be in this file, so that we can identify
+    # the first non-Script.SConscript frame as the user's local calling
+    # environment, and the locals and globals dictionaries from that
+    # frame as the calling namespaces.  See the comment below preceding
+    # the DefaultEnvironmentCall block for even more explanation.
+    while frame.f_globals.get("__name__") == __name__:
+        frame = frame.f_back
 
-def _scons_add_args(alist):
-    global arguments
-    for arg in alist:
-        a, b = string.split(arg, '=', 2)
-        arguments[a] = b
+    return frame.f_locals, frame.f_globals
+
+
+def compute_exports(exports):
+    """Compute a dictionary of exports given one of the parameters
+    to the Export() function or the exports argument to SConscript()."""
+
+    loc, glob = get_calling_namespaces()
+
+    retval = {}
+    try:
+        for export in exports:
+            if SCons.Util.is_Dict(export):
+                retval.update(export)
+            else:
+                try:
+                    retval[export] = loc[export]
+                except KeyError:
+                    retval[export] = glob[export]
+    except KeyError, x:
+        raise SCons.Errors.UserError, "Export of non-existent variable '%s'"%x
+
+    return retval
 
 class Frame:
     """A frame on the SConstruct/SConscript call stack"""
-    def __init__(self, exports):
+    def __init__(self, fs, exports, sconscript):
         self.globals = BuildDefaultGlobals()
-        self.retval = None 
-        self.prev_dir = SCons.Node.FS.default_fs.getcwd()
-        self.exports = {} # exports from the calling SConscript
-
-        try:
-            if SCons.Util.is_List(exports):
-                for export in exports:
-                    self.exports[export] = stack[-1].globals[export]
-            else:
-                for export in string.split(exports):
-                    self.exports[export] = stack[-1].globals[export]
-        except KeyError, x:
-            raise SCons.Errors.UserError, "Export of non-existant variable '%s'"%x
+        self.retval = None
+        self.prev_dir = fs.getcwd()
+        self.exports = compute_exports(exports)  # exports from the calling SConscript
+        # make sure the sconscript attr is a Node.
+        if isinstance(sconscript, SCons.Node.Node):
+            self.sconscript = sconscript
+        elif sconscript == '-':
+            self.sconscript = None
+        else:
+            self.sconscript = fs.File(str(sconscript))
 
-        
 # the SConstruct/SConscript call stack:
-stack = []
+call_stack = []
 
 # For documentation on the methods in this file, see the scons man-page
 
-def Return(*vars):
+def Return(*vars, **kw):
     retval = []
     try:
-        for var in vars:
-            for v in string.split(var):
-                retval.append(stack[-1].globals[v])
+        fvars = SCons.Util.flatten(vars)
+        for var in fvars:
+            for v in var.split():
+                retval.append(call_stack[-1].globals[v])
     except KeyError, x:
-        raise SCons.Errors.UserError, "Return of non-existant variable '%s'"%x
-        
+        raise SCons.Errors.UserError, "Return of non-existent variable '%s'"%x
+
     if len(retval) == 1:
-        stack[-1].retval = retval[0]
+        call_stack[-1].retval = retval[0]
     else:
-        stack[-1].retval = tuple(retval)
+        call_stack[-1].retval = tuple(retval)
 
-# This function is responsible for converting the parameters passed to
-# SConscript() calls into a list of files and export variables.  If
-# the parameters are invalid, throws SCons.Errors.UserError. Returns a
-# tuple (l, e) where l is a list of filenames and e is a list of
-# exports.
+    stop = kw.get('stop', True)
 
-def GetSConscriptFilenames(ls, kw):
-    files = []
-    exports = []
+    if stop:
+        raise SConscriptReturn
 
-    if len(ls) == 0:
-        try:
-            dirs = map(str, SCons.Util.argmunge(kw["dirs"]))
-        except KeyError:
-            raise SCons.Errors.UserError, \
-                  "Invalid SConscript usage - no parameters"
-
-        name = kw.get('name', 'SConscript')
-
-        if kw.get('exports'):
-            exports = SCons.Util.argmunge(kw['exports'])
 
-        files = map(lambda n, name = name: os.path.join(n, name), dirs)
+stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
 
-    elif len(ls) == 1:
-
-        files = SCons.Util.argmunge(ls[0])
-        if kw.get('exports'):
-            exports = SCons.Util.argmunge(kw['exports'])
-
-    elif len(ls) == 2:
-
-        files   = SCons.Util.argmunge(ls[0])
-        exports = SCons.Util.argmunge(ls[1])
-
-        if kw.get('exports'):
-            exports.extend(SCons.Util.argmunge(kw.get('exports')))
-
-    else:
-        raise SCons.Errors.UserError, \
-              "Invalid SConscript() usage - too many arguments"
-
-    return (files, exports)
-
-def SConscript(*ls, **kw):
-    files, exports = GetSConscriptFilenames(ls, kw)
+def _SConscript(fs, *files, **kw):
+    top = fs.Top
+    sd = fs.SConstruct_dir.rdir()
+    exports = kw.get('exports', [])
 
     # evaluate each SConscript file
     results = []
     for fn in files:
-        stack.append(Frame(exports))
-        old_dir = None
+        call_stack.append(Frame(fs, exports, fn))
         old_sys_path = sys.path
         try:
+            SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
             if fn == "-":
-                exec sys.stdin in stack[-1].globals
+                exec sys.stdin in call_stack[-1].globals
             else:
                 if isinstance(fn, SCons.Node.Node):
                     f = fn
                 else:
-                    f = SCons.Node.FS.default_fs.File(str(fn))
+                    f = fs.File(str(fn))
+                _file_ = None
+
+                # Change directory to the top of the source
+                # tree to make sure the os's cwd and the cwd of
+                # fs match so we can open the SConscript.
+                fs.chdir(top, change_os_dir=1)
                 if f.rexists():
-                    file = open(f.rstr(), "r")
-                    SCons.Node.FS.default_fs.chdir(f.dir)
-                    if sconscript_chdir:
-                        old_dir = os.getcwd()
-                        os.chdir(str(f.dir))
-
-                    # prepend the SConscript directory to sys.path so
-                    # that Python modules in the SConscript directory can
-                    # be easily imported
-                    sys.path = [os.path.abspath(str(f.dir))] + sys.path
-
-                    exec file in stack[-1].globals
+                    actual = f.rfile()
+                    _file_ = open(actual.get_abspath(), "r")
+                elif f.srcnode().rexists():
+                    actual = f.srcnode().rfile()
+                    _file_ = open(actual.get_abspath(), "r")
+                elif f.has_src_builder():
+                    # The SConscript file apparently exists in a source
+                    # code management system.  Build it, but then clear
+                    # the builder so that it doesn't get built *again*
+                    # during the actual build phase.
+                    f.build()
+                    f.built()
+                    f.builder_set(None)
+                    if f.exists():
+                        _file_ = open(f.get_abspath(), "r")
+                if _file_:
+                    # Chdir to the SConscript directory.  Use a path
+                    # name relative to the SConstruct file so that if
+                    # we're using the -f option, we're essentially
+                    # creating a parallel SConscript directory structure
+                    # in our local directory tree.
+                    #
+                    # XXX This is broken for multiple-repository cases
+                    # where the SConstruct and SConscript files might be
+                    # in different Repositories.  For now, cross that
+                    # bridge when someone comes to it.
+                    try:
+                        src_dir = kw['src_dir']
+                    except KeyError:
+                        ldir = fs.Dir(f.dir.get_path(sd))
+                    else:
+                        ldir = fs.Dir(src_dir)
+                        if not ldir.is_under(f.dir):
+                            # They specified a source directory, but
+                            # it's above the SConscript directory.
+                            # Do the sensible thing and just use the
+                            # SConcript directory.
+                            ldir = fs.Dir(f.dir.get_path(sd))
+                    try:
+                        fs.chdir(ldir, change_os_dir=sconscript_chdir)
+                    except OSError:
+                        # There was no local directory, so we should be
+                        # able to chdir to the Repository directory.
+                        # Note that we do this directly, not through
+                        # fs.chdir(), because we still need to
+                        # interpret the stuff within the SConscript file
+                        # relative to where we are logically.
+                        fs.chdir(ldir, change_os_dir=0)
+                        os.chdir(actual.dir.get_abspath())
+
+                    # Append the SConscript directory to the beginning
+                    # of sys.path so Python modules in the SConscript
+                    # directory can be easily imported.
+                    sys.path = [ f.dir.get_abspath() ] + sys.path
+
+                    # This is the magic line that actually reads up
+                    # and executes the stuff in the SConscript file.
+                    # The locals for this frame contain the special
+                    # bottom-of-the-stack marker so that any
+                    # exceptions that occur when processing this
+                    # SConscript can base the printed frames at this
+                    # level and not show SCons internals as well.
+                    call_stack[-1].globals.update({stack_bottom:1})
+                    old_file = call_stack[-1].globals.get('__file__')
+                    try:
+                        del call_stack[-1].globals['__file__']
+                    except KeyError:
+                        pass
+                    try:
+                        try:
+                            exec _file_ in call_stack[-1].globals
+                        except SConscriptReturn:
+                            pass
+                    finally:
+                        if old_file is not None:
+                            call_stack[-1].globals.update({__file__:old_file})
                 else:
-                    sys.stderr.write("Ignoring missing SConscript '%s'\n" %
-                                     f.path)
-                
+                    SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
+                             "Ignoring missing SConscript '%s'" % f.path)
+
         finally:
+            SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
             sys.path = old_sys_path
-            frame = stack.pop()
-            SCons.Node.FS.default_fs.chdir(frame.prev_dir)
-            if old_dir:
-                os.chdir(old_dir)
+            frame = call_stack.pop()
+            try:
+                fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
+            except OSError:
+                # There was no local directory, so chdir to the
+                # Repository directory.  Like above, we do this
+                # directly.
+                fs.chdir(frame.prev_dir, change_os_dir=0)
+                rdir = frame.prev_dir.rdir()
+                rdir._create()  # Make sure there's a directory there.
+                try:
+                    os.chdir(rdir.get_abspath())
+                except OSError, e:
+                    # We still couldn't chdir there, so raise the error,
+                    # but only if actions are being executed.
+                    #
+                    # If the -n option was used, the directory would *not*
+                    # have been created and we should just carry on and
+                    # let things muddle through.  This isn't guaranteed
+                    # to work if the SConscript files are reading things
+                    # from disk (for example), but it should work well
+                    # enough for most configurations.
+                    if SCons.Action.execute_actions:
+                        raise e
 
             results.append(frame.retval)
 
@@ -200,155 +303,339 @@ def SConscript(*ls, **kw):
         return results[0]
     else:
         return tuple(results)
-    
-def Default(*targets):
-    for t in targets:
-        if isinstance(t, SCons.Node.Node):
-            default_targets.append(t)
-        else:
-            default_targets.extend(SCons.Node.arg2nodes(t,
-                                         SCons.Node.FS.default_fs.Entry))
 
-def Local(*targets):
-    for targ in targets:
-        if isinstance(targ, SCons.Node.Node):
-            targ.set_local()
+def SConscript_exception(file=sys.stderr):
+    """Print an exception stack trace just for the SConscript file(s).
+    This will show users who have Python errors where the problem is,
+    without cluttering the output with all of the internal calls leading
+    up to where we exec the SConscript."""
+    exc_type, exc_value, exc_tb = sys.exc_info()
+    tb = exc_tb
+    while tb and stack_bottom not in tb.tb_frame.f_locals:
+        tb = tb.tb_next
+    if not tb:
+        # We did not find our exec statement, so this was actually a bug
+        # in SCons itself.  Show the whole stack.
+        tb = exc_tb
+    stack = traceback.extract_tb(tb)
+    try:
+        type = exc_type.__name__
+    except AttributeError:
+        type = str(exc_type)
+        if type[:11] == "exceptions.":
+            type = type[11:]
+    file.write('%s: %s:\n' % (type, exc_value))
+    for fname, line, func, text in stack:
+        file.write('  File "%s", line %d:\n' % (fname, line))
+        file.write('    %s\n' % text)
+
+def annotate(node):
+    """Annotate a node with the stack frame describing the
+    SConscript file and line number that created it."""
+    tb = sys.exc_info()[2]
+    while tb and stack_bottom not in tb.tb_frame.f_locals:
+        tb = tb.tb_next
+    if not tb:
+        # We did not find any exec of an SConscript file: what?!
+        raise SCons.Errors.InternalError, "could not find SConscript stack frame"
+    node.creator = traceback.extract_stack(tb)[0]
+
+# The following line would cause each Node to be annotated using the
+# above function.  Unfortunately, this is a *huge* performance hit, so
+# leave this disabled until we find a more efficient mechanism.
+#SCons.Node.Annotate = annotate
+
+class SConsEnvironment(SCons.Environment.Base):
+    """An Environment subclass that contains all of the methods that
+    are particular to the wrapper SCons interface and which aren't
+    (or shouldn't be) part of the build engine itself.
+
+    Note that not all of the methods of this class have corresponding
+    global functions, there are some private methods.
+    """
+
+    #
+    # Private methods of an SConsEnvironment.
+    #
+    def _exceeds_version(self, major, minor, v_major, v_minor):
+        """Return 1 if 'major' and 'minor' are greater than the version
+        in 'v_major' and 'v_minor', and 0 otherwise."""
+        return (major > v_major or (major == v_major and minor > v_minor))
+
+    def _get_major_minor_revision(self, version_string):
+        """Split a version string into major, minor and (optionally)
+        revision parts.
+
+        This is complicated by the fact that a version string can be
+        something like 3.2b1."""
+        version = version_string.split(' ')[0].split('.')
+        v_major = int(version[0])
+        v_minor = int(re.match('\d+', version[1]).group())
+        if len(version) >= 3:
+            v_revision = int(re.match('\d+', version[2]).group())
         else:
-            for t in SCons.Node.arg2nodes(targ, SCons.Node.FS.default_fs.Entry):
-               t.set_local()
+            v_revision = 0
+        return v_major, v_minor, v_revision
 
-def Help(text):
-    HelpFunction(text)
+    def _get_SConscript_filenames(self, ls, kw):
+        """
+        Convert the parameters passed to # SConscript() calls into a list
+        of files and export variables.  If the parameters are invalid,
+        throws SCons.Errors.UserError. Returns a tuple (l, e) where l
+        is a list of SConscript filenames and e is a list of exports.
+        """
+        exports = []
 
-def BuildDir(build_dir, src_dir, duplicate=1):
-    SCons.Node.FS.default_fs.BuildDir(build_dir, src_dir, duplicate)
+        if len(ls) == 0:
+            try:
+                dirs = kw["dirs"]
+            except KeyError:
+                raise SCons.Errors.UserError, \
+                      "Invalid SConscript usage - no parameters"
 
-def GetBuildPath(files):
-    nodes = SCons.Node.arg2nodes(files, SCons.Node.FS.default_fs.Entry)
-    ret = map(str, nodes)
-    if len(ret) == 1:
-        return ret[0]
-    return ret
+            if not SCons.Util.is_List(dirs):
+                dirs = [ dirs ]
+            dirs = list(map(str, dirs))
 
-def FindFile(file, dirs):
-    nodes = SCons.Node.arg2nodes(dirs, SCons.Node.FS.default_fs.Dir)
-    return SCons.Node.FS.find_file(file, nodes)
+            name = kw.get('name', 'SConscript')
 
-def Export(*vars):
-    try:
-        for var in vars:
-            for v in string.split(var):
-                global_exports[v] = stack[-1].globals[v]
-    except KeyError, x:
-        raise SCons.Errors.UserError, "Export of non-existant variable '%s'"%x
+            files = [os.path.join(n, name) for n in dirs]
 
-def Import(*vars):
-    try:
-        for var in vars:
-            for v in string.split(var):
-                if stack[-1].exports.has_key(v):
-                    stack[-1].globals[v] = stack[-1].exports[v]
+        elif len(ls) == 1:
+
+            files = ls[0]
+
+        elif len(ls) == 2:
+
+            files   = ls[0]
+            exports = self.Split(ls[1])
+
+        else:
+
+            raise SCons.Errors.UserError, \
+                  "Invalid SConscript() usage - too many arguments"
+
+        if not SCons.Util.is_List(files):
+            files = [ files ]
+
+        if kw.get('exports'):
+            exports.extend(self.Split(kw['exports']))
+
+        variant_dir = kw.get('variant_dir') or kw.get('build_dir')
+        if variant_dir:
+            if len(files) != 1:
+                raise SCons.Errors.UserError, \
+                    "Invalid SConscript() usage - can only specify one SConscript with a variant_dir"
+            duplicate = kw.get('duplicate', 1)
+            src_dir = kw.get('src_dir')
+            if not src_dir:
+                src_dir, fname = os.path.split(str(files[0]))
+                files = [os.path.join(str(variant_dir), fname)]
+            else:
+                if not isinstance(src_dir, SCons.Node.Node):
+                    src_dir = self.fs.Dir(src_dir)
+                fn = files[0]
+                if not isinstance(fn, SCons.Node.Node):
+                    fn = self.fs.File(fn)
+                if fn.is_under(src_dir):
+                    # Get path relative to the source directory.
+                    fname = fn.get_path(src_dir)
+                    files = [os.path.join(str(variant_dir), fname)]
                 else:
-                    stack[-1].globals[v] = global_exports[v]
-    except KeyError,x:
-        raise SCons.Errors.UserError, "Import of non-existant variable '%s'"%x
-
-def GetLaunchDir():
-    return launch_dir
-
-def SetBuildSignatureType(type):
-    import SCons.Sig
-    if type == 'build':
-        SCons.Sig.build_signature = 1
-    elif type == 'content':
-        SCons.Sig.build_signature = 0
-    else:
-        raise SCons.Errors.UserError, "Unknown build signature type '%s'"%type
-
-def SetContentSignatureType(type):
-    import SCons.Script
-    if type == 'MD5':
-        import SCons.Sig.MD5
-        SCons.Script.sig_module = SCons.Sig.MD5
-    elif type == 'timestamp':
-        import SCons.Sig.TimeStamp
-        SCons.Script.sig_module = SCons.Sig.TimeStamp
-    else:
-        raise SCons.Errors.UserError, "Unknown content signature type '%s'"%type
+                    files = [fn.abspath]
+                kw['src_dir'] = variant_dir
+            self.fs.VariantDir(variant_dir, src_dir, duplicate)
+
+        return (files, exports)
+
+    #
+    # Public methods of an SConsEnvironment.  These get
+    # entry points in the global name space so they can be called
+    # as global functions.
+    #
+
+    def Configure(self, *args, **kw):
+        if not SCons.Script.sconscript_reading:
+            raise SCons.Errors.UserError, "Calling Configure from Builders is not supported."
+        kw['_depth'] = kw.get('_depth', 0) + 1
+        return SCons.Environment.Base.Configure(self, *args, **kw)
+
+    def Default(self, *targets):
+        SCons.Script._Set_Default_Targets(self, targets)
+
+    def EnsureSConsVersion(self, major, minor, revision=0):
+        """Exit abnormally if the SCons version is not late enough."""
+        scons_ver = self._get_major_minor_revision(SCons.__version__)
+        if scons_ver < (major, minor, revision):
+            if revision:
+                scons_ver_string = '%d.%d.%d' % (major, minor, revision)
+            else:
+                scons_ver_string = '%d.%d' % (major, minor)
+            print "SCons %s or greater required, but you have SCons %s" % \
+                  (scons_ver_string, SCons.__version__)
+            sys.exit(2)
 
+    def EnsurePythonVersion(self, major, minor):
+        """Exit abnormally if the Python version is not late enough."""
+        try:
+            v_major, v_minor, v_micro, release, serial = sys.version_info
+            python_ver = (v_major, v_minor)
+        except AttributeError:
+            python_ver = self._get_major_minor_revision(sys.version)[:2]
+        if python_ver < (major, minor):
+            v = sys.version.split(" ", 1)[0]
+            print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
+            sys.exit(2)
+
+    def Exit(self, value=0):
+        sys.exit(value)
+
+    def Export(self, *vars, **kw):
+        for var in vars:
+            global_exports.update(compute_exports(self.Split(var)))
+        global_exports.update(kw)
 
-class Options(SCons.Options.Options):
-    def Update(self, env):
-        return SCons.Options.Options.Update(self, env, arguments)
+    def GetLaunchDir(self):
+        global launch_dir
+        return launch_dir
 
-def CheckVersion(major,minor,version_string):
-    """Return 0 if 'major' and 'minor' are greater than the version
-    in 'version_string', and 1 otherwise."""
-    version = string.split(string.split(version_string, ' ')[0], '.')
-    if major > int(version[0]) or (major == int(version[0]) and minor > int(version[1])):
-        return 0
-    else:
-        return 1
+    def GetOption(self, name):
+        name = self.subst(name)
+        return SCons.Script.Main.GetOption(name)
+
+    def Help(self, text):
+        text = self.subst(text, raw=1)
+        SCons.Script.HelpFunction(text)
+
+    def Import(self, *vars):
+        try:
+            frame = call_stack[-1]
+            globals = frame.globals
+            exports = frame.exports
+            for var in vars:
+                var = self.Split(var)
+                for v in var:
+                    if v == '*':
+                        globals.update(global_exports)
+                        globals.update(exports)
+                    else:
+                        if v in exports:
+                            globals[v] = exports[v]
+                        else:
+                            globals[v] = global_exports[v]
+        except KeyError,x:
+            raise SCons.Errors.UserError, "Import of non-existent variable '%s'"%x
+
+    def SConscript(self, *ls, **kw):
+        def subst_element(x, subst=self.subst):
+            if SCons.Util.is_List(x):
+                x = list(map(subst, x))
+            else:
+                x = subst(x)
+            return x
+        ls = list(map(subst_element, ls))
+        subst_kw = {}
+        for key, val in kw.items():
+            if SCons.Util.is_String(val):
+                val = self.subst(val)
+            elif SCons.Util.is_List(val):
+                result = []
+                for v in val:
+                    if SCons.Util.is_String(v):
+                        v = self.subst(v)
+                    result.append(v)
+                val = result
+            subst_kw[key] = val
+
+        files, exports = self._get_SConscript_filenames(ls, subst_kw)
+        subst_kw['exports'] = exports
+        return _SConscript(self.fs, *files, **subst_kw)
+
+    def SConscriptChdir(self, flag):
+        global sconscript_chdir
+        sconscript_chdir = flag
+
+    def SetOption(self, name, value):
+        name = self.subst(name)
+        SCons.Script.Main.SetOption(name, value)
 
-def EnsureSConsVersion(major, minor):
-    """Exit abnormally if the SCons version is not late enough."""
-    if not CheckVersion(major,minor,SCons.__version__):
-        print "SCons %d.%d or greater required, but you have SCons %s" %(major,minor,SCons.__version__)
-        sys.exit(2)
+#
+#
+#
+SCons.Environment.Environment = SConsEnvironment
+
+def Configure(*args, **kw):
+    if not SCons.Script.sconscript_reading:
+        raise SCons.Errors.UserError, "Calling Configure from Builders is not supported."
+    kw['_depth'] = 1
+    return SCons.SConf.SConf(*args, **kw)
+
+# It's very important that the DefaultEnvironmentCall() class stay in this
+# file, with the get_calling_namespaces() function, the compute_exports()
+# function, the Frame class and the SConsEnvironment.Export() method.
+# These things make up the calling stack leading up to the actual global
+# Export() or SConscript() call that the user issued.  We want to allow
+# users to export local variables that they define, like so:
+#
+#       def func():
+#           x = 1
+#           Export('x')
+#
+# To support this, the get_calling_namespaces() function assumes that
+# the *first* stack frame that's not from this file is the local frame
+# for the Export() or SConscript() call.
+
+_DefaultEnvironmentProxy = None
+
+def get_DefaultEnvironmentProxy():
+    global _DefaultEnvironmentProxy
+    if not _DefaultEnvironmentProxy:
+        default_env = SCons.Defaults.DefaultEnvironment()
+        _DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env)
+    return _DefaultEnvironmentProxy
+
+class DefaultEnvironmentCall:
+    """A class that implements "global function" calls of
+    Environment methods by fetching the specified method from the
+    DefaultEnvironment's class.  Note that this uses an intermediate
+    proxy class instead of calling the DefaultEnvironment method
+    directly so that the proxy can override the subst() method and
+    thereby prevent expansion of construction variables (since from
+    the user's point of view this was called as a global function,
+    with no associated construction environment)."""
+    def __init__(self, method_name, subst=0):
+        self.method_name = method_name
+        if subst:
+            self.factory = SCons.Defaults.DefaultEnvironment
+        else:
+            self.factory = get_DefaultEnvironmentProxy
+    def __call__(self, *args, **kw):
+        env = self.factory()
+        method = getattr(env, self.method_name)
+        return method(*args, **kw)
 
-def EnsurePythonVersion(major, minor):
-    """Exit abnormally if the Python version is not late enough."""
-    if not CheckVersion(major,minor,sys.version):
-       v = string.split(sys.version, " ", 1)[0]
-        print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
-        sys.exit(2)
 
 def BuildDefaultGlobals():
     """
-    Create a dictionary containing all the default globals for 
-    SConscruct and SConscript files.
+    Create a dictionary containing all the default globals for
+    SConstruct and SConscript files.
     """
 
-    globals = {}
-    globals['Action']            = SCons.Action.Action
-    globals['ARGUMENTS']         = arguments
-    globals['BuildDir']          = BuildDir
-    globals['Builder']           = SCons.Builder.Builder
-    globals['CScan']             = SCons.Defaults.CScan
-    globals['Default']           = Default
-    globals['Dir']               = SCons.Node.FS.default_fs.Dir
-    globals['EnsurePythonVersion'] = EnsurePythonVersion
-    globals['EnsureSConsVersion'] = EnsureSConsVersion
-    globals['Environment']       = SCons.Environment.Environment
-    globals['Export']            = Export
-    globals['File']              = SCons.Node.FS.default_fs.File
-    globals['FindFile']          = FindFile
-    globals['GetBuildPath']      = GetBuildPath
-    globals['GetCommandHandler'] = SCons.Action.GetCommandHandler
-    globals['GetLaunchDir']      = GetLaunchDir
-    globals['Help']              = Help
-    globals['Import']            = Import
-    globals['Library']           = SCons.Defaults.StaticLibrary
-    globals['Literal']           = SCons.Util.Literal
-    globals['Local']             = Local
-    globals['Object']            = SCons.Defaults.StaticObject
-    globals['Options']           = Options
-    globals['Repository']        = SCons.Node.FS.default_fs.Repository
-    globals['SetBuildSignatureType'] = SetBuildSignatureType
-    globals['SetContentSignatureType'] = SetContentSignatureType
-    globals['StaticLibrary']     = SCons.Defaults.StaticLibrary
-    globals['StaticObject']      = SCons.Defaults.StaticObject
-    globals['SharedLibrary']     = SCons.Defaults.SharedLibrary
-    globals['SharedObject']      = SCons.Defaults.SharedObject
-    globals['ParseConfig']       = SCons.Util.ParseConfig
-    globals['Platform']          = SCons.Platform.Platform
-    globals['Program']           = SCons.Defaults.Program
-    globals['Return']            = Return
-    globals['Scanner']           = SCons.Scanner.Base
-    globals['SConscript']        = SConscript
-    globals['SConscriptChdir']   = SConscriptChdir
-    globals['SetCommandHandler'] = SCons.Action.SetCommandHandler
-    globals['Split']             = SCons.Util.Split
-    globals['Tool']              = SCons.Tool.Tool
-    globals['WhereIs']           = SCons.Util.WhereIs
-    return globals
+    global GlobalDict
+    if GlobalDict is None:
+        GlobalDict = {}
+
+        import SCons.Script
+        d = SCons.Script.__dict__
+        def not_a_module(m, d=d, mtype=type(SCons.Script)):
+             return not isinstance(d[m], mtype)
+        for m in filter(not_a_module, dir(SCons.Script)):
+             GlobalDict[m] = d[m]
+
+    return GlobalDict.copy()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: