It is rare that all of the software in a large,
complicated system needs to be built the same way.
For example, different source files may need different options
enabled on the command line,
or different executable programs need to be linked
with different libraries.
SCons accomodates these different build
requirements by allowing you to create and
configure multiple construction environments
that control how the software is built.
Technically, a construction environment is an object
that has a number of associated
construction variables, each with a name and a value.
(A construction environment also has an attached
about which we'll learn more later.)
A construction environment is created by the Environment method:
env = Environment()
By default, SCons intializes every new construction environment with a set of construction variables based on the tools that it finds on your system, plus the default set of builder methods necessary for using those tools. The construction variables are initialized with values describing the C compiler, the Fortran compiler, the linker, etc., as well as the command lines to invoke them.
When you initialize a construction environment you can set the values of the environment's construction variables to control how a program is built. For example:
env = Environment(CC = 'gcc', CCFLAGS = '-O2') env.Program('foo.c')
The construction environment in this example is still initialized with the same default construction variable values, except that the user has explicitly specified use of the GNU C compiler gcc, and further specifies that the -O2 (optimization level two) flag should be used when compiling the object file. In other words, the explicit initializations of $CC and $CCFLAGS override the default values in the newly-created construction environment. So a run from this example would look like:
% scons -Q gcc -o foo.o -c -O2 foo.c gcc -o foo foo.o
The real advantage of construction environments is that you can create as many different construction environments as you need, each tailored to a different way to build some piece of software or other file. If, for example, we need to build one program with the -O2 flag and another with the -g (debug) flag, we would do this like so:
opt = Environment(CCFLAGS = '-O2') dbg = Environment(CCFLAGS = '-g') opt.Program('foo', 'foo.c') dbg.Program('bar', 'bar.c')
% scons -Q cc -o bar.o -c -g bar.c cc -o bar bar.o cc -o foo.o -c -O2 foo.c cc -o foo foo.o
We can even use multiple construction environments to build multiple versions of a single program. If you do this by simply trying to use the Program builder with both environments, though, like this:
opt = Environment(CCFLAGS = '-O2') dbg = Environment(CCFLAGS = '-g') opt.Program('foo', 'foo.c') dbg.Program('foo', 'foo.c')
Then SCons generates the following error:
% scons -Q scons: *** Two environments with different actions were specified for the same target: foo.o File "/home/my/project/SConstruct", line 6, in ?
This is because the two Program calls have each implicitly told SCons to generate an object file named foo.o, one with a $CCFLAGS value of -O2 and one with a $CCFLAGS value of -g. SCons can't just decide that one of them should take precedence over the other, so it generates the error. To avoid this problem, we must explicitly specify that each environment compile foo.c to a separately-named object file using the Object builder, like so:
opt = Environment(CCFLAGS = '-O2') dbg = Environment(CCFLAGS = '-g') o = opt.Object('foo-opt', 'foo.c') opt.Program(o) d = dbg.Object('foo-dbg', 'foo.c') dbg.Program(d)
Notice that each call to the Object builder returns a value, an internal SCons object that represents the object file that will be built. We then use that object as input to the Program builder. This avoids having to specify explicitly the object file name in multiple places, and makes for a compact, readable SConstruct file. Our SCons output then looks like:
% scons -Q cc -o foo-dbg.o -c -g foo.c cc -o foo-dbg foo-dbg.o cc -o foo-opt.o -c -O2 foo.c cc -o foo-opt foo-opt.o