9.9. Canned Build Options

SCons provides a number of functions that provide ready-made behaviors for various types of command-line build options.

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

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

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

The user can now explicity set the COLOR build option 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
        File "/home/my/project/SConstruct", line 5, in <module>
      

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

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
        cc -o foo.o -c -DCOLOR="blue" foo.c
      

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: *** Invalid value for option COLOR: Red
        File "/home/my/project/SConstruct", line 5, in <module>
        % scons -Q COLOR=BLUE foo.o
        
        scons: *** Invalid value for option COLOR: BLUE
        File "/home/my/project/SConstruct", line 5, in <module>
        % scons -Q COLOR=nAvY foo.o
        
        scons: *** Invalid value for option COLOR: nAvY
        File "/home/my/project/SConstruct", line 5, in <module>
      

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

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:


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

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
      

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

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

9.9.4. 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 controls the location of a configuration file:


           opts = Options('custom.py')
           opts.Add(PathOption('CONFIG',
                               'Path to configuration file',
                               '/etc/my_config'))
           env = Environment(options = opts,
                             CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'})
           env.Program('foo.c')
      

This then allows the user to override the CONFIG build option 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, PathOption 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>
      

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',
                               '/etc/my_config',
                               PathOption.PathIsFile))
           env = Environment(options = opts,
                             CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'})
           env.Program('foo.c')
      

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',
                               '/var/my_dbdir',
                               PathOption.PathIsDir))
           env = Environment(options = opts,
                             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 PathOption.PathIsDirCreate method:


           opts = Options('custom.py')
           opts.Add(PathOption('DBDIR',
                               'Path to database directory',
                               '/var/my_dbdir',
                               PathOption.PathIsDirCreate))
           env = Environment(options = opts,
                             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 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')
      

9.9.5. 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',
                                  '/opt/location'))
           env = Environment(options = opts,
                             CPPDEFINES={'PACKAGE' : '"$PACKAGE"'})
           env.Program('foo.c')
      

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