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: %s"%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.