Apply Memoizer to cache more return values from various methods. (Kevin Quick)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 31 Dec 2004 01:08:05 +0000 (01:08 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 31 Dec 2004 01:08:05 +0000 (01:08 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1197 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/CHANGES.txt
src/engine/SCons/Environment.py
src/engine/SCons/Memoize.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/Util.py

index 6242524984a6d9894a7a520753a938381038db7d..a35f378106aed0ad5a99ef44985f9d82c551f6b0 100644 (file)
@@ -347,8 +347,8 @@ RELEASE 0.97 - XXX
   - Use the correct scanner if the same source file is used for targets in
     two different environments with the same path but different scanners.
 
-  - Collect logic for caching values in memory in a Memoizer class.
-    This cleans up a lot of special-case code in various methods and
+  - Collect logic for caching values in memory in a Memoizer class,
+    which cleans up a lot of special-case code in various methods and
     caches additional values to speed up most configurations.
 
   From Levi Stephen:
index 2e146e3b65c7c7543e8965e4ac82e06c74966586..8c2e767a714139996b60cf2a76adee23777771c1 100644 (file)
@@ -264,12 +264,14 @@ class SubstitutionEnvironment:
         return cmp(sdict, odict)
 
     def __delitem__(self, key):
+        "__cache_reset__"
         del self._dict[key]
 
     def __getitem__(self, key):
         return self._dict[key]
 
     def __setitem__(self, key, value):
+        "__cache_reset__"
         if key in reserved_construction_var_names:
             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
                                 "Ignoring attempt to set reserved variable `%s'" % key)
@@ -599,6 +601,7 @@ class Base(SubstitutionEnvironment):
     def _update(self, dict):
         """Update an environment's values directly, bypassing the normal
         checks that occur when users try to set items.
+        __cache_reset__
         """
         self._dict.update(dict)
 
@@ -743,7 +746,7 @@ class Base(SubstitutionEnvironment):
         return clone
 
     def Detect(self, progs):
-        """Return the first available program in progs.
+        """Return the first available program in progs.  __cacheable__
         """
         if not SCons.Util.is_List(progs):
             progs = [ progs ]
@@ -1011,7 +1014,7 @@ class Base(SubstitutionEnvironment):
         except KeyError:
             pass
         kw = copy_non_reserved_keywords(kw)
-        self._dict.update(our_deepcopy(kw))
+        self._update(our_deepcopy(kw))
         self.scanner_map_delete(kw)
 
     def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
@@ -1052,7 +1055,7 @@ class Base(SubstitutionEnvironment):
         tool(self)
 
     def WhereIs(self, prog, path=None, pathext=None, reject=[]):
-        """Find prog in the path.  
+        """Find prog in the path.  __cacheable__
         """
         if path is None:
             try:
index ce3d9e6745dd028070b1822434d5c60dc4ba0658..72adbcd7f4e49d6b195be7c1d94116fe20ab0091 100644 (file)
@@ -89,6 +89,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 #TBD: for pickling, should probably revert object to unclassed state...
 
 import copy
+import os
 import string
 import sys
 
@@ -448,6 +449,15 @@ def Analyze_Class(klass):
 # for traceback or profile output, which generate things like 'File
 # "<string>", line X'.  X will be the number of \n's plus 1.
 
+# Also use the following routine to specify the "filename" portion so
+# that it provides useful information.  In addition, make sure it
+# contains 'os.sep + "SCons" + os.sep' for the
+# SCons.Script.find_deepest_user_frame operation.
+
+def whoami(memoizer_funcname, real_funcname):
+    return '...'+os.sep+'SCons'+os.sep+'Memoizer-'+ \
+           memoizer_funcname+'-lambda<'+real_funcname+'>'
+
 def memoize_classdict(modelklass, new_klassdict, cacheable, resetting):
     new_klassdict.update(modelklass.__dict__)
     new_klassdict['_MeMoIZeR_converted'] = 1
@@ -457,35 +467,39 @@ def memoize_classdict(modelklass, new_klassdict, cacheable, resetting):
                not code.func_code.co_flags & 0xC:
             newmethod = eval(
                 compile("\n"*1 +
-                "lambda self: Memoizer_cache_get_self(methcode, methcached, self)",
-                        "Memoizer_cache_get_self_lambda",
+                        "lambda self: MCGS(methcode, methcached, self)",
+                        whoami('cache_get_self', name),
                         "eval"),
                 {'methcode':code, 'methcached':{},
-                 'Memoizer_cache_get_self':Memoizer_cache_get_self},
+                 'MCGS':Memoizer_cache_get_self},
                 {})
         elif code.func_code.co_argcount == 2 and \
                not code.func_code.co_flags & 0xC:
             newmethod = eval(
                 compile("\n"*2 +
-                "lambda self, arg: Memoizer_cache_get_one(methcode, methcached, self, arg)",
-                        "Memoizer_cache_get_one_lambda",
+                "lambda self, arg: MCGO(methcode, methcached, self, arg)",
+                        whoami('cache_get_one', name),
                         "eval"),
                 {'methcode':code, 'methcached':{},
-                 'Memoizer_cache_get_one':Memoizer_cache_get_one},
+                 'MCGO':Memoizer_cache_get_one},
                 {})
         else:
             newmethod = eval(
                 compile("\n"*3 +
-                "lambda *args, **kw: Memoizer_cache_get(methcode, methcached, args, kw)",
-                        "Memoizer_cache_get_lambda",
+                "lambda *args, **kw: MCG(methcode, methcached, args, kw)",
+                        whoami('cache_get', name),
                         "eval"),
                 {'methcode':code, 'methcached':{},
-                 'Memoizer_cache_get':Memoizer_cache_get}, {})
+                 'MCG':Memoizer_cache_get}, {})
         new_klassdict[name] = newmethod
 
     for name,code in resetting.items():
