Save memory by allowing Nodes to clean up their Executor's build environments after...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 9 Feb 2004 06:59:46 +0000 (06:59 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 9 Feb 2004 06:59:46 +0000 (06:59 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@897 fdb21ef1-2011-0410-befe-b5e4ea1792b1

14 files changed:
src/CHANGES.txt
src/engine/SCons/Builder.py
src/engine/SCons/Executor.py
src/engine/SCons/ExecutorTests.py
src/engine/SCons/Job.py
src/engine/SCons/JobTests.py
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/SConfTests.py
src/engine/SCons/Script/SConscript.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py

index f93a78a23d80770918d38deadaeba1efb51a6d40..e6c3b536f5f3018bf59b835271c615e6c80bd013 100644 (file)
@@ -170,6 +170,9 @@ RELEASE 0.95 - XXX
   - Fix the M4 Builder so that it chdirs to the Repository directory
     when the input file is in the source directory of a BuildDir.
 
+  - Save memory at build time by allowing Nodes to delete their build
+    environments after they've been built.
+
   From Vincent Risi:
 
   - Add support for the bcc32, ilink32 and tlib Borland tools.
index 6714a3d4808e96740d007a795fcfadfe4e613976..871a80398b1d1977d82b43e3f215ed9d6bc81e6b 100644 (file)
@@ -203,7 +203,7 @@ def _init_nodes(builder, env, overrides, tlist, slist):
             executor.add_sources(slist)
     if executor is None:
         executor = SCons.Executor.Executor(builder,
-                                           tlist[0].generate_build_env(env),
+                                           env,
                                            overrides,
                                            tlist,
                                            slist)
index 2a0882e0f2d98c1d45087123807bd7fb501fee0e..8151f87d1c0af50230c4b192305463fe40cd2ba7 100644 (file)
@@ -32,6 +32,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 
 from SCons.Debug import logInstanceCreation
+import SCons.Util
 
 
 class Executor:
@@ -71,6 +72,13 @@ class Executor:
             overrides = {}
             overrides.update(self.builder.overrides)
             overrides.update(self.overrides)
+            try:
+                generate_build_dict = self.targets[0].generate_build_dict
+            except AttributeError:
+                pass
+            else:
+                overrides.update(generate_build_dict())
+            overrides.update(SCons.Util.subst_dict(self.targets, self.sources))
             self.build_env = env.Override(overrides)
             return self.build_env
 
@@ -101,6 +109,12 @@ class Executor:
         for action in action_list:
             func(action, self.targets, self.sources, env)
 
