Fix Export(), add Import() and Return()
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 20 Dec 2001 04:40:12 +0000 (04:40 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 20 Dec 2001 04:40:12 +0000 (04:40 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@166 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/engine/MANIFEST.in
src/engine/SCons/SConscript.py [new file with mode: 0644]
src/engine/SCons/SConscriptTests.py [new file with mode: 0644]
src/engine/SCons/Script.py
test/CPPPATH.py
test/Depends.py
test/SConscript.py
test/actions.py
test/errors.py

index 51ad838eee0e4469804f334f991353574a970efa..5b7b985efb11185b2c13575862d97c225bd968a2 100644 (file)
@@ -680,6 +680,126 @@ method:
 env2 = env.Copy(CC="cl.exe")
 .RE
 
+.B scons
+also provides various function not associated with a construction
+environment that configuration files can use to affect the build:
+
+.TP
+.RI SConscript( script ", [" exports ])
+This tells
+.B scons
+to execute 
+.I script
+as a configuration file. The optional 
+.I exports
+argument provides a list of variable names to export to
+.IR script ". " exports
+can also be a space delimited string of variables names. 
+.I script
+must use the
+.BR Import ()
+function to import the variables. Any variables returned by 
+.I script 
+using 
+.BR Return ()
+will be returned by the call to
+.BR SConscript (). 
+Example:
+
+.RS
+foo = SConscript('subdir/SConscript', "env")
+.RE
+
+.TP
+.RI Export( vars )
+This tells 
+.B scons
+to export a list of variables from the current
+configuration file to all other configuration files. The exported variables
+are kept in a global collection, so subsequent exports
+will over-write previous exports that have the same name. 
+Multiple variable names can be passed to
+.BR Export ()
+in a space delimited string or as seperate arguments. Example:
+
+.RS
+Export("env")
+.RE
+
+.TP 
+.RI Import( vars )
+This tells 
+.B scons
+to import a list of variables into the current configuration file. This
+will import variables that were exported with
+.BR Export ()
+or in the 
+.I exports
+argument to 
+.BR SConscript ().
+Variables exported by 
+.BR SConscript ()
+have precedence. Multiple variable names can be passed to 
+.BR Import ()
+in a space delimited string or as seperate arguments. Example:
+
+.RS
+Import("env")
+.RE
+
+.TP
+.RI Return( vars )
+This tells
+.B scons
+what variable(s) to use as the return value(s) of the current configuration
+file. These variables will be returned to the "calling" configuration file
+as the return value(s) of 
+.BR SConscript ().
+Multiple variable names can be passed to 
+.BR Return ()
+in a space delimited string or as seperate arguments. Example:
+
+.RS
+Return("foo")
+.RE
+
+.TP 
+.RI Default( targets )
+This specifies a list of default targets. Default targets will be built by
+.B scons
+if no explicit targets are given on the comamnd line. Multiple targets can
+be specified either as a space delimited string of target file names or as
+seperate arguments.
+.BR Default ()
+will also accept the return value of any of the ccnstruction environment
+builder methods.
+
+.TP
+.RI Help( text )
+This specifies help text to be printed if the 
+.B -h 
+argument is given to
+.BR scons .
+This function can be called multiple times to print multiple help messages.
+
+.TP
+.RI BuildDir( build_dir ", " src_dir )
+This specifies a build directory to use for all derived files.  
+.I build_dir
+specifies the build directory to be used for all derived files that would
+normally be built under
+.IR src_dir .
+Multiple build directories can be set up for multiple build variants, for
+example. 
+.B scons
+will link or copy (depending on the platform) all the source files into the
+build directory, so the build commands will not be modifed if 
+.BR BuildDir ()
+is used.
+
+
+
+
 .SH EXTENDING
 .B scons
 can be extended by adding new builders to a construction
@@ -767,6 +887,9 @@ Design Document,
 .B scons
 source code.
 
-.SH AUTHOR
-Steven Knight <knight@baldmt.com>, et. al.
+.SH AUTHORS
+Steven Knight <knight@baldmt.com>
+.RS
+.RE
+Anthony Roach <aroach@electriceyeball.com>
 
index c22aebc4561efedc387b4cbfa2d03c0a629c3df6..ad38b185cf8d3735488212497fd7a4e4b1fd9f28 100644 (file)
@@ -10,6 +10,7 @@ SCons/Node/FS.py
 SCons/Scanner/__init__.py
 SCons/Scanner/C.py
 SCons/Scanner/Prog.py
+SCons/SConscript.py
 SCons/Script.py
 SCons/Sig/__init__.py
 SCons/Sig/MD5.py
diff --git a/src/engine/SCons/SConscript.py b/src/engine/SCons/SConscript.py
new file mode 100644 (file)
index 0000000..1479c7a
--- /dev/null
@@ -0,0 +1,177 @@
+"""engine.SCons.SConscript
+
+This module defines the Python API provided to SConscript and SConstruct 
+files.
+
+"""
+
+#
+# Copyright (c) 2001 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "src/engine/SCons/SConscript.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Errors
+import SCons.Builder
+import SCons.Defaults
+import SCons.Node
+import SCons.Node.FS
+import SCons.Environment
+import string
+import sys
+
+default_targets = []
+help_option = None
+
+# global exports set by Export():
+global_exports = {}
+
+class Frame:
+    """A frame on the SConstruct/SConscript call stack"""
+    def __init__(self, exports):
+        self.globals = BuildDefaultGlobals()
+        self.retval = None 
+        self.prev_dir = SCons.Node.FS.default_fs.getcwd()
+        self.exports = {} # exports from the calling SConscript
+
+        try:
+            if type(exports) == type([]):
+                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
+
+        
+# the SConstruct/SConscript call stack:
+stack = []
+
+# For documentation on the methods in this file, see the scons man-page
+
+def Return(*vars):
+    retval = []
+    try:
+        for var in vars:
+            for v in string.split(var):
+                retval.append(stack[-1].globals[v])
+    except KeyError, x:
+        raise SCons.Errors.UserError, "Return of non-existant variable '%s'"%x
+        
+    if len(retval) == 1:
+        stack[-1].retval = retval[0]
+    else:
+        stack[-1].retval = tuple(retval)
+
+def SConscript(script, exports=[]):
+    retval = ()
+
+    # push:
+    stack.append(Frame(exports))
+
+    # call:
+    if script == "-":
+        exec sys.stdin in stack[-1].globals
+    else:
+        f = SCons.Node.FS.default_fs.File(script)
+        if f.exists():
+            file = open(str(f), "r")
+            SCons.Node.FS.default_fs.chdir(f.dir)
+            exec file in stack[-1].globals
+        else:
+            sys.stderr.write("Ignoring missing SConscript '%s'\n" % f.path)
+    
+
+    # pop:
+    frame = stack.pop()
+    SCons.Node.FS.default_fs.chdir(frame.prev_dir)
+    
+    return frame.retval
+    
+def Default(*targets):
+    for t in targets:
+        if isinstance(t, SCons.Node.Node):
+            default_targets.append(t)
+        else:
+            for s in string.split(t):
+                default_targets.append(s)
+
+def Help(text):
+    global help_option
+    if help_option == 'h':
+        print text
+        print "Use scons -H for help about command-line options."
+        sys.exit(0)
+
+def BuildDir(build_dir, src_dir):
+    SCons.Node.FS.default_fs.BuildDir(build_dir, src_dir)
+
+def GetBuildPath(files):
+    nodes = SCons.Util.scons_str2nodes(files,
+                                       SCons.Node.FS.default_fs.Entry)
+    ret = map(str, nodes)
+    if len(ret) == 1:
+        return ret[0]
+    return ret
+
+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
+
+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]
+                else:
+                    stack[-1].globals[v] = global_exports[v]
+    except KeyError,x:
+        raise SCons.Errors.UserError, "Import of non-existant variable '%s'"%x
+
+def BuildDefaultGlobals():
+    """
+    Create a dictionary containing all the default globals for 
+    SConscruct and SConscript files.
+    """
+
+    globals = {}
+    globals['Builder'] = SCons.Builder.Builder
+    globals['Environment'] = SCons.Environment.Environment
+    globals['Object'] = SCons.Defaults.Object
+    globals['Program'] = SCons.Defaults.Program
+    globals['Library'] = SCons.Defaults.Library
+    globals['CScan'] = SCons.Defaults.CScan
+    globals['SConscript'] = SConscript
+    globals['Default'] = Default
+    globals['Help'] = Help
+    globals['BuildDir'] = BuildDir
+    globals['GetBuildPath'] = GetBuildPath
+    globals['Export'] = Export
+    globals['Import'] = Import
+    globals['Return'] = Return
+    return globals
diff --git a/src/engine/SCons/SConscriptTests.py b/src/engine/SCons/SConscriptTests.py
new file mode 100644 (file)
index 0000000..d44af5c
--- /dev/null
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2001 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "src/engine/SCons/SConscriptTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.SConscript
+
+# all of the SConscript.py tests are in test/SConscript.py 
index 1298a9e4c1f271455344c422cf07c18fbae6b201..e35e03d6eb6a21550fa8dd9890fb88b8612cd65e 100644 (file)
@@ -58,15 +58,8 @@ from SCons.Errors import *
 import SCons.Sig
 import SCons.Sig.MD5
 from SCons.Taskmaster import Taskmaster
