When searching paths for Files or Dirs, match Entries,too.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 16 Nov 2005 09:11:10 +0000 (09:11 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 16 Nov 2005 09:11:10 +0000 (09:11 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1389 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py

index 883b82c9037a600d8f81ddeefd0746bbd7e7ebbd..b7d7af9a7c6a39d1a932347429b95ddfdb6470be 100644 (file)
@@ -143,7 +143,7 @@ def LinkFunc(target, source, env):
     src = source[0].abspath
     dest = target[0].abspath
     dir, file = os.path.split(dest)
-    if dir and not os.path.isdir(dir):
+    if dir and not target[0].fs.isdir(dir):
         os.makedirs(dir)
     if not Link_Funcs:
         # Set a default order of link functions.
@@ -263,7 +263,8 @@ def get_DefaultSCCSBuilder():
         import SCons.Builder
         # "env" will get filled in by Executor.get_build_env()
         # calling SCons.Defaults.DefaultEnvironment() when necessary.
-        DefaultSCCSBuilder = SCons.Builder.Builder(action = '$SCCSCOM',
+        act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
+        DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
                                                    env = None,
                                                    name = "DefaultSCCSBuilder")
     return DefaultSCCSBuilder
@@ -274,7 +275,8 @@ def get_DefaultRCSBuilder():
         import SCons.Builder
         # "env" will get filled in by Executor.get_build_env()
         # calling SCons.Defaults.DefaultEnvironment() when necessary.
-        DefaultRCSBuilder = SCons.Builder.Builder(action = '$RCS_COCOM',
+        act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
+        DefaultRCSBuilder = SCons.Builder.Builder(action = act,
                                                   env = None,
                                                   name = "DefaultRCSBuilder")
     return DefaultRCSBuilder
@@ -417,6 +419,12 @@ class EntryProxy(SCons.Util.Proxy):
             except AttributeError:
                 entry = self.get()
                 classname = string.split(str(entry.__class__), '.')[-1]
+                if classname[-2:] == "'>":
+                    # new-style classes report their name as:
+                    #   "<class 'something'>"
+                    # instead of the classic classes:
+                    #   "something"
+                    classname = classname[:-2]
                 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
             return attr
 
@@ -445,7 +453,6 @@ class Base(SCons.Node.Node):
 
         self.name = name
         self.fs = fs
-        self.relpath = {self : '.'}
 
         assert directory, "A directory must be provided"
 
@@ -463,26 +470,15 @@ class Base(SCons.Node.Node):
         """Completely clear a Node.FS.Base object of all its cached
         state (so that it can be re-evaluated by interfaces that do
         continuous integration builds).
+        __cache_reset__
         """
         SCons.Node.Node.clear(self)
-        try:
-            delattr(self, '_exists')
-        except AttributeError:
-            pass
-        try:
-            delattr(self, '_rexists')
-        except AttributeError:
-            pass
-        try:
-            delattr(self, '_str_val')
-        except AttributeError:
-            pass
-        self.relpath = {self : '.'}
 
     def get_dir(self):
         return self.dir
 
     def get_suffix(self):
+        "__cacheable__"
         return SCons.Util.splitext(self.name)[1]
 
     def rfile(self):
@@ -491,33 +487,29 @@ class Base(SCons.Node.Node):
     def __str__(self):
         """A Node.FS.Base object's string representation is its path
         name."""
-        try:
-            return self._str_val
-        except AttributeError:
-            global Save_Strings
-            if self.duplicate or self.is_derived():
-                str_val = self.get_path()
-            else:
-                str_val = self.srcnode().get_path()
-            if Save_Strings:
-                self._str_val = str_val
-            return str_val
+        global Save_Strings
+        if Save_Strings:
+            return self._save_str()
+        return self._get_str()
+
+    def _save_str(self):
+        "__cacheable__"
+        return self._get_str()
+
+    def _get_str(self):
+        if self.duplicate or self.is_derived():
+            return self.get_path()
+        return self.srcnode().get_path()
 
     rstr = __str__
 
     def exists(self):
-        try:
-            return self._exists
-        except AttributeError:
-            self._exists = self.fs.exists(self.abspath)
-            return self._exists
+        "__cacheable__"
+        return self.fs.exists(self.abspath)
 
     def rexists(self):
-        try:
-            return self._rexists
-        except AttributeError:
-            self._rexists = self.rfile().exists()
-            return self._rexists
+        "__cacheable__"
+        return self.rfile().exists()
 
     def is_under(self, dir):
         if self is dir:
@@ -531,39 +523,35 @@ class Base(SCons.Node.Node):
     def srcnode(self):
         """If this node is in a build path, return the node
         corresponding to its source file.  Otherwise, return
-        ourself."""
-        try:
-            return self._srcnode
-        except AttributeError:
-            dir=self.dir
-            name=self.name
-            while dir:
-                if dir.srcdir:
-                    self._srcnode = self.fs.Entry(name, dir.srcdir,
-                                                  klass=self.__class__)
-                    return self._srcnode
-                name = dir.name + os.sep + name
-                dir=dir.get_dir()
-            self._srcnode = self
-            return self._srcnode
+        ourself.
+        __cacheable__"""
+        dir=self.dir
+        name=self.name
+        while dir:
+            if dir.srcdir:
+                srcnode = self.fs.Entry(name, dir.srcdir,
+                                        klass=self.__class__)
+                return srcnode
+            name = dir.name + os.sep + name
+            dir=dir.get_dir()
+        return self
 
     def get_path(self, dir=None):
         """Return path relative to the current working directory of the
         Node.FS.Base object that owns us."""
         if not dir:
             dir = self.fs.getcwd()
-        try:
-            return self.relpath[dir]
-        except KeyError:
-            path_elems = []
-            d = self
+        path_elems = []
+        d = self
+        if d == dir:
+            path_elems.append('.')
+        else:
             while d != dir and not isinstance(d, ParentOfRoot):
                 path_elems.append(d.name)
                 d = d.dir
             path_elems.reverse()
-            ret = string.join(path_elems, os.sep)
-            self.relpath[dir] = ret
-            return ret
+        ret = string.join(path_elems, os.sep)
+        return ret
             
     def set_src_builder(self, builder):
         """Set the source code builder for this node."""
@@ -611,6 +599,16 @@ class Entry(Base):
     time comes, and then call the same-named method in the transformed
     class."""
 
+    def disambiguate(self):
+        if self.fs.isdir(self.abspath):
+            self.__class__ = Dir
+            self._morph()
+        else:
+            self.__class__ = File
+            self._morph()
+            self.clear()
+        return self
+
     def rfile(self):
         """We're a generic Entry, but the caller is actually looking for
         a File at this point, so morph into one."""
@@ -619,11 +617,10 @@ class Entry(Base):
         self.clear()
         return File.rfile(self)
 
-    def get_found_includes(self, env, scanner, target):
+    def get_found_includes(self, env, scanner, path):
         """If we're looking for included files, it's because this Entry
         is really supposed to be a File itself."""
-        node = self.rfile()
-        return node.get_found_includes(env, scanner, target)
+        return self.disambiguate().get_found_includes(env, scanner, path)
 
     def scanner_key(self):
         return self.get_suffix()
@@ -637,11 +634,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
@@ -650,35 +647,20 @@ class Entry(Base):
         """Return if the Entry exists.  Check the file system to see
         what we should turn into first.  Assume a file if there's no
         directory."""
-        if self.fs.isdir(self.abspath):
-            self.__class__ = Dir
-            self._morph()
-            return Dir.exists(self)
-        else:
-            self.__class__ = File
-            self._morph()
-            self.clear()
-            return File.exists(self)
+        return self.disambiguate().exists()
 
     def calc_signature(self, calc=None):
         """Return the Entry's calculated signature.  Check the file
         system to see what we should turn into first.  Assume a file if
         there's no directory."""
-        if self.fs.isdir(self.abspath):
-            self.__class__ = Dir
-            self._morph()
-            return Dir.calc_signature(self, calc)
-        else:
-            self.__class__ = File
-            self._morph()
-            self.clear()
-            return File.calc_signature(self, calc)
+        return self.disambiguate().calc_signature(calc)
 
     def must_be_a_Dir(self):
         """Called to make sure a Node is a Dir.  Since we're an
         Entry, we can morph into one."""
         self.__class__ = Dir
         self._morph()
+        return self
 
 # This is for later so we can differentiate between Entry the class and Entry
 # the method of the FS class.
@@ -686,6 +668,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
@@ -743,6 +728,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.
@@ -753,6 +746,7 @@ class LocalFS:
 
 
 class FS(LocalFS):
+
     def __init__(self, path = None):
         """Initialize the Node.FS subsystem.
 
@@ -762,7 +756,7 @@ class FS(LocalFS):
 
         The path argument must be a valid absolute path.
         """
-        if __debug__: logInstanceCreation(self)
+        if __debug__: logInstanceCreation(self, 'Node.FS')
         self.Top = None
         if path == None:
             self.pathTop = os.getcwd()
@@ -778,12 +772,16 @@ 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
         
     def __setTopLevelDir(self):
         if not self.Top:
-            self.Top = self.__doLookup(Dir, os.path.normpath(self.pathTop))
+            self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
             self.Top.path = '.'
             self._cwd = self.Top
         
@@ -801,13 +799,14 @@ class FS(LocalFS):
         raise TypeError, "Tried to lookup %s '%s' as a %s." % \
               (node.__class__.__name__, node.path, klass.__name__)
         
-    def __doLookup(self, fsclass, name, directory = None, create = 1):
+    def _doLookup(self, fsclass, name, directory = None, create = 1):
         """This method differs from the File and Dir factory methods in
         one important way: the meaning of the directory parameter.
         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
@@ -861,8 +860,7 @@ class FS(LocalFS):
                 directory.add_wkid(dir_temp)
                 directory = dir_temp
             else:
-                d.must_be_a_Dir()
-                directory = d
+                directory = d.must_be_a_Dir()
 
         entry_norm = _my_normcase(path_comp[-1])
         try:
@@ -946,7 +944,7 @@ class FS(LocalFS):
             if directory and not isinstance(directory, Dir):
                 directory = self.Dir(directory)
             name, directory = self.__transformPath(name, directory)
-            return self.__doLookup(klass, name, directory, create)
+            return self._doLookup(klass, name, directory, create)
     
     def File(self, name, directory = None, create = 1):
         """Lookup or create a File node with the specified name.  If
@@ -1001,130 +999,143 @@ class FS(LocalFS):
             self.__setTopLevelDir()
             self.Top.addRepository(d)
 
-    def Rsearch(self, path, clazz=_classEntry, cwd=None):
+    def do_Rsearch(self, path, dir, func, clazz=_classEntry):
         """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__
+        """
+        d, name = os.path.split(path)
+        norm_name = _my_normcase(name)
+        if d:
+            dir = dir.Dir(d)
+        try:
+            node = dir.entries[norm_name]
+        except KeyError:
+            node = dir.node_on_disk(name, clazz)
+        else:
+            node = func(node)
+            if node:
+                dir = node.get_dir()
+        if node:
+            return node, dir
+        fname = '.'
+        while dir:
+            for rep in dir.getRepositories():
+                rdir = rep.Dir(fname)
+                try:
+                    node = rdir.entries[norm_name]
+                except KeyError:
+                    node = rdir.node_on_disk(name, clazz)
+                else:
+                    node = func(node)
+                if node:
+                    return node, dir
+            fname = dir.name + os.sep + fname
+            dir = dir.get_dir()
+        return None, None
+
+    def Rsearch(self, path, clazz=_classEntry, cwd=None):
         if isinstance(path, SCons.Node.Node):
             return path
-        else:
-            name, d = self.__transformPath(path, cwd)
-            n = self.__doLookup(clazz, name, d)
-            if n.exists():
-                return n
-            if isinstance(n, Dir):
-                # If n is a Directory that has Repositories directly
-                # attached to it, then any of those is a valid Repository
-                # path.  Return the first one that exists.
-                reps = filter(lambda x: x.exists(), n.getRepositories())
-                if len(reps):
-                    return reps[0]
-            d = n.get_dir()
-            name = n.name
-            # Search repositories of all directories that this file is under.
-            while d:
-                for rep in d.getRepositories():
-                    try:
-                        rnode = self.__doLookup(clazz, name, rep)
-                        # Only find the node if it exists and it is not
-                       # a derived file.  If for some reason, we are
-                       # explicitly building a file IN a Repository, we
-                       # don't want it to show up in the build tree.
-                       # This is usually the case with BuildDir().
-                       # We only want to find pre-existing files.
-                        if rnode.exists() and \
-                           (isinstance(rnode, Dir) or not rnode.is_derived()):
-                            return rnode
-                    except TypeError:
-                        pass # Wrong type of node.
-                # Prepend directory name
-                name = d.name + os.sep + name
-                # Go up one directory
-                d = d.get_dir()
-        return None
+        def func(node, clazz=clazz):
+            if node.exists() and \
+               (isinstance(node, clazz) or isinstance(node, Entry) \
+                or not node.is_derived()):
+                   return node
+            return None
+        path, dir = self.__transformPath(path, cwd)
+        return self.do_Rsearch(path, dir, func, clazz)[0]
 
     def Rsearchall(self, pathlist, must_exist=1, clazz=_classEntry, cwd=None):
-        """Search for a list of somethings in the Repository list."""
-        ret = []
+        """Search for a list of somethings in the Repository list.
+        __cacheable__
+        """
+        result = []
         if SCons.Util.is_String(pathlist):
             pathlist = string.split(pathlist, os.pathsep)
         if not SCons.Util.is_List(pathlist):
             pathlist = [pathlist]
+
+        if must_exist:
+            select = lambda x, clazz=clazz: isinstance(x, clazz) and x.exists()
+        else:
+            select = lambda x, clazz=clazz: isinstance(x, clazz)
+
         for path in filter(None, pathlist):
             if isinstance(path, SCons.Node.Node):
-                ret.append(path)
+                result.append(path)
+                continue
+
+            path, dir = self.__transformPath(path, cwd)
+            d, name = os.path.split(path)
+            norm_name = _my_normcase(name)
+            if d:
+                dir = dir.Dir(d)
+            try:
+                node = dir.entries[norm_name]
+            except KeyError:
+                # If there's no Node on disk, we'll filter
+                # out the returned None below.
+                if must_exist:
+                    n = dir.node_on_disk(name, clazz)
+                else:
+                    n = self._doLookup(clazz, name, dir)
+                    dir.srcdir_duplicate(name, clazz)
+                result.append(n)
             else:
-                name, d = self.__transformPath(path, cwd)
-                n = self.__doLookup(clazz, name, d)
-                if not must_exist or n.exists():
-                    ret.append(n)
-                if isinstance(n, Dir):
-                    # If this node is a directory, then any repositories
-                    # attached to this node can be repository paths.
-                    ret.extend(filter(lambda x, me=must_exist, clazz=clazz: isinstance(x, clazz) and (not me or x.exists()),
-                                      n.getRepositories()))
-                    
-                d = n.get_dir()
-                name = n.name
-                # Search repositories of all directories that this file
-                # is under.
-                while d:
-                    for rep in d.getRepositories():
-                        try:
-                            rnode = self.__doLookup(clazz, name, rep)
-                            # Only find the node if it exists (or
-                            # must_exist is zero) and it is not a
-                            # derived file.  If for some reason, we
-                            # are explicitly building a file IN a
-                            # Repository, we don't want it to show up in
-                            # the build tree.  This is usually the case
-                            # with BuildDir().  We only want to find
-                            # pre-existing files.
-                            if (not must_exist or rnode.exists()) and \
-                               (not rnode.is_derived() or isinstance(rnode, Dir)):
-                                ret.append(rnode)
-                        except TypeError:
-                            pass # Wrong type of node.
-                    # Prepend directory name
-                    name = d.name + os.sep + name
-                    # Go up one directory
-                    d = d.get_dir()
-        return ret
+                if not must_exist or node.exists():
+                    result.append(node)
+            fname = '.'
+            while dir:
+                for rep in dir.getRepositories():
+                    rdir = rep.Dir(fname)
+                    try:
+                        node = rdir.entries[norm_name]
+                    except KeyError:
+                        # If there's no Node on disk, we'll filter
+                        # out the returned None below.
+                        if must_exist:
+                            n = rdir.node_on_disk(name, clazz)
+                        else:
+                            n = self._doLookup(clazz, name, rdir)
+                            rdir.srcdir_duplicate(name, clazz)
+                        result.append(n)
+                    else:
+                        if (not must_exist or node.exists()) and \
+                           (isinstance(node, Dir) or not node.is_derived()):
+                            result.append(node)
+                fname = dir.name + os.sep + fname
+                dir = dir.get_dir()
+
+        return filter(None, result)
 
     def CacheDir(self, path):
         self.CachePath = path
 
-    def build_dir_target_climb(self, dir, tail):
+    def build_dir_target_climb(self, orig, dir, tail):
         """Create targets in corresponding build directories
 
         Climb the directory tree, and look up path names
         relative to any linked build directories we find.
+        __cacheable__
         """
         targets = []
         message = None
+        fmt = "building associated BuildDir targets: %s"
+        start_dir = dir
         while dir:
             for bd in dir.build_dirs:
+                if start_dir.is_under(bd):
+                    # If already in the build-dir location, don't reflect
+                    return [orig], fmt % str(orig)
                 p = apply(os.path.join, [bd.path] + tail)
                 targets.append(self.Entry(p))
             tail = [dir.name] + tail
             dir = dir.up()
         if targets:
-            message = "building associated BuildDir targets: %s" % string.join(map(str, targets))
+            message = fmt % string.join(map(str, targets))
         return targets, message
 
-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):
-        return ''
-    def get_timestamp(self):
-        return 0
-    def get_build_env(self):
-        return None
 
 class Dir(Base):
     """A class for directories in a file system.
@@ -1141,7 +1152,8 @@ class Dir(Base):
 
         Set up this directory's entries and hook it into the file
         system tree.  Specify that directories (this Node) don't use
-        signatures for calculating whether they're current."""
+        signatures for calculating whether they're current.
+        __cache_reset__"""
 
         self.repositories = []
         self.srcdir = None
@@ -1155,6 +1167,9 @@ class Dir(Base):
         self._sconsign = None
         self.build_dirs = []
 
+    def disambiguate(self):
+        return self
+
     def __clearRepositoryCache(self, duplicate=None):
         """Called when we change the repository(ies) for a directory.
         This clears any cached information that is invalidated by changing
@@ -1165,30 +1180,11 @@ class Dir(Base):
                 if node != self and isinstance(node, Dir):
                     node.__clearRepositoryCache(duplicate)
                 else:
+                    node.clear()
                     try:
                         del node._srcreps
                     except AttributeError:
                         pass
-                    try:
-                        del node._rfile
-                    except AttributeError:
-                        pass
-                    try:
-                        del node._rexists
-                    except AttributeError:
-                        pass
-                    try:
-                        del node._exists
-                    except AttributeError:
-                        pass
-                    try:
-                        del node._srcnode
-                    except AttributeError:
-                        pass
-                    try:
-                        del node._str_val
-                    except AttributeError:
-                        pass
                     if duplicate != None:
                         node.duplicate=duplicate
     
@@ -1250,19 +1246,33 @@ class Dir(Base):
         self.implicit = []
         self.implicit_dict = {}
         self._children_reset()
-        try:
-            for filename in self.fs.listdir(self.abspath):
-                if filename != '.sconsign':
-                    self.Entry(filename)
-        except OSError:
-            # Directory does not exist.  No big deal
-            pass
-        keys = filter(lambda k: k != '.' and k != '..', self.entries.keys())
-        kids = map(lambda x, s=self: s.entries[x], keys)
-        def c(one, two):
-            return cmp(one.abspath, two.abspath)
-        kids.sort(c)
-        self._add_child(self.implicit, self.implicit_dict, kids)
+
+        dont_scan = lambda k: k not in ['.', '..', '.sconsign']
+        deps = filter(dont_scan, self.entries.keys())
+        # keys() is going to give back the entries in an internal,
+        # unsorted order.  Sort 'em so the order is deterministic.
+        deps.sort()
+        entries = map(lambda n, e=self.entries: e[n], deps)
+
+        self._add_child(self.implicit, self.implicit_dict, entries)
+
+    def get_found_includes(self, env, scanner, path):
+        """Return the included implicit dependencies in this file.
+        Cache results so we only scan the file once per path
+        regardless of how many times this information is requested.
+        __cacheable__"""
+        if not scanner:
+            return []
+        # Clear cached info for this Node.  If we already visited this
+        # directory on our walk down the tree (because we didn't know at
+        # that point it was being used as the source for another Node)
+        # then we may have calculated build signature before realizing
+        # we had to scan the disk.  Now that we have to, though, we need
+        # to invalidate the old calculated signature so that any node
+        # dependent on our directory structure gets one that includes
+        # info about everything on disk.
+        self.clear()
+        return scanner(self, env, path)
 
     def build(self, **kw):
         """A null "builder" for directories."""
@@ -1270,6 +1280,36 @@ class Dir(Base):
         if not self.builder is MkdirBuilder:
             apply(SCons.Node.Node.build, [self,], kw)
 
+    def _create(self):
+        """Create this directory, silently and without worrying about
+        whether the builder is the default or not."""
+        listDirs = []
+        parent = self
+        while parent:
+            if parent.exists():
+                break
+            listDirs.append(parent)
+            p = parent.up()
+            if isinstance(p, ParentOfRoot):
+                raise SCons.Errors.StopError, parent.path
+            parent = p
+        listDirs.reverse()
+        for dirnode in listDirs:
+            try:
+                # Don't call dirnode.build(), call the base Node method
+                # directly because we definitely *must* create this
+                # directory.  The dirnode.build() method will suppress
+                # the build if it's the default builder.
+                SCons.Node.Node.build(dirnode)
+                dirnode.get_executor().nullify()
+                # The build() action may or may not have actually
+                # created the directory, depending on whether the -n
+                # option was used or not.  Delete the _exists and
+                # _rexists attributes so they can be reevaluated.
+                dirnode.clear()
+            except OSError:
+                pass
+
     def multiple_side_effect_has_builder(self):
         global MkdirBuilder
         return not self.builder is MkdirBuilder and self.has_builder()
@@ -1277,7 +1317,7 @@ class Dir(Base):
     def alter_targets(self):
         """Return any corresponding targets in a build directory.
         """
-        return self.fs.build_dir_target_climb(self, [])
+        return self.fs.build_dir_target_climb(self, self, [])
 
     def scanner_key(self):
         """A directory does not get scanned."""
@@ -1289,10 +1329,13 @@ class Dir(Base):
         for kid in self.children():
             contents.write(kid.get_contents())
         return contents.getvalue()
-    
+
     def prepare(self):
         pass
 
+    def do_duplicate(self, src):
+        pass
+
     def current(self, calc=None):
         """If all of our children were up-to-date, then this
         directory was up-to-date, too."""
@@ -1310,15 +1353,13 @@ class Dir(Base):
             return 0
 
     def rdir(self):
-        try:
-            return self._rdir
-        except AttributeError:
-            self._rdir = self
-            if not self.exists():
-                n = self.fs.Rsearch(self.path, clazz=Dir, cwd=self.fs.Top)
-                if n:
-                    self._rdir = n
-            return self._rdir
+        "__cacheable__"
+        rdir = self
+        if not self.exists():
+            n = self.fs.Rsearch(self.path, clazz=Dir, cwd=self.fs.Top)
+            if n:
+                rdir = n
+        return rdir
 
     def sconsign(self):
         """Return the .sconsign file info for this directory,
@@ -1352,7 +1393,82 @@ class Dir(Base):
     def must_be_a_Dir(self):
         """Called to make sure a Node is a Dir.  Since we're already
         one, this is a no-op for us."""
-        pass
+        return self
+
+    def entry_exists_on_disk(self, name):
+        """__cacheable__"""
+        return self.fs.exists(self.entry_abspath(name))
+
+    def rcs_on_disk(self, name):
+        rcspath = 'RCS' + os.sep + name+',v'
+        return self.entry_exists_on_disk(rcspath)
+
+    def sccs_on_disk(self, name):
+        sccspath = 'SCCS' + os.sep + 's.'+name
+        return self.entry_exists_on_disk(sccspath)
+
+    def srcdir_list(self):
+        """__cacheable__"""
+        result = []
+
+        dirname = '.'
+        dir = self
+        while dir:
+            if dir.srcdir:
+                d = dir.srcdir.Dir(dirname)
+                if d.is_under(dir):
+                    # Shouldn't source from something in the build path:
+                    # build_dir is probably under src_dir, in which case
+                    # we are reflecting.
+                    break
+                result.append(d)
+            dirname = dir.name + os.sep + dirname
+            dir = dir.get_dir()
+
+        return result
+
+    def srcdir_duplicate(self, name, clazz):
+        for dir in self.srcdir_list():
+            if dir.entry_exists_on_disk(name):
+                srcnode = self.fs._doLookup(clazz, name, dir)
+                if self.duplicate:
+                    node = self.fs._doLookup(clazz, name, self)
+                    node.do_duplicate(srcnode)
+                    return node
+                else:
+                    return srcnode
+        return None
+
+    def srcdir_find_file(self, filename):
+        """__cacheable__"""
+        fs = self.fs
+        do_Rsearch = fs.do_Rsearch
+
+        def func(node):
+            if (isinstance(node, File) or isinstance(node, Entry)) and \
+               (node.is_derived() or node.is_pseudo_derived() or node.exists()):
+                    return node
+            return None
+
+        node, d = do_Rsearch(filename, self, func, File)
+        if node:
+            return node, d
+
+        for dir in self.srcdir_list():
+            node, d = do_Rsearch(filename, dir, func, File)
+            if node:
+                return File(filename, self, fs), d
+        return None, None
+
+    def node_on_disk(self, name, clazz):
+        if self.entry_exists_on_disk(name) or \
+           self.sccs_on_disk(name) or \
+           self.rcs_on_disk(name):
+            try:
+                return self.fs._doLookup(clazz, name, self)
+            except TypeError:
+                pass
+        return self.srcdir_duplicate(name, clazz)
 
 class RootDir(Dir):
     """A class for the root directory of a file system.
@@ -1411,20 +1527,15 @@ class File(Base):
         return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
                                   cwd=self.cwd)
 
-    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."""
+        """Turn a file system node into a File object.  __cache_reset__"""
         self.scanner_paths = {}
-        self.found_includes = {}
         if not hasattr(self, '_local'):
             self._local = 0
 
+    def disambiguate(self):
+        return self
+
     def root(self):
         return self.dir.root()
 
@@ -1453,8 +1564,12 @@ 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):
+            return BuildInfo()
+        else:
             if isinstance(stored, BuildInfo):
                 return stored
             # The stored build information isn't a BuildInfo object.
@@ -1466,8 +1581,6 @@ class File(Base):
             for key, val in stored.__dict__.items():
                 setattr(binfo, key, val)
             return binfo
-        except:
-            return BuildInfo()
 
     def get_stored_implicit(self):
         binfo = self.get_stored_info()
@@ -1476,73 +1589,19 @@ class File(Base):
         except AttributeError:
             return None
 
-    def get_found_includes(self, env, scanner, target):
+    def get_found_includes(self, env, scanner, path):
         """Return the included implicit dependencies in this file.
-        Cache results so we only scan the file once regardless of
-        how many times this information is requested."""
+        Cache results so we only scan the file once per path
+        regardless of how many times this information is requested.
+        __cacheable__"""
         if not scanner:
             return []
-
-        try:
-            path = target.scanner_paths[scanner]
-        except AttributeError:
-            # The target had no scanner_paths attribute, which means
-            # it's an Alias or some other node that's not actually a
-            # file.  In that case, back off and use the path for this
-            # node itself.
-            try:
-                path = self.scanner_paths[scanner]
-            except KeyError:
-                path = scanner.path(env, self.cwd)
-                self.scanner_paths[scanner] = path
-        except KeyError:
-            path = scanner.path(env, target.cwd)
-            target.scanner_paths[scanner] = path
-
-        try:
-            includes = self.found_includes[path]
-        except KeyError:
-            includes = scanner(self, env, path)
-            self.found_includes[path] = includes
-
-        return includes
+        return scanner(self, env, path)
 
     def _createDir(self):
         # ensure that the directories for this node are
         # created.
-
-        listDirs = []
-        parent=self.dir
-        while parent:
-            if parent.exists():
-                break
-            listDirs.append(parent)
-            p = parent.up()
-            if isinstance(p, ParentOfRoot):
-                raise SCons.Errors.StopError, parent.path
-            parent = p
-        listDirs.reverse()
-        for dirnode in listDirs:
-            try:
-                # Don't call dirnode.build(), call the base Node method
-                # directly because we definitely *must* create this
-                # directory.  The dirnode.build() method will suppress
-                # the build if it's the default builder.
-                SCons.Node.Node.build(dirnode)
-                # The build() action may or may not have actually
-                # created the directory, depending on whether the -n
-                # option was used or not.  Delete the _exists and
-                # _rexists attributes so they can be reevaluated.
-                try:
-                    delattr(dirnode, '_exists')
-                except AttributeError:
-                    pass
-                try:
-                    delattr(dirnode, '_rexists')
-                except AttributeError:
-                    pass
-            except OSError:
-                pass
+        self.dir._create()
 
     def retrieve_from_cache(self):
         """Try to retrieve the node's content from a cache
@@ -1585,22 +1644,15 @@ class File(Base):
         return None
 
     def built(self):
-        """Called just after this node is sucessfully built."""
+        """Called just after this node is successfully built.
+        __cache_reset__"""
         # Push this file out to cache before the superclass Node.built()
         # method has a chance to clear the build signature, which it
         # 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)
-        self.found_includes = {}
-        try:
-            delattr(self, '_exists')
-        except AttributeError:
-            pass
-        try:
-            delattr(self, '_rexists')
-        except AttributeError:
-            pass
 
     def visited(self):
         if self.fs.CachePath and self.fs.cache_force and self.fs.exists(self.path):
@@ -1625,19 +1677,12 @@ class File(Base):
             else:
                 scb = self.dir.src_builder()
                 if scb is _null:
-                    scb = None
-                    dir = self.dir.path
-                    sccspath = os.path.join('SCCS', 's.' + self.name)
-                    if dir != '.':
-                        sccspath = os.path.join(dir, sccspath)
-                    if self.fs.exists(sccspath):
+                    if self.dir.sccs_on_disk(self.name):
                         scb = get_DefaultSCCSBuilder()
+                    elif self.dir.rcs_on_disk(self.name):
+                        scb = get_DefaultRCSBuilder()
                     else:
-                        rcspath = os.path.join('RCS', self.name + ',v')
-                        if dir != '.':
-                            rcspath = os.path.join(dir, rcspath)
-                        if os.path.exists(rcspath):
-                            scb = get_DefaultRCSBuilder()
+                        scb = None
                 if scb is not None:
                     self.builder_set(scb)
             self.sbuilder = scb
@@ -1648,11 +1693,16 @@ class File(Base):
         """
         if self.is_derived():
             return [], None
-        return self.fs.build_dir_target_climb(self.dir, [self.name])
+        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):
+        '__cache_reset__'
+        Unlink(self, [], None)
+        
     def prepare(self):
         """Prepare for this file to be created."""
         SCons.Node.Node.prepare(self)
