Provide uniform access to (some) command-line options. (Anthony Roach)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 30 Apr 2003 15:35:30 +0000 (15:35 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 30 Apr 2003 15:35:30 +0000 (15:35 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@665 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/RELEASE.txt
src/engine/SCons/Script/SConscript.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Sig/__init__.py
test/option--implicit-cache.py
test/option--max-drift.py
test/option-c.py
test/option-j.py

index 5c9db8ccffab8386dfb889cde9a5f1e9261d4ced..c96c8b805a94f80b056ca0a86df068022fc63e6f 100644 (file)
@@ -3136,7 +3136,7 @@ is returned.
 .TP
 .RI CheckContext.TryBuild( self ", " builder ", [" text ", " extension ])
 Low level implementation for testing specific builds;
-the methods above are based on this metho.
+the methods above are based on this method.
 Given the Builder instance
 .I builder
 and the optional 
@@ -3163,14 +3163,14 @@ int main(int argc, char **argv) {
   QApplication qapp(argc, argv);
   return 0;
 }
-"""
+""")
     if not ret:
         context.env.Replace(LIBS = lastLIBS, LIBPATH=lastLIBPATH, CPPPATH=lastCPPPATH)
     context.Result( ret )
     return ret
 
 env = Environment()
-conf = Configure( env, custom_tests = 'CheckQt' : CheckQt )
+conf = Configure( env, custom_tests = { 'CheckQt' : CheckQt } )
 if not conf.CheckQt('/usr/lib/qt'):
     print 'We really need qt!'
     Exit(1)
@@ -3694,11 +3694,11 @@ foo = FindFile('foo', ['dir1', 'dir2'])
 .\"XXX
 
 .TP
-.RI GetJobs()
-Get the number of jobs (commands) that will be run simultaneously. See also 
-.I -j
-and
-.IR SetJobs() .
+.RI GetOption( name )
+This function provides a way to query a select subset of the scons command line
+options from a SConscript file. See 
+.IR SetOption () 
+for a description of the options available.
 
 .TP
 .RI Help( text )
@@ -3865,6 +3865,20 @@ Return("foo")
 Return(["foo", "bar"])
 .EE
 
+.TP
+.RI SetOption( name , value )
+This function provides a way to set a select subset of the scons command
+line options from a SConscript file. The options supported are: clean which
+cooresponds to -c, --clean, and --remove; implicit_cache which corresponds
+to --implicit-cache; max_drift which corresponds to --max-drift; and
+num_jobs which corresponds to -j and --jobs. See the documentation for the
+corresponding command line object for information about each specific
+option. Example:
+
+.EE
+SetOption('max_drift', 1)
+.ES
+
 .TP
 .RI SConscript( script ", [" exports ", " build_dir ", " src_dir ", " duplicate ])
 This tells
@@ -3981,35 +3995,40 @@ SConscriptChdir(1)
 SConscript('bar/SConscript')   # will chdir to bar
 .EE
 
-.TP 
-.RI SetBuildSignatureType( type )
-
-This function tells SCons what type of build signature to use: "build" or
-"content". "build" means to concatenate the signatures of all source files
-of a derived file to make its signature, and "content" means to use
-the derived files content signature as its signature. "build" signatures
-are usually faster to compute, but "content" signatures can prevent
-redundant rebuilds. The default is "build".
-
 .TP
-.RI SetContentSignatureType( type )
+.RI TargetSignatures( type )
 
-This function tells SCons what type of content signature to use: "MD5" or
-"timestamp". "MD5" means to use the MD5 checksum of a files contents as
-its signature, and "timestamp" means to use a files timestamp as its
-signature. When using "timestamp" signatures, changes in the
-command line will not cause files to be rebuilt. "MD5" signatures take
-longer to compute, but "timestamp" signatures are less accurate. The
-default is "MD5".
+This function tells SCons what type of signatures to use
+for target files:
+.B "build"
+or
+.BR "content" .
+"build" means the signature of a target file
+is made by concatenating all of the
+signatures of all its source files.
+"content" means the signature of a target
+file is an MD5 checksum of its contents.
+"build" signatures are usually faster to compute,
+but "content" signatures can prevent unnecessary rebuilds
+when a target file is rebuilt to the exact same contents as last time.
+The default is "build".
 
 .TP
