object. To create an Options object, call the Options() function:
.TP
-.RI Options([ file ])
+.RI Options([ files ])
This creates an Options object that will read construction variables from
-the filename based in the
-.I file
-argument. If no filename is given, then no file will be read. Example:
+the file or list of filenames given in the
+.I files
+argument. If no files are given, then no files will be read. Example:
.ES
opts = Options('custom.py')
.I default
is the default value of the variable.
.I validater
-is called to validate the value of the variable, and should take two
-arguments: key and value.
+is called to validate the value of the variable, and should take three
+arguments: key, value, and environment
.I converter
is called to convert the value before putting it in the environment, and
should take a single argument: value. Example:
env = Environment(options=opts)
.EE
+.TP
+.RI Save( filename ", " env )
+This saves the currently set options into a script file named
+.I filename
+that can be used on the next invocation to automatically load the current
+settings. This method combined with the Options method can be used to
+support caching of options between runs.
+
+.ES
+env = Environment()
+opts = Options(['options.cache', 'custom.py'])
+opts.Add(...)
+opts.Update(env)
+opts.Save('options.cache', env)
+.EE
+
.TP
.RI GenerateHelpText( env )
This generates help text documenting the customizable construction
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Errors
+import SCons.Util
import os.path
Holds all the options, updates the environment with the variables,
and renders the help text.
"""
- def __init__(self, file=None):
+ def __init__(self, files=None):
"""
- file - [optional] the name of the customizable file.
+ files - [optional] List of option configuration files to load
+ (backward compatibility) If a single string is passed it is
+ automatically placed in a file list
"""
self.options = []
- self.file = file
+ self.files = None
+ if SCons.Util.is_String(files):
+ self.files = [ files ]
+ elif files:
+ self.files = files
+
def Add(self, key, help="", default=None, validater=None, converter=None):
"""
help - optional help text for the options
default - optional default value
validater - optional function that is called to validate the option's value
+ Called with (key, value, environment)
converter - optional function that is called to convert the option's value before
- putting it in the environment.
+ putting it in the environment.
"""
class Option:
option.default = default
option.validater = validater
option.converter = converter
+ option.should_save = 0
self.options.append(option)
values[option.key] = option.default
# next set the value specified in the options file
- if self.file and os.path.exists(self.file):
- execfile(self.file, values)
+ if self.files:
+ for filename in self.files:
+ if os.path.exists(filename):
+ execfile(filename, values)
# finally set the values specified on the command line
values.update(args)
+
+ # Update the should save state
+ # This will mark options that have either been set on command line
+ # or in a loaded option file
+ # KeyError occurs when an option has default of None and has not been set
+ for option in self.options:
+ try:
+ if values[option.key] != option.default:
+ option.should_save = 1
+ except KeyError:
+ pass
# put the variables in the environment:
# (don't copy over variables that are not declared
# Finally validate the values:
for option in self.options:
if option.validater:
- option.validater(option.key, env.subst('${%s}'%option.key))
+ option.validater(option.key, env.subst('${%s}'%option.key), env)
+
+
+
+ def Save(self, filename, env):
+ """
+ Saves all the options in the given file. This file can
+ then be used to load the options next run. This can be used
+ to create an option cache file.
+
+ filename - Name of the file to save into
+ env - the environment get the option values from
+ """
+
+ # Create the file and write out the header
+ try:
+ fh = open(filename, 'w')
+
+ # Make an assignment in the file for each option within the environment
+ # that was assigned a value other than the default.
+ for option in self.options:
+ try:
+ value = env[option.key]
+ if option.should_save:
+ fh.write('%s = \'%s\'\n' % (option.key, value))
+ except KeyError:
+ pass
+
+ fh.close()
+ except IOError, x:
+ raise SCons.Errors.UserError, 'Error writing options to file: %s\n%s' % (filename, x)
def GenerateHelpText(self, env):
"""
import sys
import string
+
class Environment:
def __init__(self):
self.dict = {}
return self.dict.has_key(key)
-def check(key,value):
+def check(key,value, env):
assert value == 6 * 9,key
+
+# Check saved option file by executing and comparing against
+# the expected dictionary
+def checkSave(file, expected):
+ gdict = {}
+ ldict = {}
+ execfile(file, gdict, ldict)
+ assert expected == ldict
class OptionsTestCase(unittest.TestCase):
def test_Add(self):
assert o.default == None
assert o.validater == None
assert o.converter == None
+ assert o.should_save == 0
o = opts.options[1]
assert o.key == 'ANSWER'
assert o.help == 'THE answer to THE question'
assert o.default == "42"
- o.validater(o.key, o.converter(o.default))
+ o.validater(o.key, o.converter(o.default), {})
def test_Update(self):
opts.Update(env, {'ANSWER':'42'})
assert env['ANSWER'] == 54
+
+ def test_Save(self):
+
+ test = TestSCons.TestSCons()
+ cache_file = test.workpath('cached.options')
+ opts = SCons.Options.Options()
+
+ # test saving out empty file
+ opts.Add('OPT_VAL',
+ 'An option to test',
+ 21,
+ None,
+ None)
+
+ env = Environment()
+ opts.Update(env, {})
+ assert env['OPT_VAL'] == 21
+ opts.Save(cache_file, env)
+ checkSave(cache_file, {})
+
+
def test_GenerateHelpText(self):
opts = SCons.Options.Options()
cc, ccflags = string.split(test.stdout(), '\n')[1:3]
test.write('SConstruct', """
+# test validator. Change a key and add a new one to the environment
+def validator(key, value, environ):
+ environ[key] = "v"
+ environ["valid_key"] = "v"
+
opts = Options('custom.py')
opts.Add('RELEASE_BUILD',
'Set to 1 to build a release build',
opts.Add('CC',
'The C compiler')
+opts.Add('VALIDATE',
+ 'An option for testing validation',
+ "notset",
+ validator,
+ None)
+
opts.Add('UNSPECIFIED',
'An option with no value')
env['CCFLAGS'] = env['CCFLAGS'] + ' -O'
if env['DEBUG_BUILD']:
env['CCFLAGS'] = env['CCFLAGS'] + ' -g'
-
+
env = Environment(options=opts, tools=['default', test_tool])
print env['DEBUG_BUILD']
print env['CC']
print env['CCFLAGS']
+print env['VALIDATE']
+print env['valid_key']
# unspecified options should not be set:
assert not env.has_key('UNSPECIFIED')
assert env['RELEASE_BUILD'] == r
Default(env.Alias('dummy', None))
-
+
""")
def check(expect):
assert result[1:len(expect)+1] == expect, (result[1:len(expect)+1], expect)
test.run()
-check(['0', '1', cc, ccflags + ' -g'])
+check(['0', '1', cc, ccflags + ' -g', 'v', 'v'])
test.run(arguments='"RELEASE_BUILD=1"')
-check(['1', '1', cc, ccflags + ' -O -g'])
+check(['1', '1', cc, ccflags + ' -O -g', 'v', 'v'])
test.run(arguments='"RELEASE_BUILD=1" "DEBUG_BUILD=0"')
-check(['1', '0', cc, ccflags + ' -O'])
+check(['1', '0', cc, ccflags + ' -O', 'v', 'v'])
test.run(arguments='"CC=not_a_c_compiler"')
-check(['0', '1', 'not_a_c_compiler', ccflags + ' -g'])
+check(['0', '1', 'not_a_c_compiler', ccflags + ' -g', 'v', 'v'])
test.run(arguments='"UNDECLARED=foo"')
-check(['0', '1', cc, ccflags + ' -g'])
+check(['0', '1', cc, ccflags + ' -g', 'v', 'v'])
test.run(arguments='"CCFLAGS=--taco"')
-check(['0', '1', cc, ccflags + ' -g'])
+check(['0', '1', cc, ccflags + ' -g', 'v', 'v'])
test.write('custom.py', """
DEBUG_BUILD=0
""")
test.run()
-check(['1', '0', cc, ccflags + ' -O'])
+check(['1', '0', cc, ccflags + ' -O', 'v', 'v'])
test.run(arguments='"DEBUG_BUILD=1"')
-check(['1', '1', cc, ccflags + ' -O -g'])
-
+check(['1', '1', cc, ccflags + ' -O -g', 'v', 'v'])
+
test.run(arguments='-h',
stdout = """scons: Reading SConscript files ...
scons: done reading SConscript files.
default: None
actual: %s
+VALIDATE: An option for testing validation
+ default: notset
+ actual: v
+
UNSPECIFIED: An option with no value
default: None
actual: None
Use scons -H for help about command-line options.
"""%cc)
+# Test saving of options and multi loading
+#
+test.write('SConstruct', """
+opts = Options(['custom.py', 'options.saved'])
+opts.Add('RELEASE_BUILD',
+ 'Set to 1 to build a release build',
+ 0,
+ None,
+ int)
+
+opts.Add('DEBUG_BUILD',
+ 'Set to 1 to build a debug build',
+ 1,
+ None,
+ int)
+
+opts.Add('UNSPECIFIED',
+ 'An option with no value')
+
+env = Environment(options = opts)
+
+print env['RELEASE_BUILD']
+print env['DEBUG_BUILD']
+
+opts.Save('options.saved', env)
+""")
+
+# Check the save file by executing and comparing against
+# the expected dictionary
+def checkSave(file, expected):
+ gdict = {}
+ ldict = {}
+ execfile(file, gdict, ldict)
+ assert expected == ldict
+
+# First test with no command line options
+# This should just leave the custom.py settings
+test.run()
+check(['1','0'])
+checkSave('options.saved', { 'RELEASE_BUILD':'1', 'DEBUG_BUILD':'0'})
+
+# Override with command line arguments
+test.run(arguments='"DEBUG_BUILD=3"')
+check(['1','3'])
+checkSave('options.saved', {'RELEASE_BUILD':'1', 'DEBUG_BUILD':'3'})
+
+# Now make sure that saved options are overridding the custom.py
+test.run()
+check(['1','3'])
+checkSave('options.saved', {'DEBUG_BUILD':'3', 'RELEASE_BUILD':'1'})
+
+# Load no options from file(s)
+# Used to test for correct output in save option file
+test.write('SConstruct', """
+opts = Options()
+opts.Add('RELEASE_BUILD',
+ 'Set to 1 to build a release build',
+ '0',
+ None,
+ int)
+
+opts.Add('DEBUG_BUILD',
+ 'Set to 1 to build a debug build',
+ '1',
+ None,
+ int)
+
+opts.Add('UNSPECIFIED',
+ 'An option with no value')
+
+env = Environment(options = opts)
+
+print env['RELEASE_BUILD']
+print env['DEBUG_BUILD']
+
+opts.Save('options.saved', env)
+""")
+
+# First check for empty output file when nothing is passed on command line
+test.run()
+check(['0','1'])
+checkSave('options.saved', {})
+
+# Now specify one option the same as default and make sure it doesn't write out
+test.run(arguments='"DEBUG_BUILD=1"')
+check(['0','1'])
+checkSave('options.saved', {})
+
+# Now specify same option non-default and make sure only it is written out
+test.run(arguments='"DEBUG_BUILD=0"')
+check(['0','0'])
+checkSave('options.saved',{'DEBUG_BUILD':'0'})
+
test.pass_test()