@@ -1660,11 +1710,7 @@ class File(Base):
         if self.get_state() != SCons.Node.up_to_date:
             if self.exists():
                 if self.is_derived() and not self.precious:
-                    Unlink(self, [], None)
-                    try:
-                        delattr(self, '_exists')
-                    except AttributeError:
-                        pass
+                    self._rmv_existing()
             else:
                 try:
                     self._createDir()
@@ -1679,34 +1725,34 @@ class File(Base):
             return 1
         return None
 
+    def do_duplicate(self, src):
+        self._createDir()
+        try:
+            Unlink(self, None, None)
+        except SCons.Errors.BuildError:
+            pass
+        try:
+            Link(self, src, None)
+        except SCons.Errors.BuildError, e:
+            desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
+            raise SCons.Errors.StopError, desc
+        self.linked = 1
+        # The Link() action may or may not have actually
+        # created the file, depending on whether the -n
+        # option was used or not.  Delete the _exists and
+        # _rexists attributes so they can be reevaluated.
+        self.clear()
+
     def exists(self):
+        "__cacheable__"
         # Duplicate from source path if we are set up to do this.
         if self.duplicate and not self.is_derived() and not self.linked:
-            src=self.srcnode().rfile()
-            if src.exists() and src.abspath != self.abspath:
-                self._createDir()
-                try:
-                    Unlink(self, None, None)
-                except SCons.Errors.BuildError:
-                    pass
-                try:
-                    Link(self, src, None)
-                except SCons.Errors.BuildError, e:
-                    desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
-                    raise SCons.Errors.StopError, desc
-                self.linked = 1
-                # The Link() action may or may not have actually
-                # created the file, depending on whether the -n
-                # option was used or not.  Delete the _exists and
-                # _rexists attributes so they can be reevaluated.
-                try:
-                    delattr(self, '_exists')
-                except AttributeError:
-                    pass
-                try:
-                    delattr(self, '_rexists')
-                except AttributeError:
-                    pass
+            src=self.srcnode()
+            if src is self:
+                return Base.exists(self)
+            src = src.rfile()
+            if src.abspath != self.abspath and src.exists():
+                self.do_duplicate(src)
         return Base.exists(self)
 
     def new_binfo(self):