-.RI SetJobs( num )
-Specifies the number of jobs (commands) to run simultaneously. 
-.I -j
-overrides this function. See also 
-.I -j
-and 
-.IR GetJobs() .
+.RI SourceSignatures( type )
+
+This function tells SCons what type of signature to use for source files:
+.B "MD5"
+or
+.BR "timestamp" .
+"MD5" means the signature of a source file
+is the MD5 checksum of its contents.
+"timestamp" means the signature of a source file
+is its timestamp (modification time).
+When using "timestamp" signatures,
+changes in the command line will not cause files to be rebuilt.
+"MD5" signatures take longer to compute,
+but are more accurate than "timestamp" signatures.
+The default is "MD5".
 
 .TP
 .RI Split( arg )
index 04afab8fe132632e5a55b5570a0170efb5c86588..f6bdee0f7ec9135a21f06d82be0f25f57a5dae17 100644 (file)
@@ -90,6 +90,12 @@ RELEASE 0.14 - XXX
 
   - Fix an undefined exitvalmap on Win32 systems.
 
+  - Support new SetOption() and GetOption() functions for setting
+    various command-line options from with an SConscript file.
+
+  - Deprecate the old SetJobs() and GetJobs() functions in favor of
+    using the new generic {Set,Get}Option() functions.
+
   From David Snopek:
 
   - Contribute the "Autoscons" code for Autoconf-like checking for
index db275b7b15fdb4ae0441c9be50125ace17650bc7..e8d4b376234173111d223e4b28f061d1544b4e95 100644 (file)
@@ -45,6 +45,12 @@ RELEASE 0.14 - XXX
     file, you may need to modify the module to make the previously
     global variables available to your Export() or SConscript() call.
 
+  - The SetJobs() and GetJobs() functions have been deprecated.
+    Their new equivalents are:
+
+        SetOption('num_jobs', num)
+        GetOption('num_jobs')
+
   Please note the following important changes since release 0.11:
 
   - The default behavior of SCons is now to change to the directory in
index e3da64fda4806756e6682fa0c7b543b8e629062d..6b28b928b9e1149e2100ad5b2d66fe8ef443b79c 100644 (file)
@@ -443,17 +443,18 @@ def EnsurePythonVersion(major, minor):
         sys.exit(2)
 
 def GetJobs():
-    return SCons.Script.get_num_jobs(SCons.Script.options)
+    SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
+                        "The GetJobs() function has been deprecated;\n" +\
+                        "\tuse GetOption('num_jobs') instead.")
 
+    return GetOption('num_jobs')
 def SetJobs(num):
-    try:
-        tmp = int(num)
-        if tmp < 1:
-            raise ValueError
-        SCons.Script.num_jobs = tmp
-    except ValueError, x:
-        raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(num)
-    
+    SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
+                        "The SetJobs() function has been deprecated;\n" +\
+                        "\tuse SetOption('num_jobs', num) instead.")
+    SetOption('num_jobs', num)
+
 def Clean(target, files):
     if not isinstance(target, SCons.Node.Node):
         target = SCons.Node.FS.default_fs.Entry(target, create=1)
@@ -493,6 +494,12 @@ def Alias(name):
         alias = SCons.Node.Alias.default_ans.Alias(name)
     return alias
 
