You may want to control various aspects
of your build by allowing
variable
=value
values to be specified on the command line.
For example, suppose you want 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.
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.
Two usage notes (both shown in the example above):
No matter how you intend to use them, the values read from a command line (i.e., external to the program) are always strings. You may need to do type conversion.
When you retrieve from the ARGUMENTS
dictionary,
it is useful to use the Python dictionary
get
method,
so you can supply a default value if the variable is
not given on the command line. Otherwise, the build
will fail with a KeyError
if the variable is not set.
SCons keeps track of the precise build command used to build each object file,
and as a result can determine that the object and executable files
need rebuilding 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 map each keyword to one value,
and thus only "remembers" the last setting
for each keyword on the command line.
This makes the ARGUMENTS
dictionary
less than ideal if you want to allow
specifying 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
(Python versions since 3.6 now maintain dictionaries in
insertion order, so this problem is mitigated).
To accommodate these requirements,
SCons also provides an ARGLIST
variable
that gives you direct access to build variable
settings from 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 lets you
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 rather provide slightly different views
into how you 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 Python dictionary access),
and the ARGLIST
list
is more flexible
(since you can examine the
specific order in which
the command-line variable settings were given).
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 provides a Variables
container class to
hold definitions of such build variables,
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 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 build command
with the appropriate define
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', default=0) env = Environment(variables=vars, CPPDEFINES={'RELEASE_BUILD': '${RELEASE}'}) env.Program(['foo.c', 'bar.c'])
This SConstruct
snippet first creates a Variables
object which
uses the values from the command-line variables dictionary ARGUMENTS
.
It then uses the object's Add
method to indicate that the RELEASE
variable can be set on the command line, and that
if not set the default value is 0
.
The newly created Variables
object
is passed to the Environment
call
used to create the construction environment
using a variables
keyword argument.
This then allows you 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
The Variables()
call in this example looks
a little awkward. The function takes two optional arguments:
a script name and a dictionary. In order to specify the
dictionary as the second argument, you must provide the
script argument as the first; since there's actually no script,
use None
as a sentinel value.
However, if you omit all the arguments,
the default behavior is to read from the ARGUMENTS
dictionary anyway,
which is what we want. The example shows it this way because the arguments
were introduced in this order, but you should feel free to just
leave off the arguments if the default behavior is what you want.
Historical note: In old SCons (prior to 0.98.1 from 2008),
these build variables were known as "command-line build options."
At that time, the class was named Options
and the predefined functions to construct options were named
BoolOption
, EnumOption
, ListOption
,
PathOption
, PackageOption
and AddOptions
(contrast
with the current names in
Section 10.2.4, “Pre-Defined Build Variable Functions”, below).
Because the Internet has a very long memory,
you may encounter these names in older
SConscript
files, wiki pages, blog entries, StackExchange
articles, etc.
These old names no longer work, but a mental substitution
of “Variable” for “Option”
allows the concepts to transfer to current usage models.
To make command-line build variables more useful,
you may want to provide
some help text to describe the available variables
when you ask for help (run scons -h
).
You can write this text by hand,
but SCons provides some assistance.
Variables objects provide a
GenerateHelpText
method to
generate text that describes
the various variables that
have been added to it. The default text includes
the help string itself plus other information
such as allowed values.
(The generated text can also be customized by
replacing the FormatVariableHelpText
method).
You then pass the output from this method to
the Help
function:
vars = Variables() vars.Add('RELEASE', help='Set to 1 to build for release', default=0) env = Environment(variables=vars) Help(vars.GenerateHelpText(env))
scons now displays 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 SCons built-in command-line options.
You can see the help output shows the default value as well as the current actual value of the build variable.
Being able to specify the
value of a build variable on the command line
is useful,
but can still become tedious
if you have to specify the variable
every time you run SCons.
To make this easier,
you can provide customized build variable settings
in a Python script by providing a file name when the
Variables
object is created:
vars = Variables('custom.py') vars.Add('RELEASE', help='Set to 1 to build for release', default=0) env = Environment(variables=vars, CPPDEFINES={'RELEASE_BUILD': '${RELEASE}'}) env.Program(['foo.c', 'bar.c']) Help(vars.GenerateHelpText(env))
This then allows you to control the RELEASE
variable by setting it in the custom.py
script:
RELEASE = 1
Note that this file is actually executed like a Python script. Now when you 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 you 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)
If both a variables script and a dictionary are supplied, the dictionary is evaluated last, so values from the command line "win" if there are any duplicate keys. This rule allows you to move some common settings to a variables script, but still be able to override those for a given build without changing the script.
SCons provides a number of convenience functions
that provide behavior definitions
for various types of command-line build variables.
These functions all return a tuple which is ready
to be passed to the Add
or AddVariables
method call.
You are of course free to define your own behaviors
as well.
It is 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 accommodate
different preferences for how to represent
true
or false
values.
The BoolVariable
function
makes it easy to accommodate 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', help='Set to build for release', default=False)) env = Environment(variables=vars, CPPDEFINES={'RELEASE_BUILD': '${RELEASE}'}) env.Program('foo.c')
With this build variable in place,
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 you try 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 variable: 'bad_value'
File "/home/my/project/SConstruct", line 3, in <module>
Suppose that you want to allow
setting a COLOR
variable
that selects a background color to be
displayed by an application,
but that you want to restrict the
choices to a specific set of allowed colors.
You can set this up quite easily
using the EnumVariable
function,
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', help='Set background color', default='red', allowed_values=('red', 'green', 'blue'), ) ) env = Environment(variables=vars, CPPDEFINES={'COLOR': '"${COLOR}"'}) env.Program('foo.c') Help(vars.GenerateHelpText(env))
You can now explicitly 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, 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 enum variable 'COLOR': 'magenta'. Valid values are: ('red', 'green', 'blue')
File "/home/my/project/SConstruct", line 10, in <module>
This example can also serve to further illustrate help
generation: the help message here picks up not only the
help
text, but augments it with
information gathered from allowed_values
and default
:
% scons -Q -h
COLOR: Set background color (red|green|blue)
default: red
actual: red
Use scons -H for help about SCons built-in command-line options.
The EnumVariable
function also provides a way
to map alternate names to allowed values.
Suppose, for example, you want to allow
the word navy
to be used as a synonym for
blue
.
You do this by adding a map
dictionary
that maps its key values
to the desired allowed value:
vars = Variables('custom.py') vars.Add( EnumVariable( 'COLOR', help='Set background color', default='red', allowed_values=('red', 'green', 'blue'), map={'navy': 'blue'}, ) ) env = Environment(variables=vars, CPPDEFINES={'COLOR': '"${COLOR}"'}) env.Program('foo.c')
Now you can supply
navy
on the command line,
and SCons translates that 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,
the allowed values are case-sensitive:
%scons -Q COLOR=Red foo.o
scons: *** Invalid value for enum variable 'COLOR': 'Red'. Valid values are: ('red', 'green', 'blue') File "/home/my/project/SConstruct", line 10, in <module> %scons -Q COLOR=BLUE foo.o
scons: *** Invalid value for enum variable 'COLOR': 'BLUE'. Valid values are: ('red', 'green', 'blue') File "/home/my/project/SConstruct", line 10, in <module> %scons -Q COLOR=nAvY foo.o
scons: *** Invalid value for enum variable 'COLOR': 'nAvY'. Valid values are: ('red', 'green', 'blue') File "/home/my/project/SConstruct", line 10, 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', help='Set background color', default='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 supplied,
only ignoring the case for matching.
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', help='Set background color', default='red', allowed_values=('red', 'green', 'blue'), map={'navy': 'blue'}, ignorecase=2, ) ) env = Environment(variables=vars, CPPDEFINES={'COLOR': '"${COLOR}"'}) env.Program('foo.c')
Now SCons uses values of
red
,
green
or
blue
regardless of how those values are spelled
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 control a build variable is to
specify a list of allowed values, of which one or more can be chosen
(where EnumVariable
allows exactly one value to be chosen).
SCons provides this through the ListVariable
function.
If, for example, you want to be able to set a
COLORS
variable to one or more of the allowed values:
vars = Variables('custom.py') vars.Add( ListVariable( 'COLORS', help='List of colors', default=0, names=['red', 'green', 'blue'] ) ) env = Environment(variables=vars, CPPDEFINES={'COLORS': '"${COLORS}"'}) env.Program('foo.c')
You can now specify a comma-separated list of allowed values, which get translated into a space-separated list for passing to the build commands:
%scons -Q COLORS=red,blue foo.o
cc -o foo.o -c -DCOLORS="red -Dblue" foo.c %scons -Q COLORS=blue,green,red foo.o
cc -o foo.o -c -DCOLORS="blue -Dgreen -Dred" foo.c
In addition, the ListVariable
function
lets you specify explicit keywords of
all
or none
to select all of the allowed values,
or none of them, respectively:
%scons -Q COLORS=all foo.o
cc -o foo.o -c -DCOLORS="red -Dgreen -Dblue" 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: *** Invalid value(s) for variable 'COLORS': 'magenta'. Valid values are: blue,green,red,all,none
File "/home/my/project/SConstruct", line 7, in <module>
You can use this last characteristic as a way to enforce at least
one of your valid options being chosen by specifying the valid
values with the names
parameter and then
giving a value not in that list as the default
parameter - that way if no value is given on the command line,
the default is chosen, SCons errors out as this is invalid.
The example is, in fact, set up that way by using
0
as the default:
% scons -Q foo.o
scons: *** Invalid value(s) for variable 'COLORS': '0'. Valid values are: blue,green,red,all,none
File "/home/my/project/SConstruct", line 7, in <module>
This technique works for EnumVariable
as well.
SCons provides 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 preprocessor macro
that controls the location of a
configuration file:
vars = Variables('custom.py') vars.Add( PathVariable( 'CONFIG', help='Path to configuration file', default='/etc/my_config' ) ) env = Environment(variables=vars, CPPDEFINES={'CONFIG_FILE': '"$CONFIG"'}) env.Program('foo.c')
This allows you 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 variable 'CONFIG' does not exist: /does/not/exist
File "/home/my/project/SConstruct", line 7, 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 as the validation function:
vars = Variables('custom.py') vars.Add( PathVariable( 'CONFIG', help='Path to configuration file', default='/etc/my_config', validator=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 as the validation function:
vars = Variables('custom.py') vars.Add( PathVariable( 'DBDIR', help='Path to database directory', default='/var/my_dbdir', validator=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 as the validation function:
vars = Variables('custom.py') vars.Add( PathVariable( 'DBDIR', help='Path to database directory', default='/var/my_dbdir', validator=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 you supply:
vars = Variables('custom.py') vars.Add( PathVariable( 'OUTPUT', help='Path to output file or directory', default=None, validator=PathVariable.PathAccept, ) ) env = Environment(variables=vars, CPPDEFINES={'OUTPUT': '"$OUTPUT"'}) env.Program('foo.c')
Sometimes you want to give
even more control over a path name variable,
allowing them to be explicitly enabled or disabled
by using yes
or no
keywords,
in addition to allowing supplying an explicit path name.
SCons provides the PackageVariable
function to support this:
vars = Variables("custom.py") vars.Add( PackageVariable("PACKAGE", help="Location package", default="/opt/location") ) env = Environment(variables=vars, CPPDEFINES={"PACKAGE": '"$PACKAGE"'}) env.Program("foo.c")
When the SConscript
file uses the PackageVariable
function,
you can still use the default
or supply an overriding path name,
but you 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 the build variables to be added to the object.
Each build variable is specified
as either a tuple of arguments,
or as a call to one of the pre-defined
functions for pre-packaged command-line build variables,
which returns such a tuple. Note that an individual tuple
cannot take keyword arguments in the way that a call to
Add
or one of the build variable functions can.
The order of variables given to AddVariables
does not
matter.
vars = Variables() vars.AddVariables( ('RELEASE', 'Set to 1 to build for release', 0), ('CONFIG', 'Configuration file', '/etc/my_config'), BoolVariable('warnings', help='compilation with -Wall and similar', default=True), EnumVariable( 'debug', help='debug output and symbols', default='no', allowed_values=('yes', 'no', 'full'), map={}, ignorecase=0, ), ListVariable( 'shared', help='libraries to build as shared libraries', default='all', names=list_of_libs, ), PackageVariable( 'x11', help='use X11 installed here (yes = search some places)', default='yes' ), PathVariable('qtdir', help='where the root of Qt is installed', default=qtdir), )
Humans, of course,
occasionally misspell variable names in their command-line settings.
SCons does not generate an error or warning
for any unknown variables specified on the command line,
because it can not reliably tell
whether a given "misspelled" variable is
really unknown and a potential problem or not.
After all, you might be processing arguments directly
using ARGUMENTS
or ARGLIST
with some Python
code in your SConscript
file.
If, however, you are using a Variables
object to
define a specific set of command-line build variables
that you expect to be able to set,
you may want to provide an error
message or warning of your own
if a variable setting is specified
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 to get the
settings Variables
did not recognize:
vars = Variables(None) vars.Add('RELEASE', help='Set to 1 to build for release', default=0) env = Environment(variables=vars, CPPDEFINES={'RELEASE_BUILD': '${RELEASE}'}) unknown = vars.UnknownVariables() if unknown: print("Unknown variables: %s" % " ".join(unknown.keys())) Exit(1) env.Program('foo.c')
The UnknownVariables
method returns a dictionary
containing the keywords and values
of any variables specified on the command line
that are not
among the variables known to the Variables
object
(from having been specified using
the Variables
object's Add
method).
The example above,
checks whether the dictionary
returned by UnknownVariables
is non-empty,
and if so prints the Python list
containing the names of the unknown variables
and then calls 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: the variables
in the object are not fully processed until this has happened.