diff options
-rw-r--r-- | src/engine/SCons/Defaults.py | 12 | ||||
-rw-r--r-- | src/engine/SCons/Node/FS.py | 43 | ||||
-rw-r--r-- | test/ExecuteInvalidateCache.py | 108 |
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() |