+    def cleanup(self):
+        try:
+            del self.build_env
+        except AttributeError:
+            pass
+
     def add_sources(self, sources):
         """Add source files to this Executor's list.  This is necessary
         for "multi" Builders that can be called repeatedly to build up
index 2a79e2db8092f250c0919d0ab8575a72280b09fb..2cdc0e2321993d412c59411fa11e94524ebb5275 100644 (file)
@@ -90,7 +90,8 @@ class ExecutorTestCase(unittest.TestCase):
                                     't',
                                     ['s1', 's2'])
         be = x.get_build_env()
-        assert be == {'O':'o2', 'X':'xxx'}, be
+        assert be['O'] == 'o2', be['O']
+        assert be['X'] == 'xxx', be['X']
 
         env = MyEnvironment(Y='yyy')
         x = SCons.Executor.Executor(MyBuilder(env, {'O':'ob3'}),
@@ -99,14 +100,16 @@ class ExecutorTestCase(unittest.TestCase):
                                     't',
                                     's')
         be = x.get_build_env()
-        assert be == {'O':'oo3', 'Y':'yyy'}, be
+        assert be['O'] == 'oo3', be['O']
+        assert be['Y'] == 'yyy', be['Y']
         x = SCons.Executor.Executor(MyBuilder(env, {'O':'ob3'}),
                                     None,
                                     {},
                                     't',
                                     's')
         be = x.get_build_env()
-        assert be == {'O':'ob3', 'Y':'yyy'}, be
+        assert be['O'] == 'ob3', be['O']
+        assert be['Y'] == 'yyy', be['Y']
 
     def test_get_action_list(self):
         """Test fetching and generating an action list"""
@@ -131,10 +134,24 @@ class ExecutorTestCase(unittest.TestCase):
             a.append(action)
             assert target == ['t1', 't2'], target
             assert source == ['s1', 's2'], source
-            assert env == {'CALL':'call'}, env
+            assert env['CALL'] == 'call', env['CALL']
         x(MyNode(['pre'], ['post']), func)
         assert actions == ['pre', 'action1', 'action2', 'post'], actions
 
+    def test_cleanup(self):
+        """Test cleaning up an Executor"""
+        x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
+
+        x.cleanup()
+
+        x.build_env = 'eee'
+        be = x.get_build_env()
+        assert be == 'eee', be
+
+        x.cleanup()
+
+        assert not hasattr(x, 'build_env')
+
     def test_add_sources(self):
         """Test adding sources to an Executor"""
         x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
index ee84e344cf2a1c40eb87f5806c56284fabe554a1..1ba7d62c918e97754738936a80693faf836152a9 100644 (file)
@@ -118,6 +118,8 @@ class Serial:
             else:
                 task.executed()
 
+            task.postprocess()
+
 
 # Trap import failure so that everything in the Job module but the
 # Parallel class (and its dependent classes) will work if the interpreter
@@ -251,3 +253,5 @@ else:
                         task.executed()
                     else:
                         task.failed()
+
+                    task.postprocess()
index 1b0128b3f62f6cc96be15bd6c863706f7014e42b..48caa16a360c9e1ac1999e8510eacd7a405eaebc 100644 (file)
@@ -100,6 +100,9 @@ class Task:
         self.taskmaster.test_case.failUnless(self.was_prepared,
                                   "the task wasn't prepared")
 
+    def postprocess(self):
+        self.taskmaster.num_postprocessed = self.taskmaster.num_postprocessed + 1
+
 
 class ExceptionTask:
     """A dummy task class for testing purposes."""
@@ -130,6 +133,9 @@ class ExceptionTask:
         self.taskmaster.test_case.failUnless(self.was_prepared,
                                   "the task wasn't prepared")
 
+    def postprocess(self):
+        self.taskmaster.num_postprocessed = self.taskmaster.num_postprocessed + 1
+
 class Taskmaster:
     """A dummy taskmaster class for testing the job classes."""
 
@@ -142,6 +148,7 @@ class Taskmaster:
         self.num_iterated = 0
         self.num_executed = 0
         self.num_failed = 0
+        self.num_postprocessed = 0
         self.Task = Task
         # 'guard' guards 'task_begin_list' and 'task_end_list'
         try:
@@ -170,6 +177,9 @@ class Taskmaster:
     def all_tasks_are_iterated(self):
         return self.num_iterated == self.num_tasks
 
+    def all_tasks_are_postprocessed(self):
+        return self.num_postprocessed == self.num_tasks
+
     def is_blocked(self):
         if self.stop or self.all_tasks_are_executed():
             return 0
@@ -206,6 +216,8 @@ class ParallelTestCase(unittest.TestCase):
                         "all the tests were not executed")
         self.failUnless(taskmaster.all_tasks_are_iterated(),
                         "all the tests were not iterated over")
+        self.failUnless(taskmaster.all_tasks_are_postprocessed(),
+                        "all the tests were not postprocessed")
         self.failIf(taskmaster.num_failed,
                     "some task(s) failed to execute") 
 
@@ -223,6 +235,8 @@ class SerialTestCase(unittest.TestCase):
                         "all the tests were not executed")
         self.failUnless(taskmaster.all_tasks_are_iterated(),
                         "all the tests were not iterated over")
+        self.failUnless(taskmaster.all_tasks_are_postprocessed(),
+                        "all the tests were not postprocessed")
         self.failIf(taskmaster.num_failed,
                     "some task(s) failed to execute") 
 
@@ -245,6 +259,8 @@ class NoParallelTestCase(unittest.TestCase):
                             "all the tests were not executed")
             self.failUnless(taskmaster.all_tasks_are_iterated(),
                             "all the tests were not iterated over")
+            self.failUnless(taskmaster.all_tasks_are_postprocessed(),
+                            "all the tests were not postprocessed")
             self.failIf(taskmaster.num_failed,
                         "some task(s) failed to execute") 
         finally:
@@ -265,6 +281,8 @@ class SerialExceptionTestCase(unittest.TestCase):
                     "exactly one task should have been iterated")
         self.failUnless(taskmaster.num_failed == 1,
                     "exactly one task should have failed")
+        self.failUnless(taskmaster.num_postprocessed == 1,
+                    "exactly one task should have been postprocessed")
 
 class ParallelExceptionTestCase(unittest.TestCase):
     def runTest(self):
@@ -280,6 +298,8 @@ class ParallelExceptionTestCase(unittest.TestCase):
                     "one or more task should have been iterated")
         self.failUnless(taskmaster.num_failed >= 1,
                     "one or more tasks should have failed") 
+        self.failUnless(taskmaster.num_postprocessed >= 1,
+                    "one or more tasks should have been postprocessed")
 
 
 def suite():
index 438cd7fd307580a26d3dfa81db420f3b144c09b5..98c7dec9242d46533cf6d165c49cc43ce20f2dcd 100644 (file)
@@ -938,6 +938,8 @@ class DummyExecutor:
     """Dummy executor class returned by Dir nodes to bamboozle SCons
     into thinking we are an actual derived node, where our sources are
     our directory entries."""
+    def cleanup(self):
+        pass
     def get_raw_contents(self):
         return ''
     def get_contents(self):
@@ -1214,11 +1216,12 @@ class File(Base):
         return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
                                   cwd=self.cwd)
 
-    def generate_build_env(self, env):
-        """Generate an appropriate Environment to build this File."""
-        return env.Override({'Dir' : self.Dir,
-                             'File' : self.File,
-                             'RDirs' : self.RDirs})
+    def generate_build_dict(self):
+        """Return an appropriate dictionary of values for building
+        this File."""
+        return {'Dir' : self.Dir,
+                'File' : self.File,
+                'RDirs' : self.RDirs}
 
     def _morph(self):
         """Turn a file system node into a File object."""
@@ -1343,9 +1346,9 @@ class File(Base):
         if b and self.fs.CachePath:
             if self.fs.cache_show:
                 if CacheRetrieveSilent(self, None, None) == 0:
-                    def do_print(action, targets, sources, env, self=self):
+                    def do_print(action, targets, sources, env, s=self):
                         if action.strfunction:
-                            al = action.strfunction(targets, self.sources, env)
+                            al = action.strfunction(targets, s.sources, env)
                             if not SCons.Util.is_List(al):
                                 al = [al]
                             for a in al:
index a84a993f5620ba9ab6cc7209af0edaea6f91221a..35ccead55f42aaddc802ec2ce897e54445e778b0 100644 (file)
@@ -1694,6 +1694,7 @@ class CacheDirTestCase(unittest.TestCase):
 
 class clearTestCase(unittest.TestCase):
     def runTest(self):
+        """Test clearing FS nodes of cached data."""
         fs = SCons.Node.FS.FS()
 
         e = fs.Entry('e')
@@ -1717,6 +1718,20 @@ class clearTestCase(unittest.TestCase):
         assert not hasattr(f, '_exists')
         assert not hasattr(f, '_rexists')
 
+class postprocessTestCase(unittest.TestCase):
+    def runTest(self):
+        """Test calling the postprocess() method."""
+        fs = SCons.Node.FS.FS()
+
+        e = fs.Entry('e')
+        e.postprocess()
+
+        d = fs.Dir('d')
+        d.postprocess()
+
+        f = fs.File('f')
+        f.postprocess()
+
 class SpecialAttrTestCase(unittest.TestCase):
     def runTest(self):
         """Test special attributes of file nodes."""
@@ -1869,6 +1884,7 @@ if __name__ == "__main__":
     suite.addTest(SConstruct_dirTestCase())
     suite.addTest(CacheDirTestCase())
     suite.addTest(clearTestCase())
+    suite.addTest(postprocessTestCase())
     suite.addTest(SpecialAttrTestCase())
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
         sys.exit(1)
index 6b8c8ae704bde9d33d0b8b121e2237c57e822f53..2e053bed90bcf89c56401fe0a3bba41d80c1fb7a 100644 (file)
@@ -925,6 +925,17 @@ class NodeTestCase(unittest.TestCase):
         siginfo = n.get_prevsiginfo()
         assert siginfo == (None, None, None), siginfo
 
+    def test_generate_build_dict(self):
+        """Test the base Node generate_build_dict() method"""
+        n = SCons.Node.Node()
+        dict = n.generate_build_dict()
+        assert dict == {}, dict
+
+    def test_postprocess(self):
+        """Test calling the base Node postprocess() method"""
+        n = SCons.Node.Node()
+        n.postprocess()
+
 
 
 if __name__ == "__main__":
index 4d513709ca9268f27049bd56fffcb8b071b87af0..88aa120ae575af648a51fae6e2b13505644cb8c9 100644 (file)
@@ -135,9 +135,10 @@ class Node:
         # what line in what file created the node, for example).
         Annotate(self)
 
-    def generate_build_env(self, env):
-        """Generate the appropriate Environment to build this node."""
-        return env
+    def generate_build_dict(self):
+        """Return an appropriate dictionary of values for building
+        this Node."""
+        return {}
 
     def get_build_env(self):
         """Fetch the appropriate Environment to build this node."""
@@ -157,10 +158,9 @@ class Node:
             if not create:
                 raise
             import SCons.Executor
-            env = self.generate_build_env(self.builder.env)
             executor = SCons.Executor.Executor(self.builder,
-                                               env,
-                                               self.builder.overrides,
+                                               self.builder.env,
+                                               {},
                                                [self],
                                                self.sources)
             self.executor = executor
@@ -196,10 +196,10 @@ class Node:
         so only do thread safe stuff here. Do thread unsafe stuff in
         built().
         """
