Implement implicit dependency caching.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 11 Apr 2002 01:34:07 +0000 (01:34 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 11 Apr 2002 01:34:07 +0000 (01:34 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@325 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Sig/SigTests.py
src/engine/SCons/Sig/__init__.py
src/engine/SCons/Taskmaster.py
test/option--implicit-cache.py [new file with mode: 0644]
test/sconsign.py

index 116a5f791978a395a420f548c4ea6f70f0cc5f43..ed7978f64336da9b6642fb40ad301baf66b72bf1 100644 (file)
@@ -373,6 +373,14 @@ imported Python modules.  If several
 options
 are used, the directories are searched in the order specified.
 
+.TP
+--implicit-cache
+Cache implicit dependencies. This can cause 
+.B scons
+to miss changes in the implicit dependencies in cases where a new implicit
+dependency is added earlier in the implicit dependency search path
+(e.g. CPPPATH) than a current implicit dependency with the same name.
+
 .TP
 .RI -j " N" ", --jobs=" N
 Specifies the number of jobs (commands) to run simultaneously.
index c49361da852f74d148966676c95523ad1e8f2009..1c506c91863917f9a356be302329b6dc433a1bff 100644 (file)
@@ -467,10 +467,8 @@ class Dir(Entry):
         """Return the .sconsign file info for this directory,
         creating it first if necessary."""
         if not self._sconsign:
-            #XXX Rework this to get rid of the hard-coding
             import SCons.Sig
-            import SCons.Sig.MD5
-            self._sconsign = SCons.Sig.SConsignFile(self, SCons.Sig.MD5)
+            self._sconsign = SCons.Sig.SConsignFile(self)
         return self._sconsign
 
 
@@ -534,11 +532,15 @@ class File(Entry):
                                 self.get_bsig(),
                                 old[2])
 
+    def store_implicit(self):
+        self.dir.sconsign().set_implicit(self.name, self.implicit)
+
     def get_prevsiginfo(self):
-        """Fetch the previous signature information from the
-        .sconsign entry."""
         return self.dir.sconsign().get(self.name)
 
+    def get_stored_implicit(self):
+        return self.dir.sconsign().get_implicit(self.name)
+
     def get_implicit_deps(self, env, scanner, target):
         if scanner:
             return scanner.scan(self, env, target)
index 0e19cecdc86bb05356bcf84d2b0260678f929d1f..6b35adbfc17dac145b0626883d1a29d2ec0ae396 100644 (file)
@@ -36,10 +36,16 @@ import stat
 built_it = None
 
 class Builder:
+    def __init__(self, factory):
+        self.factory = factory
+
     def execute(self, **kw):
         global built_it
         built_it = 1
         return 0
+    
+    def source_factory(self, name):
+        return self.factory(name)
 
 scanner_count = 0
 
@@ -285,7 +291,7 @@ class FSTestCase(unittest.TestCase):
         built_it = None
         assert not built_it
         d1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
-        d1.builder_set(Builder())
+        d1.builder_set(Builder(fs.File))
         d1.env_set(Environment())
         d1.build()
         assert not built_it
@@ -293,7 +299,7 @@ class FSTestCase(unittest.TestCase):
         built_it = None
         assert not built_it
         f1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
-        f1.builder_set(Builder())
+        f1.builder_set(Builder(fs.File))
         f1.env_set(Environment())
         f1.build()
         assert built_it
@@ -381,6 +387,7 @@ class FSTestCase(unittest.TestCase):
         match(d12.path_, "subdir/d12/")
         e13 = fs.Entry("subdir/e13")
         match(e13.path, "subdir/subdir/e13")
+        fs.chdir(fs.Dir('..'))
 
         # Test scanning
         f1.target_scanner = Scanner()
@@ -391,7 +398,9 @@ class FSTestCase(unittest.TestCase):
         assert f1.implicit == []
         f1.implicit = None
         f1.scan()
-        assert f1.implicit[0].path_ == os.path.join("d1", "f1")
+        assert f1.implicit[0].path_ == os.path.join("d1", "f1"), f1.implicit[0].path_
+        f1.store_implicit()
+        assert f1.get_stored_implicit()[0] == os.path.join("d1", "f1")
 
         # Test building a file whose directory is not there yet...
         f1 = fs.File(test.workpath("foo/bar/baz/ack"))
index a9ca79088a5256e3868167eb9f1bbb603ed360d4..cbaed7b5f2f89dc58ca06a3566db0c8d23356bfc 100644 (file)
@@ -35,6 +35,7 @@ import string
 import types
 import copy
 import sys
+import SCons.Sig
 
 from SCons.Errors import BuildError
 import SCons.Util
@@ -53,6 +54,9 @@ executed = 4
 failed = 5
 stack = 6 # nodes that are in the current Taskmaster execution stack
 
+# controls whether implicit depedencies are cached:
+implicit_cache = 0
+
 class Node:
     """The base Node class, for entities that we know how to
     build, or use to build other Nodes.
@@ -116,9 +120,9 @@ class Node:
         if self.source_scanner:
             self.found_includes = {}
             self.includes = None
-            
+
             def get_parents(node, parent): return node.get_parents()
-            def clear_cache(node, parent): 
+            def clear_cache(node, parent):
                 node.implicit = None
                 node.bsig = None
             w = Walker(self, get_parents, ignore_cycle, clear_cache)
@@ -170,17 +174,38 @@ class Node:
         # Don't bother scanning non-derived files, because we don't
         # care what their dependencies are.
         # Don't scan again, if we already have scanned.
-        if self.implicit is None:
-            if self.builder:
-                self.implicit = []
-                for child in self.children(scan=0):
-                    self._add_child(self.implicit, child.get_implicit_deps(self.env, child.source_scanner, self))
-            
-                # scan this node itself for implicit dependencies
-                self._add_child(self.implicit, self.get_implicit_deps(self.env, self.target_scanner, self))
-            else:
-                self.implicit = []
-    
+        if not self.implicit is None:
+            return
+        self.implicit = []
+        if not self.builder:
+            return
+
+        if implicit_cache:
+            implicit = self.get_stored_implicit()
+            if implicit is not None:
+                implicit = map(self.builder.source_factory, implicit)
+                self._add_child(self.implicit, implicit)
+                calc = SCons.Sig.default_calc
+                if calc.current(self, calc.bsig(self)):
+                    return
+                else:
+                    self.implicit = []
+
+        for child in self.children(scan=0):
+            self._add_child(self.implicit,
+                            child.get_implicit_deps(self.env,
+                                                    child.source_scanner,
+                                                    self))
+
+        # scan this node itself for implicit dependencies
+        self._add_child(self.implicit,
+                        self.get_implicit_deps(self.env,
+                                               self.target_scanner,
+                                               self))
+
+        if implicit_cache:
+            self.store_implicit()
+
     def scanner_key(self):
         return None
 
@@ -225,6 +250,15 @@ class Node:
     def get_timestamp(self):
         return 0
 
+    def store_implicit(self):
+        """Make the implicit deps permanent (that is, store them in the
+        .sconsign file or equivalent)."""
+        pass
+
+    def get_stored_implicit(self):
+        """Fetch the stored implicit dependencies"""
+        return None
+
     def set_precious(self, precious = 1):
         """Set the Node's precious value."""
         self.precious = precious
@@ -276,7 +310,7 @@ class Node:
             return self.sources + self.depends
         else:
             return self.sources + self.depends + self.implicit
-    
+
     def get_parents(self):
         return self.parents.keys()
 
@@ -300,10 +334,10 @@ class Walker:
     The Walker object can be initialized with any node, and
     returns the next node on the descent with each next() call.
     'kids_func' is an optional function that will be called to
-    get the children of a node instead of calling 'children'. 
+    get the children of a node instead of calling 'children'.
     'cycle_func' is an optional function that will be called
     when a cycle is detected.
-    
+
     This class does not get caught in node cycles caused, for example,
     by C header file include loops.
     """
index d9406ecaaad1826a5b59bde5a2af10327546cfc8..308552ddf1f97ce20510b189e5513b6b4d7be7e6 100644 (file)
@@ -163,7 +163,7 @@ max_drift = None
 
 def get_all_children(node): return node.all_children(None)
 
-def get_derived_children(node): 
+def get_derived_children(node):
     children = node.all_children(None)
     return filter(lambda x: x.builder, children)
 
@@ -224,7 +224,7 @@ opt_func = {}               # mapping of option strings to functions
 
 def options_init():
     """Initialize command-line options processing.
-    
+
     This is in a subroutine mainly so we can easily single-step over
     it in the debugger.
     """
@@ -471,6 +471,14 @@ def options_init():
        short = 'I', long = ['include-dir'], arg = 'DIRECTORY',
        help = "Search DIRECTORY for imported Python modules.")
 
+    def opt_implicit_cache(opt, arg):
+        import SCons.Node
+        SCons.Node.implicit_cache = 1
+
+    Option(func = opt_implicit_cache,
+        long = ['implicit-cache'],
+        help = "Cache implicit dependencies")
+
     def opt_j(opt, arg):
        global num_jobs
        try:
@@ -515,7 +523,7 @@ def options_init():
         global max_drift
         try:
             max_drift = int(arg)
-        except ValueError: 
+        except ValueError:
             raise UserError, "The argument for --max-drift must be an integer."
 
     Option(func = opt_max_drift,
@@ -727,7 +735,7 @@ def _main():
             os.chdir(script_dir)
         else:
             raise UserError, "No SConstruct file found."
-        
+
     SCons.Node.FS.default_fs.set_toplevel_dir(os.getcwd())
 
     if not scripts:
@@ -810,10 +818,11 @@ def _main():
     nodes = filter(lambda x: x is not None, map(Entry, targets))
 
     if not calc:
-        if max_drift is None:
-            calc = SCons.Sig.Calculator(SCons.Sig.MD5)
-        else:
-            calc = SCons.Sig.Calculator(SCons.Sig.MD5, max_drift)
+        if max_drift is not None:
+            SCons.Sig.default_calc = SCons.Sig.Calculator(SCons.Sig.MD5,
+                                                          max_drift)
+
+        calc = SCons.Sig.default_calc
 
     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, calc)
 
index 2a2560ff22d9be6d61c1e4e2630b8ba115c58693..cc39656bae19024399824f721d90856e2279227a 100644 (file)
@@ -58,11 +58,11 @@ class DummyNode:
         self.oldtime = 0
         self.oldbsig = 0
         self.oldcsig = 0
-        
+
     def get_contents(self):
         # a file that doesn't exist has no contents:
         assert self.exists()
-        
+
         return self.file.contents
 
     def get_timestamp(self):
@@ -80,11 +80,11 @@ class DummyNode:
         except AttributeError:
             self.exists_cache = self.exists()
             return self.exists_cache
-        
+
     def children(self):
         return filter(lambda x, i=self.ignore: x not in i,
                       self.sources + self.depends)
-        
+
     def all_children(self):
         return self.sources + self.depends
 
@@ -113,7 +113,16 @@ class DummyNode:
 
     def get_prevsiginfo(self):
         return (self.oldtime, self.oldbsig, self.oldcsig)
-    
+
+    def get_stored_implicit(self):
+        return None
+
+    def store_csig(self):
+        pass
+
+    def store_bsig(self):
+        pass
+
     def builder_sig_adapter(self):
         class Adapter:
             def get_contents(self):
@@ -135,7 +144,7 @@ def create_files(test):
              (test.workpath('d1/test.c'), 'blah blah', 111, 0),#8
              (test.workpath('d1/test.o'), None, 0, 1),         #9
              (test.workpath('d1/test'), None, 0, 1)]           #10
-    
+
     files = map(lambda x: apply(DummyFile, x), args)
 
     return files
@@ -173,12 +182,12 @@ def clear(nodes):
         node.bsig = None
 
 class SigTestBase:
-    
+
     def runTest(self):
 
         test = TestCmd.TestCmd(workdir = '')
         test.subdir('d1')
-        
+
         self.files = create_files(test)
         self.test_initial()
         self.test_built()
@@ -186,9 +195,9 @@ class SigTestBase:
         self.test_modify_same_time()
         self.test_delete()
         self.test_cache()
-        
+
     def test_initial(self):
-        
+
         nodes = create_nodes(self.files)
         calc = SCons.Sig.Calculator(self.module)
 
@@ -209,7 +218,7 @@ class SigTestBase:
         calc = SCons.Sig.Calculator(self.module)
 
         write(calc, nodes)
-        
+
         for node in nodes:
             self.failUnless(current(calc, node),
                             "all of the nodes should be current")
@@ -242,7 +251,6 @@ class SigTestBase:
         self.failUnless(not current(calc, nodes[9]), "direct source modified")
         self.failUnless(not current(calc, nodes[10]), "indirect source modified")
 
-
     def test_modify_same_time(self):
 
         nodes = create_nodes(self.files)
@@ -264,9 +272,9 @@ class SigTestBase:
                             "all of the nodes should be current")
 
     def test_delete(self):
-        
+
         nodes = create_nodes(self.files)
-        
+
         calc = SCons.Sig.Calculator(self.module)
 
         write(calc, nodes)
@@ -292,13 +300,13 @@ class SigTestBase:
     def test_cache(self):
         """Test that signatures are cached properly."""
         nodes = create_nodes(self.files)
-        
+
         calc = SCons.Sig.Calculator(self.module)
         nodes[0].set_csig(1)
         nodes[1].set_bsig(1)
         assert calc.csig(nodes[0]) == 1, calc.csig(nodes[0])
         assert calc.bsig(nodes[1]) == 1, calc.bsig(nodes[1])
-        
+
 
 class MD5TestCase(unittest.TestCase, SigTestBase):
     """Test MD5 signatures"""
@@ -311,7 +319,7 @@ class TimeStampTestCase(unittest.TestCase, SigTestBase):
     module = SCons.Sig.TimeStamp
 
 class CalcTestCase(unittest.TestCase):
-    
+
     def runTest(self):
         class MySigModule:
             def collect(self, signatures):
@@ -348,6 +356,8 @@ class CalcTestCase(unittest.TestCase):
                 return self.csig
             def get_prevsiginfo(self):
                 return 0, self.bsig, self.csig
+            def get_stored_implicit(self):
+                return None
             def get_timestamp(self):
                 return 1
             def builder_sig_adapter(self):
@@ -443,11 +453,52 @@ class CalcTestCase(unittest.TestCase):
         assert not self.calc.current(nn, 30)
         assert self.calc.current(nn, 33)
 
+class SConsignEntryTestCase(unittest.TestCase):
+
+    def runTest(self):
+        class DummyModule:
+            def to_string(self, sig):
+                return str(sig)
+
+            def from_string(self, sig):
+                return int(sig)
+
+        m = DummyModule()
+        e = SCons.Sig.SConsignEntry(m)
+        assert e.timestamp == None
+        assert e.csig == None
+        assert e.bsig == None
+        assert e.get_implicit() == None
+        assert e.render(m) == "- - - -"
+
+        e = SCons.Sig.SConsignEntry(m, "- - - -")
+        assert e.timestamp == None
+        assert e.csig == None
+        assert e.bsig == None
+        assert e.get_implicit() == None
+        assert e.render(m) == "- - - -"
+
+        e = SCons.Sig.SConsignEntry(m, "- - - foo\0bar")
+        assert e.timestamp == None
+        assert e.csig == None
+        assert e.bsig == None
+        assert e.get_implicit() == ['foo', 'bar']
+        assert e.render(m) == "- - - foo\0bar"
+
+        e = SCons.Sig.SConsignEntry(m, "123 456 789 foo bletch\0bar")
+        assert e.timestamp == 123
+        assert e.bsig == 456
+        assert e.csig == 789
+        assert e.get_implicit() == ['foo bletch', 'bar']
+        assert e.render(m) == "123 456 789 foo bletch\0bar"
+
+
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(MD5TestCase())
     suite.addTest(TimeStampTestCase())
     suite.addTest(CalcTestCase())
+    suite.addTest(SConsignEntryTestCase())
     return suite
 
 if __name__ == "__main__":
@@ -455,4 +506,4 @@ if __name__ == "__main__":
     result = runner.run(suite())
     if not result.wasSuccessful():
         sys.exit(1)
-    
+
index d077114cf3679ed93c6e3df84a99dcfcb8c29531..386c9e50aedccbc9a3a1fa76d3675d1fa7d2e1a2 100644 (file)
@@ -43,19 +43,74 @@ def write():
     for sig_file in sig_files:
         sig_file.write()
 
+class SConsignEntry:
+    def __init__(self, module, entry=None):
+
+        self.timestamp = self.csig = self.bsig = self.implicit = None
+
+        if not entry is None:
+            arr = map(string.strip, string.split(entry, " ", 3))
+
+            try:
+                if arr[0] == '-': self.timestamp = None
+                else:             self.timestamp = int(arr[0])
+
+                if arr[1] == '-': self.bsig = None
+                else:             self.bsig = module.from_string(arr[1])
+
+                if arr[2] == '-': self.csig = None
+                else:             self.csig = module.from_string(arr[2])
+
+                if arr[3] == '-': self.implicit = None
+                else:             self.implicit = arr[3]
+            except IndexError:
+                pass
+
+    def render(self, module):
+        if self.timestamp is None: timestamp = '-'
+        else:                      timestamp = "%d"%self.timestamp
+
+        if self.bsig is None: bsig = '-'
+        else:                 bsig = module.to_string(self.bsig)
+
+        if self.csig is None: csig = '-'
+        else:                 csig = module.to_string(self.csig)
+
+        if self.implicit is None: implicit = '-'
+        else:                     implicit = self.implicit
+
+        return '%s %s %s %s' % (timestamp, bsig, csig, implicit)
+
+    def get_implicit(self):
+        if self.implicit is None:
+            return None
+        else:
+            return string.split(self.implicit, '\0')
+
+    def set_implicit(self, implicit):
+        if implicit is None:
+            self.implicit = None
+        else:
+            self.implicit = string.join(map(str, implicit), '\0')
+
+
 class SConsignFile:
     """
     Encapsulates reading and writing a .sconsign file.
     """
 
-    def __init__(self, dir, module):
+    def __init__(self, dir, module=None):
         """
         dir - the directory for the file
         module - the signature module being used
         """
 
         self.dir = dir
-        self.module = module
+
+        if module is None:
+            self.module = default_calc.module
+        else:
+            self.module = module
         self.sconsign = os.path.join(dir.path, '.sconsign')
         self.entries = {}
         self.dirty = None
@@ -66,8 +121,8 @@ class SConsignFile:
             pass
         else:
             for line in file.readlines():
-                filename, rest = map(string.strip, string.split(line, ":"))
-                self.entries[filename] = rest
+                filename, rest = map(string.strip, string.split(line, ":", 1))
+                self.entries[filename] = SConsignEntry(self.module, rest)
 
         global sig_files
         sig_files.append(self)
@@ -77,24 +132,13 @@ class SConsignFile:
         Get the .sconsign entry for a file
 
         filename - the filename whose signature will be returned
-        returns - (timestamp, bsig, csig)
+        returns - (timestamp, bsig, csig, implicit)
         """
-
         try:
-            arr = map(string.strip, string.split(self.entries[filename], " "))
+            entry = self.entries[filename]
+            return (entry.timestamp, entry.bsig, entry.csig)
         except KeyError:
             return (None, None, None)
-        try:
-            if arr[1] == '-': bsig = None
-            else:             bsig = self.module.from_string(arr[1])
-        except IndexError:
-            bsig = None
-        try:
-            if arr[2] == '-': csig = None
-            else:             csig = self.module.from_string(arr[2])
-        except IndexError:
-            csig = None
-        return (int(arr[0]), bsig, csig)
 
     def set(self, filename, timestamp, bsig = None, csig = None):
         """
@@ -106,13 +150,37 @@ class SConsignFile:
         bsig - the file's build signature
         csig - the file's content signature
         """
-        if bsig is None: bsig = '-'
-        else:            bsig = self.module.to_string(bsig)
-        if csig is None: csig = ''
-        else:            csig = ' ' + self.module.to_string(csig)
-        self.entries[filename] = "%d %s%s" % (timestamp, bsig, csig)
+
+        try:
+            entry = self.entries[filename]
+        except KeyError:
+            entry = SConsignEntry(self.module)
+            self.entries[filename] = entry
+
+        entry.timestamp = timestamp
+        entry.bsig = bsig
+        entry.csig = csig
+
         self.dirty = 1
 
+    def get_implicit(self, filename):
+        """Fetch the cached implicit dependencies for 'filename'"""
+        try:
+            entry = self.entries[filename]
+            return entry.get_implicit()
+        except KeyError:
+            return None
+
+    def set_implicit(self, filename, implicit):
+        """Cache the implicit dependencies for 'filename'."""
+        try:
+            entry = self.entries[filename]
+        except KeyError:
+            entry = SConsignEntry(self.module)
+            self.entries[filename] = entry
+
+        entry.set_implicit(implicit)
+
     def write(self):
         """
         Write the .sconsign file to disk.
@@ -132,12 +200,15 @@ class SConsignFile:
                 file = open(temp, 'wt')
                 fname = temp
             except:
-                file = open(self.sconsign, 'wt')
-                fname = self.sconsign
+                try:
+                    file = open(self.sconsign, 'wt')
+                    fname = self.sconsign
+                except:
+                    return
             keys = self.entries.keys()
             keys.sort()
             for name in keys:
-                file.write("%s: %s\n" % (name, self.entries[name]))
+                file.write("%s: %s\n" % (name, self.entries[name].render(self.module)))
             file.close()
             if fname != self.sconsign:
                 try:
@@ -163,7 +234,7 @@ class Calculator:
     for the build engine.
     """
 
-    def __init__(self, module, max_drift=2*24*60*60):
+    def __init__(self, module=None, max_drift=2*24*60*60):
         """
         Initialize the calculator.
 
@@ -172,7 +243,11 @@ class Calculator:
           cache content signatures. A negative value means to never cache
           content signatures. (defaults to 2 days)
         """
-        self.module = module
+        if module is None:
+            import MD5
+            self.module = MD5
+        else:
+            self.module = module
         self.max_drift = max_drift
 
     def bsig(self, node):
@@ -198,6 +273,7 @@ class Calculator:
         sigs = map(self.get_signature, node.children())
         if node.builder:
             sigs.append(self.module.signature(node.builder_sig_adapter()))
+
         bsig = self.module.collect(filter(lambda x: not x is None, sigs))
 
         node.set_bsig(bsig)
@@ -294,3 +370,6 @@ class Calculator:
             return 1
 
         return self.module.current(newsig, oldbsig)
+
+
+default_calc = Calculator()
index 22c22cbe8dce59ed79b1a2c8b237ab4bbeee4cd4..7985b2e1e2da80e63f1bc57ed212335fccaf2f77 100644 (file)
@@ -125,7 +125,6 @@ class Task:
         state = SCons.Node.up_to_date
         for t in self.targets:
             bsig = self.tm.calc.bsig(t)
-            t.set_bsig(bsig)
             if not self.tm.calc.current(t, bsig):
                 state = SCons.Node.executing
         self.set_tstates(state)
diff --git a/test/option--implicit-cache.py b/test/option--implicit-cache.py
new file mode 100644 (file)
index 0000000..fb5ace2
--- /dev/null
@@ -0,0 +1,177 @@
+#!/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 os
+import sys
+import TestSCons
+
+if sys.platform == 'win32':
+    _exe = '.exe'
+else:
+    _exe = ''
+
+prog = 'prog' + _exe
+subdir_prog = os.path.join('subdir', 'prog' + _exe)
+variant_prog = os.path.join('variant', 'prog' + _exe)
+
+args = prog + ' ' + subdir_prog + ' ' + variant_prog
+
+test = TestSCons.TestSCons()
+
+test.subdir('include', 'subdir', ['subdir', 'include'], 'inc2')
+
+test.write('SConstruct', """
+env = Environment(CPPPATH = 'inc2 include')
+obj = env.Object(target='prog', source='subdir/prog.c')
+env.Program(target='prog', source=obj)
+SConscript('subdir/SConscript', "env")
+
+BuildDir('variant', 'subdir', 0)
+include = Dir('include')
+env = Environment(CPPPATH=['inc2', include])
+SConscript('variant/SConscript', "env")
+""")
+
+test.write(['subdir', 'SConscript'],
+"""
+Import("env")
+env.Program(target='prog', source='prog.c')
+""")
+
+test.write(['include', 'foo.h'],
+r"""
+#define        FOO_STRING "include/foo.h 1\n"
+#include <bar.h>
+""")
+
+test.write(['include', 'bar.h'],
+r"""
+#define BAR_STRING "include/bar.h 1\n"
+""")
+
+test.write(['subdir', 'prog.c'],
+r"""
+#include <foo.h>
+#include <stdio.h>
+
+int
+main(int argc, char *argv[])
+{
+    argv[argc++] = "--";
+    printf("subdir/prog.c\n");
+    printf(FOO_STRING);
+    printf(BAR_STRING);
+    return 0;
+}
+""")
+
+test.write(['subdir', 'include', 'foo.h'],
+r"""
+#define        FOO_STRING "subdir/include/foo.h 1\n"
+#include "bar.h"
+""")
+
+test.write(['subdir', 'include', 'bar.h'],
+r"""
+#define BAR_STRING "subdir/include/bar.h 1\n"
+""")
+
+
+
+test.run(arguments = "--implicit-cache " + args)
+
+test.run(program = test.workpath(prog),
+         stdout = "subdir/prog.c\ninclude/foo.h 1\ninclude/bar.h 1\n")
+
+test.run(program = test.workpath(subdir_prog),
+         stdout = "subdir/prog.c\nsubdir/include/foo.h 1\nsubdir/include/bar.h 1\n")
+
+test.run(program = test.workpath(variant_prog),
+         stdout = "subdir/prog.c\ninclude/foo.h 1\ninclude/bar.h 1\n")
+
+test.up_to_date(arguments = args)
+
+# Make sure implicit dependenies work right when one is modifed:
+test.write(['include', 'foo.h'],
+r"""
+#define        FOO_STRING "include/foo.h 2\n"
+#include "bar.h"
+""")
+
+test.run(arguments = "--implicit-cache " + args)
+
+test.run(program = test.workpath(prog),
+         stdout = "subdir/prog.c\ninclude/foo.h 2\ninclude/bar.h 1\n")
+
+test.run(program = test.workpath(subdir_prog),
+         stdout = "subdir/prog.c\nsubdir/include/foo.h 1\nsubdir/include/bar.h 1\n")
+
+test.run(program = test.workpath(variant_prog),
+         stdout = "subdir/prog.c\ninclude/foo.h 2\ninclude/bar.h 1\n")
+
+test.up_to_date(arguments = args)
+
+# Add inc2/foo.h that should shadow include/foo.h, but
+# because of implicit dependency caching, scons doesn't
+# detect this:
+test.write(['inc2', 'foo.h'],
+r"""
+#define FOO_STRING "inc2/foo.h 1\n"
+#include <bar.h>
+""")
+
+test.run(arguments = "--implicit-cache " + args)
+
+test.run(arguments = "--implicit-cache " + args)
+
+test.run(program = test.workpath(prog),
+         stdout = "subdir/prog.c\ninclude/foo.h 2\ninclude/bar.h 1\n")
+
+test.run(program = test.workpath(subdir_prog),
+         stdout = "subdir/prog.c\nsubdir/include/foo.h 1\nsubdir/include/bar.h 1\n")
+
+test.run(program = test.workpath(variant_prog),
+         stdout = "subdir/prog.c\ninclude/foo.h 2\ninclude/bar.h 1\n")
+
+# Now modifying include/foo.h should make scons aware of inc2/foo.h
+test.write(['include', 'foo.h'],
+r"""
+#define        FOO_STRING "include/foo.h 3\n"
+#include "bar.h"
+""")
+
+test.run(arguments = "--implicit-cache " + args)
+
+test.run(program = test.workpath(prog),
+         stdout = "subdir/prog.c\ninc2/foo.h 1\ninclude/bar.h 1\n")
+
+test.run(program = test.workpath(subdir_prog),
+         stdout = "subdir/prog.c\nsubdir/include/foo.h 1\nsubdir/include/bar.h 1\n")
+
+test.run(program = test.workpath(variant_prog),
+         stdout = "subdir/prog.c\ninclude/foo.h 3\ninclude/bar.h 1\n")
+
+test.pass_test()
index 55ec958e0b67ec04d753b2b5a1661f16a23643d9..76d8220b16cf88ed14b3dc6d793b8f86c1ff6360 100644 (file)
@@ -29,7 +29,7 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
-test.subdir('sub1', 'sub2')
+test.subdir('sub1', 'sub2', 'sub3')
 
 test.write('SConstruct', """
 def build1(target, source, env):
@@ -49,12 +49,14 @@ B2 = Builder(name = "B2", action = build2)
 env = Environment(BUILDERS = [B1, B2])
 env.B1(target = 'sub1/foo.out', source = 'foo.in')
 env.B2(target = 'sub2/foo.out', source = 'foo.in')
+env.B2(target = 'sub3/foo.out', source = 'foo.in')
 """)
 
 test.write('foo.in', "foo.in\n")
 
 sub1__sconsign = test.workpath('sub1', '.sconsign')
 sub2__sconsign = test.workpath('sub2', '.sconsign')
+sub3__sconsign = test.workpath('sub3', '.sconsign')
 
 test.write(sub1__sconsign, "")
 test.write(sub2__sconsign, "")