From 6cc65ff3e32487eaa455776111d26a5bf07fb862 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Mon, 10 Feb 2003 07:33:07 +0000 Subject: Support more intuitive build directory specifications as arguments to SConscript(). --- doc/man/scons.1 | 130 ++++++++++++++++++++--- src/CHANGES.txt | 3 + src/engine/SCons/Script/SConscript.py | 22 +++- test/SConscript-build_dir.py | 188 ++++++++++++++++++++++++++++++++++ 4 files changed, 322 insertions(+), 21 deletions(-) create mode 100644 test/SConscript-build_dir.py diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 257dbd6..19237dc 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -2399,10 +2399,10 @@ can be converted into an Action object .TP .RI BuildDir( build_dir ", " src_dir ", [" duplicate ]) -This specifies a build directory to use for all derived files. +This specifies a build directory .I build_dir -specifies the build directory to be used for all derived files that would -normally be built under +in which to build 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. @@ -2413,20 +2413,80 @@ and may not be underneath the .I src_dir . +The default behavior is for .B scons -will link or copy (depending on the platform) all the source files into the -build directory if -.I duplicate -is set to 1 (the default). If +to duplicate all of the files in the tree underneath +.I src_dir +into +.IR build_dir , +and then build the derived files within the copied tree. +(The duplication is performed by +linking or copying, +depending on the platform.) +This guarantees correct builds +regardless of whether intermediate source files +are generated during the build, +where preprocessors or other scanners search +for included files, +or whether individual compilers or other invoked tools +are hard-coded to put derived files in the same directory as source files. + +This behavior of making a complete copy of the source tree +may be disabled by setting .I duplicate -is set to 0, then -.B scons -will not copy or link any source files, which may cause build problems in -certain situations (e.g. C source files that are generated by the -build). -.IR duplicate =0 -is usually safe, and is always more efficient than -.IR duplicate =1. +to 0. +This will cause +.B scons +to invoke Builders using the +path names of source files in +.I src_dir +and the path names of derived files within +.IR build_dir . +This is always more efficient than +.IR duplicate =1, +and is usually safe for most builds. +Specifying +.IR duplicate =0, +however, +may cause build problems +if source files are generated during the build, +if any invoked tools are hard-coded to +put derived files in the same directory as the source files. + +Note that specifying a +.B BuildDir +works most naturally +with a subsidiary SConscript file +in the source directory. +However, +you would then call the subsidiary SConscript file +not in the source directory, +but in the +.I build_dir , +as if +.B scons +had made a virtual copy of the source tree +regardless of the value of +.IR duplicate . +This is how you tell +.B scons +which variant of a source tree to build. +For example: + +.ES +BuildDir('build-variant1', 'src') +SConscript('build-variant1/SConscript') +BuildDir('build-variant2', 'src') +SConscript('build-variant2/SConscript') +.EE + +See also the +.BR SConscript () +function, described below, +for another way to +specify a build directory +in conjunction with calling a subsidiary +SConscript file.) .TP .RI AddPostAction ( target, action ) @@ -2820,7 +2880,7 @@ Return(["foo", "bar"]) .EE .TP -.RI SConscript( script ", [" exports ]) +.RI SConscript( script ", [" exports ", " build_dir ", " duplicate ]) This tells .B scons to execute @@ -2832,17 +2892,53 @@ argument provides a list of variable names to export to .I script must use the .BR Import () -function to import the variables. Any variables returned by +function to import the variables. + +The optional +.I build_dir +argument specifies that all of the target files +(for example, object files and executables) +that would normally be built in the subdirectory in which +.I script +resides should actually +be built in +.IR build_dir . +By default, +.B scons +will link or copy (depending on the platform) +all the source files into the build directory. +This behavior may be disabled by +setting the optional +.I duplicate +argument to 0 +(it is set to 1 by default), +in which case +.B scons +will refer directly to +the source files in their source directory +when building target files. +(Setting +.IR duplicate =0 +is usually safe, and always more efficient +than the default of +.IR duplicate =1, +but it may cause build problems in certain end-cases, +such as compiling from source files that +are generated by the build. + +Any variables returned by .I script using .BR Return () will be returned by the call to .BR SConscript (). + Examples: .ES SConscript('dir/SConscript') foo = SConscript('subdir/SConscript', "env") +SConscript('src/SConscript', build_dir='build', duplicate=0) .EE .TP diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 14ec5e2..068dc3d 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -80,6 +80,9 @@ RELEASE 0.11 - XXX - Add support for allowing an embedding interface to annotate a node when it's created. + - Extend the SConscript() function to accept build_dir and duplicate + keyword arguments that function like a BuildDir() call. + From Steve Leblanc: - Fix the output of -c -n when directories are involved, so it diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index a8aa732..c94ff2c 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -112,9 +112,9 @@ def Return(*vars): 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 +# 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): @@ -147,12 +147,26 @@ def GetSConscriptFilenames(ls, kw): exports = SCons.Util.argmunge(ls[1]) if kw.get('exports'): - exports.extend(SCons.Util.argmunge(kw.get('exports'))) + exports.extend(SCons.Util.argmunge(kw['exports'])) 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: + fname = os.path.split(files[0])[1] + BuildDir(build_dir, src_dir, duplicate) + files = [os.path.join(str(build_dir), fname)] + return (files, exports) def SConscript(*ls, **kw): diff --git a/test/SConscript-build_dir.py b/test/SConscript-build_dir.py new file mode 100644 index 0000000..c0b92f3 --- /dev/null +++ b/test/SConscript-build_dir.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# 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__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that specifying a build_dir argument to SConscript works properly. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +all1 = test.workpath('test', 'build', 'var1', 'all') +all2 = test.workpath('test', 'build', 'var2', 'all') +all3 = test.workpath('test', 'build', 'var3', 'all') +all4 = test.workpath('test', 'build', 'var4', 'all') +all5 = test.workpath('build', 'var5', 'all') +all6 = test.workpath('build', 'var6', 'all') +all7 = test.workpath('build', 'var7', 'all') +all8 = test.workpath('build', 'var8', 'all') + +test.subdir('test') + +test.write('test/SConstruct', """ +src = Dir('src') +alt = Dir('alt') +var1 = Dir('build/var1') +var2 = Dir('build/var2') +var3 = Dir('build/var3') +var4 = Dir('build/var4') +var5 = Dir('../build/var5') +var6 = Dir('../build/var6') +var7 = Dir('../build/var7') +var8 = Dir('../build/var8') + +def cat(env, source, target): + target = str(target[0]) + source = map(str, source) + f = open(target, "wb") + for src in source: + f.write(open(src, "rb").read()) + f.close() + +env = Environment(BUILDERS={'Cat':Builder(action=cat)}) + +Export("env") + +SConscript('src/SConscript', build_dir=var1) +SConscript('src/SConscript', build_dir='build/var2', src_dir=src) + +SConscript('src/SConscript', build_dir='build/var3', duplicate=0) + +#XXX We can't support var4 and var5 yet, because our BuildDir linkage +#XXX is to an entire source directory. We haven't yet generalized our +#XXX infrastructure to be able to take the SConscript file from one source +#XXX directory, but the rest of the files from a different one. +#XXX SConscript('src/SConscript', build_dir=var4, src_dir=alt, duplicate=0) + +#XXX SConscript('src/SConscript', build_dir='../build/var5', src_dir='alt') +SConscript('src/SConscript', build_dir=var6) + +SConscript('src/SConscript', build_dir=var7, src_dir=src, duplicate=0) +SConscript('src/SConscript', build_dir='../build/var8', duplicate=0) +""") + +test.subdir(['test', 'src'], ['test', 'alt']) + +test.write(['test', 'src', 'SConscript'], """ +Import("env") +env.Cat('aaa.out', 'aaa.in') +env.Cat('bbb.out', 'bbb.in') +env.Cat('ccc.out', 'ccc.in') +env.Cat('all', ['aaa.out', 'bbb.out', 'ccc.out']) +""") + +test.write('test/src/aaa.in', "test/src/aaa.in\n") +test.write('test/src/bbb.in', "test/src/bbb.in\n") +test.write('test/src/ccc.in', "test/src/ccc.in\n") + +test.write('test/alt/aaa.in', "test/alt/aaa.in\n") +test.write('test/alt/bbb.in', "test/alt/bbb.in\n") +test.write('test/alt/ccc.in', "test/alt/ccc.in\n") + +test.run(chdir='test', arguments = '. ../build') + +all_src = "test/src/aaa.in\ntest/src/bbb.in\ntest/src/ccc.in\n" +all_alt = "test/alt/aaa.in\ntest/alt/bbb.in\ntest/alt/ccc.in\n" + +test.fail_test(test.read(all1) != all_src) +test.fail_test(test.read(all2) != all_src) +test.fail_test(test.read(all3) != all_src) +#XXX We can't support var4 and var5 yet, because our BuildDir linkage +#XXX is to an entire source directory. We haven't yet generalized our +#XXX infrastructure to be able to take the SConscript file from one source +#XXX directory, but the rest of the files from a different one. +#XXX test.fail_test(test.read(all4) != all_alt) +#XXX test.fail_test(test.read(all5) != all_alt) +test.fail_test(test.read(all6) != all_src) +test.fail_test(test.read(all7) != all_src) +test.fail_test(test.read(all8) != all_src) + +import os +import stat +def equal_stats(x,y): + x = os.stat(x) + y = os.stat(y) + return (stat.S_IMODE(x[stat.ST_MODE]) == stat.S_IMODE(y[stat.ST_MODE]) and + x[stat.ST_MTIME] == y[stat.ST_MTIME]) + +# Make sure we did duplicate the source files in build/var1, +# and that their stats are the same: +for file in ['aaa.in', 'bbb.in', 'ccc.in']: + test.fail_test(not os.path.exists(test.workpath('test', 'build', 'var1', file))) + test.fail_test(not equal_stats(test.workpath('test', 'build', 'var1', file), + test.workpath('test', 'src', file))) + +# Make sure we did duplicate the source files in build/var2, +# and that their stats are the same: +for file in ['aaa.in', 'bbb.in', 'ccc.in']: + test.fail_test(not os.path.exists(test.workpath('test', 'build', 'var2', file))) + test.fail_test(not equal_stats(test.workpath('test', 'build', 'var2', file), + test.workpath('test', 'src', file))) + +# Make sure we didn't duplicate the source files in build/var3. +test.fail_test(os.path.exists(test.workpath('test', 'build', 'var3', 'aaa.in'))) +test.fail_test(os.path.exists(test.workpath('test', 'build', 'var3', 'bbb.in'))) +test.fail_test(os.path.exists(test.workpath('test', 'build', 'var3', 'ccc.in'))) + +#XXX We can't support var4 and var5 yet, because our BuildDir linkage +#XXX is to an entire source directory. We haven't yet generalized our +#XXX infrastructure to be able to take the SConscript file from one source +#XXX directory, but the rest of the files from a different one. +#XXX Make sure we didn't duplicate the source files in build/var4. +#XXXtest.fail_test(os.path.exists(test.workpath('test', 'build', 'var4', 'aaa.in'))) +#XXXtest.fail_test(os.path.exists(test.workpath('test', 'build', 'var4', 'bbb.in'))) +#XXXtest.fail_test(os.path.exists(test.workpath('test', 'build', 'var4', 'ccc.in'))) + +#XXX We can't support var4 and var5 yet, because our BuildDir linkage +#XXX is to an entire source directory. We haven't yet generalized our +#XXX infrastructure to be able to take the SConscript file from one source +#XXX directory, but the rest of the files from a different one. +#XXX Make sure we did duplicate the source files in build/var5, +#XXX and that their stats are the same: +#XXXfor file in ['aaa.in', 'bbb.in', 'ccc.in']: +#XXX test.fail_test(not os.path.exists(test.workpath('build', 'var5', file))) +#XXX test.fail_test(not equal_stats(test.workpath('build', 'var5', file), +#XXX test.workpath('test', 'src', file))) + +# Make sure we did duplicate the source files in build/var6, +# and that their stats are the same: +for file in ['aaa.in', 'bbb.in', 'ccc.in']: + test.fail_test(not os.path.exists(test.workpath('build', 'var6', file))) + test.fail_test(not equal_stats(test.workpath('build', 'var6', file), + test.workpath('test', 'src', file))) + +# Make sure we didn't duplicate the source files in build/var7. +test.fail_test(os.path.exists(test.workpath('build', 'var7', 'aaa.in'))) +test.fail_test(os.path.exists(test.workpath('build', 'var7', 'bbb.in'))) +test.fail_test(os.path.exists(test.workpath('build', 'var7', 'ccc.in'))) + +# Make sure we didn't duplicate the source files in build/var8. +test.fail_test(os.path.exists(test.workpath('build', 'var8', 'aaa.in'))) +test.fail_test(os.path.exists(test.workpath('build', 'var8', 'bbb.in'))) +test.fail_test(os.path.exists(test.workpath('build', 'var8', 'ccc.in'))) + +test.pass_test() -- cgit v0.12