http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / Script / SConscript.py
index 2984160dfeb305b074f635febfa7883b6e5c53a0..c55d22038b3ebb3c514c5b4d6ee5c0884406b9a5 100644 (file)
@@ -1,6 +1,6 @@
 """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.
 
 """
@@ -27,71 +27,81 @@ 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.Node.Python
 import SCons.Platform
 import SCons.SConf
-import SCons.Script
+import SCons.Script.Main
 import SCons.Tool
 import SCons.Util
-import SCons.Options
-import SCons
-import SCons.Node.Alias
 
 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 = []
 
-def do_nothing(text): pass
-HelpFunction = do_nothing
+class SConscriptReturn(Exception):
+    pass
 
-default_targets = None
-clean_targets = {}
-arguments = {}
 launch_dir = os.path.abspath(os.curdir)
 
+GlobalDict = None
+
 # global exports set by Export():
 global_exports = {}
 
 # chdir flag
 sconscript_chdir = 1
 
-def SConscriptChdir(flag):
-    global sconscript_chdir
-    sconscript_chdir = flag
-
-def _scons_add_args(alist):
-    global arguments
-    for arg in alist:
-        a, b = string.split(arg, '=', 2)
-        arguments[a] = b
-
 def get_calling_namespaces():
     """Return the locals and globals for the function that called
-    into this module in the current callstack."""
+    into this module in the current call stack."""
     try: 1/0
-    except: frame = sys.exc_info()[2].tb_frame
-    
-    while frame.f_globals.get("__name__") == __name__: frame = frame.f_back
+    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
+
+    # 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
 
     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()."""
-    exports = SCons.Util.argmunge(exports)
+
     loc, glob = get_calling_namespaces()
 
     retval = {}
@@ -105,150 +115,94 @@ def compute_exports(exports):
                 except KeyError:
                     retval[export] = glob[export]
     except KeyError, x:
-        raise SCons.Errors.UserError, "Export of non-existant variable '%s'"%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, sconscript):
+    def __init__(self, fs, exports, sconscript):
         self.globals = BuildDefaultGlobals()
-        self.retval = None 
-        self.prev_dir = SCons.Node.FS.default_fs.getcwd()
+        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 = SCons.Node.FS.default_fs.File(str(sconscript))
-        
+            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)
-
-# 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 SConscript filenames and e is a list of
-# exports.
-
-def GetSConscriptFilenames(ls, kw):
-    files = []
-    exports = []
-
-    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')
+        call_stack[-1].retval = tuple(retval)
 
-        if kw.get('exports'):
-            exports = SCons.Util.argmunge(kw['exports'])
-
-        files = map(lambda n, name = name: os.path.join(n, name), dirs)
-
-    elif len(ls) == 1:
-
-        files = SCons.Util.argmunge(ls[0])
-        if kw.get('exports'):
-            exports = SCons.Util.argmunge(kw['exports'])
+    stop = kw.get('stop', True)
 
-    elif len(ls) == 2:
+    if stop:
+        raise SConscriptReturn
 
-        files   = SCons.Util.argmunge(ls[0])
-        exports = SCons.Util.argmunge(ls[1])
 
-        if kw.get('exports'):
-            exports.extend(SCons.Util.argmunge(kw['exports']))
+stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
 
-    else:
-        raise SCons.Errors.UserError, \
-              "Invalid SConscript() usage - too many arguments"
-
-    build_dir = kw.get('build_dir')
-    if build_dir:
-        if len(files) != 1:
-            raise SCons.Errors.UserError, \
-                "Invalid SConscript() usage - can only specify one SConscript with a build_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]))
-        else:
-            if not isinstance(src_dir, SCons.Node.Node):
-                src_dir = SCons.Node.FS.default_fs.Dir(src_dir)
-            fn = files[0]
-            if not isinstance(fn, SCons.Node.Node):
-                fn = SCons.Node.FS.default_fs.File(fn)
-            if fn.is_under(src_dir):
-                # Get path relative to the source directory.
-                fname = fn.get_path(src_dir)
-            else:
-                # Fast way to only get the terminal path component of a Node.
-                fname = fn.get_path(fn.dir)
-        BuildDir(build_dir, src_dir, duplicate)
-        files = [os.path.join(str(build_dir), fname)]
-
-    return (files, exports)
-
-def SConscript(*ls, **kw):
-    files, exports = GetSConscriptFilenames(ls, kw)
-
-    default_fs = SCons.Node.FS.default_fs
-    top = default_fs.Top
-    sd = default_fs.SConstruct_dir.rdir()
+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,fn))
+        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 = 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
-                # SCons.Node.FS.default_fs match so we can open the
-                # SConscript.
-                default_fs.chdir(top, change_os_dir=1)
+                # fs match so we can open the SConscript.
+                fs.chdir(top, change_os_dir=1)
                 if f.rexists():
-                    _file_ = open(f.rstr(), "r")
+                    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)
-                    s = str(f)
-                    if os.path.exists(s):
-                        _file_ = open(s, "r")
+                    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
@@ -260,46 +214,87 @@ def SConscript(*ls, **kw):
                     # where the SConstruct and SConscript files might be
                     # in different Repositories.  For now, cross that
                     # bridge when someone comes to it.