@@ -1746,7 +1792,7 @@ class File(Base):
 
         try:
             mtime = self.get_timestamp()
-        except:
+        except OSError:
             mtime = 0
             raise SCons.Errors.UserError, "no such %s" % self
 
@@ -1770,8 +1816,11 @@ class File(Base):
 
         return csig
 
-    def current(self, calc=None, scan=1):
+    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():
@@ -1787,23 +1836,20 @@ class File(Base):
                         LocalCopy(self, r, None)
                         self.store_info(self.binfo)
                     return 1
-            self._rfile = self
             return None
         else:
             old = self.get_stored_info()
             return (old == self.binfo)
 
     def rfile(self):
-        try:
-            return self._rfile
-        except AttributeError:
-            self._rfile = self
-            if not self.exists():
-                n = self.fs.Rsearch(self.path, clazz=File,
-                                    cwd=self.fs.Top)
-                if n:
-                    self._rfile = n
-            return self._rfile
+        "__cacheable__"
+        rfile = self
+        if not self.exists():
+            n = self.fs.Rsearch(self.path, clazz=File,
+                                cwd=self.fs.Top)
+            if n:
+                rfile = n
+        return rfile
 
     def rstr(self):
         return str(self.rfile())
@@ -1831,12 +1877,14 @@ class File(Base):
 
 default_fs = FS()
 