-import SCons.Util
-
-#
-# Modules and classes that we don't use directly in this script, but
-# which we want available for use in SConstruct and SConscript files.
-#
-from SCons.Environment import Environment
-from SCons.Builder import Builder
-from SCons.Defaults import *
+import SCons.Builder
+import SCons.SConscript
 
 
 #
@@ -104,9 +97,7 @@ class CleanTask(SCons.Taskmaster.Task):
 
 # Global variables
 
-default_targets = []
 include_dirs = []
-help_option = None
 num_jobs = 1
 scripts = []
 task_class = BuildTask # default action is to build targets
@@ -115,6 +106,7 @@ calc = None
 ignore_errors = 0
 keep_going_on_error = 0
 
+
 # utility functions
 
 def _scons_syntax_error(e):
@@ -161,40 +153,6 @@ def _scons_other_errors():
 
 
 
-def SConscript(sconscript, export={}):
-    global scripts
-    scripts.append( (SCons.Node.FS.default_fs.File(sconscript), export) )
-
-def Default(*targets):
-    for t in targets:
-       if isinstance(t, SCons.Node.Node):
-           default_targets.append(t)
-       else:
-           for s in string.split(t):
-               default_targets.append(s)
-
-def Help(text):
-    global help_option
-    if help_option == 'h':
-       print text
-       print "Use scons -H for help about command-line options."
-       sys.exit(0)
-
-def BuildDir(build_dir, src_dir):
-    SCons.Node.FS.default_fs.BuildDir(build_dir, src_dir)
-
-def GetBuildPath(files):
-    nodes = SCons.Util.scons_str2nodes(files,
-                                       SCons.Node.FS.default_fs.Entry)
-    ret = map(str, nodes)
-    if len(ret) == 1:
-        return ret[0]
-    return ret
-
-def Export(**kw):
-    # A convenient shorthand to pass exports to the SConscript function.
-    return kw
-
 #
 # After options are initialized, the following variables are
 # filled in:
