summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/engine/SCons/Defaults.py12
-rw-r--r--src/engine/SCons/Node/FS.py43
-rw-r--r--test/ExecuteInvalidateCache.py108
3 files changed, 162 insertions, 1 deletions
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index aebef39..067f22d 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -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:
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 182acd2..a94171b 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -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
index 0000000..aad12e4
--- /dev/null
+++ b/test/ExecuteInvalidateCache.py
@@ -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()