-def find_file(filename, paths, node_factory=default_fs.File, verbose=None):
+def find_file(filename, paths, verbose=None):
     """
     find_file(str, [Dir()]) -> [nodes]
 
     filename - a filename to find
-    paths - a list of directory path *nodes* to search in
+    paths - a list of directory path *nodes* to search in.  Can be
+            represented as a list, a tuple, or a callable that is
+            called with no arguments and returns the list or tuple.
 
     returns - the node created from the found file.
 
@@ -1845,30 +1893,43 @@ def find_file(filename, paths, node_factory=default_fs.File, verbose=None):
 
     Only the first file found is returned, and none is returned
     if no file is found.
+    __cacheable__
     """
-    if verbose and not SCons.Util.is_String(verbose):
-        verbose = "find_file"
-    retval = None
-    for dir in paths:
-        if verbose:
-            sys.stdout.write("  %s: looking for '%s' in '%s' ...\n" % (verbose, filename, dir))
-        try:
-            node = node_factory(filename, dir)
-            # Return true if the node exists or is a derived node.
-            if node.is_derived() or \
-               node.is_pseudo_derived() or \
-               (isinstance(node, SCons.Node.FS.Base) and node.exists()):
-                retval = node
-                if verbose:
-                    sys.stdout.write("  %s: ... FOUND '%s' in '%s'\n" % (verbose, filename, dir))
-                break
-        except TypeError:
-            # If we find a directory instead of a file, we don't care
-            pass
+    if verbose:
+        if not SCons.Util.is_String(verbose):
+            verbose = "find_file"
+        if not callable(verbose):
+            verbose = '  %s: ' % verbose
+            verbose = lambda s, v=verbose: sys.stdout.write(v + s)
+    else:
+        verbose = lambda x: x
 