@@ -392,26 +350,21 @@ def options_init():
 
     def opt_f(opt, arg):
        global scripts
-       if arg == '-':
-            scripts.append( ( arg, {} ) )
-       else:
-            scripts.append( (SCons.Node.FS.default_fs.File(arg), {}) )
+        scripts.append(arg)
 
     Option(func = opt_f,
        short = 'f', long = ['file', 'makefile', 'sconstruct'], arg = 'FILE',
        help = "Read FILE as the top-level SConstruct file.")
 
     def opt_help(opt, arg):
-       global help_option
-       help_option = 'h'
+        SCons.SConscript.help_option = 'h'
 
     Option(func = opt_help,
        short = 'h', long = ['help'],
        help = "Print defined help message, or this one.")
 
     def opt_help_options(opt, arg):
-       global help_option
-       help_option = 'H'
+        SCons.SConscript.help_option = 'H'
 
     Option(func = opt_help_options,
        short = 'H', long = ['help-options'],
@@ -584,7 +537,7 @@ def UsageString():
 
 
 def _main():
-    global scripts, help_option, num_jobs, task_class, calc
+    global scripts, num_jobs, task_class, calc
 
     targets = []
 
@@ -618,18 +571,18 @@ def _main():
     if not scripts:
         for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
             if os.path.isfile(file):
-                scripts.append( (SCons.Node.FS.default_fs.File(file), {}) )
+                scripts.append(file)
                 break
 
-    if help_option == 'H':
+    if SCons.SConscript.help_option == 'H':
        print UsageString()
        sys.exit(0)
 
     if not scripts:
-       if help_option == 'h':
-           # There's no SConstruct, but they specified either -h or
-           # -H.  Give them the options usage now, before we fail
-           # trying to read a non-existent SConstruct file.
+        if SCons.SConscript.help_option == 'h':
+            # There's no SConstruct, but they specified -h.
+            # Give them the options usage now, before we fail
+            # trying to read a non-existent SConstruct file.
            print UsageString()
            sys.exit(0)
        else:
@@ -637,30 +590,19 @@ def _main():
 
     sys.path = include_dirs + sys.path
 
-    while scripts:
-        f, exports = scripts.pop(0)
-        script_env = copy.copy(globals())
-        script_env.update(exports)
-        if f == "-":
-            exec sys.stdin in script_env
-       else:
-            if f.exists():
-                file = open(str(f), "r")
-                SCons.Node.FS.default_fs.chdir(f.dir)
-                exec file in script_env
-            else:
-                sys.stderr.write("Ignoring missing SConscript '%s'\n" % f.path)
+    for script in scripts:
+        SCons.SConscript.SConscript(script)
 
     SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Top)
 
