version 2.3.2
Copyright © 2004 - 2014 The SCons Foundation
2004 - 2014
Table of Contents
Glob
Decider
FunctionDecider
Function$CPPPATH
Construction VariableDepends
FunctionParseDepends
FunctionIgnore
FunctionRequires
FunctionAlwaysBuild
FunctionConstruction Environment
: the Environment
FunctionConstruction Environment
Construction Environment
: the subst
MethodConstruction Environment
: the DefaultEnvironment
FunctionConstruction Environments
Construction Environments
: the Clone
MethodReplace
MethodSetDefault
MethodAppend
MethodAppendUnique
MethodPrepend
MethodPrependUnique
MethodSCONSFLAGS
Environment VariableGetOption
FunctionSetOption
FunctionAddOption
Functionvariable
=value
Build VariablesUnknownVariables
FunctionInstall
BuilderCopy
FactoryDelete
FactoryMove
FactoryTouch
FactoryMkdir
FactoryChmod
FactoryExecute
FunctionSConscript
CallVariantDir
FunctionVariantDir
With an SConscript
FileGlob
with VariantDir
Construction Environment
Generator
Emitter
Command
BuilderEnsurePythonVersion
FunctionEnsureSConsVersion
FunctionSConscript
Files: the Exit
FunctionFindFile
FunctionFlatten
FunctionGetLaunchDir
Function--debug=explain
OptionDump
Method--tree
Option--debug=presub
Option--debug=findlibs
Option--debug=stacktrace
Option--taskmastertrace
Option--debug=prepare
OptionList of Examples
Thank you for taking the time to read about SCons. SCons is a next-generation software construction tool, or make tool--that is, a software utility for building software (or other files) and keeping built software up-to-date whenever the underlying input files change.
The most distinctive thing about SCons is that its configuration files are actually scripts, written in the Python programming language. This is in contrast to most alternative build tools, which typically invent a new language to configure the build. SCons still has a learning curve, of course, because you have to know what functions to call to set up your build properly, but the underlying syntax used should be familiar to anyone who has ever looked at a Python script.
Paradoxically, using Python as the configuration file format makes SCons easier for non-programmers to learn than the cryptic languages of other build tools, which are usually invented by programmers for other programmers. This is in no small part due to the consistency and readability that are hallmarks of Python. It just so happens that making a real, live scripting language the basis for the configuration files makes it a snap for more accomplished programmers to do more complicated things with builds, as necessary.
There are a few overriding principles we try to live up to in designing and implementing SCons:
First and foremost, by default, SCons guarantees a correct build even if it means sacrificing performance a little. We strive to guarantee the build is correct regardless of how the software being built is structured, how it may have been written, or how unusual the tools are that build it.
Given that the build is correct, we try to make SCons build software as quickly as possible. In particular, wherever we may have needed to slow down the default SCons behavior to guarantee a correct build, we also try to make it easy to speed up SCons through optimization options that let you trade off guaranteed correctness in all end cases for a speedier build in the usual cases.
SCons tries to do as much for you out of the box as reasonable, including detecting the right tools on your system and using them correctly to build the software.
In a nutshell, we try hard to make SCons just "do the right thing" and build software correctly, with a minimum of hassles.
One word of warning as you read through this Guide: Like too much Open Source software out there, the SCons documentation isn't always kept up-to-date with the available features. In other words, there's a lot that SCons can do that isn't yet covered in this User's Guide. (Come to think of it, that also describes a lot of proprietary software, doesn't it?)
Although this User's Guide isn't as complete as we'd like it to be, our development process does emphasize making sure that the SCons man page is kept up-to-date with new features. So if you're trying to figure out how to do something that SCons supports but can't find enough (or any) information here, it would be worth your while to look at the man page to see if the information is covered there. And if you do, maybe you'd even consider contributing a section to the User's Guide so the next person looking for that information won't have to go through the same thing...?
SCons would not exist without a lot of help from a lot of people, many of whom may not even be aware that they helped or served as inspiration. So in no particular order, and at the risk of leaving out someone:
First and foremost, SCons owes a tremendous debt to Bob Sidebotham, the original author of the classic Perl-based Cons tool which Bob first released to the world back around 1996. Bob's work on Cons classic provided the underlying architecture and model of specifying a build configuration using a real scripting language. My real-world experience working on Cons informed many of the design decisions in SCons, including the improved parallel build support, making Builder objects easily definable by users, and separating the build engine from the wrapping interface.
Greg Wilson was instrumental in getting SCons started as a real project when he initiated the Software Carpentry design competition in February 2000. Without that nudge, marrying the advantages of the Cons classic architecture with the readability of Python might have just stayed no more than a nice idea.
The entire SCons team have been absolutely wonderful to work with, and SCons would be nowhere near as useful a tool without the energy, enthusiasm and time people have contributed over the past few years. The "core team" of Chad Austin, Anthony Roach, Bill Deegan, Charles Crain, Steve Leblanc, Greg Noel, Gary Oberbrunner, Greg Spencer and Christoph Wiedemann have been great about reviewing my (and other) changes and catching problems before they get in the code base. Of particular technical note: Anthony's outstanding and innovative work on the tasking engine has given SCons a vastly superior parallel build model; Charles has been the master of the crucial Node infrastructure; Christoph's work on the Configure infrastructure has added crucial Autoconf-like functionality; and Greg has provided excellent support for Microsoft Visual Studio.
Special thanks to David Snopek for contributing his underlying "Autoscons" code that formed the basis of Christoph's work with the Configure functionality. David was extremely generous in making this code available to SCons, given that he initially released it under the GPL and SCons is released under a less-restrictive MIT-style license.
Thanks to Peter Miller for his splendid change management system, Aegis, which has provided the SCons project with a robust development methodology from day one, and which showed me how you could integrate incremental regression tests into a practical development cycle (years before eXtreme Programming arrived on the scene).
And last, thanks to Guido van Rossum for his elegant scripting language, which is the basis not only for the SCons implementation, but for the interface itself.
The best way to contact people involved with SCons, including the author, is through the SCons mailing lists.
If you want to ask general questions about how to use SCons
send email to scons-users@scons.org
.
If you want to contact the SCons development community directly,
send email to scons-dev@scons.org
.
If you want to receive announcements about SCons,
join the low-volume announce@scons.tigris.org
mailing list.
This chapter will take you through the basic steps of installing SCons on your system, and building SCons if you don't have a pre-built package available (or simply prefer the flexibility of building it yourself). Before that, however, this chapter will also describe the basic steps involved in installing Python on your system, in case that is necessary. Fortunately, both SCons and Python are very easy to install on almost any system, and Python already comes installed on many systems.
Because SCons is written in Python,
you must obviously have Python installed on your system
to use SCons.
Before you try to install Python,
you should check to see if Python is already
available on your system by typing
python -V
(capital 'V')
or
python --version
at your system's command-line prompt.
$ python -V
Python 2.5.1
And on a Windows system with Python installed:
C:\>python -V
Python 2.5.1
If Python is not installed on your system, you will see an error message stating something like "command not found" (on UNIX or Linux) or "'python' is not recognized as an internal or external command, operable progam or batch file" (on Windows). In that case, you need to install Python before you can install SCons.
The standard location for information about downloading and installing Python is http://www.python.org/download/. See that page for information about how to download and install Python on your system.
SCons will work with any 2.x version of Python from 2.4 on; 3.0 and later are not yet supported. If you need to install Python and have a choice, we recommend using the most recent 2.x Python version available. Newer Pythons have significant improvements that help speed up the performance of SCons.
SCons comes pre-packaged for installation on a number of systems, including Linux and Windows systems. You do not need to read this entire section, you should need to read only the section appropriate to the type of system you're running on.
SCons comes in RPM (Red Hat Package Manager) format, pre-built and ready to install on Red Hat Linux, Fedora, or any other Linux distribution that uses RPM. Your distribution may already have an SCons RPM built specifically for it; many do, including SUSE, Mandrake and Fedora. You can check for the availability of an SCons RPM on your distribution's download servers, or by consulting an RPM search site like http://www.rpmfind.net/ or http://rpm.pbone.net/.
If your distribution supports installation via yum, you should be able to install SCons by running:
# yum install scons
If your Linux distribution does not already have
a specific SCons RPM file,
you can download and install from the
generic RPM provided by the SCons project.
This will install the
SCons script(s) in /usr/bin
,
and the SCons library modules in
/usr/lib/scons
.
To install from the command line, simply download the
appropriate .rpm
file,
and then run:
# rpm -Uvh scons-2.3.2-1.noarch.rpm
Or, you can use a graphical RPM package manager. See your package manager application's documention for specific instructions about how to use it to install a downloaded RPM.
Debian Linux systems use a different package management format that also makes it very easy to install SCons.
If your system is connected to the Internet, you can install the latest official Debian package by running:
# apt-get install scons
SCons provides a Windows installer
that makes installation extremely easy.
Download the scons-2.3.2.win32.exe
file from the SCons download page at
http://www.scons.org/download.php.
Then all you need to do is execute the file
(usually by clicking on its icon in Windows Explorer).
These will take you through a small
sequence of windows that will install
SCons on your system.
If a pre-built SCons package is not available for your system,
then you can still easily build and install SCons using the native
Python distutils
package.
The first step is to download either the
scons-2.3.2.tar.gz
or scons-2.3.2.zip
,
which are available from the SCons download page at
http://www.scons.org/download.html.
Unpack the archive you downloaded,
using a utility like tar
on Linux or UNIX,
or WinZip on Windows.
This will create a directory called
scons-2.3.2
,
usually in your local directory.
Then change your working directory to that directory
and install SCons by executing the following commands:
#cd scons-2.3.2
#python setup.py install
This will build SCons,
install the scons
script
in the python which is used to run the setup.py's scripts directory
(/usr/local/bin
or
C:\Python25\Scripts
),
and will install the SCons build engine
in the corresponding library directory for the python used
(/usr/local/lib/scons
or
C:\Python25\scons
).
Because these are system directories,
you may need root (on Linux or UNIX) or Administrator (on Windows)
privileges to install SCons like this.
The SCons setup.py
script
has some extensions that support
easy installation of multiple versions of SCons
in side-by-side locations.
This makes it easier to download and
experiment with different versions of SCons
before moving your official build process to a new version,
for example.
To install SCons in a version-specific location,
add the --version-lib
option
when you call setup.py
:
# python setup.py install --version-lib
This will install the SCons build engine
in the
/usr/lib/scons-2.3.2
or
C:\Python25\scons-2.3.2
directory, for example.
If you use the --version-lib
option
the first time you install SCons,
you do not need to specify it each time you install
a new version.
The SCons setup.py
script
will detect the version-specific directory name(s)
and assume you want to install all versions
in version-specific directories.
You can override that assumption in the future
by explicitly specifying the --standalone-lib
option.
You can install SCons in locations other than
the default by specifying the --prefix=
option:
# python setup.py install --prefix=/opt/scons
This would
install the scons script in
/opt/scons/bin
and the build engine in
/opt/scons/lib/scons
,
Note that you can specify both the --prefix=
and the --version-lib
options
at the same type,
in which case setup.py
will install the build engine
in a version-specific directory
relative to the specified prefix.
Adding --version-lib
to the
above example would install the build engine in
/opt/scons/lib/scons-2.3.2
.
If you don't have the right privileges to install SCons
in a system location,
simply use the --prefix=
option
to install it in a location of your choosing.
For example,
to install SCons in appropriate locations
relative to the user's $HOME
directory,
the scons
script in
$HOME/bin
and the build engine in
$HOME/lib/scons
,
simply type:
$ python setup.py install --prefix=$HOME
You may, of course, specify any other location you prefer,
and may use the --version-lib
option
if you would like to install version-specific directories
relative to the specified prefix.
This can also be used to experiment with a newer
version of SCons than the one installed
in your system locations.
Of course, the location in which you install the
newer version of the scons
script
($HOME/bin
in the above example)
must be configured in your PATH
variable
before the directory containing
the system-installed version
of the scons
script.
In this chapter, you will see several examples of very simple build configurations using SCons, which will demonstrate how easy it is to use SCons to build programs from several different programming languages on different types of systems.
Here's the famous "Hello, World!" program in C:
int main() { printf("Hello, world!\n"); }
And here's how to build it using SCons.
Enter the following into a file named SConstruct
:
Program('hello.c')
This minimal configuration file gives
SCons two pieces of information:
what you want to build
(an executable program),
and the input file from
which you want it built
(the hello.c
file).
Program
is a builder_method,
a Python call that tells SCons that you want to build an
executable program.
That's it. Now run the scons
command to build the program.
On a POSIX-compliant system like Linux or UNIX,
you'll see something like:
% scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o hello.o -c hello.c
cc -o hello hello.o
scons: done building targets.
On a Windows system with the Microsoft Visual C++ compiler, you'll see something like:
C:\>scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cl /Fohello.obj /c hello.c /nologo
link /nologo /OUT:hello.exe hello.obj
embedManifestExeCheck(target, source, env)
scons: done building targets.
First, notice that you only need to specify the name of the source file, and that SCons correctly deduces the names of the object and executable files to be built from the base of the source file name.
Second, notice that the same input SConstruct
file,
without any changes,
generates the correct output file names on both systems:
hello.o
and hello
on POSIX systems,
hello.obj
and hello.exe
on Windows systems.
This is a simple example of how SCons
makes it extremely easy to
write portable software builds.
(Note that we won't provide duplicate side-by-side POSIX and Windows output for all of the examples in this guide; just keep in mind that, unless otherwise specified, any of the examples should work equally well on both types of systems.)
The Program
builder method is only one of
many builder methods that SCons provides
to build different types of files.
Another is the Object
builder method,
which tells SCons to build an object file
from the specified source file:
Object('hello.c')
Now when you run the scons
command to build the program,
it will build just the hello.o
object file on a POSIX system:
% scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o hello.o -c hello.c
scons: done building targets.
And just the hello.obj
object file
on a Windows system (with the Microsoft Visual C++ compiler):
C:\>scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cl /Fohello.obj /c hello.c /nologo
scons: done building targets.
SCons also makes building with Java extremely easy.
Unlike the Program
and Object
builder methods,
however, the Java
builder method
requires that you specify
the name of a destination directory in which
you want the class files placed,
followed by the source directory
in which the .java
files live:
Java('classes', 'src')
If the src
directory
contains a single hello.java
file,
then the output from running the scons
command
would look something like this
(on a POSIX system):
% scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
javac -d classes -sourcepath src src/hello.java
scons: done building targets.
We'll cover Java builds in more detail,
including building Java archive (.jar
)
and other types of file,
in Chapter 26, Java Builds.
When using SCons, it is unnecessary to add special
commands or target names to clean up after a build.
Instead, you simply use the
-c
or --clean
option when you invoke SCons,
and SCons removes the appropriate built files.
So if we build our example above
and then invoke scons -c
afterwards, the output on POSIX looks like:
%scons
scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... cc -o hello.o -c hello.c cc -o hello hello.o scons: done building targets. %scons -c
scons: Reading SConscript files ... scons: done reading SConscript files. scons: Cleaning targets ... Removed hello.o Removed hello scons: done cleaning targets.
And the output on Windows looks like:
C:\>scons
scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... cl /Fohello.obj /c hello.c /nologo link /nologo /OUT:hello.exe hello.obj embedManifestExeCheck(target, source, env) scons: done building targets. C:\>scons -c
scons: Reading SConscript files ... scons: done reading SConscript files. scons: Cleaning targets ... Removed hello.obj Removed hello.exe scons: done cleaning targets.
Notice that SCons changes its output to tell you that it
is Cleaning targets ...
and
done cleaning targets.
If you're used to build systems like Make
you've already figured out that the SConstruct
file
is the SCons equivalent of a Makefile
.
That is, the SConstruct
file is the input file
that SCons reads to control the build.
There is, however, an important difference between
an SConstruct
file and a Makefile
:
the SConstruct
file is actually a Python script.
If you're not already familiar with Python, don't worry.
This User's Guide will introduce you step-by-step
to the relatively small amount of Python you'll
need to know to be able to use SCons effectively.
And Python is very easy to learn.
One aspect of using Python as the
scripting language is that you can put comments
in your SConstruct
file using Python's commenting convention;
that is, everything between a '#' and the end of the line
will be ignored:
# Arrange to build the "hello" program. Program('hello.c') # "hello.c" is the source file.
You'll see throughout the remainder of this Guide that being able to use the power of a real scripting language can greatly simplify the solutions to complex requirements of real-world builds.
One important way in which the SConstruct
file is not exactly like a normal Python script,
and is more like a Makefile
,
is that the order in which
the SCons functions are called in
the SConstruct
file
does not
affect the order in which SCons
actually builds the programs and object files
you want it to build.[1]
In other words, when you call the Program
builder
(or any other builder method),
you're not telling SCons to build
the program at the instant the builder method is called.
Instead, you're telling SCons to build the program
that you want, for example,
a program built from a file named hello.c
,
and it's up to SCons to build that program
(and any other files) whenever it's necessary.
(We'll learn more about how
SCons decides when building or rebuilding a file
is necessary in Chapter 6, Dependencies, below.)
SCons reflects this distinction between
calling a builder method like Program
and actually building the program
by printing the status messages that indicate
when it's "just reading" the SConstruct
file,
and when it's actually building the target files.
This is to make it clear when SCons is
executing the Python statements that make up the SConstruct
file,
and when SCons is actually executing the
commands or other actions to
build the necessary files.
Let's clarify this with an example.
Python has a print
statement that
prints a string of characters to the screen.
If we put print
statements around
our calls to the Program
builder method:
print "Calling Program('hello.c')" Program('hello.c') print "Calling Program('goodbye.c')" Program('goodbye.c') print "Finished calling Program()"
Then when we execute SCons,
we see the output from the print
statements in between the messages about
reading the SConscript
files,
indicating that that is when the
Python statements are being executed:
% scons
scons: Reading SConscript files ...
Calling Program('hello.c')
Calling Program('goodbye.c')
Finished calling Program()
scons: done reading SConscript files.
scons: Building targets ...
cc -o goodbye.o -c goodbye.c
cc -o goodbye goodbye.o
cc -o hello.o -c hello.c
cc -o hello hello.o
scons: done building targets.
Notice also that SCons built the goodbye
program first,
even though the "reading SConscript
" output
shows that we called Program('hello.c')
first in the SConstruct
file.
You've already seen how SCons prints some messages about what it's doing, surrounding the actual commands used to build the software:
C:\>scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cl /Fohello.obj /c hello.c /nologo
link /nologo /OUT:hello.exe hello.obj
embedManifestExeCheck(target, source, env)
scons: done building targets.
These messages emphasize the
order in which SCons does its work:
all of the configuration files
(generically referred to as SConscript
files)
are read and executed first,
and only then are the target files built.
Among other benefits, these messages help to distinguish between
errors that occur while the configuration files are read,
and errors that occur while targets are being built.
One drawback, of course, is that these messages clutter the output.
Fortunately, they're easily disabled by using
the -Q
option when invoking SCons:
C:\>scons -Q
cl /Fohello.obj /c hello.c /nologo
link /nologo /OUT:hello.exe hello.obj
embedManifestExeCheck(target, source, env)
Because we want this User's Guide to focus
on what SCons is actually doing,
we're going to use the -Q
option
to remove these messages from the
output of all the remaining examples in this Guide.
[1] In programming parlance,
the SConstruct
file is
declarative,
meaning you tell SCons what you want done
and let it figure out the order in which to do it,
rather than strictly imperative,
where you specify explicitly the order in
which to do things.
In this chapter, you will see several examples of very simple build configurations using SCons, which will demonstrate how easy it is to use SCons to build programs from several different programming languages on different types of systems.
You've seen that when you call the Program
builder method,
it builds the resulting program with the same
base name as the source file.
That is, the following call to build an
executable program from the hello.c
source file
will build an executable program named hello
on POSIX systems,
and an executable program named hello.exe
on Windows systems:
Program('hello.c')
If you want to build a program with a different name than the base of the source file name, you simply put the target file name to the left of the source file name:
Program('new_hello', 'hello.c')
(SCons requires the target file name first,
followed by the source file name,
so that the order mimics that of an
assignment statement in most programming languages,
including Python:
"program = source files"
.)
Now SCons will build an executable program
named new_hello
when run on a POSIX system:
% scons -Q
cc -o hello.o -c hello.c
cc -o new_hello hello.o
And SCons will build an executable program
named new_hello.exe
when run on a Windows system:
C:\>scons -Q
cl /Fohello.obj /c hello.c /nologo
link /nologo /OUT:new_hello.exe hello.obj
embedManifestExeCheck(target, source, env)
You've just seen how to configure SCons to compile a program from a single source file. It's more common, of course, that you'll need to build a program from many input source files, not just one. To do this, you need to put the source files in a Python list (enclosed in square brackets), like so:
Program(['prog.c', 'file1.c', 'file2.c'])
A build of the above example would look like:
% scons -Q
cc -o file1.o -c file1.c
cc -o file2.o -c file2.c
cc -o prog.o -c prog.c
cc -o prog prog.o file1.o file2.o
Notice that SCons
deduces the output program name
from the first source file specified
in the list--that is,
because the first source file was prog.c
,
SCons will name the resulting program prog
(or prog.exe
on a Windows system).
If you want to specify a different program name,
then (as we've seen in the previous section)
you slide the list of source files
over to the right
to make room for the output program file name.
(SCons puts the output file name to the left
of the source file names
so that the order mimics that of an
assignment statement: "program = source files".)
This makes our example:
Program('program', ['prog.c', 'file1.c', 'file2.c'])
On Linux, a build of this example would look like:
% scons -Q
cc -o file1.o -c file1.c
cc -o file2.o -c file2.c
cc -o prog.o -c prog.c
cc -o program prog.o file1.o file2.o
Or on Windows:
C:\>scons -Q
cl /Fofile1.obj /c file1.c /nologo
cl /Fofile2.obj /c file2.c /nologo
cl /Foprog.obj /c prog.c /nologo
link /nologo /OUT:program.exe prog.obj file1.obj file2.obj
embedManifestExeCheck(target, source, env)
You can also use the Glob
function to find all files matching a
certain template, using the standard shell pattern matching
characters *
, ?
and [abc]
to match any of
a
, b
or c
.
[!abc]
is also supported,
to match any character except
a
, b
or c
.
This makes many multi-source-file builds quite easy:
Program('program', Glob('*.c'))
The SCons man page has more details on using Glob
with variant directories
(see Chapter 16, Variant Builds, below)
and repositories
(see Chapter 22, Building From Code Repositories, below),
and returning strings rather than Nodes.
We've now shown you two ways to specify the source for a program, one with a list of files:
Program('hello', ['file1.c', 'file2.c'])
And one with a single file:
Program('hello', 'hello.c')
You could actually put a single file name in a list, too, which you might prefer just for the sake of consistency:
Program('hello', ['hello.c'])
SCons functions will accept a single file name in either form. In fact, internally, SCons treats all input as lists of files, but allows you to omit the square brackets to cut down a little on the typing when there's only a single file name.
Although SCons functions are forgiving about whether or not you use a string vs. a list for a single file name, Python itself is more strict about treating lists and strings differently. So where SCons allows either a string or list:
# The following two calls both work correctly: Program('program1', 'program1.c') Program('program2', ['program2.c'])
Trying to do "Python things" that mix strings and lists will cause errors or lead to incorrect results:
common_sources = ['file1.c', 'file2.c'] # THE FOLLOWING IS INCORRECT AND GENERATES A PYTHON ERROR # BECAUSE IT TRIES TO ADD A STRING TO A LIST: Program('program1', common_sources + 'program1.c') # The following works correctly, because it's adding two # lists together to make another list. Program('program2', common_sources + ['program2.c'])
One drawback to the use of a Python list
for source files is that
each file name must be enclosed in quotes
(either single quotes or double quotes).
This can get cumbersome and difficult to read
when the list of file names is long.
Fortunately, SCons and Python provide a number of ways
to make sure that
the SConstruct
file stays easy to read.
To make long lists of file names
easier to deal with, SCons provides a
Split
function
that takes a quoted list of file names,
with the names separated by spaces or other white-space characters,
and turns it into a list of separate file names.
Using the Split
function turns the
previous example into:
Program('program', Split('main.c file1.c file2.c'))
(If you're already familiar with Python,
you'll have realized that this is similar to the
split()
method
in the Python standard string
module.
Unlike the split()
member function of strings,
however, the Split
function
does not require a string as input
and will wrap up a single non-string object in a list,
or return its argument untouched if it's already a list.
This comes in handy as a way to make sure
arbitrary values can be passed to SCons functions
without having to check the type of the variable by hand.)
Putting the call to the Split
function
inside the Program
call
can also be a little unwieldy.
A more readable alternative is to
assign the output from the Split
call
to a variable name,
and then use the variable when calling the
Program
function:
src_files = Split('main.c file1.c file2.c') Program('program', src_files)
Lastly, the Split
function
doesn't care how much white space separates
the file names in the quoted string.
This allows you to create lists of file
names that span multiple lines,
which often makes for easier editing:
src_files = Split("""main.c file1.c file2.c""") Program('program', src_files)
(Note in this example that we used the Python "triple-quote" syntax, which allows a string to contain multiple lines. The three quotes can be either single or double quotes.)
SCons also allows you to identify the output file and input source files using Python keyword arguments. The output file is known as the target, and the source file(s) are known (logically enough) as the source. The Python syntax for this is:
src_files = Split('main.c file1.c file2.c') Program(target = 'program', source = src_files)
Because the keywords explicitly identify what each argument is, you can actually reverse the order if you prefer:
src_files = Split('main.c file1.c file2.c') Program(source = src_files, target = 'program')
Whether or not you choose to use keyword arguments to identify the target and source files, and the order in which you specify them when using keywords, are purely personal choices; SCons functions the same regardless.
In order to compile multiple programs
within the same SConstruct
file,
simply call the Program
method
multiple times,
once for each program you need to build:
Program('foo.c') Program('bar', ['bar1.c', 'bar2.c'])
SCons would then build the programs as follows:
% scons -Q
cc -o bar1.o -c bar1.c
cc -o bar2.o -c bar2.c
cc -o bar bar1.o bar2.o
cc -o foo.o -c foo.c
cc -o foo foo.o
Notice that SCons does not necessarily build the
programs in the same order in which you specify
them in the SConstruct
file.
SCons does, however, recognize that
the individual object files must be built
before the resulting program can be built.
We'll discuss this in greater detail in
the "Dependencies" section, below.
It's common to re-use code by sharing source files between multiple programs. One way to do this is to create a library from the common source files, which can then be linked into resulting programs. (Creating libraries is discussed in Chapter 4, Building and Linking with Libraries, below.)
A more straightforward, but perhaps less convenient, way to share source files between multiple programs is simply to include the common files in the lists of source files for each program:
Program(Split('foo.c common1.c common2.c')) Program('bar', Split('bar1.c bar2.c common1.c common2.c'))
SCons recognizes that the object files for
the common1.c
and common2.c
source files
each need to be built only once,
even though the resulting object files are
each linked in to both of the resulting executable programs:
% scons -Q
cc -o bar1.o -c bar1.c
cc -o bar2.o -c bar2.c
cc -o common1.o -c common1.c
cc -o common2.o -c common2.c
cc -o bar bar1.o bar2.o common1.o common2.o
cc -o foo.o -c foo.c
cc -o foo foo.o common1.o common2.o
If two or more programs
share a lot of common source files,
repeating the common files in the list for each program
can be a maintenance problem when you need to change the
list of common files.
You can simplify this by creating a separate Python list
to hold the common file names,
and concatenating it with other lists
using the Python +
operator:
common = ['common1.c', 'common2.c'] foo_files = ['foo.c'] + common bar_files = ['bar1.c', 'bar2.c'] + common Program('foo', foo_files) Program('bar', bar_files)
This is functionally equivalent to the previous example.
It's often useful to organize large software projects by collecting parts of the software into one or more libraries. SCons makes it easy to create libraries and to use them in the programs.
You build your own libraries by specifying Library
instead of Program
:
Library('foo', ['f1.c', 'f2.c', 'f3.c'])
SCons uses the appropriate library prefix and suffix for your system. So on POSIX or Linux systems, the above example would build as follows (although ranlib may not be called on all systems):
% scons -Q
cc -o f1.o -c f1.c
cc -o f2.o -c f2.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a
On a Windows system, a build of the above example would look like:
C:\>scons -Q
cl /Fof1.obj /c f1.c /nologo
cl /Fof2.obj /c f2.c /nologo
cl /Fof3.obj /c f3.c /nologo
lib /nologo /OUT:foo.lib f1.obj f2.obj f3.obj
The rules for the target name of the library are similar to those for programs: if you don't explicitly specify a target library name, SCons will deduce one from the name of the first source file specified, and SCons will add an appropriate file prefix and suffix if you leave them off.
The previous example shows building a library from a
list of source files.
You can, however, also give the Library
call
object files,
and it will correctly realize
In fact, you can arbitrarily mix source code files
and object files in the source list:
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])
And SCons realizes that only the source code files must be compiled into object files before creating the final library:
% scons -Q
cc -o f1.o -c f1.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o f4.o
ranlib libfoo.a
Of course, in this example, the object files must already exist for the build to succeed. See Chapter 5, Node Objects, below, for information about how you can build object files explicitly and include the built files in a library.
The Library
function builds a traditional static library.
If you want to be explicit about the type of library being built,
you can use the synonym StaticLibrary
function
instead of Library
:
StaticLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])
There is no functional difference between the
StaticLibrary
and Library
functions.
If you want to build a shared library (on POSIX systems)
or a DLL file (on Windows systems),
you use the SharedLibrary
function:
SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])
The output on POSIX:
% scons -Q
cc -o f1.os -c f1.c
cc -o f2.os -c f2.c
cc -o f3.os -c f3.c
cc -o libfoo.so -shared f1.os f2.os f3.os
And the output on Windows:
C:\>scons -Q
cl /Fof1.obj /c f1.c /nologo
cl /Fof2.obj /c f2.c /nologo
cl /Fof3.obj /c f3.c /nologo
link /nologo /dll /out:foo.dll /implib:foo.lib f1.obj f2.obj f3.obj
RegServerFunc(target, source, env)
embedManifestDllCheck(target, source, env)
Notice again that SCons takes care of
building the output file correctly,
adding the -shared
option
for a POSIX compilation,
and the /dll
option on Windows.
Usually, you build a library
because you want to link it with one or more programs.
You link libraries with a program by specifying
the libraries in the $LIBS
construction variable,
and by specifying the directory in which
the library will be found in the
$LIBPATH
construction variable:
Library('foo', ['f1.c', 'f2.c', 'f3.c']) Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.')
Notice, of course, that you don't need to specify a library
prefix (like lib
)
or suffix (like .a
or .lib
).
SCons uses the correct prefix or suffix for the current system.
On a POSIX or Linux system, a build of the above example would look like:
% scons -Q
cc -o f1.o -c f1.c
cc -o f2.o -c f2.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a
cc -o prog.o -c prog.c
cc -o prog prog.o -L. -lfoo -lbar
On a Windows system, a build of the above example would look like:
C:\>scons -Q
cl /Fof1.obj /c f1.c /nologo
cl /Fof2.obj /c f2.c /nologo
cl /Fof3.obj /c f3.c /nologo
lib /nologo /OUT:foo.lib f1.obj f2.obj f3.obj
cl /Foprog.obj /c prog.c /nologo
link /nologo /OUT:prog.exe /LIBPATH:. foo.lib bar.lib prog.obj
embedManifestExeCheck(target, source, env)
As usual, notice that SCons has taken care of constructing the correct command lines to link with the specified library on each system.
Note also that, if you only have a single library to link with, you can specify the library name in single string, instead of a Python list, so that:
Program('prog.c', LIBS='foo', LIBPATH='.')
is equivalent to:
Program('prog.c', LIBS=['foo'], LIBPATH='.')
This is similar to the way that SCons handles either a string or a list to specify a single source file.
By default, the linker will only look in
certain system-defined directories for libraries.
SCons knows how to look for libraries
in directories that you specify with the
$LIBPATH
construction variable.
$LIBPATH
consists of a list of
directory names, like so:
Program('prog.c', LIBS = 'm', LIBPATH = ['/usr/lib', '/usr/local/lib'])
Using a Python list is preferred because it's portable across systems. Alternatively, you could put all of the directory names in a single string, separated by the system-specific path separator character: a colon on POSIX systems:
LIBPATH = '/usr/lib:/usr/local/lib'
or a semi-colon on Windows systems:
LIBPATH = 'C:\\lib;D:\\lib'
(Note that Python requires that the backslash separators in a Windows path name be escaped within strings.)
When the linker is executed, SCons will create appropriate flags so that the linker will look for libraries in the same directories as SCons. So on a POSIX or Linux system, a build of the above example would look like:
% scons -Q
cc -o prog.o -c prog.c
cc -o prog prog.o -L/usr/lib -L/usr/local/lib -lm
On a Windows system, a build of the above example would look like:
C:\>scons -Q
cl /Foprog.obj /c prog.c /nologo
link /nologo /OUT:prog.exe /LIBPATH:\usr\lib /LIBPATH:\usr\local\lib m.lib prog.obj
embedManifestExeCheck(target, source, env)
Note again that SCons has taken care of the system-specific details of creating the right command-line options.
Internally, SCons represents all of the files
and directories it knows about as Nodes
.
These internal objects
(not object files)
can be used in a variety of ways
to make your SConscript
files portable and easy to read.
All builder methods return a list of
Node
objects that identify the
target file or files that will be built.
These returned Nodes
can be passed
as arguments to other builder methods.
For example, suppose that we want to build
the two object files that make up a program with different options.
This would mean calling the Object
builder once for each object file,
specifying the desired options:
Object('hello.c', CCFLAGS='-DHELLO') Object('goodbye.c', CCFLAGS='-DGOODBYE')
One way to combine these object files
into the resulting program
would be to call the Program
builder with the names of the object files
listed as sources:
Object('hello.c', CCFLAGS='-DHELLO') Object('goodbye.c', CCFLAGS='-DGOODBYE') Program(['hello.o', 'goodbye.o'])
The problem with specifying the names as strings
is that our SConstruct
file is no longer portable
across operating systems.
It won't, for example, work on Windows
because the object files there would be
named hello.obj
and goodbye.obj
,
not hello.o
and goodbye.o
.
A better solution is to assign the lists of targets
returned by the calls to the Object
builder to variables,
which we can then concatenate in our
call to the Program
builder:
hello_list = Object('hello.c', CCFLAGS='-DHELLO') goodbye_list = Object('goodbye.c', CCFLAGS='-DGOODBYE') Program(hello_list + goodbye_list)
This makes our SConstruct
file portable again,
the build output on Linux looking like:
% scons -Q
cc -o goodbye.o -c -DGOODBYE goodbye.c
cc -o hello.o -c -DHELLO hello.c
cc -o hello hello.o goodbye.o
And on Windows:
C:\>scons -Q
cl /Fogoodbye.obj /c goodbye.c -DGOODBYE
cl /Fohello.obj /c hello.c -DHELLO
link /nologo /OUT:hello.exe hello.obj goodbye.obj
embedManifestExeCheck(target, source, env)
We'll see examples of using the list of nodes returned by builder methods throughout the rest of this guide.
It's worth mentioning here that
SCons maintains a clear distinction
between Nodes that represent files
and Nodes that represent directories.
SCons supports File
and Dir
functions that, respectively,
return a file or directory Node:
hello_c = File('hello.c') Program(hello_c) classes = Dir('classes') Java(classes, 'src')
Normally, you don't need to call
File
or Dir
directly,
because calling a builder method automatically
treats strings as the names of files or directories,
and translates them into
the Node objects for you.
The File
and Dir
functions can come in handy
in situations where you need to explicitly
instruct SCons about the type of Node being
passed to a builder or other function,
or unambiguously refer to a specific
file in a directory tree.
There are also times when you may need to
refer to an entry in a file system
without knowing in advance
whether it's a file or a directory.
For those situations,
SCons also supports an Entry
function,
which returns a Node
that can represent either a file or a directory.
xyzzy = Entry('xyzzy')
The returned xyzzy
Node
will be turned into a file or directory Node
the first time it is used by a builder method
or other function that
requires one vs. the other.
One of the most common things you can do
with a Node is use it to print the
file name that the node represents.
Keep in mind, though, that because the object
returned by a builder call
is a list of Nodes,
you must use Python subscripts
to fetch individual Nodes from the list.
For example, the following SConstruct
file:
object_list = Object('hello.c') program_list = Program(object_list) print "The object file is:", object_list[0] print "The program file is:", program_list[0]
Would print the following file names on a POSIX system:
% scons -Q
The object file is: hello.o
The program file is: hello
cc -o hello.o -c hello.c
cc -o hello hello.o
And the following file names on a Windows system:
C:\>scons -Q
The object file is: hello.obj
The program file is: hello.exe
cl /Fohello.obj /c hello.c /nologo
link /nologo /OUT:hello.exe hello.obj
embedManifestExeCheck(target, source, env)
Note that in the above example,
the object_list[0]
extracts an actual Node object
from the list,
and the Python print
statement
converts the object to a string for printing.
Printing a Node
's name
as described in the previous section
works because the string representation of a Node
object
is the name of the file.
If you want to do something other than
print the name of the file,
you can fetch it by using the builtin Python
str
function.
For example, if you want to use the Python
os.path.exists
to figure out whether a file
exists while the SConstruct
file
is being read and executed,
you can fetch the string as follows:
import os.path program_list = Program('hello.c') program_name = str(program_list[0]) if not os.path.exists(program_name): print program_name, "does not exist!"
Which executes as follows on a POSIX system:
% scons -Q
hello does not exist!
cc -o hello.o -c hello.c
cc -o hello hello.o
env.GetBuildPath(file_or_list)
returns the path of a Node
or a string representing a
path. It can also take a list of Node
s and/or strings, and
returns the list of paths. If passed a single Node
, the result
is the same as calling str(node)
(see above).
The string(s) can have embedded construction variables, which are
expanded as usual, using the calling environment's set of
variables. The paths can be files or directories, and do not have
to exist.
env=Environment(VAR="value") n=File("foo.c") print env.GetBuildPath([n, "sub/dir/$VAR"])
Would print the following file names:
% scons -Q
['foo.c', 'sub/dir/value']
scons: `.' is up to date.
There is also a function version of GetBuildPath
which can
be called without an Environment
; that uses the default SCons
Environment
to do substitution on any string arguments.
So far we've seen how SCons handles one-time builds.
But one of the main functions of a build tool like SCons
is to rebuild only what is necessary
when source files change--or, put another way,
SCons should not
waste time rebuilding things that don't need to be rebuilt.
You can see this at work simply by re-invoking SCons
after building our simple hello
example:
%scons -Q
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q
scons: `.' is up to date.
The second time it is executed,
SCons realizes that the hello
program
is up-to-date with respect to the current hello.c
source file,
and avoids rebuilding it.
You can see this more clearly by naming
the hello
program explicitly on the command line:
%scons -Q hello
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q hello
scons: `hello' is up to date.
Note that SCons reports "...is up to date"
only for target files named explicitly on the command line,
to avoid cluttering the output.
Another aspect of avoiding unnecessary rebuilds
is the fundamental build tool behavior
of rebuilding
things when an input file changes,
so that the built software is up to date.
By default,
SCons keeps track of this through an
MD5 signature
, or checksum, of the contents of each file,
although you can easily configure
SCons to use the
modification times (or time stamps)
instead.
You can even specify your own Python function
for deciding if an input file has changed.
By default, SCons keeps track of whether a file has changed based on an MD5 checksum of the file's contents, not the file's modification time. This means that you may be surprised by the default SCons behavior if you are used to the Make convention of forcing a rebuild by updating the file's modification time (using the touch command, for example):
%scons -Q hello
cc -o hello.o -c hello.c cc -o hello hello.o %touch hello.c
%scons -Q hello
scons: `hello' is up to date.
Even though the file's modification time has changed,
SCons realizes that the contents of the
hello.c
file have not changed,
and therefore that the hello
program
need not be rebuilt.
This avoids unnecessary rebuilds when,
for example, someone rewrites the
contents of a file without making a change.
But if the contents of the file really do change,
then SCons detects the change
and rebuilds the program as required:
%scons -Q hello
cc -o hello.o -c hello.c cc -o hello hello.o % [CHANGE THE CONTENTS OF hello.c] %scons -Q hello
cc -o hello.o -c hello.c cc -o hello hello.o
Note that you can, if you wish,
specify this default behavior
(MD5 signatures) explicitly
using the Decider
function as follows:
Program('hello.c') Decider('MD5')
You can also use the string 'content'
as a synonym for 'MD5'
when calling the Decider
function.
Using MD5 signatures to decide if an input file has changed has one surprising benefit: if a source file has been changed in such a way that the contents of the rebuilt target file(s) will be exactly the same as the last time the file was built, then any "downstream" target files that depend on the rebuilt-but-not-changed target file actually need not be rebuilt.
So if, for example,
a user were to only change a comment in a hello.c
file,
then the rebuilt hello.o
file
would be exactly the same as the one previously built
(assuming the compiler doesn't put any build-specific
information in the object file).
SCons would then realize that it would not
need to rebuild the hello
program as follows:
%scons -Q hello
cc -o hello.o -c hello.c cc -o hello hello.o % [CHANGE A COMMENT IN hello.c] %scons -Q hello
cc -o hello.o -c hello.c scons: `hello' is up to date.
In essence, SCons
"short-circuits" any dependent builds
when it realizes that a target file
has been rebuilt to exactly the same file as the last build.
This does take some extra processing time
to read the contents of the target (hello.o
) file,
but often saves time when the rebuild that was avoided
would have been time-consuming and expensive.
If you prefer, you can configure SCons to use the modification time of a file, not the file contents, when deciding if a target needs to be rebuilt. SCons gives you two ways to use time stamps to decide if an input file has changed since the last time a target has been built.
The most familiar way to use time stamps
is the way Make does:
that is, have SCons decide
that a target must be rebuilt
if a source file's modification time is
newer
than the target file.
To do this, call the Decider
function as follows:
Object('hello.c') Decider('timestamp-newer')
This makes SCons act like Make when a file's modification time is updated (using the touch command, for example):
%scons -Q hello.o
cc -o hello.o -c hello.c %touch hello.c
%scons -Q hello.o
cc -o hello.o -c hello.c
And, in fact, because this behavior is the same
as the behavior of Make,
you can also use the string 'make'
as a synonym for 'timestamp-newer'
when calling the Decider
function:
Object('hello.c') Decider('make')
One drawback to using times stamps exactly like Make is that if an input file's modification time suddenly becomes older than a target file, the target file will not be rebuilt. This can happen if an old copy of a source file is restored from a backup archive, for example. The contents of the restored file will likely be different than they were the last time a dependent target was built, but the target won't be rebuilt because the modification time of the source file is not newer than the target.
Because SCons actually stores information
about the source files' time stamps whenever a target is built,
it can handle this situation by checking for
an exact match of the source file time stamp,
instead of just whether or not the source file
is newer than the target file.
To do this, specify the argument
'timestamp-match'
when calling the Decider
function:
Object('hello.c') Decider('timestamp-match')
When configured this way,
SCons will rebuild a target whenever
a source file's modification time has changed.
So if we use the touch -t
option to change the modification time of
hello.c
to an old date (January 1, 1989),
SCons will still rebuild the target file:
%scons -Q hello.o
cc -o hello.o -c hello.c %touch -t 198901010000 hello.c
%scons -Q hello.o
cc -o hello.o -c hello.c
In general, the only reason to prefer
timestamp-newer
instead of
timestamp-match
,
would be if you have some specific reason
to require this Make-like behavior of
not rebuilding a target when an otherwise-modified
source file is older.
As a performance enhancement,
SCons provides a way to use
MD5 checksums of file contents
but to read those contents
only when the file's timestamp has changed.
To do this, call the Decider
function with 'MD5-timestamp'
argument as follows:
Program('hello.c') Decider('MD5-timestamp')
So configured, SCons will still behave like
it does when using Decider('MD5')
:
%scons -Q hello
cc -o hello.o -c hello.c cc -o hello hello.o %touch hello.c
%scons -Q hello
scons: `hello' is up to date. %edit hello.c
[CHANGE THE CONTENTS OF hello.c] %scons -Q hello
cc -o hello.o -c hello.c cc -o hello hello.o
However, the second call to SCons in the above output,
when the build is up-to-date,
will have been performed by simply looking at the
modification time of the hello.c
file,
not by opening it and performing
an MD5 checksum calcuation on its contents.
This can significantly speed up many up-to-date builds.
The only drawback to using
Decider('MD5-timestamp')
is that SCons will not
rebuild a target file if a source file was modified
within one second of the last time SCons built the file.
While most developers are programming,
this isn't a problem in practice,
since it's unlikely that someone will have built
and then thought quickly enough to make a substantive
change to a source file within one second.
Certain build scripts or
continuous integration tools may, however,
rely on the ability to apply changes to files
automatically and then rebuild as quickly as possible,
in which case use of
Decider('MD5-timestamp')
may not be appropriate.
The different string values that we've passed to
the Decider
function are essentially used by SCons
to pick one of several specific internal functions
that implement various ways of deciding if a dependency
(usually a source file)
has changed since a target file has been built.
As it turns out,
you can also supply your own function
to decide if a dependency has changed.
For example, suppose we have an input file
that contains a lot of data,
in some specific regular format,
that is used to rebuild a lot of different target files,
but each target file really only depends on
one particular section of the input file.
We'd like to have each target file depend on
only its section of the input file.
However, since the input file may contain a lot of data,
we want to open the input file only if its timestamp has changed.
This could be done with a custom
Decider
function that might look something like this:
Program('hello.c') def decide_if_changed(dependency, target, prev_ni): if self.get_timestamp() != prev_ni.timestamp: dep = str(dependency) tgt = str(target) if specific_part_of_file_has_changed(dep, tgt): return True return False Decider(decide_if_changed)
Note that in the function definition,
the dependency
(input file) is the first argument,
and then the target
.
Both of these are passed to the functions as
SCons Node
objects,
which we convert to strings using the Python
str()
.
The third argument, prev_ni
,
is an object that holds the
signature or timestamp information
that was recorded about the dependency
the last time the target was built.
A prev_ni
object can hold
different information,
depending on the type of thing that the
dependency
argument represents.
For normal files,
the prev_ni
object
has the following attributes:
The content signature,
or MD5 checksum, of the contents of the
dependency
file the list time the target
was built.
The size in bytes of the dependency
file the list time the target was built.
The modification time of the dependency
file the list time the target
was built.
Note that ignoring some of the arguments
in your custom Decider
function
is a perfectly normal thing to do,
if they don't impact the way you want to
decide if the dependency file has changed.
Another thing to look out for is the fact that the three
attributes above may not be present at the time of the first run.
Without any prior build, no targets have been created and no
.sconsign
DB file exists yet.
So, you should always check whether the
prev_ni
attribute in question is available.
We finally present a small example for a
csig
-based decider function. Note how the
signature information for the dependency
file
has to get initialized via get_csig
during each function call (this is mandatory!).
env = Environment() def config_file_decider(dependency, target, prev_ni): import os.path # We always have to init the .csig value... dep_csig = dependency.get_csig() # .csig may not exist, because no target was built yet... if 'csig' not in dir(prev_ni): return True # Target file may not exist yet if not os.path.exists(str(target.abspath)): return True if dep_csig != prev_ni.csig: # Some change on source file => update installed one return True return False def update_file(): f = open("test.txt","a") f.write("some line\n") f.close() update_file() # Activate our own decider function env.Decider(config_file_decider) env.Install("install","test.txt")
The previous examples have all demonstrated calling
the global Decider
function
to configure all dependency decisions that SCons makes.
Sometimes, however, you want to be able to configure
different decision-making for different targets.
When that's necessary, you can use the
env.Decider
method to affect only the configuration
decisions for targets built with a
specific construction environment.
For example, if we arbitrarily want to build one program using MD5 checkums and another using file modification times from the same source we might configure it this way:
env1 = Environment(CPPPATH = ['.']) env2 = env1.Clone() env2.Decider('timestamp-match') env1.Program('prog-MD5', 'program1.c') env2.Program('prog-timestamp', 'program2.c')
If both of the programs include the same
inc.h
file,
then updating the modification time of
inc.h
(using the touch command)
will cause only prog-timestamp
to be rebuilt:
%scons -Q
cc -o program1.o -c -I. program1.c cc -o prog-MD5 program1.o cc -o program2.o -c -I. program2.c cc -o prog-timestamp program2.o %touch inc.h
%scons -Q
cc -o program2.o -c -I. program2.c cc -o prog-timestamp program2.o
SCons still supports two functions that used to be the
primary methods for configuring the
decision about whether or not an input file has changed.
These functions have been officially deprecated
as SCons version 2.0,
and their use is discouraged,
mainly because they rely on a somewhat
confusing distinction between how
source files and target files are handled.
These functions are documented here mainly in case you
encounter them in older SConscript
files.
The SourceSignatures
function is fairly straightforward,
and supports two different argument values
to configure whether source file changes should be decided
using MD5 signatures:
Program('hello.c') SourceSignatures('MD5')
Or using time stamps:
Program('hello.c') SourceSignatures('timestamp')
These are roughly equivalent to specifying
Decider('MD5')
or
Decider('timestamp-match')
,
respectively,
although it only affects how SCons makes
decisions about dependencies on
source files--that is,
files that are not built from any other files.
The TargetSignatures
function
specifies how SCons decides
when a target file has changed
when it is used as a
dependency of (input to) another target--that is,
the TargetSignatures
function configures
how the signatures of "intermediate" target files
are used when deciding if a "downstream" target file
must be rebuilt.
[2]
The TargetSignatures
function supports the same
'MD5'
and 'timestamp'
argument values that are supported by the SourceSignatures
,
with the same meanings, but applied to target files.
That is, in the example:
Program('hello.c') TargetSignatures('MD5')
The MD5 checksum of the hello.o
target file
will be used to decide if it has changed since the last
time the "downstream" hello
target file was built.
And in the example:
Program('hello.c') TargetSignatures('timestamp')
The modification time of the hello.o
target file
will be used to decide if it has changed since the last
time the "downstream" hello
target file was built.
The TargetSignatures
function supports
two additional argument values:
'source'
and 'build'
.
The 'source'
argument
specifies that decisions involving
whether target files have changed
since a previous build
should use the same behavior
for the decisions configured for source files
(using the SourceSignatures
function).
So in the example:
Program('hello.c') TargetSignatures('source') SourceSignatures('timestamp')
All files, both targets and sources, will use modification times when deciding if an input file has changed since the last time a target was built.
Lastly, the 'build'
argument
specifies that SCons should examine
the build status of a target file
and always rebuild a "downstream" target
if the target file was itself rebuilt,
without re-examining the contents or timestamp
of the newly-built target file.
If the target file was not rebuilt during
this scons
invocation,
then the target file will be examined
the same way as configured by
the SourceSignature
call
to decide if it has changed.
This mimics the behavior of
build signatures
in earlier versions of SCons.
A build signature
re-combined
signatures of all the input files
that went into making the target file,
so that the target file itself
did not need to have its contents read
to compute an MD5 signature.
This can improve performance for some configurations,
but is generally not as effective as using
Decider('MD5-timestamp')
.
Now suppose that our "Hello, World!" program
actually has an #include
line
to include the hello.h
file in the compilation:
#include <hello.h> int main() { printf("Hello, %s!\n", string); }
And, for completeness, the hello.h
file looks like this:
#define string "world"
In this case, we want SCons to recognize that,
if the contents of the hello.h
file change,
the hello
program must be recompiled.
To do this, we need to modify the
SConstruct
file like so:
Program('hello.c', CPPPATH = '.')
The $CPPPATH
value
tells SCons to look in the current directory
('.'
)
for any files included by C source files
(.c
or .h
files).
With this assignment in the SConstruct
file:
%scons -Q hello
cc -o hello.o -c -I. hello.c cc -o hello hello.o %scons -Q hello
scons: `hello' is up to date. % [CHANGE THE CONTENTS OF hello.h] %scons -Q hello
cc -o hello.o -c -I. hello.c cc -o hello hello.o
First, notice that SCons
added the -I.
argument
from the $CPPPATH
variable
so that the compilation would find the
hello.h
file in the local directory.
Second, realize that SCons knows that the hello
program must be rebuilt
because it scans the contents of
the hello.c
file
for the #include
lines that indicate
another file is being included in the compilation.
SCons records these as
implicit dependencies
of the target file,
Consequently,
when the hello.h
file changes,
SCons realizes that the hello.c
file includes it,
and rebuilds the resulting hello
program
that depends on both the hello.c
and hello.h
files.
Like the $LIBPATH
variable,
the $CPPPATH
variable
may be a list of directories,
or a string separated by
the system-specific path separation character
(':' on POSIX/Linux, ';' on Windows).
Either way, SCons creates the
right command-line options
so that the following example:
Program('hello.c', CPPPATH = ['include', '/home/project/inc'])
Will look like this on POSIX or Linux:
% scons -Q hello
cc -o hello.o -c -Iinclude -I/home/project/inc hello.c
cc -o hello hello.o
And like this on Windows:
C:\>scons -Q hello.exe
cl /Fohello.obj /c hello.c /nologo /Iinclude /I\home\project\inc
link /nologo /OUT:hello.exe hello.obj
embedManifestExeCheck(target, source, env)
Scanning each file for #include
lines
does take some extra processing time.
When you're doing a full build of a large system,
the scanning time is usually a very small percentage
of the overall time spent on the build.
You're most likely to notice the scanning time,
however, when you rebuild
all or part of a large system:
SCons will likely take some extra time to "think about"
what must be built before it issues the
first build command
(or decides that everything is up to date
and nothing must be rebuilt).
In practice, having SCons scan files saves time
relative to the amount of potential time
lost to tracking down subtle problems
introduced by incorrect dependencies.
Nevertheless, the "waiting time"
while SCons scans files can annoy
individual developers waiting for their builds to finish.
Consequently, SCons lets you cache
the implicit dependencies
that its scanners find,
for use by later builds.
You can do this by specifying the
--implicit-cache
option on the command line:
%scons -Q --implicit-cache hello
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q hello
scons: `hello' is up to date.
If you don't want to specify --implicit-cache
on the command line each time,
you can make it the default behavior for your build
by setting the implicit_cache
option
in an SConscript
file:
SetOption('implicit_cache', 1)
SCons does not cache implicit dependencies like this by default
because the --implicit-cache
causes SCons to simply use the implicit
dependencies stored during the last run, without any checking
for whether or not those dependencies are still correct.
Specifically, this means --implicit-cache
instructs SCons
to not rebuild "correctly" in the
following cases:
When --implicit-cache
is used, SCons will ignore any changes that
may have been made to search paths
(like $CPPPATH
or $LIBPATH
,).
This can lead to SCons not rebuilding a file if a change to
$CPPPATH
would normally cause a different, same-named file from
a different directory to be used.
When --implicit-cache
is used, SCons will not detect if a
same-named file has been added to a directory that is earlier in
the search path than the directory in which the file was found
last time.
When using cached implicit dependencies,
sometimes you want to "start fresh"
and have SCons re-scan the files
for which it previously cached the dependencies.
For example,
if you have recently installed a new version of
external code that you use for compilation,
the external header files will have changed
and the previously-cached implicit dependencies
will be out of date.
You can update them by
running SCons with the --implicit-deps-changed
option:
%scons -Q --implicit-deps-changed hello
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q hello
scons: `hello' is up to date.
In this case, SCons will re-scan all of the implicit dependencies and cache updated copies of the information.
By default when caching dependencies,
SCons notices when a file has been modified
and re-scans the file for any updated
implicit dependency information.
Sometimes, however, you may want
to force SCons to use the cached implicit dependencies,
even if the source files changed.
This can speed up a build for example,
when you have changed your source files
but know that you haven't changed
any #include
lines.
In this case,
you can use the --implicit-deps-unchanged
option:
%scons -Q --implicit-deps-unchanged hello
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q hello
scons: `hello' is up to date.
In this case, SCons will assume that the cached implicit dependencies are correct and will not bother to re-scan changed files. For typical builds after small, incremental changes to source files, the savings may not be very big, but sometimes every bit of improved performance counts.
Sometimes a file depends on another file
that is not detected by an SCons scanner.
For this situation,
SCons allows you to specific explicitly that one file
depends on another file,
and must be rebuilt whenever that file changes.
This is specified using the Depends
method:
hello = Program('hello.c') Depends(hello, 'other_file')
%scons -Q hello
cc -c hello.c -o hello.o cc -o hello hello.o %scons -Q hello
scons: `hello' is up to date. %edit other_file
[CHANGE THE CONTENTS OF other_file] %scons -Q hello
cc -c hello.c -o hello.o cc -o hello hello.o
Note that the dependency
(the second argument to Depends
)
may also be a list of Node objects
(for example, as returned by a call to a Builder):
hello = Program('hello.c') goodbye = Program('goodbye.c') Depends(hello, goodbye)
in which case the dependency or dependencies will be built before the target(s):
% scons -Q hello
cc -c goodbye.c -o goodbye.o
cc -o goodbye goodbye.o
cc -c hello.c -o hello.o
cc -o hello hello.o
SCons has built-in scanners for a number of languages. Sometimes these scanners fail to extract certain implicit dependencies due to limitations of the scanner implementation.
The following example illustrates a case where the built-in C scanner is unable to extract the implicit dependency on a header file.
#define FOO_HEADER <foo.h> #include FOO_HEADER int main() { return FOO; }
%scons -Q
cc -o hello.o -c -I. hello.c cc -o hello hello.o % [CHANGE CONTENTS OF foo.h] %scons -Q
scons: `.' is up to date.
Apparently, the scanner does not know about the header dependency. Being not a full-fledged C preprocessor, the scanner does not expand the macro.
In these cases, you may also use the compiler to extract the
implicit dependencies. ParseDepends
can parse the contents of
the compiler output in the style of Make, and explicitly
establish all of the listed dependencies.
The following example uses ParseDepends
to process a compiler
generated dependency file which is generated as a side effect
during compilation of the object file:
obj = Object('hello.c', CCFLAGS='-MD -MF hello.d', CPPPATH='.') SideEffect('hello.d', obj) ParseDepends('hello.d') Program('hello', obj)
%scons -Q
cc -o hello.o -c -MD -MF hello.d -I. hello.c cc -o hello hello.o % [CHANGE CONTENTS OF foo.h] %scons -Q
cc -o hello.o -c -MD -MF hello.d -I. hello.c
Parsing dependencies from a compiler-generated
.d
file has a chicken-and-egg problem, that
causes unnecessary rebuilds:
%scons -Q
cc -o hello.o -c -MD -MF hello.d -I. hello.c cc -o hello hello.o %scons -Q --debug=explain
scons: rebuilding `hello.o' because `foo.h' is a new dependency cc -o hello.o -c -MD -MF hello.d -I. hello.c %scons -Q
scons: `.' is up to date.
In the first pass, the dependency file is generated while the
object file is compiled. At that time, SCons does not know about
the dependency on foo.h
. In the second pass,
the object file is regenerated because foo.h
is detected as a new dependency.
ParseDepends
immediately reads the specified file at invocation
time and just returns if the file does not exist. A dependency
file generated during the build process is not automatically
parsed again. Hence, the compiler-extracted dependencies are not
stored in the signature database during the same build pass. This
limitation of ParseDepends
leads to unnecessary recompilations.
Therefore, ParseDepends
should only be used if scanners are not
available for the employed language or not powerful enough for the
specific task.
Sometimes it makes sense to not rebuild a program, even if a dependency file changes. In this case, you would tell SCons specifically to ignore a dependency as follows:
hello_obj=Object('hello.c') hello = Program(hello_obj) Ignore(hello_obj, 'hello.h')
%scons -Q hello
cc -c -o hello.o hello.c cc -o hello hello.o %scons -Q hello
scons: `hello' is up to date. %edit hello.h
[CHANGE THE CONTENTS OF hello.h] %scons -Q hello
scons: `hello' is up to date.
Now, the above example is a little contrived,
because it's hard to imagine a real-world situation
where you wouldn't want to rebuild hello
if the hello.h
file changed.
A more realistic example
might be if the hello
program is being built in a
directory that is shared between multiple systems
that have different copies of the
stdio.h
include file.
In that case,
SCons would notice the differences between
the different systems' copies of stdio.h
and would rebuild hello
each time you change systems.
You could avoid these rebuilds as follows:
hello = Program('hello.c', CPPPATH=['/usr/include']) Ignore(hello, '/usr/include/stdio.h')
Ignore
can also be used to prevent a generated file from being built
by default. This is due to the fact that directories depend on
their contents. So to ignore a generated file from the default build,
you specify that the directory should ignore the generated file.
Note that the file will still be built if the user specifically
requests the target on scons command line, or if the file is
a dependency of another file which is requested and/or is built
by default.
hello_obj=Object('hello.c') hello = Program(hello_obj) Ignore('.',[hello,hello_obj])
%scons -Q
scons: `.' is up to date. %scons -Q hello
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q hello
scons: `hello' is up to date.
Occasionally, it may be useful to specify that a certain file or directory must, if necessary, be built or created before some other target is built, but that changes to that file or directory do not require that the target itself be rebuilt. Such a relationship is called an order-only dependency because it only affects the order in which things must be built--the dependency before the target--but it is not a strict dependency relationship because the target should not change in response to changes in the dependent file.
For example, suppose that you want to create a file
every time you run a build
that identifies the time the build was performed,
the version number, etc.,
and which is included in every program that you build.
The version file's contents will change every build.
If you specify a normal dependency relationship,
then every program that depends on
that file would be rebuilt every time you ran SCons.
For example, we could use some Python code in
a SConstruct
file to create a new version.c
file
with a string containing the current date every time
we run SCons,
and then link a program with the resulting object file
by listing version.c
in the sources:
import time version_c_text = """ char *date = "%s"; """ % time.ctime(time.time()) open('version.c', 'w').write(version_c_text) hello = Program(['hello.c', 'version.c'])
If we list version.c
as an actual source file,
though, then the version.o
file
will get rebuilt every time we run SCons
(because the SConstruct
file itself changes
the contents of version.c
)
and the hello
executable
will get re-linked every time
(because the version.o
file changes):
%scons -Q hello
cc -o hello.o -c hello.c cc -o version.o -c version.c cc -o hello hello.o version.o %sleep 1
%scons -Q hello
cc -o version.o -c version.c cc -o hello hello.o version.o %sleep 1
%scons -Q hello
cc -o version.o -c version.c cc -o hello hello.o version.o
(Note that for the above example to work,
we sleep for one second in between each run,
so that the SConstruct
file will create a
version.c
file with a time string
that's one second later than the previous run.)
One solution is to use the Requires
function
to specify that the version.o
must be rebuilt before it is used by the link step,
but that changes to version.o
should not actually cause the hello
executable to be re-linked:
import time version_c_text = """ char *date = "%s"; """ % time.ctime(time.time()) open('version.c', 'w').write(version_c_text) version_obj = Object('version.c') hello = Program('hello.c', LINKFLAGS = str(version_obj[0])) Requires(hello, version_obj)
Notice that because we can no longer list version.c
as one of the sources for the hello
program,
we have to find some other way to get it into the link command line.
For this example, we're cheating a bit and stuffing the
object file name (extracted from version_obj
list returned by the Object
call)
into the $LINKFLAGS
variable,
because $LINKFLAGS
is already included
in the $LINKCOM
command line.
With these changes,
we get the desired behavior of only
re-linking the hello
executable
when the hello.c
has changed,
even though the version.o
is rebuilt
(because the SConstruct
file still changes the
version.c
contents directly each run):
%scons -Q hello
cc -o version.o -c version.c cc -o hello.o -c hello.c cc -o hello version.o hello.o %sleep 1
%scons -Q hello
cc -o version.o -c version.c scons: `hello' is up to date. %sleep 1
% [CHANGE THE CONTENTS OF hello.c] %scons -Q hello
cc -o version.o -c version.c cc -o hello.o -c hello.c cc -o hello version.o hello.o %sleep 1
%scons -Q hello
cc -o version.o -c version.c scons: `hello' is up to date.
How SCons handles dependencies can also be affected
by the AlwaysBuild
method.
When a file is passed to the AlwaysBuild
method,
like so:
hello = Program('hello.c') AlwaysBuild(hello)
Then the specified target file (hello
in our example)
will always be considered out-of-date and
rebuilt whenever that target file is evaluated
while walking the dependency graph:
%scons -Q
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q
cc -o hello hello.o
The AlwaysBuild
function has a somewhat misleading name,
because it does not actually mean the target file will
be rebuilt every single time SCons is invoked.
Instead, it means that the target will, in fact,
be rebuilt whenever the target file is encountered
while evaluating the targets specified on
the command line (and their dependencies).
So specifying some other target on the command line,
a target that does not
itself depend on the AlwaysBuild
target,
will still be rebuilt only if it's out-of-date
with respect to its dependencies:
%scons -Q
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q hello.o
scons: `hello.o' is up to date.
[2]
This easily-overlooked distinction between
how SCons decides if the target itself must be rebuilt
and how the target is then used to decide if a different
target must be rebuilt is one of the confusing
things that has led to the TargetSignatures
and SourceSignatures
functions being
replaced by the simpler Decider
function.
An environment
is a collection of values that
can affect how a program executes.
SCons distinguishes between three
different types of environments
that can affect the behavior of SCons itself
(subject to the configuration in the SConscript
files),
as well as the compilers and other tools it executes:
The external environment
is the set of variables in the user's environment
at the time the user runs SCons.
These variables are available within the SConscript
files
through the Python os.environ
dictionary.
See Section 7.1, “Using Values From the External Environment”, below.
Construction Environment
A construction environment
is a distinct object creating within
a SConscript
file and
and which contains values that
affect how SCons decides
what action to use to build a target,
and even to define which targets
should be built from which sources.
One of the most powerful features of SCons
is the ability to create multiple construction environments
,
including the ability to clone a new, customized
construction environment
from an existing construction environment
.
See Section 7.2, “Construction Environments”, below.
An execution environment
is the values that SCons sets
when executing an external
command (such as a compiler or linker)
to build one or more targets.
Note that this is not the same as
the external environment
(see above).
See Section 7.3, “Controlling the Execution Environment for Issued Commands”, below.
Unlike Make, SCons does not automatically
copy or import values between different environments
(with the exception of explicit clones of construction environments
,
which inherit values from their parent).
This is a deliberate design choice
to make sure that builds are,
by default, repeatable regardless of
the values in the user's external environment.
This avoids a whole class of problems with builds
where a developer's local build works
because a custom variable setting
causes a different compiler or build option to be used,
but the checked-in change breaks the official build
because it uses different environment variable settings.
Note that the SConscript
writer can
easily arrange for variables to be
copied or imported between environments,
and this is often very useful
(or even downright necessary)
to make it easy for developers
to customize the build in appropriate ways.
The point is not
that copying variables between different environments
is evil and must always be avoided.
Instead, it should be up to the
implementer of the build system
to make conscious choices
about how and when to import
a variable from one environment to another,
making informed decisions about
striking the right balance
between making the build
repeatable on the one hand
and convenient to use on the other.
The external environment
variable settings that
the user has in force
when executing SCons
are available through the normal Python
os.environ
dictionary.
This means that you must add an
import os
statement
to any SConscript
file
in which you want to use
values from the user's external environment.
import os
More usefully, you can use the
os.environ
dictionary in your SConscript
files to initialize construction environments
with values from the user's external environment.
See the next section,
Section 7.2, “Construction Environments”,
for information on how to do this.
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:", 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, 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 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
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:", 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.
If a problem occurs when expanding a construction variable,
by default it is expanded to ''
(a null string), and will not cause scons to fail.
env = Environment() print "value is:", 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:", env.subst( '->$MISSING<-' )
% scons -Q
value is:
scons: *** NameError `MISSING' 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.
All of the Builder
functions that we've introduced so far,
like Program
and Library
,
actually use a default 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.
The goal of the default construction environment
is to make many configurations to "just work"
to build software using
readily available tools
with a minimum of configuration changes.
You can, however, control the settings
in the default construction environment
by using the DefaultEnvironment
function
to initialize various settings:
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.
Note that the DefaultEnvironment
function
returns the initialized
default construction environment object,
which can then be manipulated like any
other construction environment.
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 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
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 =", 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 $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 =", 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 =", 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.
When SCons builds a target file,
it does not execute the commands with
the same external environment
that you used to execute SCons.
Instead, it uses the dictionary
stored in the $ENV
construction variable
as the external environment
for executing commands.
The most important ramification of this behavior
is that the PATH
environment variable,
which controls where the operating system
will look for commands and utilities,
is not the same as in the external environment
from which you called SCons.
This means that SCons will not, by default,
necessarily find all of the tools
that you can execute from the command line.
The default value of the PATH
environment variable
on a POSIX system
is /usr/local/bin:/bin:/usr/bin
.
The default value of the PATH
environment variable
on a Windows system comes from the Windows registry
value for the command interpreter.
If you want to execute any commands--compilers, linkers, etc.--that
are not in these default locations,
you need to set the PATH
value
in the $ENV
dictionary
in your construction environment.
The simplest way to do this is to initialize explicitly the value when you create the construction environment; this is one way to do that:
path = ['/usr/local/bin', '/bin', '/usr/bin'] env = Environment(ENV = {'PATH' : path})
Assign a dictionary to the $ENV
construction variable in this way
completely resets the external environment
so that the only variable that will be
set when external commands are executed
will be the PATH
value.
If you want to use the rest of
the values in $ENV
and only
set the value of PATH
,
the most straightforward way is probably:
env['ENV']['PATH'] = ['/usr/local/bin', '/bin', '/usr/bin']
Note that SCons does allow you to define
the directories in the PATH
in a string,
separated by the pathname-separator character
for your system (':' on POSIX systems, ';' on Windows):
env['ENV']['PATH'] = '/usr/local/bin:/bin:/usr/bin'
But doing so makes your SConscript
file less portable,
(although in this case that may not be a huge concern
since the directories you list are likley system-specific, anyway).
You may want to propagate the external PATH
to the execution environment for commands.
You do this by initializing the PATH
variable with the PATH
value from
the os.environ
dictionary,
which is Python's way of letting you
get at the external environment:
import os env = Environment(ENV = {'PATH' : os.environ['PATH']})
Alternatively, you may find it easier
to just propagate the entire external
environment to the execution environment
for commands.
This is simpler to code than explicity
selecting the PATH
value:
import os env = Environment(ENV = os.environ)
Either of these will guarantee that
SCons will be able to execute
any command that you can execute from the command line.
The drawback is that the build can behave
differently if it's run by people with
different PATH
values in their environment--for example,
if both the /bin
and
/usr/local/bin
directories
have different cc commands,
then which one will be used to compile programs
will depend on which directory is listed
first in the user's PATH
variable.
One of the most common requirements
for manipulating a variable in the execution environment
is to add one or more custom directories to a search
like the $PATH
variable on Linux or POSIX systems,
or the %PATH%
variable on Windows,
so that a locally-installed compiler or other utility
can be found when SCons tries to execute it to update a target.
SCons provides PrependENVPath
and AppendENVPath
functions
to make adding things to execution variables convenient.
You call these functions by specifying the variable
to which you want the value added,
and then value itself.
So to add some /usr/local
directories
to the $PATH
and $LIB
variables,
you might:
env = Environment(ENV = os.environ) env.PrependENVPath('PATH', '/usr/local/bin') env.AppendENVPath('LIB', '/usr/local/lib')
Note that the added values are strings,
and if you want to add multiple directories to
a variable like $PATH
,
you must include the path separate character
(:
on Linux or POSIX,
;
on Windows)
in the string.
This chapter describes the MergeFlags
, ParseFlags
, and ParseConfig
methods of a construction environment
.
SCons construction environments have a MergeFlags
method
that merges a dictionary of values into the construction environment.
MergeFlags
treats each value in the dictionary
as a list of options such as one might pass to a command
(such as a compiler or linker).
MergeFlags
will not duplicate an option
if it already exists in the construction environment variable.
MergeFlags
tries to be intelligent about merging options.
When merging options to any variable
whose name ends in PATH
,
MergeFlags
keeps the leftmost occurrence of the option,
because in typical lists of directory paths,
the first occurrence "wins."
When merging options to any other variable name,
MergeFlags
keeps the rightmost occurrence of the option,
because in a list of typical command-line options,
the last occurrence "wins."
env = Environment() env.Append(CCFLAGS = '-option -O3 -O1') flags = { 'CCFLAGS' : '-whatever -O3' } env.MergeFlags(flags) print env['CCFLAGS']
% scons -Q
['-option', '-O1', '-whatever', '-O3']
scons: `.' is up to date.
Note that the default value for $CCFLAGS
is an internal SCons object
which automatically converts
the options we specified as a string into a list.
env = Environment() env.Append(CPPPATH = ['/include', '/usr/local/include', '/usr/include']) flags = { 'CPPPATH' : ['/usr/opt/include', '/usr/local/include'] } env.MergeFlags(flags) print env['CPPPATH']
% scons -Q
['/include', '/usr/local/include', '/usr/include', '/usr/opt/include']
scons: `.' is up to date.
Note that the default value for $CPPPATH
is a normal Python list,
so we must specify its values as a list
in the dictionary we pass to the MergeFlags
function.
If MergeFlags
is passed anything other than a dictionary,
it calls the ParseFlags
method to convert it into a dictionary.
env = Environment() env.Append(CCFLAGS = '-option -O3 -O1') env.Append(CPPPATH = ['/include', '/usr/local/include', '/usr/include']) env.MergeFlags('-whatever -I/usr/opt/include -O3 -I/usr/local/include') print env['CCFLAGS'] print env['CPPPATH']
% scons -Q
['-option', '-O1', '-whatever', '-O3']
['/include', '/usr/local/include', '/usr/include', '/usr/opt/include']
scons: `.' is up to date.
In the combined example above,
ParseFlags
has sorted the options into their corresponding variables
and returned a dictionary for MergeFlags
to apply
to the construction variables
in the specified construction environment.
SCons has a bewildering array of construction variables for different types of options when building programs. Sometimes you may not know exactly which variable should be used for a particular option.
SCons construction environments have a ParseFlags
method
that takes a set of typical command-line options
and distrbutes them into the appropriate construction variables.
Historically, it was created to support the ParseConfig
method,
so it focuses on options used by the GNU Compiler Collection (GCC)
for the C and C++ toolchains.
ParseFlags
returns a dictionary containing the options
distributed into their respective construction variables.
Normally, this dictionary would be passed to MergeFlags
to merge the options into a construction environment
,
but the dictionary can be edited if desired to provide
additional functionality.
(Note that if the flags are not going to be edited,
calling MergeFlags
with the options directly
will avoid an additional step.)
env = Environment() d = env.ParseFlags("-I/opt/include -L/opt/lib -lfoo") for k,v in sorted(d.items()): if v: print k, v env.MergeFlags(d) env.Program('f1.c')
% scons -Q
CPPPATH ['/opt/include']
LIBPATH ['/opt/lib']
LIBS ['foo']
cc -o f1.o -c -I/opt/include f1.c
cc -o f1 f1.o -L/opt/lib -lfoo
Note that if the options are limited to generic types like those above, they will be correctly translated for other platform types:
C:\>scons -Q
CPPPATH ['/opt/include']
LIBPATH ['/opt/lib']
LIBS ['foo']
cl /Fof1.obj /c f1.c /nologo /I\opt\include
link /nologo /OUT:f1.exe /LIBPATH:\opt\lib foo.lib f1.obj
embedManifestExeCheck(target, source, env)
Since the assumption is that the flags are used for the GCC toolchain,
unrecognized flags are placed in $CCFLAGS
so they will be used for both C and C++ compiles:
env = Environment() d = env.ParseFlags("-whatever") for k,v in sorted(d.items()): if v: print k, v env.MergeFlags(d) env.Program('f1.c')
% scons -Q
CCFLAGS -whatever
cc -o f1.o -c -whatever f1.c
cc -o f1 f1.o
ParseFlags
will also accept a (recursive) list of strings as input;
the list is flattened before the strings are processed:
env = Environment() d = env.ParseFlags(["-I/opt/include", ["-L/opt/lib", "-lfoo"]]) for k,v in sorted(d.items()): if v: print k, v env.MergeFlags(d) env.Program('f1.c')
% scons -Q
CPPPATH ['/opt/include']
LIBPATH ['/opt/lib']
LIBS ['foo']
cc -o f1.o -c -I/opt/include f1.c
cc -o f1 f1.o -L/opt/lib -lfoo
If a string begins with a "!" (an exclamation mark, often called a bang), the string is passed to the shell for execution. The output of the command is then parsed:
env = Environment() d = env.ParseFlags(["!echo -I/opt/include", "!echo -L/opt/lib", "-lfoo"]) for k,v in sorted(d.items()): if v: print k, v env.MergeFlags(d) env.Program('f1.c')
% scons -Q
CPPPATH ['/opt/include']
LIBPATH ['/opt/lib']
LIBS ['foo']
cc -o f1.o -c -I/opt/include f1.c
cc -o f1 f1.o -L/opt/lib -lfoo
ParseFlags
is regularly updated for new options;
consult the man page for details about those currently recognized.
Configuring the right options to build programs to work with
libraries--especially shared libraries--that are available
on POSIX systems can be very complicated.
To help this situation,
various utilies with names that end in config
return the command-line options for the GNU Compiler Collection (GCC)
that are needed to use these libraries;
for example, the command-line options
to use a library named lib
would be found by calling a utility named lib-config
.
A more recent convention is that these options
are available from the generic pkg-config
program,
which has common framework, error handling, and the like,
so that all the package creator has to do is provide the set of strings
for his particular package.
SCons construction environments have a ParseConfig
method
that executes a *config
utility
(either pkg-config
or a
more specific utility)
and configures the appropriate construction variables
in the environment
based on the command-line options
returned by the specified command.
env = Environment() env['CPPPATH'] = ['/lib/compat'] env.ParseConfig("pkg-config x11 --cflags --libs") print env['CPPPATH']
SCons will execute the specified command string, parse the resultant flags, and add the flags to the appropriate environment variables.
% scons -Q
['/lib/compat', '/usr/X11/include']
scons: `.' is up to date.
In the example above, SCons has added the include directory to
CPPPATH
.
(Depending upon what other flags are emitted by the
pkg-config
command,
other variables may have been extended as well.)
Note that the options are merged with existing options using
the MergeFlags
method,
so that each option only occurs once in the construction variable:
env = Environment() env.ParseConfig("pkg-config x11 --cflags --libs") env.ParseConfig("pkg-config x11 --cflags --libs") print env['CPPPATH']
% scons -Q
['/usr/X11/include']
scons: `.' is up to date.
A key aspect of creating a usable build configuration is providing good output from the build so its users can readily understand what the build is doing and get information about how to control the build. SCons provides several ways of controlling output from the build configuration to help make the build more useful and understandable.
It's often very useful to be able to give
users some help that describes the
specific targets, build options, etc.,
that can be used for your build.
SCons provides the Help
function
to allow you to specify this help text:
Help(""" Type: 'scons program' to build the production program, 'scons debug' to build the debug version. """)
(Note the above use of the Python triple-quote syntax, which comes in very handy for specifying multi-line strings like help text.)
When the SConstruct
or SConscript
files
contain such a call to the Help
function,
the specified help text will be displayed in response to
the SCons -h
option:
% scons -h
scons: Reading SConscript files ...
scons: done reading SConscript files.
Type: 'scons program' to build the production program,
'scons debug' to build the debug version.
Use scons -H for help about command-line options.
The SConscript
files may contain
multiple calls to the Help
function,
in which case the specified text(s)
will be concatenated when displayed.
This allows you to split up the
help text across multiple SConscript
files.
In this situation, the order in
which the SConscript
files are called
will determine the order in which the Help
functions are called,
which will determine the order in which
the various bits of text will get concatenated.
Another use would be to make the help text conditional
on some variable.
For example, suppose you only want to display
a line about building a Windows-only
version of a program when actually
run on Windows.
The following SConstruct
file:
env = Environment() Help("\nType: 'scons program' to build the production program.\n") if env['PLATFORM'] == 'win32': Help("\nType: 'scons windebug' to build the Windows debug version.\n")
Will display the complete help text on Windows:
C:\>scons -h
scons: Reading SConscript files ...
scons: done reading SConscript files.
Type: 'scons program' to build the production program.
Type: 'scons windebug' to build the Windows debug version.
Use scons -H for help about command-line options.
But only show the relevant option on a Linux or UNIX system:
% scons -h
scons: Reading SConscript files ...
scons: done reading SConscript files.
Type: 'scons program' to build the production program.
Use scons -H for help about command-line options.
If there is no Help
text in the SConstruct
or
SConscript
files,
SCons will revert to displaying its
standard list that describes the SCons command-line
options.
This list is also always displayed whenever
the -H
option is used.
Sometimes the commands executed
to compile object files or link programs
(or build other targets)
can get very long,
long enough to make it difficult for users
to distinguish error messages or
other important build output
from the commands themselves.
All of the default $*COM
variables
that specify the command lines
used to build various types of target files
have a corresponding $*COMSTR
variable
that can be set to an alternative
string that will be displayed
when the target is built.
For example, suppose you want to
have SCons display a
"Compiling"
message whenever it's compiling an object file,
and a
"Linking"
when it's linking an executable.
You could write a SConstruct
file
that looks like:
env = Environment(CCCOMSTR = "Compiling $TARGET", LINKCOMSTR = "Linking $TARGET") env.Program('foo.c')
Which would then yield the output:
% scons -Q
Compiling foo.o
Linking foo
SCons performs complete variable substitution
on $*COMSTR
variables,
so they have access to all of the
standard variables like $TARGET
$SOURCES
, etc.,
as well as any construction variables
that happen to be configured in
the construction environment
used to build a specific target.
Of course, sometimes it's still important to be able to see the exact command that SCons will execute to build a target. For example, you may simply need to verify that SCons is configured to supply the right options to the compiler, or a developer may want to cut-and-paste a compile command to add a few options for a custom test.
One common way to give users
control over whether or not
SCons should print the actual command line
or a short, configured summary
is to add support for a
VERBOSE
command-line variable to your SConstruct
file.
A simple configuration for this might look like:
env = Environment() if ARGUMENTS.get('VERBOSE') != "1': env['CCCOMSTR'] = "Compiling $TARGET" env['LINKCOMSTR'] = "Linking $TARGET" env.Program('foo.c')
By only setting the appropriate
$*COMSTR
variables
if the user specifies
VERBOSE=1
on the command line,
the user has control
over how SCons
displays these particular command lines:
%scons -Q
Compiling foo.o Linking foo %scons -Q -c
Removed foo.o Removed foo %scons -Q VERBOSE=1
cc -o foo.o -c foo.c cc -o foo foo.o
Another aspect of providing good build output is to give the user feedback about what SCons is doing even when nothing is being built at the moment. This can be especially true for large builds when most of the targets are already up-to-date. Because SCons can take a long time making absolutely sure that every target is, in fact, up-to-date with respect to a lot of dependency files, it can be easy for users to mistakenly conclude that SCons is hung or that there is some other problem with the build.
One way to deal with this perception
is to configure SCons to print something to
let the user know what it's "thinking about."
The Progress
function
allows you to specify a string
that will be printed for every file
that SCons is "considering"
while it is traversing the dependency graph
to decide what targets are or are not up-to-date.
Progress('Evaluating $TARGET\n') Program('f1.c') Program('f2.c')
Note that the Progress
function does not
arrange for a newline to be printed automatically
at the end of the string (as does the Python
print
statement),
and we must specify the
\n
that we want printed at the end of the configured string.
This configuration, then,
will have SCons
print that it is Evaluating
each file that it encounters
in turn as it traverses the dependency graph:
% scons -Q
Evaluating SConstruct
Evaluating f1.c
Evaluating f1.o
cc -o f1.o -c f1.c
Evaluating f1
cc -o f1 f1.o
Evaluating f2.c
Evaluating f2.o
cc -o f2.o -c f2.c
Evaluating f2
cc -o f2 f2.o
Evaluating .
Of course, normally you don't want to add
all of these additional lines to your build output,
as that can make it difficult for the user
to find errors or other important messages.
A more useful way to display
this progress might be
to have the file names printed
directly to the user's screen,
not to the same standard output
stream where build output is printed,
and to use a carriage return character
(\r
)
so that each file name gets re-printed on the same line.
Such a configuration would look like:
Progress('$TARGET\r', file=open('/dev/tty', 'w'), overwrite=True) Program('f1.c') Program('f2.c')
Note that we also specified the
overwrite=True
argument
to the Progress
function,
which causes SCons to
"wipe out" the previous string with space characters
before printing the next Progress
string.
Without the
overwrite=True
argument,
a shorter file name would not overwrite
all of the charactes in a longer file name that
precedes it,
making it difficult to tell what the
actual file name is on the output.
Also note that we opened up the
/dev/tty
file
for direct access (on POSIX) to
the user's screen.
On Windows, the equivalent would be to open
the con:
file name.
Also, it's important to know that although you can use
$TARGET
to substitute the name of
the node in the string,
the Progress
function does not
perform general variable substitution
(because there's not necessarily a construction
environment involved in evaluating a node
like a source file, for example).
You can also specify a list of strings
to the Progress
function,
in which case SCons will
display each string in turn.
This can be used to implement a "spinner"
by having SCons cycle through a
sequence of strings:
Progress(['-\r', '\\\r', '|\r', '/\r'], interval=5) Program('f1.c') Program('f2.c')
Note that here we have also used the
interval=
keyword argument to have SCons
only print a new "spinner" string
once every five evaluated nodes.
Using an interval=
count,
even with strings that use $TARGET
like
our examples above,
can be a good way to lessen the
work that SCons expends printing Progress
strings,
while still giving the user feedback
that indicates SCons is still
working on evaluating the build.
Lastly, you can have direct control
over how to print each evaluated node
by passing a Python function
(or other Python callable)
to the Progress
function.
Your function will be called
for each evaluated node,
allowing you to
implement more sophisticated logic
like adding a counter:
screen = open('/dev/tty', 'w') count = 0 def progress_function(node) count += 1 screen.write('Node %4d: %s\r' % (count, node)) Progress(progress_function)
Of course, if you choose,
you could completely ignore the
node
argument to the function,
and just print a count,
or anything else you wish.
(Note that there's an obvious follow-on question here: how would you find the total number of nodes that will be evaluated so you can tell the user how close the build is to finishing? Unfortunately, in the general case, there isn't a good way to do that, short of having SCons evaluate its dependency graph twice, first to count the total and the second time to actually build the targets. This would be necessary because you can't know in advance which target(s) the user actually requested to be built. The entire build may consist of thousands of Nodes, for example, but maybe the user specifically requested that only a single object file be built.)
SCons, like most build tools, returns zero status to the shell on success and nonzero status on failure. Sometimes it's useful to give more information about the build status at the end of the run, for instance to print an informative message, send an email, or page the poor slob who broke the build.
SCons provides a GetBuildFailures
method that
you can use in a python atexit
function
to get a list of objects describing the actions that failed
while attempting to build targets. There can be more
than one if you're using -j
. Here's a
simple example:
import atexit def print_build_failures(): from SCons.Script import GetBuildFailures for bf in GetBuildFailures(): print "%s failed: %s" % (bf.node, bf.errstr) atexit.register(print_build_failures)
The atexit.register
call
registers print_build_failures
as an atexit
callback, to be called
before SCons exits. When that function is called,
it calls GetBuildFailures
to fetch the list of failed objects.
See the man page
for the detailed contents of the returned objects;
some of the more useful attributes are
.node
,
.errstr
,
.filename
, and
.command
.
The filename
is not necessarily
the same file as the node
; the
node
is the target that was
being built when the error occurred, while the
filename
is the file or dir that
actually caused the error.
Note: only call GetBuildFailures
at the end of the
build; calling it at any other time is undefined.
Here is a more complete example showing how to
turn each element of GetBuildFailures
into a string:
# Make the build fail if we pass fail=1 on the command line if ARGUMENTS.get('fail', 0): Command('target', 'source', ['/bin/false']) def bf_to_str(bf): """Convert an element of GetBuildFailures() to a string in a useful way.""" import SCons.Errors if bf is None: # unknown targets product None in list return '(unknown tgt)' elif isinstance(bf, SCons.Errors.StopError): return str(bf) elif bf.node: return str(bf.node) + ': ' + bf.errstr elif bf.filename: return bf.filename + ': ' + bf.errstr return 'unknown failure: ' + bf.errstr import atexit def build_status(): """Convert the build status to a 2-tuple, (status, msg).""" from SCons.Script import GetBuildFailures bf = GetBuildFailures() if bf: # bf is normally a list of build failures; if an element is None, # it's because of a target that scons doesn't know anything about. status = 'failed' failures_message = "\n".join(["Failed building %s" % bf_to_str(x) for x in bf if x is not None]) else: # if bf is None, the build completed successfully. status = 'ok' failures_message = '' return (status, failures_message) def display_build_status(): """Display the build status. Called by atexit. Here you could do all kinds of complicated things.""" status, failures_message = build_status() if status == 'failed': print "FAILED!!!!" # could display alert, ring bell, etc. elif status == 'ok': print "Build succeeded." print failures_message atexit.register(display_build_status)
When this runs, you'll see the appropriate output:
%scons -Q
scons: `.' is up to date. Build succeeded. %scons -Q fail=1
scons: *** [target] Source `source' not found, needed by target `target'. FAILED!!!! Failed building target: Source `source' not found, needed by target `target'.
SCons provides a number of ways
for the writer of the SConscript
files
to give the users who will run SCons
a great deal of control over the build execution.
The arguments that the user can specify on
the command line are broken down into three types:
Command-line options always begin with
one or two -
(hyphen) characters.
SCons provides ways for you to examine
and set options values from within your SConscript
files,
as well as the ability to define your own
custom options.
See Section 10.1, “Command-Line Options”, below.
Any command-line argument containing an =
(equal sign) is considered a variable setting with the form
variable
=value
.
SCons provides direct access to
all of the command-line variable settings,
the ability to apply command-line variable settings
to construction environments,
and functions for configuring
specific types of variables
(Boolean values, path names, etc.)
with automatic validation of the user's specified values.
See Section 10.2, “Command-Line variable
=value
Build Variables”, below.
Any command-line argument that is not an option
or a variable setting
(does not begin with a hyphen
and does not contain an equal sign)
is considered a target that the user
(presumably) wants SCons to build.
A list of Node objects representing
the target or targets to build.
SCons provides access to the list of specified targets,
as well as ways to set the default list of targets
from within the SConscript
files.
See Section 10.3, “Command-Line Targets”, below.
SCons has many command-line options
that control its behavior.
A SCons command-line option
always begins with one or two -
(hyphen)
characters.
Users may find themselves supplying
the same command-line options every time
they run SCons.
For example, you might find it saves time
to specify a value of -j 2
to have SCons run up to two build commands in parallel.
To avoid having to type -j 2
by hand
every time,
you can set the external environment variable
SCONSFLAGS
to a string containing
command-line options that you want SCons to use.
If, for example,
you're using a POSIX shell that's
compatible with the Bourne shell,
and you always want SCons to use the
-Q
option,
you can set the SCONSFLAGS
environment as follows:
%scons
scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... ... [build output] ... scons: done building targets. %export SCONSFLAGS="-Q"
%scons
... [build output] ...
Users of csh-style shells on POSIX systems
can set the SCONSFLAGS
environment as follows:
$ setenv SCONSFLAGS "-Q"
Windows users may typically want to set the
SCONSFLAGS
in the appropriate tab of the
System Properties
window.
SCons provides the GetOption
function
to get the values set by the various command-line options.
One common use of this is to check whether or not
the -h
or --help
option
has been specified.
Normally, SCons does not print its help text
until after it has read all of the SConscript
files,
because it's possible that help text has been added
by some subsidiary SConscript
file deep in the
source tree hierarchy.
Of course, reading all of the SConscript
files
takes extra time.
If you know that your configuration does not define
any additional help text in subsidiary SConscript
files,
you can speed up the command-line help available to users
by using the GetOption
function to load the
subsidiary SConscript
files only if the
the user has not specified
the -h
or --help
option,
like so:
if not GetOption('help'): SConscript('src/SConscript', export='env')
In general, the string that you pass to the
GetOption
function to fetch the value of a command-line
option setting is the same as the "most common" long option name
(beginning with two hyphen characters),
although there are some exceptions.
The list of SCons command-line options
and the GetOption
strings for fetching them,
are available in the
Section 10.1.4, “Strings for Getting or Setting Values of SCons Command-Line Options” section,
below.
You can also set the values of SCons
command-line options from within the SConscript
files
by using the SetOption
function.
The strings that you use to set the values of SCons
command-line options are available in the
Section 10.1.4, “Strings for Getting or Setting Values of SCons Command-Line Options” section,
below.
One use of the SetOption
function is to
specify a value for the -j
or --jobs
option,
so that users get the improved performance
of a parallel build without having to specify the option by hand.
A complicating factor is that a good value
for the -j
option is
somewhat system-dependent.
One rough guideline is that the more processors
your system has,
the higher you want to set the
-j
value,
in order to take advantage of the number of CPUs.
For example, suppose the administrators
of your development systems
have standardized on setting a
NUM_CPU
environment variable
to the number of processors on each system.
A little bit of Python code
to access the environment variable
and the SetOption
function
provide the right level of flexibility:
import os num_cpu = int(os.environ.get('NUM_CPU', 2)) SetOption('num_jobs', num_cpu) print "running with -j", GetOption('num_jobs')
The above snippet of code
sets the value of the --jobs
option
to the value specified in the
$NUM_CPU
environment variable.
(This is one of the exception cases
where the string is spelled differently from
the from command-line option.
The string for fetching or setting the --jobs
value is num_jobs
for historical reasons.)
The code in this example prints the num_jobs
value for illustrative purposes.
It uses a default value of 2
to provide some minimal parallelism even on
single-processor systems:
% scons -Q
running with -j 2
scons: `.' is up to date.
But if the $NUM_CPU
environment variable is set,
then we use that for the default number of jobs:
%export NUM_CPU="4"
%scons -Q
running with -j 4 scons: `.' is up to date.
But any explicit
-j
or --jobs
value the user specifies an the command line is used first,
regardless of whether or not
the $NUM_CPU
environment
variable is set:
%scons -Q -j 7
running with -j 7 scons: `.' is up to date. %export NUM_CPU="4"
%scons -Q -j 3
running with -j 3 scons: `.' is up to date.
The strings that you can pass to the GetOption
and SetOption
functions usually correspond to the
first long-form option name
(beginning with two hyphen characters: --
),
after replacing any remaining hyphen characters
with underscores.
The full list of strings and the variables they correspond to is as follows:
String for GetOption and SetOption | Command-Line Option(s) |
---|---|
cache_debug | --cache-debug |
cache_disable | --cache-disable |
cache_force | --cache-force |
cache_show | --cache-show |
clean | -c ,
--clean ,
--remove |
config | --config |
directory | -C ,
--directory |
diskcheck | --diskcheck |
duplicate | --duplicate |
file | -f ,
--file ,
--makefile ,
--sconstruct |
help | -h ,
--help |
ignore_errors | --ignore-errors |
implicit_cache | --implicit-cache |
implicit_deps_changed | --implicit-deps-changed |
implicit_deps_unchanged | --implicit-deps-unchanged |
interactive | --interact ,
--interactive |
keep_going | -k ,
--keep-going |
max_drift | --max-drift |
no_exec | -n ,
--no-exec ,
--just-print ,
--dry-run ,
--recon |
no_site_dir | --no-site-dir |
num_jobs | -j ,
--jobs |
profile_file | --profile |
question | -q ,
--question |
random | --random |
repository | -Y ,
--repository ,
--srcdir |
silent | -s ,
--silent ,
--quiet |
site_dir | --site-dir |
stack_size | --stack-size |
taskmastertrace_file | --taskmastertrace |
warn | --warn --warning |
SCons also allows you to define your own
command-line options with the AddOption
function.
The AddOption
function takes the same arguments
as the optparse.add_option
function
from the standard Python library.
[3]
Once you have added a custom command-line option
with the AddOption
function,
the value of the option (if any) is immediately available
using the standard GetOption
function.
(The value can also be set using SetOption
,
although that's not very useful in practice
because a default value can be specified in
directly in the AddOption
call.)
One useful example of using this functionality
is to provide a --prefix
for users:
AddOption('--prefix', dest='prefix', type='string', nargs=1, action='store', metavar='DIR', help='installation prefix') env = Environment(PREFIX = GetOption('prefix')) installed_foo = env.Install('$PREFIX/usr/bin', 'foo.in') Default(installed_foo)
The above code uses the GetOption
function
to set the $PREFIX
construction variable to any
value that the user specifies with a command-line
option of --prefix
.
Because $PREFIX
will expand to a null string if it's not initialized,
running SCons without the
option of --prefix
will install the file in the
/usr/bin/
directory:
% scons -Q -n
Install file: "foo.in" as "/usr/bin/foo.in"
But specifying --prefix=/tmp/install
on the command line causes the file to be installed in the
/tmp/install/usr/bin/
directory:
% scons -Q -n --prefix=/tmp/install
Install file: "foo.in" as "/tmp/install/usr/bin/foo.in"
You may want to control various aspects
of your build by allowing the user
to specify variable
=value
values on the command line.
For example, suppose you
want users to be able to
build a debug version of a program
by running SCons as follows:
% scons -Q debug=1
SCons provides an ARGUMENTS
dictionary
that stores all of the
variable
=value
assignments from the command line.
This allows you to modify
aspects of your build in response
to specifications on the command line.
(Note that unless you want to require
that users always
specify a variable,
you probably want to use
the Python
ARGUMENTS.get()
function,
which allows you to specify a default value
to be used if there is no specification
on the command line.)
The following code sets the $CCFLAGS
construction
variable in response to the debug
flag being set in the ARGUMENTS
dictionary:
env = Environment() debug = ARGUMENTS.get('debug', 0) if int(debug): env.Append(CCFLAGS = '-g') env.Program('prog.c')
This results in the -g
compiler option being used when
debug=1
is used on the command line:
%scons -Q debug=0
cc -o prog.o -c prog.c cc -o prog prog.o %scons -Q debug=0
scons: `.' is up to date. %scons -Q debug=1
cc -o prog.o -c -g prog.c cc -o prog prog.o %scons -Q debug=1
scons: `.' is up to date.
Notice that SCons keeps track of
the last values used to build the object files,
and as a result correctly rebuilds
the object and executable files
only when the value of the debug
argument has changed.
The ARGUMENTS
dictionary has two minor drawbacks.
First, because it is a dictionary,
it can only store one value for each specified keyword,
and thus only "remembers" the last setting
for each keyword on the command line.
This makes the ARGUMENTS
dictionary
inappropriate if users should be able to
specify multiple values
on the command line for a given keyword.
Second, it does not preserve
the order in which the variable settings
were specified,
which is a problem if
you want the configuration to
behave differently in response
to the order in which the build
variable settings were specified on the command line.
To accomodate these requirements,
SCons provides an ARGLIST
variable
that gives you direct access to
variable
=value
settings on the command line,
in the exact order they were specified,
and without removing any duplicate settings.
Each element in the ARGLIST
variable
is itself a two-element list
containing the keyword and the value
of the setting,
and you must loop through,
or otherwise select from,
the elements of ARGLIST
to
process the specific settings you want
in whatever way is appropriate for your configuration.
For example,
the following code to let the user
add to the CPPDEFINES
construction variable
by specifying multiple
define=
settings on the command line:
cppdefines = [] for key, value in ARGLIST: if key == 'define': cppdefines.append(value) env = Environment(CPPDEFINES = cppdefines) env.Object('prog.c')
Yields the following output:
%scons -Q define=FOO
cc -o prog.o -c -DFOO prog.c %scons -Q define=FOO define=BAR
cc -o prog.o -c -DFOO -DBAR prog.c
Note that the ARGLIST
and ARGUMENTS
variables do not interfere with each other,
but merely provide slightly different views
into how the user specified
variable
=value
settings on the command line.
You can use both variables in the same
SCons configuration.
In general, the ARGUMENTS
dictionary
is more convenient to use,
(since you can just fetch variable
settings through a dictionary access),
and the ARGLIST
list
is more flexible
(since you can examine the
specific order in which
the user's command-line variabe settings).
Being able to use a command-line build variable like
debug=1
is handy,
but it can be a chore to write specific Python code
to recognize each such variable,
check for errors and provide appropriate messages,
and apply the values to a construction variable.
To help with this,
SCons supports a class to
define such build variables easily,
and a mechanism to apply the
build variables to a construction environment.
This allows you to control how the build variables affect
construction environments.
For example, suppose that you want users to set
a RELEASE
construction variable on the
command line whenever the time comes to build
a program for release,
and that the value of this variable
should be added to the command line
with the appropriate -D
option
(or other command line option)
to pass the value to the C compiler.
Here's how you might do that by setting
the appropriate value in a dictionary for the
$CPPDEFINES
construction variable:
vars = Variables(None, ARGUMENTS) vars.Add('RELEASE', 'Set to 1 to build for release', 0) env = Environment(variables = vars, CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) env.Program(['foo.c', 'bar.c'])
This SConstruct
file first creates a Variables
object
which uses the values from the command-line options dictionary ARGUMENTS
(the vars = Variables(None, ARGUMENTS)
call).
It then uses the object's Add
method to indicate that the RELEASE
variable can be set on the command line,
and that its default value will be 0
(the third argument to the Add
method).
The second argument is a line of help text;
we'll learn how to use it in the next section.
We then pass the created Variables
object as a variables
keyword argument
to the Environment
call
used to create the construction environment.
This then allows a user to set the
RELEASE
build variable on the command line
and have the variable show up in
the command line used to build each object from
a C source file:
% scons -Q RELEASE=1
cc -o bar.o -c -DRELEASE_BUILD=1 bar.c
cc -o foo.o -c -DRELEASE_BUILD=1 foo.c
cc -o foo foo.o bar.o
NOTE: Before SCons release 0.98.1, these build variables
were known as "command-line build options."
The class was actually named the Options
class,
and in the sections below,
the various functions were named
BoolOption
, EnumOption
, ListOption
,
PathOption
, PackageOption
and AddOptions
.
These older names still work,
and you may encounter them in older
SConscript
files,
but they have been officially deprecated
as of SCons version 2.0.
To make command-line build variables most useful,
you ideally want to provide
some help text that will describe
the available variables
when the user runs scons -h
.
You could write this text by hand,
but SCons provides an easier way.
Variables
objects support a
GenerateHelpText
method
that will, as its name suggests,
generate text that describes
the various variables that
have been added to it.
You then pass the output from this method to
the Help
function:
vars = Variables(None, ARGUMENTS) vars.Add('RELEASE', 'Set to 1 to build for release', 0) env = Environment(variables = vars) Help(vars.GenerateHelpText(env))
SCons will now display some useful text
when the -h
option is used:
% scons -Q -h
RELEASE: Set to 1 to build for release
default: 0
actual: 0
Use scons -H for help about command-line options.
Notice that the help output shows the default value, and the current actual value of the build variable.
Giving the user a way to specify the
value of a build variable on the command line
is useful,
but can still be tedious
if users must specify the variable
every time they run SCons.
We can let users provide customized build variable settings
in a local file by providing a
file name when we create the
Variables
object:
vars = Variables('custom.py') vars.Add('RELEASE', 'Set to 1 to build for release', 0) env = Environment(variables = vars, CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) env.Program(['foo.c', 'bar.c']) Help(vars.GenerateHelpText(env))
This then allows the user to control the RELEASE
variable by setting it in the custom.py
file:
RELEASE = 1
Note that this file is actually executed like a Python script. Now when we run SCons:
% scons -Q
cc -o bar.o -c -DRELEASE_BUILD=1 bar.c
cc -o foo.o -c -DRELEASE_BUILD=1 foo.c
cc -o foo foo.o bar.o
And if we change the contents of custom.py
to:
RELEASE = 0
The object files are rebuilt appropriately with the new variable:
% scons -Q
cc -o bar.o -c -DRELEASE_BUILD=0 bar.c
cc -o foo.o -c -DRELEASE_BUILD=0 foo.c
cc -o foo foo.o bar.o
Finally, you can combine both methods with:
vars = Variables('custom.py', ARGUMENTS)
where values in the option file custom.py
get overwritten
by the ones specified on the command line.
SCons provides a number of functions that provide ready-made behaviors for various types of command-line build variables.
It's often handy to be able to specify a
variable that controls a simple Boolean variable
with a true
or false
value.
It would be even more handy to accomodate
users who have different preferences for how to represent
true
or false
values.
The BoolVariable
function
makes it easy to accomodate these
common representations of
true
or false
.
The BoolVariable
function takes three arguments:
the name of the build variable,
the default value of the build variable,
and the help string for the variable.
It then returns appropriate information for
passing to the Add
method of a Variables
object, like so:
vars = Variables('custom.py') vars.Add(BoolVariable('RELEASE', 'Set to build for release', 0)) env = Environment(variables = vars, CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) env.Program('foo.c')
With this build variable,
the RELEASE
variable can now be enabled by
setting it to the value yes
or t
:
% scons -Q RELEASE=yes foo.o
cc -o foo.o -c -DRELEASE_BUILD=True foo.c
% scons -Q RELEASE=t foo.o
cc -o foo.o -c -DRELEASE_BUILD=True foo.c
Other values that equate to true
include
y
,
1
,
on
and
all
.
Conversely, RELEASE
may now be given a false
value by setting it to
no
or
f
:
% scons -Q RELEASE=no foo.o
cc -o foo.o -c -DRELEASE_BUILD=False foo.c
% scons -Q RELEASE=f foo.o
cc -o foo.o -c -DRELEASE_BUILD=False foo.c
Other values that equate to false
include
n
,
0
,
off
and
none
.
Lastly, if a user tries to specify any other value, SCons supplies an appropriate error message:
% scons -Q RELEASE=bad_value foo.o
scons: *** Error converting option: RELEASE
Invalid value for boolean option: bad_value
File "/home/my/project/SConstruct", line 4, in <module>
Suppose that we want a user to be able to
set a COLOR
variable
that selects a background color to be
displayed by an application,
but that we want to restrict the
choices to a specific set of allowed colors.
This can be set up quite easily
using the EnumVariable
,
which takes a list of allowed_values
in addition to the variable name,
default value,
and help text arguments:
vars = Variables('custom.py') vars.Add(EnumVariable('COLOR', 'Set background color', 'red', allowed_values=('red', 'green', 'blue'))) env = Environment(variables = vars, CPPDEFINES={'COLOR' : '"${COLOR}"'}) env.Program('foo.c')
The user can now explicity set the COLOR
build variable
to any of the specified allowed values:
%scons -Q COLOR=red foo.o
cc -o foo.o -c -DCOLOR="red" foo.c %scons -Q COLOR=blue foo.o
cc -o foo.o -c -DCOLOR="blue" foo.c %scons -Q COLOR=green foo.o
cc -o foo.o -c -DCOLOR="green" foo.c
But, almost more importantly,
an attempt to set COLOR
to a value that's not in the list
generates an error message:
% scons -Q COLOR=magenta foo.o
scons: *** Invalid value for option COLOR: magenta. Valid values are: ('red', 'green', 'blue')
File "/home/my/project/SConstruct", line 5, in <module>
The EnumVariable
function also supports a way
to map alternate names to allowed values.
Suppose, for example,
that we want to allow the user
to use the word navy
as a synonym for
blue
.
We do this by adding a map
dictionary
that will map its key values
to the desired legal value:
vars = Variables('custom.py') vars.Add(EnumVariable('COLOR', 'Set background color', 'red', allowed_values=('red', 'green', 'blue'), map={'navy':'blue'})) env = Environment(variables = vars, CPPDEFINES={'COLOR' : '"${COLOR}"'}) env.Program('foo.c')
As desired, the user can then use
navy
on the command line,
and SCons will translate it into blue
when it comes time to use the COLOR
variable to build a target:
% scons -Q COLOR=navy foo.o
cc -o foo.o -c -DCOLOR="blue" foo.c
By default, when using the EnumVariable
function,
arguments that differ
from the legal values
only in case
are treated as illegal values:
%scons -Q COLOR=Red foo.o
scons: *** Invalid value for option COLOR: Red. Valid values are: ('red', 'green', 'blue') File "/home/my/project/SConstruct", line 5, in <module> %scons -Q COLOR=BLUE foo.o
scons: *** Invalid value for option COLOR: BLUE. Valid values are: ('red', 'green', 'blue') File "/home/my/project/SConstruct", line 5, in <module> %scons -Q COLOR=nAvY foo.o
scons: *** Invalid value for option COLOR: nAvY. Valid values are: ('red', 'green', 'blue') File "/home/my/project/SConstruct", line 5, in <module>
The EnumVariable
function can take an additional
ignorecase
keyword argument that,
when set to 1
,
tells SCons to allow case differences
when the values are specified:
vars = Variables('custom.py') vars.Add(EnumVariable('COLOR', 'Set background color', 'red', allowed_values=('red', 'green', 'blue'), map={'navy':'blue'}, ignorecase=1)) env = Environment(variables = vars, CPPDEFINES={'COLOR' : '"${COLOR}"'}) env.Program('foo.c')
Which yields the output:
%scons -Q COLOR=Red foo.o
cc -o foo.o -c -DCOLOR="Red" foo.c %scons -Q COLOR=BLUE foo.o
cc -o foo.o -c -DCOLOR="BLUE" foo.c %scons -Q COLOR=nAvY foo.o
cc -o foo.o -c -DCOLOR="blue" foo.c %scons -Q COLOR=green foo.o
cc -o foo.o -c -DCOLOR="green" foo.c
Notice that an ignorecase
value of 1
preserves the case-spelling that the user supplied.
If you want SCons to translate the names
into lower-case,
regardless of the case used by the user,
specify an ignorecase
value of 2
:
vars = Variables('custom.py') vars.Add(EnumVariable('COLOR', 'Set background color', 'red', allowed_values=('red', 'green', 'blue'), map={'navy':'blue'}, ignorecase=2)) env = Environment(variables = vars, CPPDEFINES={'COLOR' : '"${COLOR}"'}) env.Program('foo.c')
Now SCons will use values of
red
,
green
or
blue
regardless of how the user spells
those values on the command line:
%scons -Q COLOR=Red foo.o
cc -o foo.o -c -DCOLOR="red" foo.c %scons -Q COLOR=nAvY foo.o
cc -o foo.o -c -DCOLOR="blue" foo.c %scons -Q COLOR=GREEN foo.o
cc -o foo.o -c -DCOLOR="green" foo.c
Another way in which you might want to allow users
to control a build variable is to
specify a list of one or more legal values.
SCons supports this through the ListVariable
function.
If, for example, we want a user to be able to set a
COLORS
variable to one or more of the legal list of values:
vars = Variables('custom.py') vars.Add(ListVariable('COLORS', 'List of colors', 0, ['red', 'green', 'blue'])) env = Environment(variables = vars, CPPDEFINES={'COLORS' : '"${COLORS}"'}) env.Program('foo.c')
A user can now specify a comma-separated list of legal values, which will get translated into a space-separated list for passing to the any build commands:
%scons -Q COLORS=red,blue foo.o
cc -o foo.o -c -DCOLORS="red blue" foo.c %scons -Q COLORS=blue,green,red foo.o
cc -o foo.o -c -DCOLORS="blue green red" foo.c
In addition, the ListVariable
function
allows the user to specify explicit keywords of
all
or none
to select all of the legal values,
or none of them, respectively:
%scons -Q COLORS=all foo.o
cc -o foo.o -c -DCOLORS="red green blue" foo.c %scons -Q COLORS=none foo.o
cc -o foo.o -c -DCOLORS="" foo.c
And, of course, an illegal value still generates an error message:
% scons -Q COLORS=magenta foo.o
scons: *** Error converting option: COLORS
Invalid value(s) for option: magenta
File "/home/my/project/SConstruct", line 5, in <module>
SCons supports a PathVariable
function
to make it easy to create a build variable
to control an expected path name.
If, for example, you need to
define a variable in the preprocessor
that controls the location of a
configuration file:
vars = Variables('custom.py') vars.Add(PathVariable('CONFIG', 'Path to configuration file', '/etc/my_config')) env = Environment(variables = vars, CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'}) env.Program('foo.c')
This then allows the user to
override the CONFIG
build variable
on the command line as necessary:
%scons -Q foo.o
cc -o foo.o -c -DCONFIG_FILE="/etc/my_config" foo.c %scons -Q CONFIG=/usr/local/etc/other_config foo.o
scons: `foo.o' is up to date.
By default, PathVariable
checks to make sure
that the specified path exists and generates an error if it
doesn't:
% scons -Q CONFIG=/does/not/exist foo.o
scons: *** Path for option CONFIG does not exist: /does/not/exist
File "/home/my/project/SConstruct", line 6, in <module>
PathVariable
provides a number of methods
that you can use to change this behavior.
If you want to ensure that any specified paths are,
in fact, files and not directories,
use the PathVariable.PathIsFile
method:
vars = Variables('custom.py') vars.Add(PathVariable('CONFIG', 'Path to configuration file', '/etc/my_config', PathVariable.PathIsFile)) env = Environment(variables = vars, CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'}) env.Program('foo.c')
Conversely, to ensure that any specified paths are
directories and not files,
use the PathVariable.PathIsDir
method:
vars = Variables('custom.py') vars.Add(PathVariable('DBDIR', 'Path to database directory', '/var/my_dbdir', PathVariable.PathIsDir)) env = Environment(variables = vars, CPPDEFINES={'DBDIR' : '"$DBDIR"'}) env.Program('foo.c')
If you want to make sure that any specified paths
are directories,
and you would like the directory created
if it doesn't already exist,
use the PathVariable.PathIsDirCreate
method:
vars = Variables('custom.py') vars.Add(PathVariable('DBDIR', 'Path to database directory', '/var/my_dbdir', PathVariable.PathIsDirCreate)) env = Environment(variables = vars, CPPDEFINES={'DBDIR' : '"$DBDIR"'}) env.Program('foo.c')
Lastly, if you don't care whether the path exists,
is a file, or a directory,
use the PathVariable.PathAccept
method
to accept any path that the user supplies:
vars = Variables('custom.py') vars.Add(PathVariable('OUTPUT', 'Path to output file or directory', None, PathVariable.PathAccept)) env = Environment(variables = vars, CPPDEFINES={'OUTPUT' : '"$OUTPUT"'}) env.Program('foo.c')
Sometimes you want to give users
even more control over a path name variable,
allowing them to explicitly enable or
disable the path name
by using yes
or no
keywords,
in addition to allow them
to supply an explicit path name.
SCons supports the PackageVariable
function to support this:
vars = Variables('custom.py') vars.Add(PackageVariable('PACKAGE', 'Location package', '/opt/location')) env = Environment(variables = vars, CPPDEFINES={'PACKAGE' : '"$PACKAGE"'}) env.Program('foo.c')
When the SConscript
file uses the PackageVariable
funciton,
user can now still use the default
or supply an overriding path name,
but can now explicitly set the
specified variable to a value
that indicates the package should be enabled
(in which case the default should be used)
or disabled:
%scons -Q foo.o
cc -o foo.o -c -DPACKAGE="/opt/location" foo.c %scons -Q PACKAGE=/usr/local/location foo.o
cc -o foo.o -c -DPACKAGE="/usr/local/location" foo.c %scons -Q PACKAGE=yes foo.o
cc -o foo.o -c -DPACKAGE="True" foo.c %scons -Q PACKAGE=no foo.o
cc -o foo.o -c -DPACKAGE="False" foo.c
Lastly, SCons provides a way to add
multiple build variables to a Variables
object at once.
Instead of having to call the Add
method
multiple times,
you can call the AddVariables
method with a list of build variables
to be added to the object.
Each build variable is specified
as either a tuple of arguments,
just like you'd pass to the Add
method itself,
or as a call to one of the pre-defined
functions for pre-packaged command-line build variables.
in any order:
vars = Variables() vars.AddVariables( ('RELEASE', 'Set to 1 to build for release', 0), ('CONFIG', 'Configuration file', '/etc/my_config'), BoolVariable('warnings', 'compilation with -Wall and similiar', 1), EnumVariable('debug', 'debug output and symbols', 'no', allowed_values=('yes', 'no', 'full'), map={}, ignorecase=0), # case sensitive ListVariable('shared', 'libraries to build as shared libraries', 'all', names = list_of_libs), PackageVariable('x11', 'use X11 installed here (yes = search some places)', 'yes'), PathVariable('qtdir', 'where the root of Qt is installed', qtdir), )
Users may, of course,
occasionally misspell variable names in their command-line settings.
SCons does not generate an error or warning
for any unknown variables the users specifies on the command line.
(This is in no small part because you may be
processing the arguments directly using the ARGUMENTS
dictionary,
and therefore SCons can't know in the general case
whether a given "misspelled" variable is
really unknown and a potential problem,
or something that your SConscript
file
will handle directly with some Python code.)
If, however, you're using a Variables
object to
define a specific set of command-line build variables
that you expect users to be able to set,
you may want to provide an error
message or warning of your own
if the user supplies a variable setting
that is not among
the defined list of variable names known to the Variables
object.
You can do this by calling the UnknownVariables
method of the Variables
object:
vars = Variables(None) vars.Add('RELEASE', 'Set to 1 to build for release', 0) env = Environment(variables = vars, CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) unknown = vars.UnknownVariables() if unknown: print "Unknown variables:", unknown.keys() Exit(1) env.Program('foo.c')
The UnknownVariables
method returns a dictionary
containing the keywords and values
of any variables the user specified on the command line
that are not
among the variables known to the Variables
object
(from having been specified using
the Variables
object'sAdd
method).
In the examble above,
we check for whether the dictionary
returned by the UnknownVariables
is non-empty,
and if so print the Python list
containing the names of the unknwown variables
and then call the Exit
function
to terminate SCons:
% scons -Q NOT_KNOWN=foo
Unknown variables: ['NOT_KNOWN']
Of course, you can process the items in the
dictionary returned by the UnknownVariables
function
in any way appropriate to your build configuration,
including just printing a warning message
but not exiting,
logging an error somewhere,
etc.
Note that you must delay the call of UnknownVariables
until after you have applied the Variables
object
to a construction environment
with the variables=
keyword argument of an Environment
call.
SCons supports a COMMAND_LINE_TARGETS
variable
that lets you fetch the list of targets that the
user specified on the command line.
You can use the targets to manipulate the
build in any way you wish.
As a simple example,
suppose that you want to print a reminder
to the user whenever a specific program is built.
You can do this by checking for the
target in the COMMAND_LINE_TARGETS
list:
if 'bar' in COMMAND_LINE_TARGETS: print "Don't forget to copy `bar' to the archive!" Default(Program('foo.c')) Program('bar.c')
Then, running SCons with the default target
works as it always does,
but explicity specifying the bar
target
on the command line generates the warning message:
%scons -Q
cc -o foo.o -c foo.c cc -o foo foo.o %scons -Q bar
Don't forget to copy `bar' to the archive! cc -o bar.o -c bar.c cc -o bar bar.o
Another practical use for the COMMAND_LINE_TARGETS
variable
might be to speed up a build
by only reading certain subsidiary SConscript
files if a specific target is requested.
One of the most basic things you can control
is which targets SCons will build by default--that is,
when there are no targets specified on the command line.
As mentioned previously,
SCons will normally build every target
in or below the current directory
by default--that is, when you don't
explicitly specify one or more targets
on the command line.
Sometimes, however, you may want
to specify explicitly that only
certain programs, or programs in certain directories,
should be built by default.
You do this with the Default
function:
env = Environment() hello = env.Program('hello.c') env.Program('goodbye.c') Default(hello)
This SConstruct
file knows how to build two programs,
hello
and goodbye
,
but only builds the
hello
program by default:
%scons -Q
cc -o hello.o -c hello.c cc -o hello hello.o %scons -Q
scons: `hello' is up to date. %scons -Q goodbye
cc -o goodbye.o -c goodbye.c cc -o goodbye goodbye.o
Note that, even when you use the Default
function in your SConstruct
file,
you can still explicitly specify the current directory
(.
) on the command line
to tell SCons to build
everything in (or below) the current directory:
% scons -Q .
cc -o goodbye.o -c goodbye.c
cc -o goodbye goodbye.o
cc -o hello.o -c hello.c
cc -o hello hello.o
You can also call the Default
function more than once,
in which case each call
adds to the list of targets to be built by default:
env = Environment() prog1 = env.Program('prog1.c') Default(prog1) prog2 = env.Program('prog2.c') prog3 = env.Program('prog3.c') Default(prog3)
Or you can specify more than one target
in a single call to the Default
function:
env = Environment() prog1 = env.Program('prog1.c') prog2 = env.Program('prog2.c') prog3 = env.Program('prog3.c') Default(prog1, prog3)
Either of these last two examples will build only the prog1 and prog3 programs by default:
% scons -Q