10.2. Command-Line variable=value Build Variables

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.

Note

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).

10.2.1. Controlling Command-Line Build Variables

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.

10.2.2. Providing Help for Command-Line Build Variables

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.

10.2.3. Reading Build Variables From a File

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.

10.2.4. Pre-Defined Build Variable Functions

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.

10.2.4.1. True/False Values: the BoolVariable Build Variable Function

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>

10.2.4.2. Single Value From a Selection: the EnumVariable Build Variable Function

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

10.2.4.3. Multiple Values From a List: the ListVariable Build Variable Function

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.

10.2.4.4. Path Names: the PathVariable Build Variable Function

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')
          

10.2.4.5. Enabled/Disabled Path Names: the PackageVariable Build Variable Function

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

10.2.5. Adding Multiple Command-Line Build Variables at Once

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),
)
        

10.2.6. Handling Unknown Command-Line Build Variables: the UnknownVariables Function

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.