-    return retval
+    if callable(paths):
+        paths = paths()
+
+    # Give Entries a chance to morph into Dirs.
+    paths = map(lambda p: p.must_be_a_Dir(), paths)
+
+    filedir, filename = os.path.split(filename)
+    if filedir:
+        def filedir_lookup(p, fd=filedir):
+            try:
+                return p.Dir(fd)
+            except TypeError:
+                # We tried to look up a Dir, but it seems there's already
+                # a File (or something else) there.  No big.
+                return None
+        paths = filter(None, map(filedir_lookup, paths))
+
+    for dir in paths:
+        verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
+        node, d = dir.srcdir_find_file(filename)
+        if node:
+            verbose("... FOUND '%s' in '%s'\n" % (filename, d))
+            return node
+    return None
 
-def find_files(filenames, paths, node_factory = default_fs.File):
+def find_files(filenames, paths):
     """
     find_files([str], [Dir()]) -> [nodes]
 
@@ -1883,7 +1944,5 @@ def find_files(filenames, paths, node_factory = default_fs.File):
     Only the first file found is returned for each filename,
     and any files that aren't found are ignored.
     """
-    nodes = map(lambda x, paths=paths, node_factory=node_factory:
-                       find_file(x, paths, node_factory),
-                filenames)
-    return filter(lambda x: x != None, nodes)
+    nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
+    return filter(None, nodes)
index 565384f7e2727019847ae037767de48f7c9af319..adef880dca12b83715bc37f9174b319f3c5588fe 100644 (file)
@@ -307,7 +307,6 @@ class BuildDirTestCase(unittest.TestCase):
         try:
             dir_made = []
             d9.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
-            d9.reset_executor()
             f9.exists()
             expect = os.path.join('build', 'var2', 'new_dir')
             assert dir_made[0].path == expect, dir_made[0].path
@@ -380,10 +379,13 @@ class BuildDirTestCase(unittest.TestCase):
         assert (stat.S_IMODE(st[stat.ST_MODE]) & stat.S_IWRITE), \
                stat.S_IMODE(st[stat.ST_MODE])
 
-        # This used to generate a UserError when we forbid the source
-        # directory from being outside the top-level SConstruct dir.
-        fs = SCons.Node.FS.FS()
-        fs.BuildDir('build', '/test/foo')
+        exc_caught = 0
+        try:
+            fs = SCons.Node.FS.FS()
+            fs.BuildDir('build', '/test/foo')
+        except SCons.Errors.UserError:
+            exc_caught = 1
+        assert exc_caught, "Should have caught a UserError."
 
         exc_caught = 0
         try:
@@ -603,170 +605,15 @@ class BuildDirTestCase(unittest.TestCase):
 
         self.failIf(errors)
 
