Fix retrieving multiple target files from cache. (Bob Halley)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sat, 17 Jan 2004 14:56:46 +0000 (14:56 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sat, 17 Jan 2004 14:56:46 +0000 (14:56 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@882 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/CHANGES.txt
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py
test/CacheDir.py

index 925273663577c1e817698f1d28c95df752fb9faa..3e0feaa43cdc10617b15c27c1dfc4967f62f68b4 100644 (file)
@@ -78,6 +78,12 @@ RELEASE 0.95 - XXX
     environments so long as the builders have the same signature for
     the environments in questions (that is, they're the same action).
 
+  From Bob Halley:
+
+  - When multiple targets are built by a single action, retrieve all
+    of them from cache, not just the first target, and exec the build
+    command if any of the targets isn't present in the cache.
+
   From Steven Knight:
 
   - Fix EnsureSConsVersion() so it checks against the SCons version,
index 7c4b38336581eaa74c97e81567c45ff5c5640c0e..46d72d750a54f8387b360903e03c7bf44314c8a7 100644 (file)
@@ -48,6 +48,7 @@ import SCons.Action
 import SCons.Errors
 import SCons.Node
 import SCons.Util
+import SCons.Sig.MD5
 import SCons.Warnings
 
 #
@@ -1311,20 +1312,18 @@ class File(Base):
             except OSError:
                 pass
 
-    def build(self):
-        """Actually build the file.
-
-        This overrides the base class build() method to check for the
-        existence of derived files in a CacheDir before going ahead and
-        building them.
+    def retrieve_from_cache(self):
+        """Try to retrieve the node's content from a cache
 
         This method is called from multiple threads in a parallel build,
         so only do thread safe stuff here. Do thread unsafe stuff in
         built().
+
+        Returns true iff the node was successfully retrieved.
         """
         b = self.is_derived()
         if not b and not self.has_src_builder():
-            return
+            return None
         if b and self.fs.CachePath:
             if self.fs.cache_show:
                 if CacheRetrieveSilent(self, None, None) == 0:
@@ -1336,10 +1335,10 @@ class File(Base):
                             for a in al:
                                 action.show(a)
                     self._for_each_action(do_print)
-                    return
+                    return 1
             elif CacheRetrieve(self, None, None) == 0:
-                return
-        SCons.Node.Node.build(self)
+                return 1
+        return None
 
     def built(self):
         """Called just after this node is sucessfully built."""
@@ -1509,10 +1508,13 @@ class File(Base):
             bsig = self.get_bsig()
             if bsig is None:
                 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
-            bsig = str(bsig)
-            subdir = string.upper(bsig[0])
+            # Add the path to the cache signature, because multiple
+            # targets built by the same action will all have the same
+            # build signature, and we have to differentiate them somehow.
+            cache_sig = SCons.Sig.MD5.collect([bsig, self.path])
+            subdir = string.upper(cache_sig[0])
             dir = os.path.join(self.fs.CachePath, subdir)
-            return dir, os.path.join(dir, bsig)
+            return dir, os.path.join(dir, cache_sig)
         return None, None
 
     def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
index 739d4e9d1797007603f6f0d82089c999d907afa4..132e8f61e90da1c0ee3bb75a7fea16841552d528 100644 (file)
@@ -1528,7 +1528,8 @@ class CacheDirTestCase(unittest.TestCase):
             self.retrieved = []
             built_it = None
 
-            f1.build()
+            r = f1.retrieve_from_cache()
+            assert r == 1, r
             assert self.retrieved == [f1], self.retrieved
             assert built_it is None, built_it
 
@@ -1536,9 +1537,10 @@ class CacheDirTestCase(unittest.TestCase):
             self.retrieved = []
             built_it = None
 
-            f1.build()
+            r = f1.retrieve_from_cache()
+            assert r is None, r
             assert self.retrieved == [f1], self.retrieved
-            assert built_it, built_it
+            assert built_it is None, built_it
         finally:
             SCons.Node.FS.CacheRetrieve = save_CacheRetrieve
 
@@ -1554,7 +1556,8 @@ class CacheDirTestCase(unittest.TestCase):
             self.retrieved = []
             built_it = None
 
-            f2.build()
+            r = f2.retrieve_from_cache()
+            assert r == 1, r
             assert self.retrieved == [f2], self.retrieved
             assert built_it is None, built_it
 
@@ -1562,9 +1565,10 @@ class CacheDirTestCase(unittest.TestCase):
             self.retrieved = []
             built_it = None
 
-            f2.build()
+            r = f2.retrieve_from_cache()
+            assert r is None, r
             assert self.retrieved == [f2], self.retrieved
-            assert built_it, built_it
+            assert built_it is None, built_it
         finally:
             SCons.Node.FS.CacheRetrieveSilent = save_CacheRetrieveSilent
 
@@ -1602,12 +1606,19 @@ class CacheDirTestCase(unittest.TestCase):
 
         # Verify how the cachepath() method determines the name
         # of the file in cache.
-        f5 = fs.File("cd.f5")
-        f5.set_bsig('a_fake_bsig')
-        cp = f5.cachepath()
-        dirname = os.path.join('cache', 'A')
-        filename = os.path.join(dirname, 'a_fake_bsig')
-        assert cp == (dirname, filename), cp
+        def my_collect(list):
+            return list[0]
+        save_collect = SCons.Sig.MD5.collect
+        SCons.Sig.MD5.collect = my_collect
+        try:
+            f5 = fs.File("cd.f5")
+            f5.set_bsig('a_fake_bsig')
+            cp = f5.cachepath()
+            dirname = os.path.join('cache', 'A')
+            filename = os.path.join(dirname, 'a_fake_bsig')
+            assert cp == (dirname, filename), cp
+        finally:
+            SCons.Sig.MD5.collect = save_collect
 
         # Verify that no bsig raises an InternalERror
         f6 = fs.File("cd.f6")
@@ -1661,7 +1672,8 @@ class CacheDirTestCase(unittest.TestCase):
             self.retrieved = []
             built_it = None
 
-            f8.build()
+            r = f8.retrieve_from_cache()
+            assert r == 1, r
             assert self.retrieved == [f8], self.retrieved
             assert built_it is None, built_it
 
@@ -1669,9 +1681,10 @@ class CacheDirTestCase(unittest.TestCase):
             self.retrieved = []
             built_it = None
 
-            f8.build()
+            r = f8.retrieve_from_cache()
+            assert r is None, r
             assert self.retrieved == [f8], self.retrieved
-            assert built_it, built_it
+            assert built_it is None, built_it
         finally:
             SCons.Node.FS.CacheRetrieveSilent = save_CacheRetrieveSilent
 
index 08e42604e1882e4f8c8360bd5e3d42f59dc1a896..6b8c8ae704bde9d33d0b8b121e2237c57e822f53 100644 (file)
@@ -253,6 +253,12 @@ class NodeTestCase(unittest.TestCase):
             assert str(act.built_target[0]) == "xxx", str(act.built_target[0])
             assert act.built_source == ["yyy", "zzz"], act.built_source
 
+    def test_retrieve_from_cache(self):
+        """Test the base retrieve_from_cache() method"""
+        n = SCons.Node.Node()
+        r = n.retrieve_from_cache()
+        assert r == 0, r
+
     def test_visited(self):
         """Test the base visited() method
 
index 28264cef3d5bc36bf0933666824f2db145ddf315..0e453d2fd12b2e72fa79fe091a003f08bb338473 100644 (file)
@@ -176,6 +176,17 @@ class Node:
         executor = self.get_executor()
         executor(self, func)
 
+    def retrieve_from_cache(self):
+        """Try to retrieve the node's content from a cache
+
+        This method is called from multiple threads in a parallel build,
+        so only do thread safe stuff here. Do thread unsafe stuff in
+        built().
+
+        Returns true iff the node was successfully retrieved.
+        """
+        return 0
+        
     def build(self):
         """Actually build the node.
 
index 90d14528fb2ecce95080f7fb83f0fc112d6b77ab..66492ded8110dabc8e67e3f9abf3587e063ae50b 100644 (file)
@@ -92,7 +92,13 @@ class Task:
         prepare(), executed() or failed()."""
 
         try:
-            self.targets[0].build()
+            everything_was_cached = 1
+            for t in self.targets:
+                if not t.retrieve_from_cache():
+                    everything_was_cached = 0
+                    break
+            if not everything_was_cached:
+                self.targets[0].build()
         except KeyboardInterrupt:
             raise
         except SystemExit:
index 019611a69c5d217127ad4e6950f1601611ac9386..63190a5be30a76668636c48fdbcf86bc06a8f190 100644 (file)
@@ -31,6 +31,7 @@ import SCons.Errors
 
 
 built_text = None
+cache_text = []
 visited_nodes = []
 executed = None
 scan_called = 0
@@ -40,6 +41,7 @@ class Node:
         self.name = name
         self.kids = kids
         self.scans = scans
+        self.cached = 0
         self.scanned = 0
         self.scanner = None
         self.builder = Node.build
@@ -55,6 +57,12 @@ class Node:
         for kid in kids:
             kid.parents.append(self)
 
+    def retrieve_from_cache(self):
+        global cache_text
+        if self.cached:
+            cache_text.append(self.name + " retrieved")
+        return self.cached
+
     def build(self):
         global built_text
         built_text = self.name + " built"
@@ -654,6 +662,7 @@ class TaskmasterTestCase(unittest.TestCase):
 
         """
         global built_text
+        global cache_text
 
         n1 = Node("n1")
         tm = SCons.Taskmaster.Taskmaster([n1])
@@ -708,6 +717,43 @@ class TaskmasterTestCase(unittest.TestCase):
         else:
             raise TestFailed, "did not catch expected BuildError"
 
+        built_text = None
+        cache_text = []
+        n5 = Node("n5")
+        n6 = Node("n6")
+        n6.cached = 1
+        tm = SCons.Taskmaster.Taskmaster([n5])
+        t = tm.next_task()
+        # This next line is moderately bogus.  We're just reaching
+        # in and setting the targets for this task to an array.  The
+        # "right" way to do this would be to have the next_task() call
+        # set it up by having something that approximates a real Builder
+        # return this list--but that's more work than is probably
+        # warranted right now.
+        t.targets = [n5, n6]
+        t.execute()
+        assert built_text == "n5 built", built_text
+        assert cache_text == [], cache_text
+
+        built_text = None
+        cache_text = []
+        n7 = Node("n7")
+        n8 = Node("n8")
+        n7.cached = 1
+        n8.cached = 1
+        tm = SCons.Taskmaster.Taskmaster([n7])
+        t = tm.next_task()
+        # This next line is moderately bogus.  We're just reaching
+        # in and setting the targets for this task to an array.  The
+        # "right" way to do this would be to have the next_task() call
+        # set it up by having something that approximates a real Builder
+        # return this list--but that's more work than is probably
+        # warranted right now.
+        t.targets = [n7, n8]
+        t.execute()
+        assert built_text is None, built_text
+        assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text
+
     def test_exception(self):
         """Test generic Taskmaster exception handling
 
index a906b6a775b491ea9c70c1d17ed9d39742c272e7..44dd708e98785490744a545b3bbceaed4ee3701f 100644 (file)
@@ -270,5 +270,33 @@ test.run(chdir = 'subdir')
 
 test.fail_test(os.path.exists(test.workpath('cache3', 'N', 'None')))
 
+#############################################################################
+# Test that multiple target files get retrieved from cache correctly.
+
+test.subdir('multiple', 'cache4')
+
+test.write(['multiple', 'SConstruct'], """\
+CacheDir(r'%s')
+env = Environment()
+env.Command(['foo', 'bar'], ['input'], 'touch foo bar')
+""" % (test.workpath('cache4')))
+
+test.write(['multiple', 'input'], "multiple/input\n")
+
+test.run(chdir = 'multiple')
+
+test.fail_test(not os.path.exists(test.workpath('multiple', 'foo')))
+test.fail_test(not os.path.exists(test.workpath('multiple', 'bar')))
+
+test.run(chdir = 'multiple', arguments = '-c')
+
+test.fail_test(os.path.exists(test.workpath('multiple', 'foo')))
+test.fail_test(os.path.exists(test.workpath('multiple', 'bar')))
+
+test.run(chdir = 'multiple')
+
+test.fail_test(not os.path.exists(test.workpath('multiple', 'foo')))
+test.fail_test(not os.path.exists(test.workpath('multiple', 'bar')))
+
 # All done.
 test.pass_test()