From 1c2ac0f2ca0c14d1181add9cc66d9650fece1481 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Fri, 12 Sep 2003 13:07:20 +0000 Subject: Add SourceSignatures() and TargetSignatures() environment methods. --- doc/man/scons.1 | 25 ++++++++++++- src/CHANGES.txt | 4 +-- src/engine/SCons/Defaults.py | 3 ++ src/engine/SCons/Environment.py | 50 +++++++++++++++++++++++++- src/engine/SCons/EnvironmentTests.py | 48 +++++++++++++++++++++++++ src/engine/SCons/Node/__init__.py | 7 +++- src/engine/SCons/Script/SConscript.py | 28 +++------------ src/engine/SCons/Script/__init__.py | 6 +--- src/engine/SCons/Sig/__init__.py | 4 --- src/engine/SCons/Taskmaster.py | 4 ++- test/SourceSignatures.py | 47 +++++++++++++++++++----- test/TargetSignatures.py | 68 ++++++++++++++++++++++++----------- 12 files changed, 227 insertions(+), 67 deletions(-) diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 26e5595..696f38b 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -3141,10 +3141,21 @@ env.SourceCode('no_source.c', None) '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .TP .RI SourceSignatures( type ) +.TP +.RI env.SourceSignatures( type ) This function tells SCons what type of signature to use for source files: .B "MD5" or .BR "timestamp" . +If the environment method is used, +the specified type of source signature +is only used when deciding whether targets +built with that environment are up-to-date or must be rebuilt. +If the global function is used, +the specified type of source signature becomes the default +used for all decisions +about whether targets are up-to-date. + "MD5" means the signature of a source file is the MD5 checksum of its contents. "timestamp" means the signature of a source file @@ -3181,11 +3192,22 @@ files = Split(""" '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .TP .RI TargetSignatures( type ) +.TP +.RI env.TargetSignatures( type ) This function tells SCons what type of signatures to use for target files: .B "build" or .BR "content" . +If the environment method is used, +the specified type of signature is only used +for targets built with that environment. +If the global function is used, +the specified type of signature becomes the default +used for all target files that +don't have an explicit target signature type +specified for their environments. + "build" means the signature of a target file is made by concatenating all of the signatures of all its source files. @@ -3193,7 +3215,8 @@ signatures of all its source files. 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. +when a target file is rebuilt to the exact same contents +as the previous build. The default is "build". '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 259e199..7e6b70f 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -44,8 +44,8 @@ RELEASE X.XX - XXX file and directory arguments to Builder calls and Environment methods. - Add Environment-method versions of the following global function: - AddPreAction(), AddPostAction(), Clean(), Default(), FindFile() - and Local(). + AddPreAction(), AddPostAction(), Clean(), Default(), FindFile(), + Local(), SourceSignatures(), TargetSignatures(). - Add the following global functions that correspond to the same-named Environment methods: AlwaysBuild(), Command(), Depends(), Ignore(), diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index 66e8b00..798abb3 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -51,6 +51,7 @@ import SCons.Node.FS import SCons.Scanner.C import SCons.Scanner.Fortran import SCons.Scanner.Prog +import SCons.Sig # A placeholder for a default Environment (for fetching source files # from source code management systems and the like). This must be @@ -64,6 +65,8 @@ def DefaultEnvironment(*args, **kw): global _default_env if not _default_env: _default_env = apply(SCons.Environment.Environment, args, kw) + _default_env._build_signature = 1 + _default_env._calc_module = SCons.Sig.default_module return _default_env diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index f2cc03f..3d00837 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -64,7 +64,12 @@ _null = _Null DefaultTargets = None CleanTargets = {} -SigModule = None +CalculatorArgs = {} + +# Pull UserError into the global name space for the benefit of +# Environment().SourceSignatures(), which has some import statements +# which seem to mess up its ability to reference SCons directly. +UserError = SCons.Errors.UserError def installFunc(target, source, env): """Install a source file into a target using the function specified @@ -312,6 +317,21 @@ class Environment: return nodes + def get_calculator(self): + try: + return self._calculator + except AttributeError: + try: + module = self._calc_module + c = apply(SCons.Sig.Calculator, (module,), CalculatorArgs) + except AttributeError: + # Note that we're calling get_calculator() here, so the + # DefaultEnvironment() must have a _calc_module attribute + # to avoid infinite recursion. + c = SCons.Defaults.DefaultEnvironment().get_calculator() + self._calculator = c + return c + def get_builder(self, name): """Fetch the builder with the specified name from the environment. """ @@ -358,6 +378,14 @@ class Environment: return SCons.Util.scons_subst_list(string, self, mode, target, source) + def use_build_signature(self): + try: + return self._build_signature + except AttributeError: + b = SCons.Defaults.DefaultEnvironment()._build_signature + self._build_signature = b + return b + ####################################################################### # Public methods for manipulating an Environment. These begin with # upper-case letters. The essential characteristic of methods in @@ -765,3 +793,23 @@ class Environment: if len(entries) == 1: return entries[0] return entries + + def SourceSignatures(self, type): + type = self.subst(type) + if type == 'MD5': + import SCons.Sig.MD5 + self._calc_module = SCons.Sig.MD5 + elif type == 'timestamp': + import SCons.Sig.TimeStamp + self._calc_module = SCons.Sig.TimeStamp + else: + raise UserError, "Unknown source signature type '%s'"%type + + def TargetSignatures(self, type): + type = self.subst(type) + if type == 'build': + self._build_signature = 1 + elif type == 'content': + self._build_signature = 0 + else: + raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 1dc47e2..baf501e 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -1310,6 +1310,54 @@ class EnvironmentTestCase(unittest.TestCase): s = e.src_builder() assert s is None, s + def test_SourceSignatures(type): + """Test the SourceSignatures() method""" + env = Environment(M = 'MD5', T = 'timestamp') + + exc_caught = None + try: + env.SourceSignatures('invalid_type') + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch expected UserError" + assert not hasattr(env, '_calc_module') + + env.SourceSignatures('MD5') + m = env._calc_module + + env.SourceSignatures('$M') + assert env._calc_module is m + + env.SourceSignatures('timestamp') + t = env._calc_module + + env.SourceSignatures('$T') + assert env._calc_module is t + + def test_TargetSignatures(type): + """Test the TargetSignatures() method""" + env = Environment(B = 'build', C = 'content') + + exc_caught = None + try: + env.TargetSignatures('invalid_type') + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch expected UserError" + assert not hasattr(env, '_build_signature') + + env.TargetSignatures('build') + assert env._build_signature == 1, env._build_signature + + env.TargetSignatures('content') + assert env._build_signature == 0, env._build_signature + + env.TargetSignatures('$B') + assert env._build_signature == 1, env._build_signature + + env.TargetSignatures('$C') + assert env._build_signature == 0, env._build_signature + if __name__ == "__main__": suite = unittest.makeSuite(EnvironmentTestCase, 'test_') diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index ea61de6..3802aad 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -408,6 +408,10 @@ class Node: return self.env = env + def calculator(self): + env = self.env or SCons.Defaults.DefaultEnvironment() + return env.get_calculator() + def calc_signature(self, calc): """ Select and calculate the appropriate build signature for a node. @@ -420,7 +424,8 @@ class Node: return self._calculated_sig except AttributeError: if self.is_derived(): - if SCons.Sig.build_signature: + env = self.env or SCons.Defaults.DefaultEnvironment() + if env.use_build_signature(): sig = self.rfile().calc_bsig(calc, self) else: sig = self.rfile().calc_csig(calc, self) diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index b79c34a..4f29f26 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -394,33 +394,13 @@ def SetBuildSignatureType(type): SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, "The SetBuildSignatureType() function has been deprecated;\n" +\ "\tuse the TargetSignatures() function instead.") - TargetSignatures(type) - -def TargetSignatures(type): - import SCons.Sig - if type == 'build': - SCons.Sig.build_signature = 1 - elif type == 'content': - SCons.Sig.build_signature = 0 - else: - raise SCons.Errors.UserError, "Unknown build signature type '%s'"%type + SCons.Defaults.DefaultEnvironment().TargetSignatures(type) def SetContentSignatureType(type): SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, "The SetContentSignatureType() function has been deprecated;\n" +\ "\tuse the SourceSignatures() function instead.") - SourceSignatures(type) - -def SourceSignatures(type): - if type == 'MD5': - import SCons.Sig.MD5 - SCons.Script.sig_module = SCons.Sig.MD5 - elif type == 'timestamp': - import SCons.Sig.TimeStamp - SCons.Script.sig_module = SCons.Sig.TimeStamp - else: - raise SCons.Errors.UserError, "Unknown content signature type '%s'"%type - + SCons.Defaults.DefaultEnvironment().SourceSignatures(type) class Options(SCons.Options.Options): def __init__(self, files=None, args=arguments): @@ -534,9 +514,7 @@ def BuildDefaultGlobals(): globals['SetContentSignatureType'] = SetContentSignatureType globals['SetJobs'] = SetJobs globals['SetOption'] = SetOption - globals['SourceSignatures'] = SourceSignatures globals['Split'] = SCons.Util.Split - globals['TargetSignatures'] = TargetSignatures globals['Tool'] = SCons.Tool.Tool globals['Value'] = SCons.Node.Python.Value globals['WhereIs'] = SCons.Util.WhereIs @@ -566,6 +544,8 @@ def BuildDefaultGlobals(): 'Precious', 'SideEffect', 'SourceCode', + 'SourceSignatures', + 'TargetSignatures', ] for name in EnvironmentMethods: diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index 34b112f..61e7561 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -233,7 +233,6 @@ command_time = 0 exit_status = 0 # exit status, assume success by default profiling = 0 repositories = [] -sig_module = SCons.Sig.default_module num_jobs = 1 # this is modifed by SConscript.SetJobs() # Exceptions for this module @@ -942,10 +941,7 @@ def _main(args, parser): except AttributeError: pass - if not calc: - SCons.Sig.default_calc = SCons.Sig.Calculator(module=sig_module, - max_drift=ssoptions.get('max_drift')) - calc = SCons.Sig.default_calc + SCons.Environment.CalculatorArgs['max_drift'] = ssoptions.get('max_drift') if options.random: def order(dependencies): diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index 0245a2c..4898f29 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -51,10 +51,6 @@ sig_files = [] SConsign_db = None -# 1 means use build signature for derived source files -# 0 means use content signature for derived source files -build_signature = 1 - def write(): global sig_files for sig_file in sig_files: diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 7937569..9b13b60 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -161,8 +161,10 @@ class Task: def make_ready(self): """Make a task ready for execution.""" state = SCons.Node.up_to_date + calc = self.tm.calc for t in self.targets: - if not t.current(self.tm.calc): + c = calc or t.calculator() + if not t.current(c): state = SCons.Node.executing for t in self.targets: if state == SCons.Node.executing: diff --git a/test/SourceSignatures.py b/test/SourceSignatures.py index 26a389e..e982fbe 100644 --- a/test/SourceSignatures.py +++ b/test/SourceSignatures.py @@ -125,20 +125,51 @@ env.B(target = 'f4.out', source = 'f4.in') test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out') -test.pass_test() - test.write('SConstruct', """ def build(env, target, source): open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read()) B = Builder(action = build) env = Environment(BUILDERS = { 'B' : B }) -env.B(target = 'f1.out', source = 'f1.in') -env.B(target = 'f2.out', source = 'f2.in') -env.B(target = 'f3.out', source = 'f3.in') -env.B(target = 'f4.out', source = 'f4.in') +env2 = env.Copy() +env2.SourceSignatures('MD5') +env.B(target = 'f5.out', source = 'f5.in') +env.B(target = 'f6.out', source = 'f6.in') +env2.B(target = 'f7.out', source = 'f7.in') +env2.B(target = 'f8.out', source = 'f8.in') SourceSignatures('timestamp') """) -test.run(arguments = 'f1.out f2.out f3.out f4.out', - stdout = test.wrap_stdout('')) +test.write('f5.in', "f5.in\n") +test.write('f6.in', "f6.in\n") +test.write('f7.in', "f7.in\n") +test.write('f8.in', "f8.in\n") + +test.run(arguments = 'f5.out f7.out') + +test.run(arguments = 'f5.out f6.out f7.out f8.out', + stdout = test.wrap_stdout("""\ +scons: `f5.out' is up to date. +build("f6.out", "f6.in") +scons: `f7.out' is up to date. +build("f8.out", "f8.in") +""")) + +os.utime(test.workpath('f5.in'), + (os.path.getatime(test.workpath('f5.in')), + os.path.getmtime(test.workpath('f5.in'))+10)) +os.utime(test.workpath('f7.in'), + (os.path.getatime(test.workpath('f7.in')), + os.path.getmtime(test.workpath('f7.in'))+10)) + +test.run(arguments = 'f5.out f6.out f7.out f8.out', + stdout = test.wrap_stdout("""\ +build("f5.out", "f5.in") +scons: `f6.out' is up to date. +scons: `f7.out' is up to date. +scons: `f8.out' is up to date. +""")) + +test.up_to_date(arguments = 'f5.out f6.out f7.out f8.out') + +test.pass_test() diff --git a/test/TargetSignatures.py b/test/TargetSignatures.py index 3c7ed0f..5060157 100644 --- a/test/TargetSignatures.py +++ b/test/TargetSignatures.py @@ -40,21 +40,29 @@ def copy2(env, source, target): env['BUILDERS']['Copy1'] = Builder(action=copy1) env['BUILDERS']['Copy2'] = Builder(action=copy2) -env.Copy2('foo.out', 'foo.in') -env.Copy1('foo.out.out', 'foo.out') +env.Copy2('foo.mid', 'foo.in') +env.Copy1('foo.out', 'foo.mid') + +env2 = env.Copy() +env2.TargetSignatures('build') +env2.Copy2('bar.mid', 'bar.in') +env2.Copy1('bar.out', 'bar.mid') TargetSignatures('content') """) test.write('foo.in', 'foo.in') +test.write('bar.in', 'bar.in') -test.run(arguments='foo.out.out', +test.run(arguments="bar.out foo.out", stdout=test.wrap_stdout("""\ -copy2("foo.out", "foo.in") -copy1("foo.out.out", "foo.out") +copy2("bar.mid", "bar.in") +copy1("bar.out", "bar.mid") +copy2("foo.mid", "foo.in") +copy1("foo.out", "foo.mid") """)) -test.up_to_date(arguments='foo.out.out') +test.up_to_date(arguments='bar.out foo.out') test.write('SConstruct', """ env = Environment() @@ -69,16 +77,23 @@ def copy2(env, source, target): env['BUILDERS']['Copy1'] = Builder(action=copy1) env['BUILDERS']['Copy2'] = Builder(action=copy2) -env.Copy2('foo.out', 'foo.in') -env.Copy1('foo.out.out', 'foo.out') +env.Copy2('foo.mid', 'foo.in') +env.Copy1('foo.out', 'foo.mid') + +env2 = env.Copy() +env2.TargetSignatures('build') +env2.Copy2('bar.mid', 'bar.in') +env2.Copy1('bar.out', 'bar.mid') TargetSignatures('content') """) -test.run(arguments='foo.out.out', +test.run(arguments="bar.out foo.out", stdout=test.wrap_stdout("""\ -copy2("foo.out", "foo.in") -scons: `foo.out.out' is up to date. +copy2("bar.mid", "bar.in") +copy1("bar.out", "bar.mid") +copy2("foo.mid", "foo.in") +scons: `foo.out' is up to date. """)) test.write('SConstruct', """ @@ -94,15 +109,21 @@ def copy2(env, source, target): env['BUILDERS']['Copy1'] = Builder(action=copy1) env['BUILDERS']['Copy2'] = Builder(action=copy2) -env.Copy2('foo.out', 'foo.in') -env.Copy1('foo.out.out', 'foo.out') +env.Copy2('foo.mid', 'foo.in') +env.Copy1('foo.out', 'foo.mid') + +env2 = env.Copy() +env2.TargetSignatures('content') +env2.Copy2('bar.mid', 'bar.in') +env2.Copy1('bar.out', 'bar.mid') TargetSignatures('build') """) -test.run(arguments='foo.out.out', +test.run(arguments="bar.out foo.out", stdout=test.wrap_stdout("""\ -copy1("foo.out.out", "foo.out") +copy1("bar.out", "bar.mid") +copy1("foo.out", "foo.mid") """)) test.write('SConstruct', """ @@ -117,16 +138,23 @@ def copy2(env, source, target): env['BUILDERS']['Copy1'] = Builder(action=copy1) env['BUILDERS']['Copy2'] = Builder(action=copy2) -env.Copy2('foo.out', 'foo.in') -env.Copy1('foo.out.out', 'foo.out') +env.Copy2('foo.mid', 'foo.in') +env.Copy1('foo.out', 'foo.mid') + +env2 = env.Copy() +env2.TargetSignatures('content') +env2.Copy2('bar.mid', 'bar.in') +env2.Copy1('bar.out', 'bar.mid') TargetSignatures('build') """) -test.run(arguments='foo.out.out', +test.run(arguments='bar.out foo.out', stdout=test.wrap_stdout("""\ -copy2("foo.out", "foo.in") -copy1("foo.out.out", "foo.out") +copy2("bar.mid", "bar.in") +scons: `bar.out' is up to date. +copy2("foo.mid", "foo.in") +copy1("foo.out", "foo.mid") """)) -- cgit v0.12