-class BaseTestCase(_tempdirTestCase):
-    def test_stat(self):
-        """Test the Base.stat() method"""
-        test = self.test
-        test.write("e1", "e1\n")
-        fs = SCons.Node.FS.FS()
-
-        e1 = fs.Entry('e1')
-        s = e1.stat()
-        assert not s is None, s
-
-        e2 = fs.Entry('e2')
-        s = e2.stat()
-        assert s is None, s
-
-    def test_getmtime(self):
-        """Test the Base.getmtime() method"""
-        test = self.test
-        test.write("file", "file\n")
-        fs = SCons.Node.FS.FS()
-
-        file = fs.Entry('file')
-        assert file.getmtime()
-
-        file = fs.Entry('nonexistent')
-        mtime = file.getmtime()
-        assert mtime is None, mtime
-
-    def test_getsize(self):
-        """Test the Base.getsize() method"""
-        test = self.test
-        test.write("file", "file\n")
-        fs = SCons.Node.FS.FS()
-
-        file = fs.Entry('file')
-        size = file.getsize()
-        assert size == 5, size
-
-        file = fs.Entry('nonexistent')
-        size = file.getsize()
-        assert size is None, size
-
-    def test_isdir(self):
-        """Test the Base.isdir() method"""
-        test = self.test
-        test.subdir('dir')
-        test.write("file", "file\n")
-        fs = SCons.Node.FS.FS()
-
-        dir = fs.Entry('dir')
-        assert dir.isdir()
-
-        file = fs.Entry('file')
-        assert not file.isdir()
-
-        nonexistent = fs.Entry('nonexistent')
-        assert not nonexistent.isdir()
-
-    def test_isfile(self):
-        """Test the Base.isfile() method"""
-        test = self.test
-        test.subdir('dir')
-        test.write("file", "file\n")
-        fs = SCons.Node.FS.FS()
-
-        dir = fs.Entry('dir')
-        assert not dir.isfile()
-
-        file = fs.Entry('file')
-        assert file.isfile()
-
-        nonexistent = fs.Entry('nonexistent')
-        assert not nonexistent.isfile()
-
-    if hasattr(os, 'symlink'):
-        def test_islink(self):
-            """Test the Base.islink() method"""
-            test = self.test
-            test.subdir('dir')
-            test.write("file", "file\n")
-            test.symlink("symlink", "symlink")
-            fs = SCons.Node.FS.FS()
-
-            dir = fs.Entry('dir')
-            assert not dir.islink()
-
-            file = fs.Entry('file')
-            assert not file.islink()
-
-            symlink = fs.Entry('symlink')
-            assert symlink.islink()
-
-            nonexistent = fs.Entry('nonexistent')
-            assert not nonexistent.islink()
-
-class NodeInfoTestCase(_tempdirTestCase):
-    def test___init__(self):
-        """Test NodeInfo initialization"""
-        ni = SCons.Node.FS.NodeInfo()
-        assert not hasattr(ni, 'bsig')
-
-    def test___cmp__(self):
-        """Test comparing NodeInfo objects"""
-        ni1 = SCons.Node.FS.NodeInfo()
-        ni2 = SCons.Node.FS.NodeInfo()
-
-        msg = "cmp(%s, %s) returned %s, not %s"
-
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1, ni2, c, 1)
-
-        ni1.bsig = 777
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1.bsig, ni2, c, 1)
-
-        ni2.bsig = 666
-        c = cmp(ni1, ni2)
-        assert c == 1, msg % (ni1.bsig, ni2.bsig, c, 1)
-
-        ni2.bsig = 777
-        c = cmp(ni1, ni2)
-        assert c == 0, msg % (ni1.bsig, ni2.bsig, c, 0)
-
-        ni2.bsig = 888
-        c = cmp(ni1, ni2)
-        assert c == -1, msg % (ni1.bsig, ni2.bsig, c, -1)
-
-    def test_update(self):
-        """Test updating a NodeInfo with on-disk information"""
-        test = self.test
-        test.write('fff', "fff\n")
-        fff = self.fs.File('fff')
-
-        ni = SCons.Node.FS.NodeInfo()
-        assert not hasattr(ni, 'timestamp')
-        assert not hasattr(ni, 'size')
-
-        ni.update(fff)
-        assert ni.timestamp == os.path.getmtime('fff'), ni.timestamp
-        assert ni.size == os.path.getsize('fff'), ni.size
-
-class BuildInfoTestCase(_tempdirTestCase):
-    def test___init__(self):
-        """Test BuildInfo initialization"""
-        fff = self.fs.File('fff')
-        bi = SCons.Node.FS.BuildInfo(fff)
-        assert bi.node is fff, bi.node
-
-    def test_convert_to_sconsign(self):
-        """Test converting to .sconsign file format"""
-
-    def test_convert_from_sconsign(self):
-        """Test converting from .sconsign file format"""
-
-class FSTestCase(_tempdirTestCase):
-    def test_runTest(self):
+class FSTestCase(unittest.TestCase):
+    def runTest(self):
         """Test FS (file system) Node operations
 
         This test case handles all of the file system node
         tests in one environment, so we don't have to set up a
         complicated directory structure for each test individually.
         """
-        test = self.test
-
+        test = TestCmd(workdir = '')
         test.subdir('sub', ['sub', 'dir'])
 
         wp = test.workpath('')
@@ -970,7 +817,6 @@ class FSTestCase(_tempdirTestCase):
         assert not built_it
         d1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
         d1.builder_set(Builder(fs.File))
-        d1.reset_executor()
         d1.env_set(Environment())
         d1.build()
         assert built_it
@@ -979,7 +825,6 @@ class FSTestCase(_tempdirTestCase):
         assert not built_it
         f1.add_source([SCons.Node.Node()])    # XXX FAKE SUBCLASS ATTRIBUTE
         f1.builder_set(Builder(fs.File))
-        f1.reset_executor()
         f1.env_set(Environment())
         f1.build()
         assert built_it
@@ -1126,7 +971,7 @@ class FSTestCase(_tempdirTestCase):
         # the reading of files in text mode.  This tests that
         # get_contents() returns the binary contents.
         test.write("binary_file", "Foo\x1aBar")
-        f1 = fs.File(test.workpath("binary_file"))
+        f1 = SCons.Node.FS.default_fs.File(test.workpath("binary_file"))
         assert f1.get_contents() == "Foo\x1aBar", f1.get_contents()
 
         def nonexistent(method, s):
@@ -1339,112 +1184,8 @@ class FSTestCase(_tempdirTestCase):
         t = z.target_from_source('pre-', '-suf', lambda x: x[:-1])
         assert str(t) == 'pre-z-suf', str(t)
 
-    def test_same_name(self):
-        """Test that a local same-named file isn't found for # Dir lookup"""
-        test = self.test
-        fs = self.fs
-
-        test.subdir('subdir')
-        test.write(['subdir', 'build'], "subdir/build\n")
-
-        subdir = fs.Dir('subdir')
-        fs.chdir(subdir, change_os_dir=1)
-        path, dir = fs._transformPath('#build/file', subdir)
-        self.fs._doLookup(SCons.Node.FS.File, path, dir)
-
-    def test_above_root(self):
-        """Testing looking up a path above the root directory"""
-        test = self.test
-        fs = self.fs
-
-        d1 = fs.Dir('d1')
-        d2 = d1.Dir('d2')
-        dirs = string.split(os.path.normpath(d2.abspath), os.sep)
-        above_path = apply(os.path.join, ['..']*len(dirs) + ['above'])
-        above = d2.Dir(above_path)
-
-    def test_rel_path(self):
-        """Test the rel_path() method"""
-        test = self.test
-        fs = self.fs
-
-        d1 = fs.Dir('d1')
-        d1_f = d1.File('f')
-        d1_d2 = d1.Dir('d2')
-        d1_d2_f = d1_d2.File('f')
-
-        d3 = fs.Dir('d3')
-        d3_f = d3.File('f')
-        d3_d4 = d3.Dir('d4')
-        d3_d4_f = d3_d4.File('f')
-
-        cases = [
-                d1,             d1,             '.',
-                d1,             d1_f,           'f',
-                d1,             d1_d2,          'd2',
-                d1,             d1_d2_f,        'd2/f',
-                d1,             d3,             '../d3',
-                d1,             d3_f,           '../d3/f',
-                d1,             d3_d4,          '../d3/d4',
-                d1,             d3_d4_f,        '../d3/d4/f',
-
-                d1_f,           d1,             '.',
-                d1_f,           d1_f,           'f',
-                d1_f,           d1_d2,          'd2',
-                d1_f,           d1_d2_f,        'd2/f',
-                d1_f,           d3,             '../d3',
-                d1_f,           d3_f,           '../d3/f',
-                d1_f,           d3_d4,          '../d3/d4',
-                d1_f,           d3_d4_f,        '../d3/d4/f',
-
-                d1_d2,          d1,             '..',
-                d1_d2,          d1_f,           '../f',
-                d1_d2,          d1_d2,          '.',
-                d1_d2,          d1_d2_f,        'f',
-                d1_d2,          d3,             '../../d3',
-                d1_d2,          d3_f,           '../../d3/f',
-                d1_d2,          d3_d4,          '../../d3/d4',
-                d1_d2,          d3_d4_f,        '../../d3/d4/f',
-
-                d1_d2_f,        d1,             '..',
-                d1_d2_f,        d1_f,           '../f',
-                d1_d2_f,        d1_d2,          '.',
-                d1_d2_f,        d1_d2_f,        'f',
-                d1_d2_f,        d3,             '../../d3',
-                d1_d2_f,        d3_f,           '../../d3/f',
-                d1_d2_f,        d3_d4,          '../../d3/d4',
-                d1_d2_f,        d3_d4_f,        '../../d3/d4/f',
-        ]
-
-        d1.rel_path(d3)
-
-        failed = 0
-        while cases:
-            dir, other, expect = cases[:3]
-            expect = os.path.normpath(expect)
-            del cases[:3]
-            result = dir.rel_path(other)
-            if result != expect:
-                if failed == 0: print
-                fmt = "    dir_path(%(dir)s, %(other)s) => '%(result)s' did not match '%(expect)s'"
-                print fmt % locals()
-                failed = failed + 1
-        assert failed == 0, "%d rel_path() cases failed" % failed
-
 class DirTestCase(_tempdirTestCase):
 