-        newmethod = eval("lambda obj_self, *args, **kw: (obj_self._MeMoIZeR_reset(), apply(rmethcode, (obj_self,)+args, kw))[1]",
-                         {'rmethcode':code}, {})
+        newmethod = eval(
+            compile(
+            "lambda obj_self, *args, **kw: (obj_self._MeMoIZeR_reset(), apply(rmethcode, (obj_self,)+args, kw))[1]",
+            whoami('cache_reset', name),
+            'eval'),
+            {'rmethcode':code}, {})
         new_klassdict[name] = newmethod
 
     return new_klassdict
@@ -667,7 +681,8 @@ else:
             newinitcode = compile(
                 "\n"*(init.func_code.co_firstlineno-1) +
                 "lambda self, args, kw: _MeMoIZeR_init(real_init, self, args, kw)",
-                init.func_code.co_filename, 'eval')
+                whoami('init', init.func_code.co_filename),
+                'eval')
             newinit = eval(newinitcode,
                            {'real_init':init,
                             '_MeMoIZeR_init':_MeMoIZeR_init},
index a67fa76cf262972b9971c0c12c09290089e4876e..1f2b0a860358900cd032ff75dc586273799cf188 100644 (file)
@@ -630,11 +630,11 @@ class Entry(Base):
         if self.fs.isfile(self.abspath):
             self.__class__ = File
             self._morph()
-            return File.get_contents(self)
+            return self.get_contents()
         if self.fs.isdir(self.abspath):
             self.__class__ = Dir
             self._morph()
-            return Dir.get_contents(self)
+            return self.get_contents()
         if self.fs.islink(self.abspath):
             return ''             # avoid errors for dangling symlinks
         raise AttributeError
@@ -679,6 +679,9 @@ _classEntry = Entry
 
 
 class LocalFS:
+
+    __metaclass__ = SCons.Memoize.Memoized_Metaclass
+    
     # This class implements an abstraction layer for operations involving
     # a local file system.  Essentially, this wraps any function in
     # the os, os.path or shutil modules that we use to actually go do
@@ -736,6 +739,14 @@ class LocalFS:
             return 0                    # no symlinks
         exists_or_islink = exists
 
+if not SCons.Memoize.has_metaclass:
+    _FSBase = LocalFS
+    class LocalFS(SCons.Memoize.Memoizer, _FSBase):
+        def __init__(self, *args, **kw):
+            apply(_FSBase.__init__, (self,)+args, kw)
+            SCons.Memoize.Memoizer.__init__(self)
+
+
 #class RemoteFS:
 #    # Skeleton for the obvious methods we might need from the
 #    # abstraction layer for a remote filesystem.
@@ -746,6 +757,7 @@ class LocalFS:
 
 
 class FS(LocalFS):
+
     def __init__(self, path = None):
         """Initialize the Node.FS subsystem.
 
@@ -771,6 +783,10 @@ class FS(LocalFS):
         assert not self.Top, "You can only set the top-level path on an FS object that has not had its File, Dir, or Entry methods called yet."
         self.pathTop = path
 
+    def clear_cache(self):
+        "__cache_reset__"
+        pass
+    
     def set_SConstruct_dir(self, dir):
         self.SConstruct_dir = dir
         
@@ -800,7 +816,8 @@ class FS(LocalFS):
         In this method, if directory is None or not supplied, the supplied
         name is expected to be an absolute path.  If you try to look up a
         relative path with directory=None, then an AssertionError will be
-        raised."""
+        raised.
+        __cacheable__"""
 
         if not name:
             # This is a stupid hack to compensate for the fact
@@ -996,7 +1013,9 @@ class FS(LocalFS):
 
     def Rsearch(self, path, clazz=_classEntry, cwd=None):
         """Search for something in a Repository.  Returns the first
-        one found in the list, or None if there isn't one."""
+        one found in the list, or None if there isn't one.
+        __cacheable__
+        """
         if isinstance(path, SCons.Node.Node):
             return path
         else:
@@ -1036,7 +1055,9 @@ class FS(LocalFS):
         return None
 
     def Rsearchall(self, pathlist, must_exist=1, clazz=_classEntry, cwd=None):
-        """Search for a list of somethings in the Repository list."""
+        """Search for a list of somethings in the Repository list.
+        __cacheable__
+        """
         ret = []
         if SCons.Util.is_String(pathlist):
             pathlist = string.split(pathlist, os.pathsep)
@@ -1091,6 +1112,7 @@ class FS(LocalFS):
 
         Climb the directory tree, and look up path names
         relative to any linked build directories we find.
+        __cacheable__
         """
         targets = []
         message = None
@@ -1109,6 +1131,7 @@ class FS(LocalFS):
             message = fmt % string.join(map(str, targets))
         return targets, message
 
+
 class Dir(Base):
     """A class for directories in a file system.
     """
@@ -1415,6 +1438,7 @@ class File(Base):
         self.dir.sconsign().set_entry(self.name, entry)
 
     def get_stored_info(self):
+        "__cacheable__"
         try:
             stored = self.dir.sconsign().get_entry(self.name)
         except (KeyError, OSError):
@@ -1543,6 +1567,7 @@ class File(Base):
         # will do if this file has a source scanner.
         if self.fs.CachePath and self.fs.exists(self.path):
             CachePush(self, [], None)
+        self.fs.clear_cache()
         SCons.Node.Node.built(self)
 
     def visited(self):
@@ -1594,6 +1619,7 @@ class File(Base):
         return self.fs.build_dir_target_climb(self, self.dir, [self.name])
 
     def is_pseudo_derived(self):
+        "__cacheable__"
         return self.has_src_builder()
 
     def _rmv_existing(self):
@@ -1712,6 +1738,9 @@ class File(Base):
 
     def current(self, calc=None):
         self.binfo = self.gen_binfo(calc)
+        return self._cur2()
+    def _cur2(self):
+        "__cacheable__"
         if self.always_build:
             return None
         if not self.exists():
index e239e93d45b275b4942fd2f6009a3ccab41234d3..7b443abcfae052c3d3c2e9a5f579cc6e83509cdf 100644 (file)
@@ -286,7 +286,7 @@ class Node:
         pass
 
     def depends_on(self, nodes):
-        """Does this node depend on any of 'nodes'?"""
+        """Does this node depend on any of 'nodes'? __cacheable__"""
         return reduce(lambda D,N,C=self.children(): D or (N in C), nodes, 0)
 
     def builder_set(self, builder):
@@ -342,6 +342,7 @@ class Node:
         signatures when they are used as source files to other derived files. For
         example: source with source builders are not derived in this sense,
         and hence should not return true.
+        __cacheable__
         """
         return self.has_builder() or self.side_effect
 
@@ -564,6 +565,7 @@ class Node:
         node's children's signatures.  We expect that they're
         already built and updated by someone else, if that's
         what's wanted.
+        __cacheable__
         """
 
         if calc is None:
index 404ded1a481a3e09305bd1c2bd05f617436edca4..178479848cd2ae95ed2af6107c45c412e74f8695 100644 (file)
@@ -201,10 +201,7 @@ class Task:
 
         This is the default behavior for building only what's necessary.
         """
-        self.out_of_date = []
-        for t in self.targets:
-            if not t.current():
-                self.out_of_date.append(t)
+        self.out_of_date = filter(lambda T: not T.current(), self.targets)
         if self.out_of_date:
             self.mark_targets_and_side_effects(SCons.Node.executing)
         else:
index 25713a8403fe3523782fb5ebc04c5a40d434eca4..08977cbf36a2d6a0683a7204070e4c3cd77249c0 100644 (file)
@@ -667,7 +667,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
             """Substitute expansions in an argument or list of arguments.
 
             This serves as a wrapper for splitting up a string into
-            separate tokens.
+            separate tokens.  __cacheable__
             """
             if is_String(args) and not isinstance(args, CmdStringHolder):
                 try:
@@ -1274,95 +1274,103 @@ if can_read_reg:
 
 if sys.platform == 'win32':
 
-    def WhereIs(file, path=None, pathext=None, reject=[]):
-        if path is None:
-            try:
-                path = os.environ['PATH']
-            except KeyError:
-                return None
-        if is_String(path):
-            path = string.split(path, os.pathsep)
-        if pathext is None:
-            try:
-                pathext = os.environ['PATHEXT']
-            except KeyError:
-                pathext = '.COM;.EXE;.BAT;.CMD'
-        if is_String(pathext):
-            pathext = string.split(pathext, os.pathsep)
-        for ext in pathext:
-            if string.lower(ext) == string.lower(file[-len(ext):]):
-                pathext = ['']
-                break
-        if not is_List(reject):
-            reject = [reject]
-        for dir in path:
-            f = os.path.join(dir, file)
+    class _WhereIs:
+        def __call__(self, file, path=None, pathext=None, reject=[]):
+            "__cacheable__"
+            if path is None:
+                try:
+                    path = os.environ['PATH']
+                except KeyError:
+                    return None
+            if is_String(path):
+                path = string.split(path, os.pathsep)
+            if pathext is None:
+                try:
+                    pathext = os.environ['PATHEXT']
+                except KeyError:
+                    pathext = '.COM;.EXE;.BAT;.CMD'
+            if is_String(pathext):
+                pathext = string.split(pathext, os.pathsep)
             for ext in pathext:
-                fext = f + ext
-                if os.path.isfile(fext):
-                    try:
-                        reject.index(fext)
-                    except ValueError:
-                        return os.path.normpath(fext)
-                    continue
-        return None
+                if string.lower(ext) == string.lower(file[-len(ext):]):
+                    pathext = ['']
+                    break
+            if not is_List(reject):
+                reject = [reject]
+            for dir in path:
+                f = os.path.join(dir, file)
+                for ext in pathext:
+                    fext = f + ext
+                    if os.path.isfile(fext):
+                        try:
+                            reject.index(fext)
+                        except ValueError:
+                            return os.path.normpath(fext)
+                        continue
+            return None
 
 elif os.name == 'os2':
 
-    def WhereIs(file, path=None, pathext=None, reject=[]):
-        if path is None:
-            try:
-                path = os.environ['PATH']
-            except KeyError:
-                return None
-        if is_String(path):
-            path = string.split(path, os.pathsep)
-        if pathext is None:
-            pathext = ['.exe', '.cmd']
-        for ext in pathext:
-            if string.lower(ext) == string.lower(file[-len(ext):]):
-                pathext = ['']
-                break
-        if not is_List(reject):
-            reject = [reject]
-        for dir in path:
-            f = os.path.join(dir, file)
+    class _WhereIs:
+        def __call__(self, file, path=None, pathext=None, reject=[]):
+            "__cacheable__"
+            if path is None:
+                try:
+                    path = os.environ['PATH']
+                except KeyError:
+                    return None
+            if is_String(path):
+                path = string.split(path, os.pathsep)
+            if pathext is None:
+                pathext = ['.exe', '.cmd']
             for ext in pathext:
-                fext = f + ext
-                if os.path.isfile(fext):
-                    try:
-                        reject.index(fext)
-                    except ValueError:
-                        return os.path.normpath(fext)
-                    continue
-        return None
+                if string.lower(ext) == string.lower(file[-len(ext):]):
+                    pathext = ['']
+                    break
+            if not is_List(reject):
+                reject = [reject]
+            for dir in path:
+                f = os.path.join(dir, file)
+                for ext in pathext:
+                    fext = f + ext
+                    if os.path.isfile(fext):
+                        try:
+                            reject.index(fext)
+                        except ValueError:
+                            return os.path.normpath(fext)
+                        continue
+            return None
 
 else:
 
-    def WhereIs(file, path=None, pathext=None, reject=[]):
-        if path is None:
-            try:
-                path = os.environ['PATH']
-            except KeyError:
-                return None
-        if is_String(path):
-            path = string.split(path, os.pathsep)
-        if not is_List(reject):
-            reject = [reject]
-        for d in path:
-            f = os.path.join(d, file)
-            if os.path.isfile(f):
+    class _WhereIs:
+        def __call__(self, file, path=None, pathext=None, reject=[]):
+            "__cacheable__"
+            if path is None:
                 try:
-                    st = os.stat(f)
-                except OSError:
-                    continue
-                if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
+                    path = os.environ['PATH']
+                except KeyError:
+                    return None
+            if is_String(path):
+                path = string.split(path, os.pathsep)
+            if not is_List(reject):
+                reject = [reject]
+            for d in path:
+                f = os.path.join(d, file)
+                if os.path.isfile(f):
                     try:
-                        reject.index(f)
-                    except ValueError:
-                        return os.path.normpath(f)
-                    continue
-        return None
+                        st = os.stat(f)
+                    except OSError:
+                        continue
+                    if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
+                        try:
+                            reject.index(f)
+                        except ValueError:
+                            return os.path.normpath(f)
+                        continue
+            return None
+
+WhereIs = _WhereIs()
 
 def PrependPath(oldpath, newpath, sep = os.pathsep):
     """This prepends newpath elements to the given oldpath.  Will only