From f4ce0ffc77b72655fc909eb7d0cdf278a91c6c10 Mon Sep 17 00:00:00 2001 From: stevenknight Date: Thu, 20 Dec 2001 04:40:12 +0000 Subject: [PATCH] Fix Export(), add Import() and Return() git-svn-id: http://scons.tigris.org/svn/scons/trunk@166 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- doc/man/scons.1 | 127 +++++++++++++++++++- src/engine/MANIFEST.in | 1 + src/engine/SCons/SConscript.py | 177 ++++++++++++++++++++++++++++ src/engine/SCons/SConscriptTests.py | 28 +++++ src/engine/SCons/Script.py | 92 +++------------ test/CPPPATH.py | 3 +- test/Depends.py | 3 +- test/SConscript.py | 91 +++++++++++++- test/actions.py | 2 + test/errors.py | 12 +- 10 files changed, 453 insertions(+), 83 deletions(-) create mode 100644 src/engine/SCons/SConscript.py create mode 100644 src/engine/SCons/SConscriptTests.py diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 51ad838e..5b7b985e 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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 , et. al. +.SH AUTHORS +Steven Knight +.RS +.RE +Anthony Roach diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index c22aebc4..ad38b185 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -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 index 00000000..1479c7ac --- /dev/null +++ b/src/engine/SCons/SConscript.py @@ -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 index 00000000..d44af5c6 --- /dev/null +++ b/src/engine/SCons/SConscriptTests.py @@ -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 diff --git a/src/engine/SCons/Script.py b/src/engine/SCons/Script.py index 1298a9e4..e35e03d6 100644 --- a/src/engine/SCons/Script.py +++ b/src/engine/SCons/Script.py @@ -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): diff --git a/test/CPPPATH.py b/test/CPPPATH.py index aa750194..fb21426e 100644 --- a/test/CPPPATH.py +++ b/test/CPPPATH.py @@ -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') """) diff --git a/test/Depends.py b/test/Depends.py index ab045aeb..1ccf7f4b 100644 --- a/test/Depends.py +++ b/test/Depends.py @@ -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') """) diff --git a/test/SConscript.py b/test/SConscript.py index b463f7ee..51720402 100644 --- a/test/SConscript.py +++ b/test/SConscript.py @@ -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)) diff --git a/test/actions.py b/test/actions.py index 9ba689b9..fca14bdf 100644 --- a/test/actions.py +++ b/test/actions.py @@ -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' diff --git a/test/errors.py b/test/errors.py index 5edd8f92..cb2248da 100644 --- a/test/errors.py +++ b/test/errors.py @@ -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 -- 2.26.2