-    def test__morph(self):
-        """Test handling of actions when morphing an Entry into a Dir"""
-        test = self.test
-        e = self.fs.Entry('eee')
-        x = e.get_executor()
-        x.add_pre_action('pre')
-        x.add_post_action('post')
-        e.must_be_a_Dir()
-        a = x.get_action_list()
-        assert a[0] == 'pre', a
-        assert a[2] == 'post', a
-
     def test_entry_exists_on_disk(self):
         """Test the Dir.entry_exists_on_disk() method
         """
@@ -1455,15 +1196,11 @@ class DirTestCase(_tempdirTestCase):
 
         test.subdir('d')
         test.write(['d', 'exists'], "d/exists\n")
-        test.write(['d', 'Case-Insensitive'], "d/Case-Insensitive\n")
 
         d = self.fs.Dir('d')
         assert d.entry_exists_on_disk('exists')
         assert not d.entry_exists_on_disk('does_not_exist')
 
-        if os.path.normcase("TeSt") != os.path.normpath("TeSt") or sys.platform == "cygwin":
-            assert d.entry_exists_on_disk('case-insensitive')
-
     def test_srcdir_list(self):
         """Test the Dir.srcdir_list() method
         """
@@ -1529,11 +1266,11 @@ class DirTestCase(_tempdirTestCase):
         src0 = self.fs.Dir('src0')
         self.fs.BuildDir(bld0, src0, duplicate=0)
 
-        n = bld0.srcdir_duplicate('does_not_exist')
+        n = bld0.srcdir_duplicate('does_not_exist', SCons.Node.FS.File)
         assert n is None, n
         assert not os.path.exists(test.workpath('bld0', 'does_not_exist'))
 
-        n = bld0.srcdir_duplicate('exists')
+        n = bld0.srcdir_duplicate('exists', SCons.Node.FS.File)
         assert str(n) == os.path.normpath('src0/exists'), str(n)
         assert not os.path.exists(test.workpath('bld0', 'exists'))
 
@@ -1544,11 +1281,11 @@ class DirTestCase(_tempdirTestCase):
         src1 = self.fs.Dir('src1')
         self.fs.BuildDir(bld1, src1, duplicate=1)
 
-        n = bld1.srcdir_duplicate('does_not_exist')
+        n = bld1.srcdir_duplicate('does_not_exist', SCons.Node.FS.File)
         assert n is None, n
         assert not os.path.exists(test.workpath('bld1', 'does_not_exist'))
 
-        n = bld1.srcdir_duplicate('exists')
+        n = bld1.srcdir_duplicate('exists', SCons.Node.FS.File)
         assert str(n) == os.path.normpath('bld1/exists'), str(n)
         assert os.path.exists(test.workpath('bld1', 'exists'))
 
@@ -1584,7 +1321,7 @@ class DirTestCase(_tempdirTestCase):
         exists_e.exists = return_true
 
         def check(result, expect):
-            result = map(str, result)
+           result = map(str, result)
             expect = map(os.path.normpath, expect)
             assert result == expect, result
 
@@ -1700,38 +1437,8 @@ class DirTestCase(_tempdirTestCase):
         n = bld1.srcdir_find_file('on-disk-e2')
         check(n, ['bld1/on-disk-e2', 'bld1'])
 
-    def test_dir_on_disk(self):
-        """Test the Dir.dir_on_disk() method"""
-        self.test.subdir('sub', ['sub', 'exists'])
-        self.test.write(['sub', 'file'], "self/file\n")
-        sub = self.fs.Dir('sub')
-
-        r = sub.dir_on_disk('does_not_exist')
-        assert not r, r
-
-        r = sub.dir_on_disk('exists')
-        assert r, r
-
-        r = sub.dir_on_disk('file')
-        assert not r, r
-
-    def test_file_on_disk(self):
-        """Test the Dir.file_on_disk() method"""
-        self.test.subdir('sub', ['sub', 'dir'])
-        self.test.write(['sub', 'exists'], "self/exists\n")
-        sub = self.fs.Dir('sub')
-
-        r = sub.file_on_disk('does_not_exist')
-        assert not r, r
-
-        r = sub.file_on_disk('exists')
-        assert r, r
-
-        r = sub.file_on_disk('dir')
-        assert not r, r
-
-class EntryTestCase(_tempdirTestCase):
-    def test_runTest(self):
+class EntryTestCase(unittest.TestCase):
+    def runTest(self):
         """Test methods specific to the Entry sub-class.
         """
         test = TestCmd(workdir='')
@@ -1820,57 +1527,6 @@ class EntryTestCase(_tempdirTestCase):
         # Doesn't exist, no sources, and no builder: no sig
         assert sig is None, sig
 
-    def test_Entry_Entry_lookup(self):
-        """Test looking up an Entry within another Entry"""
-        self.fs.Entry('#topdir')
-        self.fs.Entry('#topdir/a/b/c')
-
-
-
-class FileTestCase(_tempdirTestCase):
-
-    def test_Dirs(self):
-        """Test the File.Dirs() method"""
-        fff = self.fs.File('subdir/fff')
-        # This simulates that the SConscript file that defined
-        # fff is in subdir/.
-        fff.cwd = self.fs.Dir('subdir')
-        d1 = self.fs.Dir('subdir/d1')
-        d2 = self.fs.Dir('subdir/d2')
-        dirs = fff.Dirs(['d1', 'd2'])
-        assert dirs == [d1, d2], map(str, dirs)
-
-    def test_exists(self):
-        """Test the File.exists() method"""
-        fs = self.fs
-        test = self.test
-
-        src_f1 = fs.File('src/f1')
-        assert not src_f1.exists(), "%s apparently exists?" % src_f1
-
-        test.subdir('src')
-        test.write(['src', 'f1'], "src/f1\n")
-
-        assert not src_f1.exists(), "%s did not cache previous exists() value" % src_f1
-        src_f1.clear()
-        assert src_f1.exists(), "%s apparently does not exist?" % src_f1
-
-        test.subdir('build')
-        fs.BuildDir('build', 'src')
-        build_f1 = fs.File('build/f1')
-
-        assert build_f1.exists(), "%s did not realize that %s exists" % (build_f1, src_f1)
-        assert os.path.exists(build_f1.abspath), "%s did not get duplicated on disk" % build_f1.abspath
-
-        test.unlink(['src', 'f1'])
-        src_f1.clear()  # so the next exists() call will look on disk again
-
-        assert build_f1.exists(), "%s did not cache previous exists() value" % build_f1
-        build_f1.clear()
-        build_f1.linked = None
-        assert not build_f1.exists(), "%s did not realize that %s disappeared" % (build_f1, src_f1)
-        assert not os.path.exists(build_f1.abspath), "%s did not get removed after %s was removed" % (build_f1, src_f1)
-
 
 
 class RepositoryTestCase(_tempdirTestCase):
@@ -1910,28 +1566,6 @@ class RepositoryTestCase(_tempdirTestCase):
         r = map(lambda x, np=os.path.normpath: np(str(x)), rep)
         assert r == expect, r
 
-    def test_get_all_rdirs(self):
-        """Test the Dir.get_all_rdirs() method"""
-        self.fs.Repository('foo')
-        self.fs.Repository(os.path.join('foo', 'bar'))
-        self.fs.Repository('bar/foo')
-        self.fs.Repository('bar')
-
-        expect = [
-            '.',
-            self.rep1,
-            self.rep2,
-            self.rep3,
-            'foo',
-            os.path.join('foo', 'bar'),
-            os.path.join('bar', 'foo'),
-            'bar'
-        ]
-
-        rep = self.fs.Dir('#').get_all_rdirs()
-        r = map(lambda x, np=os.path.normpath: np(str(x)), rep)
-        assert r == expect, r
-
     def test_rdir(self):
         """Test the Dir.rdir() method"""
         return_true = lambda: 1
@@ -2026,42 +1660,95 @@ class RepositoryTestCase(_tempdirTestCase):
         r = e2.rfile()
         assert r is re2, r
 
-    def test_Rfindalldirs(self):
-        """Test the Rfindalldirs() methods"""
+    def test_Rsearches(self):
+        """Test the Rsearch() methods"""
         fs = self.fs
         test = self.test
 