-        def do_action(action, targets, sources, env, self=self):
+        def do_action(action, targets, sources, env, s=self):
             stat = action(targets, sources, env)
             if stat:
-                raise SCons.Errors.BuildError(node = self,
+                raise SCons.Errors.BuildError(node = s,
                                               errstr = "Error %d" % stat)
         self._for_each_action(do_action)
 
@@ -225,6 +225,16 @@ class Node:
         # node were presumably just changed:
         self.del_csig()
 
+    def postprocess(self):
+        """Clean up anything we don't need to hang onto after we've
+        been built."""
+        try:
+            executor = self.get_executor(create=None)
+        except AttributeError:
+            pass
+        else:
+            executor.cleanup()
+
     def clear(self):
         """Completely clear a Node of all its cached state (so that it
         can be re-evaluated by interfaces that do continuous integration
index 2028c3bd3456d6b23e83edb7ea27affe1f4429f0..918f4b93dce8e0aba436cffe2b90293f06ba143c 100644 (file)
@@ -176,6 +176,8 @@ class SConfTestCase(unittest.TestCase):
                         return [], None
                     def depends_on(self, nodes):
                         return None
+                    def postprocess(self):
+                        pass
                 return [MyNode('n1'), MyNode('n2')]
         self.scons_env.Append(BUILDERS = {'SConfActionBuilder' : MyBuilder()})
         sconf.TryBuild(self.scons_env.SConfActionBuilder)
index 90e1fe1751fe4fe809202f83c316d354ecb105a6..2fa15103dbfed367db45c1ba0b31da4a984c0cc5 100644 (file)
@@ -270,12 +270,18 @@ def SConscript_exception(file=sys.stderr):
     up to where we exec the SConscript."""
     stack = traceback.extract_tb(sys.exc_traceback)
     last_text = ""
+    found = 0
     i = 0
     for frame in stack:
         if is_our_exec_statement(last_text):
+            found = 1
             break
         i = i + 1
         last_text = frame[3]
+    if not found:
+        # We did not find our exec statement, so this was actually a bug
+        # in SCons itself.  Show the whole stack.
+        i = 0
     type = str(sys.exc_type)
     if type[:11] == "exceptions.":
         type = type[11:]
index 66492ded8110dabc8e67e3f9abf3587e063ae50b..8afd847105f9c178c9d10df4f04982723df5b812 100644 (file)
@@ -181,6 +181,13 @@ class Task:
                     side_effect.set_state(state)
             t.set_state(state)
 
+    def postprocess(self):
+        """Post process a task after it's been executed."""
+        for t in self.targets:
+            t.postprocess()
+
+
+
 def order(dependencies):
     """Re-order a list of dependencies (if we need to)."""
     return dependencies
index 63190a5be30a76668636c48fdbcf86bc06a8f190..d20b805b2de0b96358380a2d7e1560a372d6f7d4 100644 (file)
@@ -53,6 +53,7 @@ class Node:
         self.side_effect = 0
         self.side_effects = []
         self.alttargets = []
+        self.postprocessed = None
 
         for kid in kids:
             kid.parents.append(self)
@@ -137,6 +138,9 @@ class Node:
     def __str__(self):
         return self.name
 
+    def postprocess(self):
+        self.postprocessed = 1
+
 
 class OtherError(Exception):
     pass
@@ -791,6 +795,33 @@ class TaskmasterTestCase(unittest.TestCase):
         else:
             assert 0, "did not catch expected exception"
 
+    def test_postprocess(self):
+        """Test postprocessing targets to give them a chance to clean up
+        
+        """
+        n1 = Node("n1")
+        tm = SCons.Taskmaster.Taskmaster([n1])
+
+        t = tm.next_task()
+        assert not n1.postprocessed
+        t.postprocess()
+        assert n1.postprocessed
+
+        n2 = Node("n2")
+        n3 = Node("n3")
+        tm = SCons.Taskmaster.Taskmaster([n2, n3])
+
+        assert not n2.postprocessed
+        assert not n3.postprocessed
+        t = tm.next_task()
+        t.postprocess()
+        assert n2.postprocessed
+        assert not n3.postprocessed
+        t = tm.next_task()
+        t.postprocess()
+        assert n2.postprocessed
+        assert n3.postprocessed
+
 
 
 if __name__ == "__main__":