From aa4a41bed59a2e155cfed1c3abd3b57b7914a505 Mon Sep 17 00:00:00 2001 From: stevenknight Date: Thu, 10 Apr 2003 06:37:27 +0000 Subject: [PATCH] Options save. (Allen Bierbaum) git-svn-id: http://scons.tigris.org/svn/scons/trunk@639 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- doc/man/scons.1 | 28 +++++-- src/CHANGES.txt | 5 ++ src/engine/SCons/Options.py | 67 ++++++++++++++-- src/engine/SCons/OptionsTests.py | 35 +++++++- test/Options.py | 132 ++++++++++++++++++++++++++++--- 5 files changed, 241 insertions(+), 26 deletions(-) diff --git a/doc/man/scons.1 b/doc/man/scons.1 index a3f35af2..09de821d 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -2863,11 +2863,11 @@ command line or a text-based SConscript file through an Options 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') @@ -2885,8 +2885,8 @@ is the help text for the variable. .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: @@ -2907,6 +2907,22 @@ the Environment() function: 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 diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 1c01d1bc..d3221ace 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -18,6 +18,11 @@ RELEASE 0.14 - XXX - Remove the "platform" argument from tool specifications. + From Allen Bierbaum: + + - Pass an Environment to the Options validator method, and + add an Options.Save() method. + From Steven Knight: - Add support for Java (javac and jar). diff --git a/src/engine/SCons/Options.py b/src/engine/SCons/Options.py index 84365af8..d43be287 100644 --- a/src/engine/SCons/Options.py +++ b/src/engine/SCons/Options.py @@ -30,6 +30,7 @@ variables to a scons build. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import SCons.Errors +import SCons.Util import os.path @@ -38,13 +39,20 @@ class Options: 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): """ @@ -54,8 +62,9 @@ class Options: 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: @@ -67,6 +76,7 @@ class Options: option.default = default option.validater = validater option.converter = converter + option.should_save = 0 self.options.append(option) @@ -86,11 +96,24 @@ class Options: 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 @@ -114,8 +137,38 @@ class Options: # 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): """ diff --git a/src/engine/SCons/OptionsTests.py b/src/engine/SCons/OptionsTests.py index 3a02eb8d..70207878 100644 --- a/src/engine/SCons/OptionsTests.py +++ b/src/engine/SCons/OptionsTests.py @@ -29,6 +29,7 @@ import SCons.Options import sys import string + class Environment: def __init__(self): self.dict = {} @@ -42,8 +43,16 @@ class Environment: 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): @@ -62,12 +71,13 @@ class OptionsTestCase(unittest.TestCase): 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): @@ -132,6 +142,27 @@ class OptionsTestCase(unittest.TestCase): 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() diff --git a/test/Options.py b/test/Options.py index 9c817ba5..57424076 100644 --- a/test/Options.py +++ b/test/Options.py @@ -39,6 +39,11 @@ test.run() 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', @@ -55,6 +60,12 @@ opts.Add('DEBUG_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') @@ -63,7 +74,7 @@ def test_tool(env): env['CCFLAGS'] = env['CCFLAGS'] + ' -O' if env['DEBUG_BUILD']: env['CCFLAGS'] = env['CCFLAGS'] + ' -g' - + env = Environment(options=opts, tools=['default', test_tool]) @@ -73,6 +84,8 @@ print env['RELEASE_BUILD'] 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') @@ -88,7 +101,7 @@ opts.Update(env) assert env['RELEASE_BUILD'] == r Default(env.Alias('dummy', None)) - + """) def check(expect): @@ -96,22 +109,22 @@ 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 @@ -119,11 +132,11 @@ RELEASE_BUILD=1 """) 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. @@ -141,6 +154,10 @@ CC: The C compiler default: None actual: %s +VALIDATE: An option for testing validation + default: notset + actual: v + UNSPECIFIED: An option with no value default: None actual: None @@ -148,4 +165,97 @@ UNSPECIFIED: An option with no value 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() -- 2.26.2