Issue 1307: Invalidate node caches after Execute()
authorpankrat <pankrat@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 8 Sep 2008 20:23:30 +0000 (20:23 +0000)
committerpankrat <pankrat@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 8 Sep 2008 20:23:30 +0000 (20:23 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@3374 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Defaults.py
src/engine/SCons/Node/FS.py
test/ExecuteInvalidateCache.py [new file with mode: 0644]

index aebef3998f02e409c74fc80405219df429d9f984..067f22d012693f80a9c13516841620f7916ad350 100644 (file)
@@ -169,6 +169,7 @@ def get_paths_str(dest):
         return '"' + str(dest) + '"'
 
 def chmod_func(dest, mode):
+    SCons.Node.FS.invalidate_node_memos(dest)
     if not SCons.Util.is_List(dest):
         dest = [dest]
     for element in dest:
@@ -180,6 +181,7 @@ def chmod_strfunc(dest, mode):
 Chmod = ActionFactory(chmod_func, chmod_strfunc)
 
 def copy_func(dest, src):
+    SCons.Node.FS.invalidate_node_memos(dest)
     if SCons.Util.is_List(src) and os.path.isdir(dest):
         for file in src:
             shutil.copy2(file, dest)
@@ -194,6 +196,7 @@ Copy = ActionFactory(copy_func,
                      convert=str)
 
 def delete_func(dest, must_exist=0):
+    SCons.Node.FS.invalidate_node_memos(dest)
     if not SCons.Util.is_List(dest):
         dest = [dest]
     for entry in dest:
@@ -213,6 +216,7 @@ def delete_strfunc(dest, must_exist=0):
 Delete = ActionFactory(delete_func, delete_strfunc)
 
 def mkdir_func(dest):
+    SCons.Node.FS.invalidate_node_memos(dest)
     if not SCons.Util.is_List(dest):
         dest = [dest]
     for entry in dest:
@@ -221,11 +225,17 @@ def mkdir_func(dest):
 Mkdir = ActionFactory(mkdir_func,
                       lambda dir: 'Mkdir(%s)' % get_paths_str(dir))
 
-Move = ActionFactory(lambda dest, src: os.rename(src, dest),
+def move_func(dest, src):
+    SCons.Node.FS.invalidate_node_memos(dest)
+    SCons.Node.FS.invalidate_node_memos(src)
+    os.rename(src, dest)
+
+Move = ActionFactory(move_func,
                      lambda dest, src: 'Move("%s", "%s")' % (dest, src),
                      convert=str)
 
 def touch_func(dest):
+    SCons.Node.FS.invalidate_node_memos(dest)
     if not SCons.Util.is_List(dest):
         dest = [dest]
     for file in dest:
index 182acd29d834fe5c08fe0876857fb0fd9e614168..a94171b9b185ba7fb7eb4a0cf9ac13c524229bb4 100644 (file)
@@ -2994,3 +2994,46 @@ class FileFinder:
         return result
 
 find_file = FileFinder().find_file
+
+
+def invalidate_node_memos(targets):
+    """
+    Invalidate the memoized values of all Nodes (files or directories)
+    that are associated with the given entries. Has been added to
+    clear the cache of nodes affected by a direct execution of an
+    action (e.g.  Delete/Copy/Chmod). Existing Node caches become
+    inconsistent if the action is run through Execute().  The argument
+    `targets` can be a single Node object or filename, or a sequence
+    of Nodes/filenames.
+    """
+    from traceback import extract_stack
+
+    # First check if the cache really needs to be flushed. Only
+    # actions run in the SConscript with Execute() seem to be
+    # affected. XXX The way to check if Execute() is in the stacktrace
+    # is a very dirty hack and should be replaced by a more sensible
+    # solution.
+    must_invalidate = 0
+    tb = extract_stack()
+    for f in tb:
+        if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
+            must_invalidate = 1
+    if not must_invalidate:
+        return
+
+    if not SCons.Util.is_List(targets):
+        targets = [targets]
+    
+    for entry in targets:
+        # If the target is a Node object, clear the cache. If it is a
+        # filename, look up potentially existing Node object first.
+        try:
+            entry.clear_memoized_values()
+        except AttributeError:
+            # Not a Node object, try to look up Node by filename.  XXX
+            # This creates Node objects even for those filenames which
+            # do not correspond to an existing Node object.
+            node = get_default_fs().Entry(entry)
+            if node:
+                node.clear_memoized_values()                        
+
diff --git a/test/ExecuteInvalidateCache.py b/test/ExecuteInvalidateCache.py
new file mode 100644 (file)
index 0000000..aad12e4
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the Execute() functions clears the memoized values of affected target Nodes
+when used with Delete(). Derived from Tigris issue 1307.
+"""
+
+import TestSCons
+import os.path
+
+test = TestSCons.TestSCons()
+
+subfn = os.path.join('sub', 'foo')
+
+test.write('SConstruct', """\
+def exists(node):
+    if node.exists():
+        print str(node), "exists"
+    else:
+        print str(node), "does not exist"
+
+Execute(Delete('abc'))
+n1 = File('abc')
+exists( n1 )
+Execute(Touch('abc'))
+exists( n1 )
+Execute(Delete('abc'))
+exists( n1 )
+
+env = Environment()
+env.Execute(Delete('def'))
+n2 = env.File('def')
+exists( n2 )
+env.Execute(Touch('def'))
+exists( n2 )
+env.Execute(Delete(n2))
+exists( n2 )
+
+Execute(Touch('abc'))
+exists( n1 )
+Execute(Move('def', 'abc'))
+exists( n1 )
+exists( n2 )
+
+Execute(Copy('abc', 'def'))
+exists( n1 )
+
+n3 = File("%(subfn)s")
+exists( n3 )
+Execute(Mkdir('sub'))
+Execute(Touch("%(subfn)s"))
+exists( n3 )
+""" % locals())
+
+
+expect = test.wrap_stdout(read_str="""\
+Delete("abc")
+abc does not exist
+Touch("abc")
+abc exists
+Delete("abc")
+abc does not exist
+Delete("def")
+def does not exist
+Touch("def")
+def exists
+Delete("def")
+def does not exist
+Touch("abc")
+abc exists
+Move("def", "abc")
+abc does not exist
+def exists
+Copy("abc", "def")
+abc exists
+%(subfn)s does not exist
+Mkdir("sub")
+Touch("%(subfn)s")
+%(subfn)s exists
+""" % locals(), build_str = "scons: `.' is up to date.\n")
+
+test.run(arguments = '.', stdout = expect)
+
+test.pass_test()