+def SetOption(name, value):
+    SCons.Script.ssoptions.set(name, value)
+
+def GetOption(name):
+    return SCons.Script.ssoptions.get(name)
+
 def BuildDefaultGlobals():
     """
     Create a dictionary containing all the default globals for 
@@ -504,6 +511,7 @@ def BuildDefaultGlobals():
     globals['Action']            = SCons.Action.Action
     globals['AddPostAction']     = AddPostAction
     globals['AddPreAction']      = AddPreAction
+    globals['Alias']             = Alias
     globals['ARGUMENTS']         = arguments
     globals['BuildDir']          = BuildDir
     globals['Builder']           = SCons.Builder.Builder
@@ -524,6 +532,7 @@ def BuildDefaultGlobals():
     globals['GetCommandHandler'] = SCons.Action.GetCommandHandler
     globals['GetJobs']           = GetJobs
     globals['GetLaunchDir']      = GetLaunchDir
+    globals['GetOption']         = GetOption    
     globals['Help']              = Help
     globals['Import']            = Import
     globals['Library']           = SCons.Defaults.StaticLibrary
@@ -543,6 +552,7 @@ def BuildDefaultGlobals():
     globals['SetCommandHandler'] = SCons.Action.SetCommandHandler
     globals['SetContentSignatureType'] = SetContentSignatureType
     globals['SetJobs']           = SetJobs
+    globals['SetOption']         = SetOption
     globals['SharedLibrary']     = SCons.Defaults.SharedLibrary
     globals['SharedObject']      = SCons.Defaults.SharedObject
     globals['SourceSignatures']  = SourceSignatures
@@ -552,5 +562,4 @@ def BuildDefaultGlobals():
     globals['TargetSignatures']  = TargetSignatures
     globals['Tool']              = SCons.Tool.Tool
     globals['WhereIs']           = SCons.Util.WhereIs
-    globals['Alias']             = Alias
     return globals
index d67b94985712ff7e8c58e9a81aba439e0432cc77..a1a54e69c6b6e9a7de32e7531a2a15beb6372f4d 100644 (file)
@@ -216,7 +216,7 @@ command_time = 0
 exit_status = 0 # exit status, assume success by default
 profiling = 0
 repositories = []
-sig_module = None
+sig_module = SCons.Sig.default_module
 num_jobs = 1 # this is modifed by SConscript.SetJobs()
 
 # Exceptions for this module
@@ -225,12 +225,6 @@ class PrintHelp(Exception):
 
 # utility functions
 
-def get_num_jobs(options):
-    if hasattr(options, 'num_jobs'):
-        return options.num_jobs
-    else:
-        return num_jobs
-
 def get_all_children(node): return node.all_children(None)
 
 def get_derived_children(node):
@@ -433,7 +427,7 @@ class OptParser(OptionParser):
                         help="Ignored for compatibility.")
 
         self.add_option('-c', '--clean', '--remove', action="store_true",
-                        default=0, dest="clean",
+                        dest="clean",
                         help="Remove specified targets and dependencies.")
 
         self.add_option('-C', '--directory', type="string", action = "append",
@@ -507,7 +501,7 @@ class OptParser(OptionParser):
                         dest='include_dir', metavar="DIRECTORY",
                         help="Search DIRECTORY for imported Python modules.")
 
-        self.add_option('--implicit-cache', action="store_true", default=0,
+        self.add_option('--implicit-cache', action="store_true",
                         dest='implicit_cache',
                         help="Cache implicit dependencies")
 
@@ -650,6 +644,53 @@ class OptParser(OptionParser):
             opt.implicit_cache = 1
         return opt, arglist
 
+class SConscriptSettableOptions:
+    """This class wraps an OptParser instance and provides
+    uniform access to options that can be either set on the command
+    line or from a SConscript file. A value specified on the command
+    line always overrides a value set in a SConscript file.
+    Not all command line options are SConscript settable, and the ones
+    that are must be explicitly added to settable dictionary and optionally
+    validated and coerced in the set() method."""
+    
+    def __init__(self, options):
+        self.options = options
+
+        # This dictionary stores the defaults for all the SConscript
+        # settable options, as well as indicating which options
+        # are SConscript settable. 
+        self.settable = {'num_jobs':1,
+                         'max_drift':SCons.Sig.default_max_drift,
+                         'implicit_cache':0,
+                         'clean':0}
+
+    def get(self, name):
+        if not self.settable.has_key(name):
+            raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
+        if hasattr(self.options, name) and getattr(self.options, name) is not None:
+            return getattr(self.options, name)
+        else:
+            return self.settable[name]
+
+    def set(self, name, value):
+        if not self.settable.has_key(name):
+            raise SCons.Error.UserError, "This option is not settable from a SConscript file: %s"%name
+
+        if name == 'num_jobs':
+            try:
+                value = int(value)
+                if value < 1:
+                    raise ValueError
+            except ValueError, x:
+                raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
+        elif name == 'max_drift':
+            try:
+                value = int(value)
+            except ValueError, x:
+                raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
+            
+        self.settable[name] = value
+    
 
 def _main():
     targets = []
@@ -666,8 +707,9 @@ def _main():
             # it's OK if there's no SCONSFLAGS
             pass
     parser = OptParser()
-    global options
+    global options, ssoptions
     options, args = parser.parse_args(all_args)
+    ssoptions = SConscriptSettableOptions(options)
 
     if options.help_msg:
         def raisePrintHelp(text):
@@ -808,6 +850,10 @@ def _main():
         parser.print_help(sys.stdout)
         sys.exit(0)
 
+    # Now that we've read the SConscripts we can set the options
+    # that are SConscript settable:
+    SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
+
     if target_top:
         target_top = SCons.Node.FS.default_fs.Dir(target_top)
         
@@ -865,7 +911,7 @@ def _main():
     if options.question:
         task_class = QuestionTask
     try:
-        if options.clean:
+        if ssoptions.get('clean'):
             task_class = CleanTask
             class CleanCalculator:
                 def bsig(self, node):
@@ -881,15 +927,8 @@ def _main():
         pass
 
     if not calc:
-        if options.max_drift is not None:
-           if sig_module is not None:
-                SCons.Sig.default_calc = SCons.Sig.Calculator(module=sig_module,
-                                                              max_drift=options.max_drift)
-           else:
-                SCons.Sig.default_calc = SCons.Sig.Calculator(max_drift=options.max_drift)
-        elif sig_module is not None:
-            SCons.Sig.default_calc = SCons.Sig.Calculator(module=sig_module)
-
+        SCons.Sig.default_calc = SCons.Sig.Calculator(module=sig_module,
+                                                      max_drift=ssoptions.get('max_drift'))
         calc = SCons.Sig.default_calc
 
     if options.random:
@@ -910,7 +949,7 @@ def _main():
     display("scons: Building targets ...")
     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, calc, order)
 
-    jobs = SCons.Job.Jobs(get_num_jobs(options), taskmaster)
+    jobs = SCons.Job.Jobs(ssoptions.get('num_jobs'), taskmaster)
 
     try:
         jobs.run()
index 196d06541d789633ff1df6eb82cdb7dbfc6e39e5..cd6fe7e8a4fcced4415fc332a808740e6e25f037 100644 (file)
@@ -44,6 +44,8 @@ except ImportError:
     import TimeStamp
     default_module = TimeStamp
 
+default_max_drift = 2*24*60*60
+
 #XXX Get rid of the global array so this becomes re-entrant.
 sig_files = []
 
@@ -237,7 +239,7 @@ class Calculator:
     for the build engine.
     """
 
