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 accommodates these different build
requirements by allowing you to create and
configure multiple construction environments
that control how the software is built.
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
set of Builder
methods,
about which we'll learn more later.)
A construction environment is created by the Environment
method:
env = Environment()
By default, SCons initializes 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
You can fetch individual construction variables using the normal syntax for accessing individual named items in a Python dictionary:
env = Environment() print("CC is: %s" % env['CC'])
This example SConstruct
file doesn't build anything,
but because it's actually a Python script,
it will print the value of $CC
for us:
% scons -Q
CC is: cc
scons: `.' is up to date.
A construction environment
is actually an object with associated methods and
attributes.
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') cvars = env.Dictionary() for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']: print("key = %s, value = %s" % (key, cvars[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 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() for item in sorted(env.Dictionary().items()): print("construction variable = '%s', value = '%s'" % item)
It should be noted that for the previous example, there is actually a construction environment method that does the same thing more simply, and tries to format the output nicely as well:
env = Environment() print(env.Dump())
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 $CC
could also be written as:
env = Environment() print("CC is: %s"%env.subst('$CC'))
One 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
$CCCOM
:
env = Environment(CCFLAGS = '-DFOO') print("CCCOM is: %s"%env['CCCOM'])
Will print the unexpanded value of $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: %s"%env.subst('$CCCOM'))
Will recursively expand all of
the construction variables prefixed
with $
(dollar signs),
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 $TARGET
and $SOURCES
to expand.
If a problem occurs when expanding a construction variable,
by default it is expanded to ''
(an empty string), and will not cause scons to fail.
env = Environment() print("value is: %s"%env.subst( '->$MISSING<-' ))
% scons -Q
value is: -><-
scons: `.' is up to date.
This default behaviour can be changed using the AllowSubstExceptions
function.
When a problem occurs with a variable expansion it generates
an exception, and the AllowSubstExceptions
function controls
which of these exceptions are actually fatal and which are
allowed to occur safely. By default, NameError
and IndexError
are the two exceptions that are allowed to occur: so instead of
causing scons to fail, these are caught, the variable expanded to
''
and scons execution continues.
To require that all construction variable names exist, and that
indexes out of range are not allowed, call AllowSubstExceptions
with no extra arguments.
AllowSubstExceptions() env = Environment() print("value is: %s"%env.subst( '->$MISSING<-' ))
% scons -Q
scons: *** NameError `name 'MISSING' is not defined' trying to evaluate `$MISSING'
File "/home/my/project/SConstruct", line 3, in <module>
This can also be used to allow other exceptions that might occur,
most usefully with the ${...}
construction
variable syntax. For example, this would allow zero-division to
occur in a variable expansion in addition to the default exceptions
allowed
AllowSubstExceptions(IndexError, NameError, ZeroDivisionError) env = Environment() print("value is: %s"%env.subst( '->${1 / 0}<-' ))
% scons -Q
value is: -><-
scons: `.' is up to date.
If AllowSubstExceptions
is called multiple times, each call
completely overwrites the previous list of allowed exceptions.
All of the Builder
functions that we've introduced so far,
like Program
and Library
, use a construction environment
that contains settings for the various compilers
and other tools that SCons configures by default,
or otherwise knows about and has discovered on your system.
If not invoked as methods of a specific construction environment,
they use the default construction environment
The goal of the default construction environment
is to make many configurations "just work"
to build software using readily available tools
with a minimum of configuration changes.
If needed, you can control the default construction environment
by using the DefaultEnvironment
function
to initialize various settings by passing
them as keyword arguments:
DefaultEnvironment(CC='/usr/local/bin/gcc')
When configured as above,
all calls to the Program
or Object
Builder
will build object files with the
/usr/local/bin/gcc
compiler.
The DefaultEnvironment
function
returns the initialized default construction environment object,
which can then be manipulated like any other construction environment
(note that the default environment works like a singleton -
it can have only one instance - so the keyword arguments
are processed only on the first call. On any subsequent
call the existing object is returned).
So the following would be equivalent to the
previous example, setting the $CC
variable to /usr/local/bin/gcc
but as a separate step after
the default construction environment has been initialized:
env = DefaultEnvironment() env['CC'] = '/usr/local/bin/gcc'
One very common use of the DefaultEnvironment
function
is to speed up SCons initialization.
As part of trying to make most default
configurations "just work,"
SCons will actually
search the local system for installed
compilers and other utilities.
This search can take time,
especially on systems with
slow or networked file systems.
If you know which compiler(s) and/or
other utilities you want to configure,
you can control the search
that SCons performs
by specifying some specific
tool modules with which to
initialize the default construction environment:
env = DefaultEnvironment(tools=['gcc', 'gnulink'], CC='/usr/local/bin/gcc')
So the above example would tell SCons
to explicitly configure the default environment
to use its normal GNU Compiler and GNU Linker settings
(without having to search for them,
or any other utilities for that matter),
and specifically to use the compiler found at
/usr/local/bin/gcc
.
The real advantage of construction environments
is that you can create as many different ones 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 <module>
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
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 construction variable 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 $CC
to gcc,
and then creating two copies,
one which sets $CCFLAGS
for optimization
and the other which sets $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
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 = %s"%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 = %s"%env['CCFLAGS']) env.Program('foo.c') env.Replace(CCFLAGS = '-DDEFINE2') print("CCFLAGS = %s"%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 $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.
Sometimes it's useful to be able to specify
that a construction variable should be
set to a value only if the construction environment
does not already have that variable defined
You can do this with the SetDefault
method,
which behaves similarly to the set_default
method of Python dictionary objects:
env.SetDefault(SPECIAL_FLAG = '-extra-option')
This is especially useful
when writing your own Tool
modules
to apply variables to construction environments.
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 = %s"%env['NEW_VARIABLE'])
Which yields:
% scons -Q
NEW_VARIABLE = added
scons: `.' is up to date.
Note that the Append
function tries to be "smart"
about how the new value is appended to the old value.
If both are strings, the previous and new strings
are simply concatenated.
Similarly, if both are lists,
the lists are concatenated.
If, however, one is a string and the other is a list,
the string is added as a new element to the list.
Some times it's useful to add a new value
only if the existing construction variable
doesn't already contain the value.
This can be done using the AppendUnique
method:
env.AppendUnique(CCFLAGS=['-g'])
In the above example,
the -g
would be added
only if the $CCFLAGS
variable
does not already contain a -g
value.
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 = %s"%env['NEW_VARIABLE'])
Which yields:
% scons -Q
NEW_VARIABLE = added
scons: `.' is up to date.
Like the Append
function,
the Prepend
function tries to be "smart"
about how the new value is appended to the old value.
If both are strings, the previous and new strings
are simply concatenated.
Similarly, if both are lists,
the lists are concatenated.
If, however, one is a string and the other is a list,
the string is added as a new element to the list.
Some times it's useful to add a new value
to the beginning of a construction variable
only if the existing value
doesn't already contain the to-be-added value.
This can be done using the PrependUnique
method:
env.PrependUnique(CCFLAGS=['-g'])
In the above example,
the -g
would be added
only if the $CCFLAGS
variable
does not already contain a -g
value.