7.2. Construction Environments

It is rare that all of the software in a large, complicated system needs to be built exactly 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, just like a dictionary. (A construction environment also has an attached set of Builder methods, about which we'll learn more later.)

7.2.1. Creating a Construction Environment: the Environment Function

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 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

7.2.2. Fetching Values From a Construction Environment

You can fetch individual values, known as Construction Variables, using the same syntax used for accessing individual named items in a Python dictionary:

env = Environment()
print("CC is: %s" % env['CC'])
print("LATEX is: %s" % env.get('LATEX', None))
        

This example SConstruct file doesn't contain instructions for building any targets, but because it's still a valid SConstruct it will be evaluated and the Python print calls will output the values of $CC and $LATEX for us (remember using the .get() method for fetching means we get a default value back, rather than a failure, if the variable is not set):

% scons -Q
CC is: cc
LATEX is: None
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 env.Dictionary method (although it's rarely necessary to use this 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())
      

7.2.3. Expanding Values From a Construction Environment: the subst Method

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.

7.2.4. Handling Problems With Value Expansion

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.

7.2.5. Controlling the Default Construction Environment: the DefaultEnvironment Function

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.

7.2.6. Multiple Construction Environments

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

7.2.7. Making Copies of Construction Environments: the Clone Method

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 env.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

7.2.8. Replacing Values: the Replace Method

You can replace existing construction variable values using the env.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.

7.2.9. Setting Values Only If They're Not Already Defined: the SetDefault Method

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 env.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.

7.2.10. Appending to the End of Values: the Append Method

You can append a value to an existing construction variable using the env.Append method:

env = Environment(CPPDEFINES=['MY_VALUE'])
env.Append(CPPDEFINES=['LAST'])
env.Program('foo.c')
        

SCons then generates the preprocessor define arguments from CPPDEFINES values with the correct prefix/suffix. For example on Linux or POSIX, the following arguments would be generated: -DMY_VALUE and -DLAST

% 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.

7.2.11. Appending Unique Values: the AppendUnique Method

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 env.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.

7.2.12. Appending to the Beginning of Values: the Prepend Method

You can append a value to the beginning of an existing construction variable using the env.Prepend method:

env = Environment(CPPDEFINES=['MY_VALUE'])
env.Prepend(CPPDEFINES=['FIRST'])
env.Program('foo.c')
        

SCons then generates the preprocessor define arguments from CPPDEFINES values with the correct prefix/suffix. For example on Linux or POSIX, the following arguments would be generated: -DFIRST and -DMY_VALUE

% 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.

7.2.13. Prepending Unique Values: the PrependUnique Method

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 env.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.

7.2.14. Overriding Construction Variable Settings

Rather than creating a cloned environmant for specific tasks, you can override or add construction variables when calling a builder method by passing them as keyword arguments. The values of these overridden or added variables will only be in effect when building that target, and will not affect other parts of the build. For example, if you want to add additional libraries for just one program:

env.Program('hello', 'hello.c', LIBS=['gl', 'glut'])
      

or generate a shared library with a non-standard suffix:

env.SharedLibrary(
    target='word',
    source='word.cpp',
    SHLIBSUFFIX='.ocx',
    LIBSUFFIXES=['.ocx'],
)
      

When overriding this way, the Python keyword arguments in the builder call mean "set to this value". If you want your override to augment an existing value, you have to take some extra steps. Inside the builder call, it is possible to substitute in the existing value by using a string containing the variable name prefaced by a dollar sign ($).

env = Environment(CPPDEFINES="FOO")
env.Object(target="foo1.o", source="foo.c")
env.Object(target="foo2.o", source="foo.c", CPPDEFINES="BAR")
env.Object(target="foo3.o", source="foo.c", CPPDEFINES=["BAR", "$CPPDEFINES"])
        

Which yields:

% scons -Q
cc -o foo1.o -c -DFOO foo.c
cc -o foo2.o -c -DBAR foo.c
cc -o foo3.o -c -DBAR -DFOO foo.c

It is also possible to use the parse_flags keyword argument in an override to merge command-line style arguments into the appropriate construction variables. This works like the env.MergeFlags method, which will be fully described in the next chapter.

This example adds 'include' to $CPPPATH, 'EBUG' to $CPPDEFINES, and 'm' to $LIBS:

env = Program('hello', 'hello.c', parse_flags='-Iinclude -DEBUG -lm')
        

So when executed:

% scons -Q
cc -o hello.o -c -DEBUG -Iinclude hello.c
cc -o hello hello.o -lm

Using temporary overrides this way is lighter weight than making a full construction environment, so it can help performance in large projects which have lots of special case values to set. However, keep in mind that this only works well when the targets are unique. Using builder overrides to try to build the same target with different sets of flags or other construction variables will lead to the scons: *** Two environments with different actions... error described in Section 7.2.6, “Multiple Construction Environments above. In this case you will actually want to create separate environments.