From 212d77d88aa4374ef13f2e6bc7edf3395ac9736c Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Wed, 30 Apr 2003 15:35:30 +0000 Subject: Provide uniform access to (some) command-line options. (Anthony Roach) --- doc/man/scons.1 | 85 +++++++++++++++++++++-------------- src/CHANGES.txt | 6 +++ src/RELEASE.txt | 6 +++ src/engine/SCons/Script/SConscript.py | 29 +++++++----- src/engine/SCons/Script/__init__.py | 81 ++++++++++++++++++++++++--------- src/engine/SCons/Sig/__init__.py | 4 +- test/option--implicit-cache.py | 47 +++++++++++++++++++ test/option--max-drift.py | 41 +++++++++++++++++ test/option-c.py | 54 +++++++++++++++++++++- test/option-j.py | 18 ++++---- 10 files changed, 296 insertions(+), 75 deletions(-) diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 5c9db8c..c96c8b8 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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 ) @@ -3866,6 +3866,20 @@ 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 .B scons @@ -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 ) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 04afab8..f6bdee0 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/RELEASE.txt b/src/RELEASE.txt index db275b7..e8d4b37 100644 --- a/src/RELEASE.txt +++ b/src/RELEASE.txt @@ -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 diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index e3da64f..6b28b92 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -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 diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index d67b949..a1a54e6 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -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() diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index 196d065..cd6fe7e 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -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. diff --git a/test/option--implicit-cache.py b/test/option--implicit-cache.py index 0e5bf24..e959856 100644 --- a/test/option--implicit-cache.py +++ b/test/option--implicit-cache.py @@ -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 + +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() diff --git a/test/option--max-drift.py b/test/option--max-drift.py index 4dacb4d..3b90b68 100644 --- a/test/option--max-drift.py +++ b/test/option--max-drift.py @@ -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() diff --git a/test/option-c.py b/test/option-c.py index d545b10..482525d 100644 --- a/test/option-c.py +++ b/test/option-c.py @@ -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() + + diff --git a/test/option-j.py b/test/option-j.py index bf80f02..cf41930 100644 --- a/test/option-j.py +++ b/test/option-j.py @@ -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(). -- cgit v0.12