-    def __init__(self, module=default_module, max_drift=2*24*60*60):
+    def __init__(self, module=default_module, max_drift=default_max_drift):
         """
         Initialize the calculator.
 
index 0e5bf24a05ac53beb69ba65e704c05dad1022fb2..e959856a1440f9da325bbfbcd5de33975b5ebb21 100644 (file)
@@ -327,5 +327,52 @@ assert string.find(test.stdout(), 'is up to date') != -1, test.stdout()
 test.run(arguments = "--implicit-deps-changed " + variant_prog)
 assert string.find(test.stdout(), 'is up to date') == -1, test.stdout()
 
+# Test that Set/GetOption('implicit_cache') works:
+test.write('SConstruct', """
+assert not GetOption('implicit_cache')
+SetOption('implicit_cache', 1)
+assert GetOption('implicit_cache')
+""")
+
+test.run()
+
+test.write('SConstruct', """
+assert GetOption('implicit_cache')
+SetOption('implicit_cache', 0)
+assert GetOption('implicit_cache')
+""")
+
+test.run(arguments='--implicit-cache')
+
+# Test to make sure SetOption('implicit_cache', 1) actually enables implicit caching
+# by detecting the one case where implicit caching causes inaccurate builds:
+test.write('SConstruct', """
+SetOption('implicit_cache', 1)
+env=Environment(CPPPATH=['i1', 'i2'])
+env.Object('foo.c')
+""")
+
+test.subdir('i1')
+test.subdir('i2')
+
+test.write('foo.c', """
+#include <foo.h>
+
+int foo(void)
+{
+    FOO_H_DEFINED
+}
+""")
+
+test.write('i2/foo.h', """
+#define FOO_H_DEFINED int x = 1;
+""")
+
+test.run()
+
+test.write('i1/foo.h', """
+""");
+
+test.run()
 
 test.pass_test()
index 4dacb4d2e320f3eff64bd2b255a6f82e456d0b2c..3b90b681f7a10d0fb5ba64c01f3e1434ec5761a6 100644 (file)
@@ -88,5 +88,46 @@ test.run(arguments = '--max-drift=-1 f1.out f2.out',
 scons: "f2.out" is up to date.
 """ % python))
 
+# Test that Set/GetOption('max_drift') works:
+test.write('SConstruct', """
+assert GetOption('max_drift') == 2*24*60*60
+SetOption('max_drift', 1)
+assert GetOption('max_drift') == 1
+""")
+
+test.run()
+
+test.write('SConstruct', """
+assert GetOption('max_drift') == 1
+SetOption('max_drift', 10)
+assert GetOption('max_drift') == 1
+""")
+
+test.run(arguments='--max-drift=1')
+
+# Test that SetOption('max_drift') actually sets max_drift
+# by mucking with the file timestamps to make SCons not realize the source has changed
+test.write('SConstruct', """
+SetOption('max_drift', 0)
+B = Builder(action = r'%s build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'foo.out', source = 'foo.in')
+""" % python)
+
+test.write('foo.in', 'foo.in\n')
+
+atime = os.path.getatime(test.workpath('foo.in'))
+mtime = os.path.getmtime(test.workpath('foo.in'))
+
+test.run()
+test.fail_test(test.read('foo.out') != 'foo.in\n')
+
+test.write('foo.in', 'foo.in delta\n')
+os.utime(test.workpath('foo.in'), (atime,mtime))
+
+test.run()
+
+test.fail_test(test.read('foo.out') != 'foo.in\n')
+
 test.pass_test()
 
