7.2. Construction Environments
Prev | Chapter 7. Environments | Next

## 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, which you'll learn more about later.)

### 7.2.1. Creating a Construction Environment: the `Environment` Function

A construction environment is created by the `Environment` function:

    env = Environment()

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 initialization of `$CC` and `$CCFLAGS` overrides 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 from the sidebar that using the `get()` method for access 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:", 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:", 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 (advanced topic)

If a problem occurs when expanding a construction variable, by default it is expanded to an empty string, that is, "replaced with nothing" - scons will not fail for unknown variables.

    env = Environment()
    print("value is:", env.subst('->$MISSING<-'))

    % scons -Q
    value is: -><-
    scons: `.' is up to date.

Sometimes this behavior leads to surprises while the build configuration is being developed, for example a typo in a variable name isn't reported, and the variable expression is just dropped (empty string). SCons provides a `AllowSubstExceptions` function to allow the behavior to be tuned. Internally, when a problem occurs with a variable expansion, it generates an exception, but before letting that exception kill the build, scons checks a list of exceptions to ignore - by default `NameError` and `IndexError`. You can call `AllowSubstExceptions` to set the list of ignored exceptions to anything you wish, including none at all. That way, when a variable fails to expand that you thought should be expanding to something, the build will stop and you'll get an error message that should help diagnose the problem. You give `AllowSubstExceptions` as many exception name arguments as you wish it to ignore, or call it with no arguments to have all expansion exceptions propagate and stop scons.

    AllowSubstExceptions()
    env = Environment()
    print("value is:", 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:",  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:

    def_env = DefaultEnvironment()
    def_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:

    def_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 new value (`-DDEFINE2` in the above example) replaces the value in the construction environment - it's like a Python assignment statement for construction variables.

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

If you have a lot of variables to replace, it may be more convenient to put them in a dictionary and pass that to the `Replace` method. That might look like:

    newvalues = {
        "F77PATH": ['foo', '$FOO/bar', blat],
        "INCPREFIX": 'foo ',
        "INCSUFFIX": 'bar',
        "FOO": 'baz',
    }
    env.Replace(**newvalues)

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 `setdefault` 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')

Note `$CPPDEFINES` is the preferred way to set preprocessor defines, as SCons will generate the command line arguments using the correct prefix/suffix for the platform, leaving the usage portable. If you use `$CCFLAGS` and `$SHCCFLAGS`, you need to include them in their final form, which is less portable.

    % 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

Sometimes 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. Prepending to the Beginning of Values: the `Prepend` Method

You can prepend 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

Sometimes 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 construction environment 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 = Environment()
    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.

Prev | Up | Next
Chapter 7. Environments | Home | 7.3. Controlling the Execution Environment for Issued Commands