Support more intuitive build directory specifications as arguments to SConscript().
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 10 Feb 2003 07:33:07 +0000 (07:33 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 10 Feb 2003 07:33:07 +0000 (07:33 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@582 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Script/SConscript.py
test/SConscript-build_dir.py [new file with mode: 0644]

index 257dbd65d8dbd8619e6a46d13393472ae21c4139..19237dc25858a4e972002b95c33d26894b1f2e99 100644 (file)
@@ -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
index 14ec5e248ec804b80e4668c3b2dfb07ba73dd76f..068dc3d6e17990c8a47183cef7791d24d6e3d71b 100644 (file)
@@ -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
index a8aa732cc61f01b773a8be0ed7ec17e4d35b1d1d..c94ff2cf8fbdc9e3c72efa3b323fe81900a3c574 100644 (file)
@@ -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 (file)
index 0000000..c0b92f3
--- /dev/null
@@ -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()