%scons; %builders-mod; %functions-mod; %tools-mod; %variables-mod; ]> Separating Source and Build Trees: Variant Directories It is often useful to keep built files completely separate from the source files. Two main benefits are the ability to have different configurations simultaneously without build conflicts, and being version-control friendly. Consider if you have a project to build an embedded software system for a variety of different controller hardware. The system is able to share a lot of code, so it makes sense to use a common source tree, but certain build options in the source code and header files differ. For a regular in-place build, the build outputs go in the same place as the source code. If you build Controller A first, followed by Controller B, on the Controller B build everything that uses different build options has to be rebuilt since those objects will be different (the build lines, including preprocessor defines, are part of &SCons;'s out-of-date calculation for this reason). If you go back and build for Controller A again, things have to be rebuilt again for the same reason. However, if you can separate the locations of the output files, so each controller has its own location for build outputs, this problem can be avoided. Having a separated build tree also helps you keep your source tree clean - there is less chance of accidentally checking in build products to version control that were not intended to be checked in. You can add a separated build directory to your version control system's list of items not to track. You can even remove the whole build tree with a single command without risking removing any of the source code. The key to making this separation work is the ability to do out-of-tree builds: building under a separate root than the sources being built. You set up out-of-tree builds by establishing what &SCons; calls a variant directory, a place where you can build a single variant of your software (of course you can define more than one of these if you need to). Since &SCons; tracks targets by their path, it is able to distinguish build products like build/A/network.obj of the Controller A build from build/B/network.obj of the Controller B build, thus avoiding conflicts. &SCons; provides two ways to establish variant directories, one through the &f-link-SConscript; function that we have already seen, and the second through a more flexible &f-link-VariantDir; function. The variant directory mechanism does support doing multiple builds in one invocation of &SCons;, but the remainder of this chapter will focus on setting up a single build. You can combine these techniques with ones from the previous chapter and elsewhere in this Guide to set up more complex scenarios. The &VariantDir; function used to be called &BuildDir;, a name which was changed because it turned out to be confusing: the &SCons; functionality differs from a familiar model of a "build directory" implemented by certain other build systems like GNU Autotools. You might still find references to the old name on the Internet in postings about &SCons;, but it no longer works.
Specifying a Variant Directory Tree as Part of an &SConscript; Call The most straightforward way to establish a variant directory tree relies on the fact that the usual way to set up a build hierarchy is to have an &SConscript; file in the source directory. If you pass a &variant_dir; argument to the &f-link-SConscript; function call: SConscript('src/SConscript', variant_dir='build') env = Environment() env.Program('hello.c') int main() { printf("Hello, world!\n"); } &SCons; will then build all of the files in the &build; directory: ls src scons -Q ls src ls build No files were built in &src;: the object file build/hello.o and the executable file build/hello were built in the &build; directory, as expected. But notice that even though our &hello_c; file actually lives in the &src; directory, &SCons; has compiled a build/hello.c file to create the object file, and that file is now seen in &build;. You can ask &SCons; to show the dependency tree to illustrate a bit more: scons -Q --tree=prune What's happened is that &SCons; has duplicated the &hello_c; file from the &src; directory to the &build; directory, and built the program from there (it also duplicated &SConscript;). The next section explains why &SCons; does this. The nice thing about the &SConscript; approach is it is almost invisible to you: this build looks just like an ordinary in-place build except for the extra &variant_dir; argument in the &f-link-SConscript; call. &SCons; handles all the path adjustments for the out-of-tree &build; directory while it processes that SConscript file.
Why &SCons; Duplicates Source Files in a Variant Directory Tree When you set up a variant directory, &SCons; conceptually behaves as if you requested a build in that directory. As noted in the previous chapter, all builds actually happen from the top level directory, but as an aid to understanding how &SCons; operates, think of it as build in place in the variant directory, not build in source but send build artifacts to the variant directory. It turns out in place builds are easier to get right than out-of-tree builds - so by default &SCons; simulates an in place build by making the variant directory look just like the source directory. The most straightforward way to do that is by making copies of the files needed for the build. The most direct reason to duplicate source files in variant directories is simply that some tools (mostly older versions) are written to only build their output files in the same directory as the source files - such tools often don't have any option to specify the output file, and the tool just uses a predefined output file name, or uses a derived variant of the source file name, dropping the result in the same directory. In this case, the choices are either to build the output file in the source directory and move it to the variant directory, or to duplicate the source files in the variant directory. Additionally, relative references between files can cause problems which are resolved by just duplicating the hierarchy of source files into the variant directory. You can see this at work in use of the C preprocessor #include mechanism with double quotes, not angle brackets: #include "file.h" The de facto standard behavior for most C compilers in this case is to first look in the same directory as the source file that contains the #include line, then to look in the directories in the preprocessor search path. Add to this that the &SCons; implementation of support for code repositories (described below) means not all of the files will be found in the same directory hierarchy, and the simplest way to make sure that the right include file is found is to duplicate the source files into the variant directory, which provides a correct build regardless of the original location(s) of the source files. Although source-file duplication guarantees a correct build even in these edge cases, it can usually be safely disabled. The next section describes how you can disable the duplication of source files in the variant directory.
Telling &SCons; to Not Duplicate Source Files in the Variant Directory Tree In most cases and with most tool sets, &SCons; can use sources directly from the source directory without duplicating them into the variant directory before building, and everything will work just fine. You can disable the default &SCons; duplication behavior by specifying duplicate=False when you call the &f-link-SConscript; function: SConscript('src/SConscript', variant_dir='build', duplicate=False) When this flag is specified, the results of a build look more like the mental model people may have from other build systems - that is, the output files end up in the variant directory while the source files do not. % ls src SConscript hello.c % scons -Q cc -c src/hello.c -o build/hello.o cc -o build/hello build/hello.o % ls build hello hello.o If disabling duplication causes any problems, just return to the more cautious approach by letting &SCons; go back to duplicating files.
The &VariantDir; Function You can also use the &f-link-VariantDir; function to establish that target files should be built in a separate directory tree from the source files: VariantDir('build', 'src') env = Environment() env.Program('build/hello.c') int main() { printf("Hello, world!\n"); } When using this form, you have to tell &SCons; that sources and targets are in the variant directory, and those references will trigger the remapping, necessary file copying, etc. for an already established variant directory. Here is the same example in a more spelled out form to show this more clearly: VariantDir('build', 'src') env = Environment() env.Program(target='build/hello', source=['build/hello.c']) When using the &VariantDir; function directly, &SCons; still duplicates the source files in the variant directory by default: ls src scons -Q ls build You can specify the same duplicate=False argument that you can specify for an &f-link-SConscript; call: VariantDir('build', 'src', duplicate=False) env = Environment() env.Program('build/hello.c') int main() { printf("Hello, world!\n"); } In which case &SCons; will disable duplication of the source files: ls src scons -Q ls build
Using &VariantDir; With an &SConscript; File Even when using the &f-link-VariantDir; function, it is more natural to use it with a subsidiary &SConscript; file, because then you don't have to adjust your individual build instructions to use the variant directory path. For example, if the src/SConscript looks like this: VariantDir('build', 'src') SConscript('build/SConscript') env = Environment() env.Program('hello.c') int main() { printf("Hello, world!\n"); } Then our &SConstruct; file could look like: Yielding the following output: ls src scons -Q ls build This is completely equivalent to the use of &f-link-SConscript; with the variant_dir argument from earlier in this chapter, but did require calling the SConscript using the already established variant directory path to trigger that behavior. If you call SConscript('src/SConscript') you would get a normal in-place build in &src;.
Using &Glob; with &VariantDir; The &f-link-Glob; file name pattern matching function works just as usual when using &f-link-VariantDir;. For example, if the src/SConscript looks like this: VariantDir('build', 'src') SConscript('build/SConscript') env = Environment() env.Program('hello', Glob('*.c')) #include "f2.h" int main() { printf(f2()); } const char * f2() { return("Hello, world!\n"); } const char * f2(); Then with the same &SConstruct; file as in the previous section, and source files f1.c and f2.c in src, we would see the following output: ls src scons -Q ls build The &Glob; function returns Nodes in the build/ tree, as you'd expect.