&SCons; provides a number of ways that
allow the writer of the &SConscript; files
to give users a great deal of control over how to run the builds.
Not Having to Specify Command-Line Options Each Time: the &SCONSFLAGS; Environment Variable
Users may find themselves supplying
the same command-line options every time
they run &SCons;.
For example, a user might find that it saves time
to specify a value of -j 2
to run the builds 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,
and 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:
def b(target, source, env):
pass
def s(target, source, env):
return " ... [build output] ..."
a = Action(b, strfunction = s)
env = Environment(BUILDERS = {'A' : Builder(action=a)})
env.A('foo.out', 'foo.in')
foo.in
scons
export SCONSFLAGS="-Q"
scons
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 this
&SCONSFLAGS; in the appropriate tab of the
System Properties window.
Getting at Command-Line Targets
&SCons; supports a &COMMAND_LINE_TARGETS; variable
that lets you get at 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')
foo.c
foo.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
scons -Q bar
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.
Controlling the Default Targets
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)
hello.c
goodbye.c
This &SConstruct; file knows how to build two programs,
&hello; and &goodbye;,
but only builds the
&hello; program by default:
scons -Q
scons -Q
scons -Q goodbye
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 .
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)
prog1.c
prog2.c
prog3.c
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
scons -Q .
You can list a directory as
an argument to &Default;:
env = Environment()
env.Program(['prog1/main.c', 'prog1/foo.c'])
env.Program(['prog2/main.c', 'prog2/bar.c'])
Default('prog1')
int main() { printf("prog1/main.c\n"); }
int foo() { printf("prog1/foo.c\n"); }
int main() { printf("prog2/main.c\n"); }
int bar() { printf("prog2/bar.c\n"); }
In which case only the target(s) in that
directory will be built by default:
scons -Q
scons -Q
scons -Q .
Lastly, if for some reason you don't want
any targets built by default,
you can use the Python None
variable:
env = Environment()
prog1 = env.Program('prog1.c')
prog2 = env.Program('prog2.c')
Default(None)
prog1.c
prog2.c
Which would produce build output like:
scons -Q
scons -Q .
Getting at the List of Default Targets
&SCons; supports a &DEFAULT_TARGETS; variable
that lets you get at the current list of default targets.
The &DEFAULT_TARGETS variable has
two important differences from the &COMMAND_LINE_TARGETS; variable.
First, the &DEFAULT_TARGETS; variable is a list of
internal &SCons; nodes,
so you need to convert the list elements to strings
if you want to print them or look for a specific target name.
Fortunately, you can do this easily
by using the Python map function
to run the list through str:
prog1 = Program('prog1.c')
Default(prog1)
print "DEFAULT_TARGETS is", map(str, DEFAULT_TARGETS)
prog1.c
(Keep in mind that all of the manipulation of the
&DEFAULT_TARGETS; list takes place during the
first phase when &SCons; is reading up the &SConscript; files,
which is obvious if
we leave off the -Q flag when we run &SCons;:)
scons
Second,
the contents of the &DEFAULT_TARGETS; list change
in response to calls to the &Default: function,
as you can see from the following &SConstruct; file:
prog1 = Program('prog1.c')
Default(prog1)
print "DEFAULT_TARGETS is now", map(str, DEFAULT_TARGETS)
prog2 = Program('prog2.c')
Default(prog2)
print "DEFAULT_TARGETS is now", map(str, DEFAULT_TARGETS)
prog1.c
prog2.c
Which yields the output:
scons
In practice, this simply means that you
need to pay attention to the order in
which you call the &Default; function
and refer to the &DEFAULT_TARGETS; list,
to make sure that you don't examine the
list before you've added the default targets
you expect to find in it.
Getting at the List of Build Targets, Regardless of Origin
We've already been introduced to the
&COMMAND_LINE_TARGETS; variable,
which contains a list of targets specified on the command line,
and the &DEFAULT_TARGETS; variable,
which contains a list of targets specified
via calls to the &Default; method or function.
Sometimes, however,
you want a list of whatever targets
&SCons; will try to build,
regardless of whether the targets came from the
command line or a &Default; call.
You could code this up by hand, as follows:
if COMMAND_LINE_TARGETS:
targets = COMMAND_LINE_TARGETS
else:
targets = DEFAULT_TARGETS
&SCons;, however, provides a convenient
&BUILD_TARGETS; variable
that eliminates the need for this by-hand manipulation.
Essentially, the &BUILD_TARGETS; variable
contains a list of the command-line targets,
if any were specified,
and if no command-line targets were specified,
it contains a list of the targets specified
via the &Default; method or function.
Because &BUILD_TARGETS; may contain a list of &SCons; nodes,
you must convert the list elements to strings
if you want to print them or look for a specific target name,
just like the &DEFAULT_TARGETS; list:
prog1 = Program('prog1.c')
Program('prog2.c')
Default(prog1)
print "BUILD_TARGETS is", map(str, BUILD_TARGETS)
prog1.c
prog2.c
Notice how the value of &BUILD_TARGETS;
changes depending on whether a target is
specified on the command line:
scons -Q
scons -Q prog2
scons -Q -c .
Command-Line variable=value Build Options
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 an option,
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 &cv-link-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')
prog.c
This results in the -g
compiler option being used when
debug=1
is used on the command line:
scons -Q debug=0
scons -Q debug=0
scons -Q debug=1
scons -Q debug=1
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.
Controlling Command-Line Build Options
Being able to use a command-line build option like
debug=1 is handy,
but it can be a chore to write specific Python code
to recognize each such option
and apply the values to a construction variable.
To help with this,
&SCons; supports a class to
define such build options easily,
and a mechanism to apply the
build options to a construction environment.
This allows you to control how the build options 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
&cv-link-CPPDEFINES; construction variable:
opts = Options()
opts.Add('RELEASE', 'Set to 1 to build for release', 0)
env = Environment(options = opts,
CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'})
env.Program(['foo.c', 'bar.c'])
foo.c
bar.c
This &SConstruct; file first creates an
&Options; object
(the opts = Options() call),
and then uses the object's &Add;
method to indicate that the &RELEASE;
option can be set on the command line,
and that it's 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 &Options;
object as an &options; keyword argument
to the &Environment; call
used to create the construction environment.
This then allows a user to set the
&RELEASE; build option 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
Providing Help for Command-Line Build Options
To make command-line build options most useful,
you ideally want to provide
some help text that will describe
the available options
when the user runs scons -h.
You could write this text by hand,
but &SCons; provides an easier way.
&Options; objects support a
&GenerateHelpText; method
that will, as its name indicates,
generate text that describes
the various options that
have been added to it.
You then pass the output from this method to
the &Help; function:
opts = Options('custom.py')
opts.Add('RELEASE', 'Set to 1 to build for release', 0)
env = Environment(options = opts)
Help(opts.GenerateHelpText(env))
&SCons; will now display some useful text
when the -h option is used:
scons -Q -h
Notice that the help output shows the default value,
and the current actual value of the build option.
Reading Build Options From a File
Being able to use a command-line build option like
debug=1 is handy,
but it can be a chore to write specific Python code
to recognize each such option
and apply the values to a construction variable.
To help with this,
&SCons; supports a class to
define such build options easily
and to read build option values from a file.
This allows you to control how the build options affect
construction environments.
The way you do this is by specifying
a file name when you call &Options;,
like &custom_py; in the following example:
opts = Options('custom.py')
opts.Add('RELEASE', 'Set to 1 to build for release', 0)
env = Environment(options = opts,
CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'})
env.Program(['foo.c', 'bar.c'])
Help(opts.GenerateHelpText(env))
foo.c
bar.c
RELEASE = 1
This then allows us to control the &RELEASE;
variable by setting it in the &custom_py; file:
Note that this file is actually executed
like a Python script.
Now when we run &SCons;:
scons -Q
And if we change the contents of &custom_py; to:
opts = Options('custom.py')
opts.Add('RELEASE', 'Set to 1 to build for release', 0)
env = Environment(options = opts,
CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'})
env.Program(['foo.c', 'bar.c'])
Help(opts.GenerateHelpText(env))
foo.c
bar.c
RELEASE = 0
The object files are rebuilt appropriately
with the new option:
scons -Q
Canned Build Options
&SCons; provides a number of functions
that provide ready-made behaviors
for various types of command-line build options.
True/False Values: the &BoolOption; Build Option
It's often handy to be able to specify an
option 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 &BoolOption; function
makes it easy to accomodate a variety of
common values that represent
&true; or &false;.
The &BoolOption; function takes three arguments:
the name of the build option,
the default value of the build option,
and the help string for the option.
It then returns appropriate information for
passing to the &Add; method of an &Options; object, like so:
opts = Options('custom.py')
opts.Add(BoolOption('RELEASE', 'Set to build for release', 0))
env = Environment(options = opts,
CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'})
env.Program('foo.c')
foo.c
With this build option,
the &RELEASE; variable can now be enabled by
setting it to the value yes
or t:
scons -Q RELEASE=yes foo.o
scons -Q RELEASE=t foo.o
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
scons -Q RELEASE=f foo.o
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
Single Value From a List: the &EnumOption; Build Option
Suppose that we want a user to be able to
set a &COLOR; option
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 &EnumOption;,
which takes a list of &allowed_values
in addition to the variable name,
default value,
and help text arguments:
opts = Options('custom.py')
opts.Add(EnumOption('COLOR', 'Set background color', 'red',
allowed_values=('red', 'green', 'blue')))
env = Environment(options = opts,
CPPDEFINES={'COLOR' : '"${COLOR}"'})
env.Program('foo.c')
foo.c
The user can now explicity set the &COLOR; build option
to any of the specified allowed values:
scons -Q COLOR=red foo.o
scons -Q COLOR=blue foo.o
scons -Q COLOR=green foo.o
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
The &EnumOption; 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 ↦ dictionary
that will map its key values
to the desired legal value:
opts = Options('custom.py')
opts.Add(EnumOption('COLOR', 'Set background color', 'red',
allowed_values=('red', 'green', 'blue'),
map={'navy':'blue'}))
env = Environment(options = opts,
CPPDEFINES={'COLOR' : '"${COLOR}"'})
env.Program('foo.c')
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;
option to build a target:
scons -Q COLOR=navy foo.o
By default, when using the &EnumOption; function,
arguments that differ
from the legal values
only in case
are treated as illegal values:
scons -Q COLOR=Red foo.o
scons -Q COLOR=BLUE foo.o
scons -Q COLOR=nAvY foo.o
The &EnumOption; function can take an additional
&ignorecase; keyword argument that,
when set to 1,
tells &SCons; to allow case differences
when the values are specified:
opts = Options('custom.py')
opts.Add(EnumOption('COLOR', 'Set background color', 'red',
allowed_values=('red', 'green', 'blue'),
map={'navy':'blue'},
ignorecase=1))
env = Environment(options = opts,
CPPDEFINES={'COLOR' : '"${COLOR}"'})
env.Program('foo.c')
foo.c
Which yields the output:
scons -Q COLOR=Red foo.o
scons -Q COLOR=BLUE foo.o
scons -Q COLOR=nAvY foo.o
scons -Q COLOR=green foo.o
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:
opts = Options('custom.py')
opts.Add(EnumOption('COLOR', 'Set background color', 'red',
allowed_values=('red', 'green', 'blue'),
map={'navy':'blue'},
ignorecase=2))
env = Environment(options = opts,
CPPDEFINES={'COLOR' : '"${COLOR}"'})
env.Program('foo.c')
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
scons -Q COLOR=nAvY foo.o
scons -Q COLOR=GREEN foo.o
Multiple Values From a List: the &ListOption; Build Option
Another way in which you might want to allow users
to control build option is to
specify a list of one or more legal values.
&SCons; supports this through the &ListOption; function.
If, for example, we want a user to be able to set a
&COLORS; option to one or more of the legal list of values:
opts = Options('custom.py')
opts.Add(ListOption('COLORS', 'List of colors', 0,
['red', 'green', 'blue']))
env = Environment(options = opts,
CPPDEFINES={'COLORS' : '"${COLORS}"'})
env.Program('foo.c')
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
scons -Q COLORS=blue,green,red foo.o
In addition, the &ListOption; 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
scons -Q COLORS=none foo.o
And, of course, an illegal value
still generates an error message:
scons -Q COLORS=magenta foo.o
Path Names: the &PathOption; Build Option
&SCons; supports a &PathOption; function
to make it easy to create a build option
to control an expected path name.
If, for example, you need to
define a variable in the preprocessor
that control the location of a
configuration file:
opts = Options('custom.py')
opts.Add(PathOption('CONFIG',
'Path to configuration file',
'__ROOT__/etc/my_config'))
env = Environment(options = opts,
CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'})
env.Program('foo.c')
foo.c
/opt/location
/opt/location
This then allows the user to
override the &CONFIG; build option
on the command line as necessary:
scons -Q foo.o
scons -Q CONFIG=__ROOT__/usr/local/etc/other_config foo.o
By default, &PathOption; checks to make sure
that the specified path exists and generates an error if it
doesn't:
scons -Q CONFIG=__ROOT__/does/not/exist foo.o
&PathOption; 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 &PathOption_PathIsFile; method:
opts = Options('custom.py')
opts.Add(PathOption('CONFIG',
'Path to configuration file',
'__ROOT__/etc/my_config',
PathOption.PathIsFile))
env = Environment(options = opts,
CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'})
env.Program('foo.c')
foo.c
/opt/location
Conversely, to ensure that any specified paths are
directories and not files,
use the &PathOption_PathIsDir; method:
opts = Options('custom.py')
opts.Add(PathOption('DBDIR',
'Path to database directory',
'__ROOT__/var/my_dbdir',
PathOption.PathIsDir))
env = Environment(options = opts,
CPPDEFINES={'DBDIR' : '"$DBDIR"'})
env.Program('foo.c')
foo.c
/opt/location
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 &PathOption_PathIsDirCreate; method:
opts = Options('custom.py')
opts.Add(PathOption('DBDIR',
'Path to database directory',
'__ROOT__/var/my_dbdir',
PathOption.PathIsDirCreate))
env = Environment(options = opts,
CPPDEFINES={'DBDIR' : '"$DBDIR"'})
env.Program('foo.c')
foo.c
/opt/location
Lastly, if you don't care whether the path exists,
is a file, or a directory,
use the &PathOption_PathAccept; method
to accept any path that the user supplies:
opts = Options('custom.py')
opts.Add(PathOption('OUTPUT',
'Path to output file or directory',
None,
PathOption.PathAccept))
env = Environment(options = opts,
CPPDEFINES={'OUTPUT' : '"$OUTPUT"'})
env.Program('foo.c')
foo.c
Enabled/Disabled Path Names: the &PackageOption; Build Option
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 &PackageOption;
function to support this:
opts = Options('custom.py')
opts.Add(PackageOption('PACKAGE',
'Location package',
'__ROOT__/opt/location'))
env = Environment(options = opts,
CPPDEFINES={'PACKAGE' : '"$PACKAGE"'})
env.Program('foo.c')
foo.c
/opt/location
/opt/location
When the &SConscript; file uses the &PackageOption; 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
scons -Q PACKAGE=__ROOT__/usr/local/location foo.o
scons -Q PACKAGE=yes foo.o
scons -Q PACKAGE=no foo.o
Adding Multiple Command-Line Build Options at Once
Lastly, &SCons; provides a way to add
multiple build options to an &Options object at once.
Instead of having to call the &Add; method
multiple times,
you can call the &AddOptions;
method with a list of build options
to be added to the object.
Each build option 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 canned
functions for pre-packaged command-line build options.
in any order:
opts = Options()
opts.AddOptions(
('RELEASE', 'Set to 1 to build for release', 0),
('CONFIG', 'Configuration file', '/etc/my_config'),
BoolOption('warnings', 'compilation with -Wall and similiar', 1),
EnumOption('debug', 'debug output and symbols', 'no',
allowed_values=('yes', 'no', 'full'),
map={}, ignorecase=0), # case sensitive
ListOption('shared',
'libraries to build as shared libraries',
'all',
names = list_of_libs),
PackageOption('x11',
'use X11 installed here (yes = search some places)',
'yes'),
PathOption('qtdir', 'where the root of Qt is installed', qtdir),
)