-    if help_option == 'h':
+    if SCons.SConscript.help_option == 'h':
        # They specified -h, but there was no Help() inside the
        # SConscript files.  Give them the options usage.
        print UsageString()
        sys.exit(0)
 
     if not targets:
-       targets = default_targets
+        targets = SCons.SConscript.default_targets
        
     def Entry(x):
        if isinstance(x, SCons.Node.Node):
index aa750194b831119baad00db69ae52f2880840a9b..fb21426efbf442e400d4113c466c469a03113bb4 100644 (file)
@@ -46,10 +46,11 @@ test.write('SConstruct', """
 env = Environment(CPPPATH = ['include'])
 obj = env.Object(target='prog', source='subdir/prog.c')
 env.Program(target='prog', source=obj)
-SConscript('subdir/SConscript', Export(env=env))
+SConscript('subdir/SConscript', "env")
 """)
 
 test.write(['subdir', 'SConscript'], """
+Import("env")
 env.Program(target='prog', source='prog.c')
 """)
 
index ab045aeb8607526471923d89cf6321846441d1bf..1ccf7f4b76c67ccacb91d89a866e9164c912f5b0 100644 (file)
@@ -52,10 +52,11 @@ env.Depends(target = 'f3.out', dependency = 'subdir/bar.dep')
 env.Foo(target = 'f1.out', source = 'f1.in')
 env.Foo(target = 'f2.out', source = 'f2.in')
 env.Bar(target = 'f3.out', source = 'f3.in')
-SConscript('subdir/SConscript', Export(env=env))
+SConscript('subdir/SConscript', "env")
 """ % (python, python))
 
 test.write(['subdir', 'SConscript'], """
+Import("env")
 env.Depends(target = 'f4.out', dependency = 'bar.dep')
 env.Bar(target = 'f4.out', source = 'f4.in')
 """)
index b463f7ee00ef230c6e544892a47be16f20b049a8..51720402e9ce870473c9c95d238b5c0537d24202 100644 (file)
@@ -30,16 +30,105 @@ test = TestSCons.TestSCons()
 
 test.write('SConstruct', """
 import os
+
 print "SConstruct", os.getcwd()
 SConscript('SConscript')
+
+x1 = "SConstruct x1"
+x2 = "SConstruct x2"
+x3,x4 = SConscript('SConscript1', "x1 x2")
+assert x3 == "SConscript1 x3"
+assert x4 == "SConscript1 x4"
+
+
+(x3,x4) = SConscript('SConscript2', ["x1","x2"])
+assert x3 == "SConscript2 x3"
+assert x4 == "SConscript2 x4"
+
+Export("x1 x2")
+SConscript('SConscript3')
+Import("x1 x2")
+assert x1 == "SConscript3 x1"
+assert x2 == "SConscript3 x2"
+
+x1 = "SConstruct x1"
+x2 = "SConstruct x2"
+Export("x1","x2")
+SConscript('SConscript4')
+Import("x1"," x2")
+assert x1 == "SConscript4 x1"
+assert x2 == "SConscript4 x2"
+
 """)
 
-# XXX I THINK THEY SHOULD HAVE TO RE-IMPORT OS HERE
 test.write('SConscript', """