index d545b102795626890e3a65210e88c8e69ee647ef..482525d3418e1e3059a4286d81bcbf058556a9b4 100644 (file)
@@ -193,7 +193,7 @@ Removed foo3.out
 Removed %s
 Removed %s
 Removed directory subd
-""" % (os.path.join('subd', 'SConscript'), os.path.join('subd','foon.in')))
+""" % (os.path.join('subd','SConscript'), os.path.join('subd', 'foon.in')))
 test.run(arguments = '-c -n .', stdout=expect)
 
 expect = test.wrap_stdout("""Removed foo1.out
@@ -207,4 +207,56 @@ test.run(arguments = '-c .', stdout=expect)
 test.fail_test(os.path.exists(test.workpath('subdir', 'foon.in')))
 test.fail_test(os.path.exists(test.workpath('subdir')))
 
+
+# Ensure that Set/GetOption('clean') works correctly:
+test.write('SConstruct', """
+B = Builder(action = r'%s build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'foo.out', source = 'foo.in')
+
+assert not GetOption('clean')
+"""%python)
+
+test.write('foo.in', '"Foo", I say!\n')
+
+test.run(arguments='foo.out')
+test.fail_test(test.read(test.workpath('foo.out')) != '"Foo", I say!\n')
+
+test.write('SConstruct', """
+B = Builder(action = r'%s build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'foo.out', source = 'foo.in')
+
+assert GetOption('clean')
+SetOption('clean', 0)
+assert GetOption('clean')
+"""%python)
+
+test.run(arguments='-c foo.out')
+test.fail_test(os.path.exists(test.workpath('foo.out')))
+
+test.write('SConstruct', """
+B = Builder(action = r'%s build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'foo.out', source = 'foo.in')
+"""%python)
+
+test.run(arguments='foo.out')
+test.fail_test(test.read(test.workpath('foo.out')) != '"Foo", I say!\n')
+
+test.write('SConstruct', """
+B = Builder(action = r'%s build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'foo.out', source = 'foo.in')
+
+assert not GetOption('clean')
+SetOption('clean', 1)
+assert GetOption('clean')
+"""%python)
+
+test.run(arguments='foo.out')
+test.fail_test(os.path.exists(test.workpath('foo.out')))
+
 test.pass_test()
+
+
index bf80f02a862e6c331f18ecf1f6e5258dd0db73d4..cf41930e91235844b76d875d6bc8893d997f0794 100644 (file)
@@ -23,8 +23,8 @@
 #
 
 """
-This tests the -j command line option, and the SetJobs() and GetJobs()
-SConscript functions.
+This tests the -j command line option, and the num_jobs
+SConscript settable option.
 """
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
@@ -134,12 +134,12 @@ def copyn(env, target, source):
 t = env.Command(target=['foo/foo1.out', 'foo/foo2.out'], source='foo/foo.in', action=copyn)
 env.Install('out', t)
 
-assert GetJobs() == 1
-SetJobs(2)
-assert GetJobs() == 2
+assert GetOption('num_jobs') == 1
+SetOption('num_jobs', 2)
+assert GetOption('num_jobs') == 2
 """ % python)
 
-# This should be a prallel build because the SConscript sets jobs to 2.
+# This should be a parallel build because the SConscript sets jobs to 2.
 # fail if the second file was not started
 # before the first one was finished
 start2, finish1 = RunTest('f1 f2', "third")
@@ -162,9 +162,9 @@ def copyn(env, target, source):
 t = env.Command(target=['foo/foo1.out', 'foo/foo2.out'], source='foo/foo.in', action=copyn)
 env.Install('out', t)
 
-assert GetJobs() == 1
-SetJobs(2)
-assert GetJobs() == 1
+assert GetOption('num_jobs') == 1
+SetOption('num_jobs', 2)
+assert GetOption('num_jobs') == 1
 """ % python)
 
 # This should be a serial build since -j 1 overrides the call to SetJobs().