Provide a Scanner hook to allow file scans to be avoided when it isn't necessary.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 15 Sep 2002 01:45:17 +0000 (01:45 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sun, 15 Sep 2002 01:45:17 +0000 (01:45 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@466 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Scanner/ScannerTests.py
src/engine/SCons/Scanner/__init__.py
test/scan-once.py [new file with mode: 0644]

index dfed6c34171f775b9ccbdfa5c8af2389da203aa8..13c31d7470a8c30ce3edd176fb35397915dbe421 100644 (file)
@@ -2681,6 +2681,16 @@ The
 argument is the argument supplied
 when the scanner was created, if any.
 
+.IP scan_check
+An optional Python function that takes a Node (file)
+as an argument and returns whether the
+Node should, in fact,
+be scanned for dependencies.
+This check can be used to eliminate unnecessary
+calls to the scanner function when,
+for example, the underlying file
+represented by a Node does not yet exist.
+
 .SH SYSTEM-SPECIFIC BEHAVIOR
 SCons and its configuration files are very portable,
 due largely to its implementation in Python.
index dc203937296dcb99c07b4944605e980532cad218..ba3ccb16617bd7558162335c2caf67855d84de61 100644 (file)
@@ -37,6 +37,10 @@ RELEASE 0.09 -
 
   - Issue a warning when -c can't remove a target.
 
+  - Eliminate unnecessary Scanner calls by checking for the
+    existence of a file before scanning it.  (This adds a generic
+    hook to check an arbitrary condition before scanning.)
+
  From Jeff Petkau:
 
   - Fix interpretation of '#/../foo' on Win32 systems.
index fce4078ef0a5b7045c459ed05cfdaa8378496af8..2d0e47a5a8f9ea51f998e75e5bdad71393afe4cc 100644 (file)
@@ -124,6 +124,21 @@ class ScannerHashTestCase(ScannerTestBase, unittest.TestCase):
         self.failUnless(hash(dict.keys()[0]) == hash(None),
                         "did not hash Scanner base class as expected")
 
+class ScannerCheckTestCase(unittest.TestCase):
+    "Test the Scanner.Base class __hash__() method"
+    def setUp(self):
+        self.checked = {}
+    def runTest(self):
+        def my_scan(filename, env, target, *args):
+            return []
+        def check(node, s=self):
+            s.checked[node] = 1
+            return 1
+        s = SCons.Scanner.Base(my_scan, "Check", scan_check = check)
+        scanned = s.scan('x', DummyEnvironment(), DummyTarget())
+        self.failUnless(self.checked['x'] == 1,
+                        "did not call check function")
+
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(ScannerPositionalTestCase())
@@ -131,6 +146,7 @@ def suite():
     suite.addTest(ScannerPositionalArgumentTestCase())
     suite.addTest(ScannerKeywordArgumentTestCase())
     suite.addTest(ScannerHashTestCase())
+    suite.addTest(ScannerCheckTestCase())
     return suite
 
 if __name__ == "__main__":
index 28159c91a17bd0b19cb668dd2521c909a2d13731..dc1064804b8230a676a5188c678e0bd6a2b2bac6 100644 (file)
@@ -52,7 +52,8 @@ class Base:
                  name = "NONE",
                  argument = _null,
                  skeys = [],
-                 node_factory = SCons.Node.FS.default_fs.File):
+                 node_factory = SCons.Node.FS.default_fs.File,
+                 scan_check = None):
         """
         Construct a new scanner object given a scanner function.
 
@@ -77,7 +78,7 @@ class Base:
         Examples:
 
         s = Scanner(my_scanner_function)
-        
+
         s = Scanner(function = my_scanner_function)
 
         s = Scanner(function = my_scanner_function, argument = 'foo')
@@ -94,6 +95,7 @@ class Base:
         self.argument = argument
         self.skeys = skeys
         self.node_factory = node_factory
+        self.scan_check = scan_check
 
     def scan(self, node, env, target):
         """
@@ -102,6 +104,8 @@ class Base:
         environment that will be passed to the scanner function. A list of
         direct dependency nodes for the specified node will be returned.
         """
+        if self.scan_check and not self.scan_check(node):
+            return []
 
         if not self.argument is _null:
             list = self.function(node, env, target, self.argument)
@@ -123,7 +127,17 @@ class Base:
     def __hash__(self):
         return hash(None)
 
-class Recursive(Base):
+class RExists(Base):
+    """
+    Scan a node only if it exists (locally or in a Repository).
+    """
+    def __init__(self, *args, **kw):
+        def rexists_check(node):
+            return node.rexists()
+        kw['scan_check'] = rexists_check
+        apply(Base.__init__, (self,) + args, kw)
+
+class Recursive(RExists):
     """
     The class for recursive dependency scanning.  This will
     re-scan any new files returned by each call to the
diff --git a/test/scan-once.py b/test/scan-once.py
new file mode 100644 (file)
index 0000000..76c3451
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001, 2002 Steven Knight
+#
+# 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__"
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', r"""
+import os.path
+
+def scan(node, env, target, arg):
+    print 'scanning',node,'for',target
+    return []
+
+def exists_check(node):
+    return os.path.exists(str(node))
+
+PScanner = Scanner(name = 'PScanner',
+                   function = scan,
+                   argument = None,
+                   scan_check = exists_check,
+                  skeys = ['.s'])
+
+def echo(env, target, source):
+    print 'create %s from %s' % (str(target[0]), str(source[0]))
+
+Echo = Builder(action = echo,
+               src_suffix = '.s',
+              suffix = '.s')
+
+env = Environment(BUILDERS = {'Echo':Echo}, SCANNERS = [PScanner])
+
+f1 = env.Echo(source=['file1'], target=['file2'])
+f2 = env.Echo(source=['file2'], target=['file3'])
+f3 = env.Echo(source=['file3'], target=['file4'])
+
+Default(f3)
+""")
+
+test.run(arguments = '.', stdout = """create file2.s from file1.s
+create file3.s from file2.s
+create file4.s from file3.s
+""")
+
+test.write('file1.s', 'file1.s\n')
+
+test.run(arguments = '.', stdout = """scanning file1.s for file2.s
+create file2.s from file1.s
+scanning file1.s for file2.s
+create file3.s from file2.s
+create file4.s from file3.s
+""")
+
+test.write('file2.s', 'file2.s\n')
+
+test.run(arguments = '.', stdout = """scanning file1.s for file2.s
+scanning file2.s for file3.s
+create file3.s from file2.s
+scanning file2.s for file3.s
+create file4.s from file3.s
+""")
+
+test.write('file3.s', 'file3.s\n')
+
+test.run(arguments = '.', stdout = """scanning file1.s for file2.s
+scanning file2.s for file3.s
+scanning file3.s for file4.s
+create file4.s from file3.s
+""")
+
+test.pass_test()