From e1660539be120f8d0a26112418c8c4de58a6d4c5 Mon Sep 17 00:00:00 2001 From: stevenknight Date: Sun, 8 May 2005 13:10:48 +0000 Subject: [PATCH] Avoid rebuilds when otherwise unmodified Python function Actions move within a file and change line numbers. git-svn-id: http://scons.tigris.org/svn/scons/trunk@1292 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- src/CHANGES.txt | 4 ++++ src/RELEASE.txt | 16 +++++++++++++++- src/engine/SCons/Action.py | 30 ++++++++++++++++++++++++++++++ src/engine/SCons/ActionTests.py | 31 +++++++++++++++++++++++++++---- 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 9f544d27..51574a5b 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -271,6 +271,10 @@ RELEASE 0.97 - XXX The old behavior of a separate .sconsign file in each directory can be specified by calling SConsignFile(None). + - Remove line number byte codes within the signature calculation + of Python function actions, so that changing the location of an + otherwise unmodified Python function doesn't cause rebuilds. + From Wayne Lee: - Avoid "maximum recursion limit" errors when removing $(-$) pairs diff --git a/src/RELEASE.txt b/src/RELEASE.txt index 47b0031a..bbf0a3a2 100644 --- a/src/RELEASE.txt +++ b/src/RELEASE.txt @@ -88,7 +88,7 @@ RELEASE 0.97 - XXX This release adds several changes to the signature mechanism that will cause SCons to rebuild most configurations after upgrading - (and if switching back from 0.97 to an earlier release). + (and when switching back to an earlier release from 0.97). These changes are: -- NORMALIZED PATHS IN SConsignFile() DATABASES ON WINDOWS @@ -116,6 +116,20 @@ RELEASE 0.97 - XXX the top of each SConstruct file, but we hope to make this an easier/more naturally supported thing in the future.) + -- PYTHON FUNCTION ACTION SIGNATURES HAVE CHANGED TO AVOID + FUTURE REBUILDS AND REBUILDS BETWEEN PYTHON VERSIONS + + SCons Actions for Python functions use the functions byte + code to generate their signature. The byte code in older + versions of Python includes indications of the line numbers + at which the function's code appeared in its original + source file, which means that changes in the location of + an otherwise unmodified Python function would trigger + rebuilds. The line number byte codes are now removed + from the signature, which will cause any targets built by + Python function Actions (including various pre-supplied + SCons Actions) be rebuilt. + -- CACHED Configure() RESULTS ARE STORED IN A DIFFERENT FILE The Configure() subsystem now stores its cached results in a diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 51e9f036..f606bb2f 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -97,6 +97,7 @@ way for wrapping up the functions. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import dis import os import os.path import re @@ -127,6 +128,28 @@ def rfile(n): def default_exitstatfunc(s): return s +try: + SET_LINENO = dis.SET_LINENO + HAVE_ARGUMENT = dis.HAVE_ARGUMENT +except AttributeError: + remove_set_lineno_codes = lambda x: x +else: + def remove_set_lineno_codes(code): + result = [] + n = len(code) + i = 0 + while i < n: + c = code[i] + op = ord(c) + if op >= HAVE_ARGUMENT: + if op != SET_LINENO: + result.append(code[i:i+3]) + i = i+3 + else: + result.append(c) + i = i+1 + return string.join(result, '') + def _actionAppend(act1, act2): # This function knows how to slap two actions together. # Mainly, it handles ListActions by concatenating into @@ -626,6 +649,11 @@ class FunctionAction(_ActionAction): By providing direct access to the code object of the function, Python makes this extremely easy. Hooray! + + Unfortunately, older versions of Python include line + number indications in the compiled byte code. Boo! + So we remove the line number byte codes to prevent + recompilations from moving a Python function. """ try: # "self.execfunction" is a function. @@ -643,6 +671,7 @@ class FunctionAction(_ActionAction): contents = str(self.execfunction) else: contents = gc(target, source, env) + contents = remove_set_lineno_codes(contents) return contents + env.subst(string.join(map(lambda v: '${'+v+'}', self.varlist))) @@ -715,6 +744,7 @@ class ActionCaller: # No __call__() method, so it might be a builtin # or something like that. Do the best we can. contents = str(actfunc) + contents = remove_set_lineno_codes(contents) return contents def subst(self, s, target, source, env): # Special-case hack: Let a custom function wrapped in an diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index f0905b68..f5aa67a2 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -1430,13 +1430,19 @@ class FunctionActionTestCase(unittest.TestCase): """Test fetching the contents of a function Action """ - a = SCons.Action.FunctionAction(GlobalFunc) + def LocalFunc(): + pass matches = [ - "\177\036\000\177\037\000d\000\000S", + "d\000\000S", "d\x00\x00S", ] + a = SCons.Action.FunctionAction(GlobalFunc) + c = a.get_contents(target=[], source=[], env=Environment()) + assert c in matches, repr(c) + + a = SCons.Action.FunctionAction(LocalFunc) c = a.get_contents(target=[], source=[], env=Environment()) assert c in matches, repr(c) @@ -1602,8 +1608,11 @@ class ActionCallerTestCase(unittest.TestCase): def strfunc(): pass + def LocalFunc(): + pass + matches = [ - "\177\036\000\177\037\000d\000\000S", + "d\000\000S", "d\x00\x00S" ] @@ -1612,16 +1621,30 @@ class ActionCallerTestCase(unittest.TestCase): c = ac.get_contents([], [], Environment()) assert c in matches, repr(c) + af = SCons.Action.ActionFactory(LocalFunc, strfunc) + ac = SCons.Action.ActionCaller(af, [], {}) + c = ac.get_contents([], [], Environment()) + assert c in matches, repr(c) + matches = [ - '\177"\000\177#\000d\000\000S', + 'd\000\000S', "d\x00\x00S" ] + class LocalActFunc: + def __call__(self): + pass + af = SCons.Action.ActionFactory(GlobalActFunc(), strfunc) ac = SCons.Action.ActionCaller(af, [], {}) c = ac.get_contents([], [], Environment()) assert c in matches, repr(c) + af = SCons.Action.ActionFactory(LocalActFunc(), strfunc) + ac = SCons.Action.ActionCaller(af, [], {}) + c = ac.get_contents([], [], Environment()) + assert c in matches, repr(c) + matches = [ "", "", -- 2.26.2