Options save. (Allen Bierbaum)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 10 Apr 2003 06:37:27 +0000 (06:37 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 10 Apr 2003 06:37:27 +0000 (06:37 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@639 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Options.py
src/engine/SCons/OptionsTests.py
test/Options.py

index a3f35af29bdc2f7c163bc5fd0d3108c305dca1d5..09de821de800e46a8f0f8adc69268cd4755b9bb5 100644 (file)
@@ -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
index 1c01d1bc9a803d8b792d20f7f5745df3c9020764..d3221acec087db5a28d6ea7b685f60b82ab513b1 100644 (file)
@@ -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).
index 84365af8f6b4be655a56d9c7c74ccdf0223d7920..d43be28768d040be46e697222d17b87c02090215 100644 (file)
@@ -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):
         """
index 3a02eb8d7738b39aed90c27aeadf0fe43c358690..70207878aa8db78adabcd08bc5efea91e4538c51 100644 (file)
@@ -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()
 
index 9c817ba5a02d85ffda42f0995f75fc85944d8fc4..57424076e546ad5c66219694d87c6e6a4797dd5c 100644 (file)
@@ -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()