Avoid rebuilds when otherwise unmodified Python function Actions move within a file...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 8 May 2005 13:10:48 +0000 (13:10 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 8 May 2005 13:10:48 +0000 (13:10 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1292 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/CHANGES.txt
src/RELEASE.txt
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py

index 9f544d27c477e233b301942cd1a342361dbcb4af..51574a5b1f58df6c0733a7eca0f7be87ea9a5222 100644 (file)
@@ -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
index 47b0031aaca9fa96cfe7be9b1c8da27fa3a85dd0..bbf0a3a24708b16721f828612f0a4d1632d8c914 100644 (file)
@@ -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
index 51e9f0367b24ebebcc13b2400606b6f0ebf111c1..f606bb2f21586646962d66402d352b6af2ea4ae0 100644 (file)
@@ -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
index f0905b68720f1edc9bc7315c2084ac9591843151..f5aa67a2f7c6f94b75d15ea6887ee37089d58c45 100644 (file)
@@ -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 = [
             "<built-in function str>",
             "<type 'str'>",