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 &consenvs;
that control how the software is built.
Technically, a &consenv; is an object
that has a number of associated
&consvars;, each with a name and a value.
(A construction environment also has an attached
set of &Builder; methods,
about which we'll learn more later.)
A &consenv; is created by the &Environment; method:
env = Environment()
By default, &SCons; intializes every
new construction environment
with a set of &consvars;
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 &consvars;
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
&cv-link-CC; and &cv-link-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
Multiple &ConsEnvs;
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
&b-link-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 &b-Program; calls have
each implicitly told &SCons; to generate an object file named
foo.o,
one with a &cv-link-CCFLAGS; value of
-O2
and one with a &cv-link-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 &b-link-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 &b-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 &b-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
Copying &ConsEnvs;
Sometimes you want more than one construction environment
to share the same values for one or more variables.
Rather than always having to repeat all of the common
variables when you create each construction environment,
you can use the &Clone; method
to create a copy of a construction environment.
Like the &Environment; call that creates a construction environment,
the &Clone; method takes &consvar; assignments,
which will override the values in the copied construction environment.
For example, suppose we want to use &gcc;
to create three versions of a program,
one optimized, one debug, and one with neither.
We could do this by creating a "base" construction environment
that sets &cv-link-CC; to &gcc;,
and then creating two copies,
one which sets &cv-link-CCFLAGS; for optimization
and the other which sets &cv-CCFLAGS; for debugging:
env = Environment(CC = 'gcc')
opt = env.Clone(CCFLAGS = '-O2')
dbg = env.Clone(CCFLAGS = '-g')
env.Program('foo', 'foo.c')
o = opt.Object('foo-opt', 'foo.c')
opt.Program(o)
d = dbg.Object('foo-dbg', 'foo.c')
dbg.Program(d)
Then our output would look like:
% scons -Q
gcc -o foo.o -c foo.c
gcc -o foo foo.o
gcc -o foo-dbg.o -c -g foo.c
gcc -o foo-dbg foo-dbg.o
gcc -o foo-opt.o -c -O2 foo.c
gcc -o foo-opt foo-opt.o
Fetching Values From a &ConsEnv;
You can fetch individual construction variables
using the normal syntax
for accessing individual named items in a Python dictionary:
env = Environment()
print "CC is:", env['CC']
This example &SConstruct; file doesn't build anything,
but because it's actually a Python script,
it will print the value of &cv-link-CC; for us:
% scons -Q
CC is: cc
scons: `.' is up to date.
A construction environment, however,
is actually an object with associated methods, etc.
If you want to have direct access to only the
dictionary of construction variables,
you can fetch this using the &Dictionary; method:
env = Environment(FOO = 'foo', BAR = 'bar')
dict = env.Dictionary()
for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']:
print "key = %s, value = %s" % (key, dict[key])
This &SConstruct; file
will print the specified dictionary items for us on POSIX
systems as follows:
% scons -Q
key = OBJSUFFIX, value = .o
key = LIBSUFFIX, value = .a
key = PROGSUFFIX, value =
scons: `.' is up to date.
And on Windows:
C:\>scons -Q
key = OBJSUFFIX, value = .obj
key = LIBSUFFIX, value = .lib
key = PROGSUFFIX, value = .exe
scons: `.' is up to date.
If you want to loop through and print the values of
all of the construction variables in a construction environment,
the Python code to do that in sorted order might look something like:
env = Environment()
dict = env.Dictionary()
keys = dict.keys()
keys.sort()
for key in keys:
print "construction variable = '%s', value = '%s'" % (key, dict[key])
Expanding Values From a &ConsEnv;
Another way to get information from
a construction environment.
is to use the &subst; method
on a string containing $-expansions
of construction variable names.
As a simple example,
the example from the previous
section that used
env['CC']
to fetch the value of &cv-link-CC;
could also be written as:
env = Environment()
print "CC is:", env.subst('$CC')
The real advantage of using
&subst; to expand strings is
that construction variables
in the result get
re-expanded until
there are no expansions left in the string.
So a simple fetch of a value like
&cv-link-CCCOM;:
env = Environment(CCFLAGS = '-DFOO')
print "CCCOM is:", env['CCCOM']
Will print the unexpanded value of &cv-CCCOM;,
showing us the construction
variables that still need to be expanded:
% scons -Q
CCCOM is: $CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES
scons: `.' is up to date.
Calling the &subst; method on $CCOM,
however:
env = Environment(CCFLAGS = '-DFOO')
print "CCCOM is:", env.subst('$CCCOM')
Will recursively expand all of
the $-prefixed construction variables,
showing us the final output:
% scons -Q
CCCOM is: gcc -DFOO -c -o
scons: `.' is up to date.
(Note that because we're not expanding this
in the context of building something
there are no target or source files
for &cv-link-TARGET; and &cv-link-SOURCES; to expand.
Modifying a &ConsEnv;
&SCons; provides various methods that
support modifying existing values in a construction environment.
Replacing Values in a &ConsEnv;
You can replace existing construction variable values
using the &Replace; method:
env = Environment(CCFLAGS = '-DDEFINE1')
env.Replace(CCFLAGS = '-DDEFINE2')
env.Program('foo.c')
The replacing value
(-DDEFINE2 in the above example)
completely replaces the value in the
construction environment:
% scons -Q
cc -o foo.o -c -DDEFINE2 foo.c
cc -o foo foo.o
You can safely call &Replace;
for construction variables that
don't exist in the construction environment:
env = Environment()
env.Replace(NEW_VARIABLE = 'xyzzy')
print "NEW_VARIABLE =", env['NEW_VARIABLE']
In this case,
the construction variable simply
gets added to the construction environment:
% scons -Q
NEW_VARIABLE = xyzzy
scons: `.' is up to date.
Because the variables
aren't expanded until the construction environment
is actually used to build the targets,
and because &SCons; function and method calls
are order-independent,
the last replacement "wins"
and is used to build all targets,
regardless of the order in which
the calls to Replace() are
interspersed with calls to
builder methods:
env = Environment(CCFLAGS = '-DDEFINE1')
print "CCFLAGS =", env['CCFLAGS']
env.Program('foo.c')
env.Replace(CCFLAGS = '-DDEFINE2')
print "CCFLAGS =", env['CCFLAGS']
env.Program('bar.c')
The timing of when the replacement
actually occurs relative
to when the targets get built
becomes apparent
if we run &scons; without the -Q
option:
% scons
scons: Reading SConscript files ...
CCFLAGS = -DDEFINE1
CCFLAGS = -DDEFINE2
scons: done reading SConscript files.
scons: Building targets ...
cc -o bar.o -c -DDEFINE2 bar.c
cc -o bar bar.o
cc -o foo.o -c -DDEFINE2 foo.c
cc -o foo foo.o
scons: done building targets.
Because the replacement occurs while
the &SConscript; files are being read,
the &cv-link-CCFLAGS;
variable has already been set to
-DDEFINE2
by the time the &foo_o; target is built,
even though the call to the &Replace;
method does not occur until later in
the &SConscript; file.
Appending to the End of Values in a &ConsEnv;
You can append a value to
an existing construction variable
using the &Append; method:
env = Environment(CCFLAGS = '-DMY_VALUE')
env.Append(CCFLAGS = ' -DLAST')
env.Program('foo.c')
&SCons; then supplies both the -DMY_VALUE and
-DLAST flags when compiling the object file:
% scons -Q
cc -o foo.o -c -DMY_VALUE -DLAST foo.c
cc -o foo foo.o
If the construction variable doesn't already exist,
the &Append; method will create it:
env = Environment()
env.Append(NEW_VARIABLE = 'added')
print "NEW_VARIABLE =", env['NEW_VARIABLE']
Which yields:
% scons -Q
NEW_VARIABLE = added
scons: `.' is up to date.
Appending to the Beginning of Values in a &ConsEnv;
You can append a value to the beginning of
an existing construction variable
using the &Prepend; method:
env = Environment(CCFLAGS = '-DMY_VALUE')
env.Prepend(CCFLAGS = '-DFIRST ')
env.Program('foo.c')
&SCons; then supplies both the -DFIRST and
-DMY_VALUE flags when compiling the object file:
% scons -Q
cc -o foo.o -c -DFIRST -DMY_VALUE foo.c
cc -o foo foo.o
If the construction variable doesn't already exist,
the &Prepend; method will create it:
env = Environment()
env.Prepend(NEW_VARIABLE = 'added')
print "NEW_VARIABLE =", env['NEW_VARIABLE']
Which yields:
% scons -Q
NEW_VARIABLE = added
scons: `.' is up to date.