-        d1 = fs.Dir('d1')
-        d2 = fs.Dir('d2')
-        rep1_d1 = fs.Dir(test.workpath('rep1', 'd1'))
-        rep2_d1 = fs.Dir(test.workpath('rep2', 'd1'))
-        rep3_d1 = fs.Dir(test.workpath('rep3', 'd1'))
-        sub = fs.Dir('sub')
-        sub_d1 = sub.Dir('d1')
-        rep1_sub_d1 = fs.Dir(test.workpath('rep1', 'sub', 'd1'))
-        rep2_sub_d1 = fs.Dir(test.workpath('rep2', 'sub', 'd1'))
-        rep3_sub_d1 = fs.Dir(test.workpath('rep3', 'sub', 'd1'))
+        test.write([self.rep1, 'f2'], "")
+        test.subdir([self.rep2, 'f3'])
+        test.write([self.rep3, 'f3'], "")
+
+        r = fs.Rsearch('f1')
+        assert r is None, r
 
-        r = fs.Rfindalldirs(d1, fs.Top)
-        assert r == [d1], map(str, r)
+        r = fs.Rsearch('f2')
+        assert r, r
+
+        f3 = fs.File('f3')
+        r = fs.Rsearch(f3)
+        assert r is f3, r
+
+    def test_Rsearchall(self):
+        """Test the Rsearchall() methods"""
+        fs = self.fs
+        test = self.test
 
-        r = fs.Rfindalldirs([d1, d2], fs.Top)
-        assert r == [d1, d2], map(str, r)
+        list = fs.Rsearchall(fs.Dir('d1'))
+        assert len(list) == 1, list
+        assert list[0].path == 'd1', list[0].path
 
-        r = fs.Rfindalldirs('d1', fs.Top)
-        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r)
+        list = fs.Rsearchall([fs.Dir('d1')])
+        assert len(list) == 1, list
+        assert list[0].path == 'd1', list[0].path
 
-        r = fs.Rfindalldirs('#d1', fs.Top)
-        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r)
+        list = fs.Rsearchall('d2')
+        assert list == [], list
 
-        r = fs.Rfindalldirs('d1', sub)
-        assert r == [sub_d1, rep1_sub_d1, rep2_sub_d1, rep3_sub_d1], map(str, r)
+        list = fs.Rsearchall('#d2')
+        assert list == [], list
 
-        r = fs.Rfindalldirs('#d1', sub)
-        assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r)
+        fs.File('d2').built() # Clear exists cache
+        test.subdir(['work', 'd2'])
 
-        r = fs.Rfindalldirs(['d1', d2], fs.Top)
-        assert r == [d1, rep1_d1, rep2_d1, rep3_d1, d2], map(str, r)
+        list = fs.Rsearchall('d2')
+        assert map(str, list) == ['d2'], list
+
+        fs.File('../rep2/d2').built() # Clear exists cache
+        test.subdir(['rep2', 'd2'])
+
+        list = fs.Rsearchall('d2')
+        assert map(str, list) == ['d2', test.workpath('rep2', 'd2')], list
+
+        fs.File('../rep1/d2').built() # Clear exists cache
+        test.subdir(['rep1', 'd2'])
+
+        list = fs.Rsearchall('d2')
+        assert map(str, list) == ['d2',
+                                  test.workpath('rep1', 'd2'),
+                                  test.workpath('rep2', 'd2')], list
+
+        list = fs.Rsearchall(['d3', 'd4'])
+        assert list == [], list
+
+        fs.File('d3').built() # Clear exists cache
+        test.subdir(['work', 'd3'])
+
+        list = map(str, fs.Rsearchall(['d3', 'd4']))
+        assert list == ['d3'], list
+
+        fs.File('../rep3/d4').built() # Clear exists cache
+        test.subdir(['rep3', 'd4'])
+
+        list = map(str, fs.Rsearchall(['d3', 'd4']))
+        assert list == ['d3', test.workpath('rep3', 'd4')], list
+
+        list = map(str, fs.Rsearchall(string.join(['d3', 'd4'], os.pathsep)))
+        assert list == ['d3', test.workpath('rep3', 'd4')], list
+
+        work_d4 = fs.File(os.path.join('work', 'd4'))
+
+        list = map(str, fs.Rsearchall(['d3', work_d4]))
+        assert list == ['d3', str(work_d4)], list
+
+        list = fs.Rsearchall('')
+        assert list == [], list
+
+        list = fs.Rsearchall([None])
+        assert list == [], list
+
+        list = fs.Rsearchall([''])
+        assert list == [], list
 
     def test_rexists(self):
         """Test the Entry.rexists() method"""
@@ -2069,17 +1756,17 @@ class RepositoryTestCase(_tempdirTestCase):
         test = self.test
 
         test.write([self.rep1, 'f2'], "")
-        test.write([self.rep2, "i_exist"], "\n")
-        test.write(["work", "i_exist_too"], "\n")
 
         fs.BuildDir('build', '.')
 
         f = fs.File(test.workpath("work", "i_do_not_exist"))
         assert not f.rexists()
 
+        test.write([self.rep2, "i_exist"], "\n")
         f = fs.File(test.workpath("work", "i_exist"))
         assert f.rexists()
 
+        test.write(["work", "i_exist_too"], "\n")
         f = fs.File(test.workpath("work", "i_exist_too"))
         assert f.rexists()
 
@@ -2216,7 +1903,7 @@ class StringDirTestCase(unittest.TestCase):
 
 class stored_infoTestCase(unittest.TestCase):
     def runTest(self):
-        """Test how we store build information"""
+        """Test how storing build information"""
         test = TestCmd(workdir = '')
         test.subdir('sub')
         fs = SCons.Node.FS.FS(test.workpath(''))
@@ -2224,8 +1911,7 @@ class stored_infoTestCase(unittest.TestCase):
         d = fs.Dir('sub')
         f = fs.File('file1', d)
         bi = f.get_stored_info()
-        assert bi.ninfo.timestamp == 0, bi.ninfo.timestamp
-        assert bi.ninfo.size == None, bi.ninfo.size
+        assert bi.bsig == None, bi.bsig
 
         class MySConsign:
             class Null:
@@ -2337,7 +2023,6 @@ class prepareTestCase(unittest.TestCase):
         dir_made = []
         new_dir = fs.Dir("new_dir")
         new_dir.builder = Builder(fs.Dir, action=MkdirAction(dir_made))
-        new_dir.reset_executor()
         xyz = fs.File(os.path.join("new_dir", "xyz"))
 
         xyz.set_state(SCons.Node.up_to_date)
@@ -2476,7 +2161,7 @@ class CacheDirTestCase(unittest.TestCase):
         try:
             f5 = fs.File("cd.f5")
             f5.binfo = f5.new_binfo()
-            f5.binfo.ninfo.bsig = 'a_fake_bsig'
+            f5.binfo.bsig = 'a_fake_bsig'
             cp = f5.cachepath()
             dirname = os.path.join('cache', 'A')
             filename = os.path.join(dirname, 'a_fake_bsig')
@@ -2511,7 +2196,7 @@ class CacheDirTestCase(unittest.TestCase):
             test.write(cd_f7, "cd.f7\n")
             f7 = fs.File(cd_f7)
             f7.binfo = f7.new_binfo()
-            f7.binfo.ninfo.bsig = 'f7_bsig'
+            f7.binfo.bsig = 'f7_bsig'
 
             warn_caught = 0
             try:
@@ -2846,7 +2531,9 @@ class SaveStringsTestCase(unittest.TestCase):
 
 if __name__ == "__main__":
     suite = unittest.TestSuite()
+    suite.addTest(FSTestCase())
     suite.addTest(BuildDirTestCase())
+    suite.addTest(EntryTestCase())
     suite.addTest(find_fileTestCase())
     suite.addTest(StringDirTestCase())
     suite.addTest(stored_infoTestCase())
@@ -2860,12 +2547,6 @@ if __name__ == "__main__":
     suite.addTest(SpecialAttrTestCase())
     suite.addTest(SaveStringsTestCase())
     tclasses = [
-        BaseTestCase,
-        BuildInfoTestCase,
-        EntryTestCase,
-        FileTestCase,
-        NodeInfoTestCase,
-        FSTestCase,
         DirTestCase,
         RepositoryTestCase,
     ]