+
+# os should not be automajically imported:
+assert not globals().has_key("os")
+
 import os
 print "SConscript " + os.getcwd()
 """)
 
+test.write('SConscript1', """
+Import("x1 x2")
+assert x1 == "SConstruct x1"
+assert x2 == "SConstruct x2"
+
+x3 = "SConscript1 x3"
+x4 = "SConscript1 x4"
+Return("x3 x4")
+""")
+
+
+
+test.write('SConscript2', """
+Import("x1","x2")
+assert x1 == "SConstruct x1"
+assert x2 == "SConstruct x2"
+x3 = "SConscript2 x3"
+x4 = "SConscript2 x4"
+Return("x3","x4")
+""")
+
+test.write('SConscript3', """
+Import("x1 x2")
+assert x1 == "SConstruct x1"
+assert x2 == "SConstruct x2"
+x1 = "SConscript3 x1"
+x2 = "SConscript3 x2"
+
+x5 = SConscript('SConscript31', "x1")
+Import("x6")
+assert x5 == "SConscript31 x5"
+assert x6 == "SConscript31 x6"
+
+Export("x1 x2")
+
+
+""")
+
+test.write('SConscript31', """
+Import("x1 x2")
+assert x1 == "SConscript3 x1"
+assert x2 == "SConstruct x2"
+x5 = "SConscript31 x5"
+x6 = "SConscript31 x6"
+Export("x6")
+Return("x5")
+""")
+
+
+test.write('SConscript4', """
+Import("x1", "x2")
+assert x1 == "SConstruct x1"
+assert x2 == "SConstruct x2"
+x1 = "SConscript4 x1"
+x2 = "SConscript4 x2"
+Export("x1", "x2")
+""")
+
+
 wpath = test.workpath()
 
 test.run(stdout = "SConstruct %s\nSConscript %s\n" % (wpath, wpath))
index 9ba689b94387a452105c3d0f0d131420c652d583..fca14bdf4a2faea9b2d708d8a776440efb0cb781 100644 (file)
@@ -86,6 +86,8 @@ test.up_to_date(arguments = '.')
 
 test.write('SConstruct', """
 import os
+assert not globals().has_key('string')
+import string
 class bld:
     def __init__(self):
         self.cmd = r'%s build.py %%s 4 %%s'
index 5edd8f92c828a3b9e2fc92e6fc2a6fe6144a1ec8..cb2248da9b12fb48c427f1b94b26fe5ff72ff27b 100644 (file)
@@ -47,17 +47,21 @@ SyntaxError: invalid syntax
 
 
 test.write('SConstruct2', """
-raise UserError, 'Depends() require both sources and targets.'
+assert not globals().has_key("UserError")
+import SCons.Errors
+raise SCons.Errors.UserError, 'Depends() require both sources and targets.'
 """)
 
 test.run(arguments='-f SConstruct2',
         stdout = "",
         stderr = """
 SCons error: Depends\(\) require both sources and targets.
-File "SConstruct2", line 2, in \?
+File "SConstruct2", line 4, in \?
 """)
 
 test.write('SConstruct3', """
+assert not globals().has_key("InternalError")
+from SCons.Errors import InternalError
 raise InternalError, 'error inside'
 """)
 
@@ -67,7 +71,9 @@ test.run(arguments='-f SConstruct3',
   File ".*Script.py", line \d+, in main
     _main\(\)
   File ".*Script.py", line \d+, in _main
-    exec file in script_env
+    SCons.SConscript.SConscript\(script\)
+  File ".*SConscript.py", line \d+, in SConscript
+    exec file in stack\[-1\].globals
   File "SConstruct3", line \d+, in \?
     raise InternalError, 'error inside'
 InternalError: error inside