Skip to content
Mats Wichmann edited this page Feb 21, 2024 · 14 revisions

If you are familiar with the make utility, SCons has similar aims: build a software or documentation project based on a description of the project you provide. As the make utility looks for a Makefile, SCons looks for a SConstruct file by default. The language used to tell SCons about your project is Python, extended by a set of interfaces (API) specific to SCons. Note that you do not have to know Python or make for basic operation with this build tool.

See SconsProcessOverview for a high level view of SCons processing.

SConstruct and Environment

The main build configuration script for SCons is the SConstruct file. When the scons script is called, it will automatically search for this file in the current directory (actually, several alternate names are also searched for and will be used if found: Sconstruct, sconstruct, SConstruct.py, Sconstruct.py, sconstruct.py).

The full SCons API is available for usage from `SConstruct, including the Environment class. The Environment class describes a Construction Environment which controls a build; a build configuration may have several of these if there are different instructions for different parts of the build. Typically, a build configuration instantiates this class very early, although it's certainly not required to do so at the very top.

env = Environment()

This sets up a basic environment. Now, you can start telling SCons what you want built.

env.Program(target='bar', source=['foo.c'])

This gives SCons three important pieces of information: that you want a program-style target - a thing you could run; that it should take the internal name bar; and that the list of sources needed to construct it consists of the single source file foo.c. Program is an example of an SCons component called a Builder, and they are usually named to give a strong hint what the outcome will be (like Object, or Jar or PDF).

SCons has rules for lots of common build tasks, so you don't have to describe the actual commands to use. The Program builder will recognize the file suffix .c and lookup the associated transformation template (think "how to call the compiler with the right arguments"). Such a template is called an Action, and it is attached to the node that will describe the target, to be filled in when SCons actually issues the build command. Behind the scenes, SCons will also work out if there are other dependencies - for example if foo.c includes header files, and maybe those header files include other header files, those are all added to the dependency graph, and SCons can detect when bar needs to be rebuilt if any of those dependencies go out of date. This is a big improvement over older build systems that could not detect if a rebuild was needed due to a dependency unless you explicitly called out the dependency.

As an additional detail, note that the target is named bar, which does not say the produced file is named bar. As a multi-platform build tool, SCons understands the needs of the platform it is generating for and deals with any required file suffixes (or prefixes) itself without you having to program in that information.

For more complex programs you must set up a more specialized environment. For example, setting up the flags the compiler will use, additional directories to search for include files, etc.

To do that you can specify named parameters such as CCFLAGS for C files or CPPFLAGS for the C/C++ Preprocessor. More of these can be seen below in this article and also in the Configuration File Reference section of the man page.

# directly when constructing your Environment
env = Environment(CCFLAGS='-O3')

# ... or appending it later
env.Append(CCFLAGS='-O3')

Some parameters require specific lists, such as the source list. Reading the Configuration File Reference should be very helpful.

Specifying A Default Target

An important note is the Default command. It tells scons what to build by default. Scons always builds the targets it is given on the command line, and any targets that are prerequisites to building the specific targets. Default sets up the list to build if no targets are supplied on the command line. If Default is not used, scons selects all the targets for building, which may be undesirable in a larger project.

t = env.Program(target='bar', source=['foo.c'])
Default(t)

You can call Default many times to build up the default target list. If it later turns out you want to build everything in spite of having a default list, you can give the current directory as an argument to scons, as in:

scons .

Tip: You can pass the target name to Default(), but Steven Knight (author of SCons) recommends that you use the return value from your target as the default value instead of specifying the name. He explains it best: "The only stylistic suggestion I'll make is that if you use the object returned by env.Program as the input to Default(), then you'll be more portable, since you won't have to worry about whether the generated program will have a .exe suffix or not."

Some Common tasks

A few common tasks are shown below. (Note that, although these examples mostly use 'Append', you can also specify the same information by using the same flags when calling e.g. Program()).

Add header search path

env.Append(CPPPATH = ['/usr/local/include/'])

Add compile-time flags

env.Append(CCFLAGS = ['-g','-O3'])

Add preprocessor define. Note scons will supply the suitable prefix when invoking.

env.Append(CPPDEFINES=['BIG_ENDIAN'])

Add preprocessor define with value (e.g. -DRELEASE_BUILD=1)

env.Append(CPPDEFINES={'RELEASE_BUILD' : '1'})

Add library search path

env.Append(LIBPATH = ['/usr/local/lib/'])

Add libraries to link against. Note you use just the base name, scons will format this to be correct for the build system.

env.Append(LIBS = ['SDL_image','GL'])

Link time flags

env.Append(LINKFLAGS = ['-Wl,--rpath,/usr/local/lib/'])

Add flags from a config (the ParseConfig method sorts the result of running an external command into the appropriate buckets - effectively it does an Append to CPPDEFINES, CCFLAGS, LINKFLAGS, etc. as necessary (see ParseFlags in the manpage for details):

env.ParseConfig('pkg-config --cflags glib-2.0')

Building a more complex program than the example outlined above from several source files, not all of which are always present, can be done by building up a list and then passing it to the builder in separate steps:

sources = ['main.cpp', 'utils.cpp', 'args.cpp']
if gui_mode:
    sources.extend(['gui.cpp',])
env.Program(target='a.out', source=sources)

Extending the configuration: SConscript

As soon as the project extends beyond "extremely simple", you will probably want to set up hierarchical builds, giving the responsability of describing a particular module/library/subprogram to a subsidiary script rather than stuffing everything in to the SConstruct file. These scripts are by default named SConscript (the same variation of names as show for SConstruct above also work here), and are invoked by a call to the SConscript method. A simple case might be to put the code for a library in a lib subdirectory, and test code in a test subdirectory.

SConscript('lib/SConscript')
SConscript('test/SConscript')

Supporting multiple builds: Variant directory

Many projects need to support building in several different ways from the same sources. A common case is a "debug" build with support for examining objects with debuggers, etc. and a "release" build that is stripped and optimized. SCons has the capability of supporting this through an instruction that tells it to use a variant directory. A variant directory is a build-specific location where the targets are placed, while the sources continue to come from the common location.

Here is an example building the same targets in release and debug modes; in addition to setting the distinct variant directories, in this example a flag named MODE is passed to the SConscript so it can determine which build it is doing, and set up the environment for that build appropriately.

SConscript('src/SConscript', variant_dir='build_release', duplicate=0, exports={'MODE':'release'})
SConscript('src/SConscript', variant_dir='build_debug',   duplicate=0, exports={'MODE':'debug'})
Clone this wiki locally