-                    ldir = default_fs.Dir(f.dir.get_path(sd))
                     try:
-                        default_fs.chdir(ldir, change_os_dir=sconscript_chdir)
+                        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
-                        # default_fs.chdir(), because we still need to
+                        # fs.chdir(), because we still need to
                         # interpret the stuff within the SConscript file
                         # relative to where we are logically.
-                        default_fs.chdir(ldir, change_os_dir=0)
-                        os.chdir(f.rfile().dir.get_abspath())
+                        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.  We
-                    # look for the "exec _file_ " from the beginning
-                    # of this line to find the right stack frame (the
-                    # next one) describing the SConscript file and line
-                    # number that creates a node.
-                    exec _file_ in stack[-1].globals
+                    # 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()
+            frame = call_stack.pop()
             try:
-                default_fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
+                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.
-                default_fs.chdir(frame.prev_dir, change_os_dir=0)
-                os.chdir(frame.prev_dir.rdir().get_abspath())
+                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)
 
@@ -309,288 +304,338 @@ def SConscript(*ls, **kw):
     else:
         return tuple(results)
 
-def is_our_exec_statement(line):
-    return not line is None and line[:12] == "exec _file_ "
-
 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."""
-    stack = traceback.extract_tb(sys.exc_traceback)
-    last_text = ""
-    i = 0
-    for frame in stack:
-        if is_our_exec_statement(last_text):
-            break
-        i = i + 1
-        last_text = frame[3]
-    type = str(sys.exc_type)
-    if type[:11] == "exceptions.":
-        type = type[11:]
-    file.write('%s: %s:\n' % (type, sys.exc_value))
-    for fname, line, func, text in stack[i:]:
+    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."""
-    stack = traceback.extract_stack()
-    last_text = ""
-    for frame in stack:
-        # If the script text of the previous frame begins with the
-        # magic "exec _file_ " string, then this frame describes the
-        # SConscript file and line number that caused this node to be
-        # created.  Record the tuple and carry on.
-        if is_our_exec_statement(last_text):
-            node.creator = frame
-            return
-        last_text = frame[3]
+    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
 
-def Default(*targets):
-    global default_targets
-    if default_targets is None:
-        default_targets = []
-    for t in targets:
-        if t is None:
-            default_targets = []
-        elif isinstance(t, SCons.Node.Node):
-            default_targets.append(t)
-        else:
-            default_targets.extend(SCons.Node.arg2nodes(t,
-                                         SCons.Node.FS.default_fs.Entry))
+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.
 
-def Local(*targets):
-    for targ in targets:
-        if isinstance(targ, SCons.Node.Node):
-            targ.set_local()
+    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 _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 = []
+
+        if len(ls) == 0:
+            try:
+                dirs = kw["dirs"]
+            except KeyError:
+                raise SCons.Errors.UserError, \
+                      "Invalid SConscript usage - no parameters"
 
-def Help(text):
-    HelpFunction(text)
+            if not SCons.Util.is_List(dirs):
+                dirs = [ dirs ]
+            dirs = list(map(str, dirs))
 
-def BuildDir(build_dir, src_dir, duplicate=1):
-    SCons.Node.FS.default_fs.BuildDir(build_dir, src_dir, duplicate)
+            name = kw.get('name', 'SConscript')
 
-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
+            files = [os.path.join(n, name) for n in dirs]
 
-def FindFile(file, dirs):
-    nodes = SCons.Node.arg2nodes(dirs, SCons.Node.FS.default_fs.Dir)
-    return SCons.Node.FS.find_file(file, nodes)
+        elif len(ls) == 1:
 
-def Export(*vars):
-    for var in vars:
-        global_exports.update(compute_exports(var))
-
-def Import(*vars):
-    try:
-        for var in vars:
-            var = SCons.Util.argmunge(var)
-            for v in var:
-                if v == '*':
-                    stack[-1].globals.update(global_exports)
-                    stack[-1].globals.update(stack[-1].exports)
-                else:
-                    if stack[-1].exports.has_key(v):
-                        stack[-1].globals[v] = stack[-1].exports[v]
-                    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):
-    SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
-                        "The SetBuildSignatureType() function has been deprecated;\n" +\
-                        "\tuse the TargetSignatures() function instead.")
-    TargetSignatures(type)
-
-def TargetSignatures(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):
-    SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
-                        "The SetContentSignatureType() function has been deprecated;\n" +\
-                        "\tuse the SourceSignatures() function instead.")
-    SourceSignatures(type)
-
-def SourceSignatures(type):
-    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 = ls[0]
 
+        elif len(ls) == 2:
 
-class Options(SCons.Options.Options):
-    def __init__(self, files=None, args=arguments):
-        SCons.Options.Options.__init__(self, files, args)
+            files   = ls[0]
+            exports = self.Split(ls[1])
 
-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 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)
-
-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 GetJobs():
-    SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
-                        "The GetJobs() function has been deprecated;\n" +\
-                        "\tuse GetOption('num_jobs') instead.")
-
-    return GetOption('num_jobs')
-def SetJobs(num):
-    SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
-                        "The SetJobs() function has been deprecated;\n" +\
-                        "\tuse SetOption('num_jobs', num) instead.")
-    SetOption('num_jobs', num)
-
-def Clean(target, files):
-    if not isinstance(target, SCons.Node.Node):
-        target = SCons.Node.FS.default_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(SCons.Node.arg2nodes(f, SCons.Node.FS.default_fs.Entry))
 
-    try:
-        clean_targets[target].extend(nodes)
-    except KeyError:
-        clean_targets[target] = nodes
+            raise SCons.Errors.UserError, \
+                  "Invalid SConscript() usage - too many arguments"
+
+        if not SCons.Util.is_List(files):
+            files = [ files ]
 
-def AddPreAction(files, action):
-    nodes = SCons.Node.arg2nodes(files, SCons.Node.FS.default_fs.Entry)
-    for n in nodes:
-        n.add_pre_action(SCons.Action.Action(action))
+        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:
+                    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 AddPostAction(files, action):
-    nodes = SCons.Node.arg2nodes(files, SCons.Node.FS.default_fs.Entry)
-    for n in nodes:
-        n.add_post_action(SCons.Action.Action(action))
+    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)
 
-def Exit(value=0):
-    sys.exit(value)
+    def GetLaunchDir(self):
+        global launch_dir
+        return launch_dir
 
+    def GetOption(self, name):
+        name = self.subst(name)
+        return SCons.Script.Main.GetOption(name)
 
-def Alias(name):
-    alias = SCons.Node.Alias.default_ans.lookup(name)
-    if alias is None:
-        alias = SCons.Node.Alias.default_ans.Alias(name)
-    return alias
+    def Help(self, text):
+        text = self.subst(text, raw=1)
+        SCons.Script.HelpFunction(text)
 
-def SetOption(name, value):
-    SCons.Script.ssoptions.set(name, value)
+    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)
+
+#
+#
+#
+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 GetOption(name):
-    return SCons.Script.ssoptions.get(name)
 
 def BuildDefaultGlobals():
     """
-    Create a dictionary containing all the default globals for 
+    Create a dictionary containing all the default globals for
     SConstruct and SConscript files.
     """
 
-    globals = {}
-    globals['Action']            = SCons.Action.Action
-    globals['AddPostAction']     = AddPostAction
-    globals['AddPreAction']      = AddPreAction
-    globals['Alias']             = Alias
-    globals['ARGUMENTS']         = arguments
-    globals['BuildDir']          = BuildDir
-    globals['Builder']           = SCons.Builder.Builder
-    globals['CacheDir']          = SCons.Node.FS.default_fs.CacheDir
-    globals['Clean']             = Clean
-    globals['Configure']         = SCons.SConf.SConf
-    globals['CScan']             = SCons.Defaults.CScan
-    globals['Default']           = Default
-    globals['DefaultEnvironment'] = SCons.Defaults.DefaultEnvironment
-    globals['Dir']               = SCons.Node.FS.default_fs.Dir
-    globals['EnsurePythonVersion'] = EnsurePythonVersion
-    globals['EnsureSConsVersion'] = EnsureSConsVersion
-    globals['Environment']       = SCons.Environment.Environment
-    globals['Exit']              = Exit
-    globals['Export']            = Export
-    globals['File']              = SCons.Node.FS.default_fs.File
-    globals['FindFile']          = FindFile
-    globals['GetBuildPath']      = GetBuildPath
-    globals['GetCommandHandler'] = SCons.Action.GetCommandHandler
-    globals['GetJobs']           = GetJobs
-    globals['GetLaunchDir']      = GetLaunchDir
-    globals['GetOption']         = GetOption    
-    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['ParseConfig']       = SCons.Util.ParseConfig
-    globals['Platform']          = SCons.Platform.Platform
-    globals['Program']           = SCons.Defaults.Program
-    globals['Repository']        = SCons.Node.FS.default_fs.Repository
-    globals['Return']            = Return
-    globals['SConscript']        = SConscript
-    globals['SConscriptChdir']   = SConscriptChdir
-    globals['Scanner']           = SCons.Scanner.Base
-    globals['SetBuildSignatureType'] = SetBuildSignatureType
-    globals['SetCommandHandler'] = SCons.Action.SetCommandHandler
-    globals['SetContentSignatureType'] = SetContentSignatureType
-    globals['SetJobs']           = SetJobs
-    globals['SetOption']         = SetOption
-    globals['SharedLibrary']     = SCons.Defaults.SharedLibrary
-    globals['SharedObject']      = SCons.Defaults.SharedObject
-    globals['SourceSignatures']  = SourceSignatures
-    globals['Split']             = SCons.Util.Split
-    globals['StaticLibrary']     = SCons.Defaults.StaticLibrary
-    globals['StaticObject']      = SCons.Defaults.StaticObject
-    globals['TargetSignatures']  = TargetSignatures
-    globals['Tool']              = SCons.Tool.Tool
-    globals['Value']             = SCons.Node.Python.Value
-    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: