Clean up the Node.FS class.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 19 Sep 2001 12:16:13 +0000 (12:16 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 19 Sep 2001 12:16:13 +0000 (12:16 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@55 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/script/scons.py

index 6dda645d565d1bf81e39223d6edc4939bc6f0fae..53e4bcc576c3b3d9d78e056c08434aa4287c8368 100644 (file)
@@ -23,12 +23,12 @@ class Builder:
                        action = None,
                        input_suffix = None,
                        output_suffix = None,
-                       node_class = SCons.Node.FS.File):
+                       node_factory = SCons.Node.FS.default_fs.File):
        self.name = name
        self.action = Action(action)
        self.insuffix = input_suffix
        self.outsuffix = output_suffix
-       self.node_class = node_class
+       self.node_factory = node_factory
        if not self.insuffix is None and self.insuffix[0] != '.':
            self.insuffix = '.' + self.insuffix
        if not self.outsuffix is None and self.outsuffix[0] != '.':
@@ -38,7 +38,7 @@ class Builder:
        return cmp(self.__dict__, other.__dict__)
 
     def __call__(self, env, target = None, source = None):
-       node = SCons.Node.FS.lookup(self.node_class, target)
+       node = self.node_factory(target)
        node.builder_set(self)
        node.env_set(self)
        node.sources = source   # XXX REACHING INTO ANOTHER OBJECT
index e19b389bdab66ea1a1d3e37afa209b254be897e2..ab7e12274c513f73cdcbbaf5548141b9eb122ca7 100644 (file)
@@ -86,13 +86,15 @@ class BuilderTestCase(unittest.TestCase):
        builder = SCons.Builder.Builder(name = 'foo')
        assert builder.name == 'foo'
 
-    def test_node_class(self):
+    def test_node_factory(self):
        """Test a Builder that creates nodes of a specified class
        """
        class Foo:
-               pass
-       builder = SCons.Builder.Builder(node_class = Foo)
-       assert builder.node_class is Foo
+           pass
+       def FooFactory(target):
+           return Foo(target)
+       builder = SCons.Builder.Builder(node_factory = FooFactory)
+       assert builder.node_factory is FooFactory
 
     def test_outsuffix(self):
        """Test Builder creation with a specified output suffix
index 2d1c8ea70e2dd648744491ca7bc8c68b9c130d58..9cd0607de0afd99aa45019823a88c0f0b47d66b3 100644 (file)
-"""SCons.Node.FS
+"""scons.Node.FS
 
 File system nodes.
 
+This initializes a "default_fs" Node with an FS at the current directory
+for its own purposes, and for use by scripts or modules looking for the
+canonical default.
+
 """
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-
-
 import os
 import os.path
-import SCons.Node
+from SCons.Node import Node
+from UserDict import UserDict
+import sys
+
+class PathName:
+    """This is a string like object with limited capabilities (i.e.,
+    cannot always be used interchangeably with strings).  This class
+    is used by PathDict to manage case-insensitive path names.  It preserves
+    the case of the string with which it was created, but on OS's with
+    case insensitive paths, it will hash equal to any case of the same
+    path when placed in a dictionary."""
 
+    try:
+        convert_path = unicode
+    except NameError:
+        convert_path = str
 
+    def __init__(self, path_name=''):
+        self.data = PathName.convert_path(path_name)
+        self.norm_path = os.path.normcase(self.data)
 
-Top = None
-Root = {}
+    def __hash__(self):
+        return hash(self.norm_path)
+    def __cmp__(self, other):
+        return cmp(self.norm_path,
+                   os.path.normcase(PathName.convert_path(other)))
+    def __rcmp__(self, other):
+        return cmp(os.path.normcase(PathName.convert_path(other)),
+                   self.norm_path)
+    def __str__(self):
+        return str(self.data)
+    def __repr__(self):
+        return repr(self.data)
 
+class PathDict(UserDict):
+    """This is a dictionary-like class meant to hold items keyed
+    by path name.  The difference between this class and a normal
+    dictionary is that string or unicode keys will act differently
+    on OS's that have case-insensitive path names.  Specifically
+    string or unicode keys of different case will be treated as
+    equal on the OS's.
 
+    All keys are implicitly converted to PathName objects before
+    insertion into the dictionary."""
 
-def init(path = None):
-    """Initialize the Node.FS subsystem.
+    def __init__(self, initdict = {}):
+        UserDict.__init__(self, initdict)
+        old_dict = self.data
+        self.data = {}
+        for key, val in old_dict.items():
+            self.data[PathName(key)] = val
 
-    The supplied path is the top of the source tree, where we
-    expect to find the top-level build file.  If no path is
-    supplied, the current directory is the default.
-    """
-    global Top
-    if path == None:
-       path = os.getcwd()
-    Top = lookup(Dir, path, directory = None)
-    Top.path = '.'
-
-def lookup(fsclass, name, directory = Top):
-    """Look up a file system node for a path name.  If the path
-    name is relative, it will be looked up relative to the
-    specified directory node, or to the top-level directory
-    if no node was specified.  An initial '#' specifies that
-    the name will be looked up relative to the top-level directory,
-    regardless of the specified directory argument.  Returns the
-    existing or newly-created node for the specified path name.
-    The node returned will be of the specified fsclass (Dir or
-    File).
-    """
-    global Top
-    head, tail = os.path.split(name)
-    if not tail:
-       drive, path = os.path.splitdrive(head)
-       if not Root.has_key(drive):
-           Root[drive] = Dir(head, None)
-           Root[drive].abspath = head
-           Root[drive].path = head
-       return Root[drive]
-    if tail[0] == '#':
-       directory = Top
-       tail = tail[1:]
-    elif directory is None:
-       directory = Top
-    if head:
-       directory = lookup(Dir, head, directory)
-    try:
-       self = directory.entries[tail]
-    except AttributeError:
-       # There was no "entries" attribute on the directory,
-       # which essentially implies that it was a file.
-       # Return it as a more descriptive exception.
-       raise TypeError, directory
-    except KeyError:
-       # There was to entry for "tail," so create the new
-       # node and link it in to the existing structure.
-       self = fsclass(tail, directory)
-       self.name = tail
-       if self.path[0:2] == "./":
-           self.path = self.path[2:]
-       directory.entries[tail] = self
-    except:
-       raise
-    if self.__class__.__name__ != fsclass.__name__:
-       # Here, we found an existing node for this path,
-       # but it was the wrong type (a File when we were
-       # looking for a Dir, or vice versa).
-       raise TypeError, self
-    return self
+    def __setitem__(self, key, val):
+        self.data[PathName(key)] = val
+
+    def __getitem__(self, key):
+        return self.data[PathName(key)]
+
+    def __delitem__(self, key):
+        del(self.data[PathName(key)])
+
+    if not hasattr(UserDict, 'setdefault'):
+        def setdefault(self, key, value):
+            try:
+                return self.data[PathName(key)]
+            except KeyError:
+                self.data[PathName(key)] = value
+                return value
+
+class FS:
+    def __init__(self, path = None):
+        """Initialize the Node.FS subsystem.
+
+        The supplied path is the top of the source tree, where we
+        expect to find the top-level build file.  If no path is
+        supplied, the current directory is the default.
+
+        The path argument must be a valid absolute path.
+        """
+        if path == None:
+            path = os.getcwd()
+        self.Root = PathDict()
+        self.Top = self.__doLookup(Dir, path)
+        self.Top.path = '.'
+
+    def __doLookup(self, fsclass, name, directory=None):
+        """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."""
 
+        head, tail = os.path.split(os.path.normpath(name))
+        if not tail:
+            # We have reached something that looks like a root
+            # of an absolute path.  What we do here is a little
+            # weird.  If we are on a UNIX system, everything is
+            # well and good, just return the root node.
+            #
+            # On DOS/Win32 things are strange, since a path
+            # starting with a slash is not technically an
+            # absolute path, but a path relative to the
+            # current drive.  Therefore if we get a path like
+            # that, we will return the root Node of the
+            # directory parameter.  If the directory parameter is
+            # None, raise an exception.
 
+            drive, tail = os.path.splitdrive(head)
+            if sys.platform is 'win32' and not drive:
+                if not directory:
+                    raise OSError, 'No drive letter supplied for absolute path.'
+                return directory.root()
+            return self.Root.setdefault(drive, Dir(tail))
+        if head:
+            # Recursively look up our parent directories.
+            directory = self.__doLookup(Dir, head, directory)
+        else:
+            # This path looks like a relative path.  No leading slash or drive
+           # letter.  Therefore, we will look up this path relative to the
+           # supplied top-level directory.
+           assert directory, "Tried to lookup a node by relative path with no top-level directory supplied."
+        ret = directory.entries.setdefault(tail, fsclass(tail, directory))
+        if not isinstance(ret, fsclass):
+            raise TypeError, ret
+        return ret
+
+    def __transformPath(self, name, directory):
+        """Take care of setting up the correct top-level directory,
+        usually in preparation for a call to doLookup().
+
+        If the path name is prepended with a '#', then it is unconditionally
+        interpreted as replative to the top-level directory of this FS.
+
+        If directory is None, and name is a relative path,
+        then the same applies.
+        """
+        if name[0] == '#':
+            directory = self.Top
+            name = os.path.join(os.path.normpath('./'), name[1:])
+        elif not directory:
+            directory = self.Top
+        return (name, directory)
+    
+    def File(self, name, directory = None):
+        """Lookup or create a File node with the specified name.  If
+        the name is a relative path (begins with ./, ../, or a file name),
+        then it is looked up relative to the supplied directory node,
+        or to the top level directory of the FS (supplied at construction
+        time) if no directory is supplied.
+
+        This method will raise TypeError if a directory is found at the
+        specified path.
+        """
+        name, directory = self.__transformPath(name, directory)
+        return self.__doLookup(File, name, directory)
+
+    def Dir(self, name, directory = None):
+        """Lookup or create a Dir node with the specified name.  If
+        the name is a relative path (begins with ./, ../, or a file name),
+        then it is looked up relative to the supplied directory node,
+        or to the top level directory of the FS (supplied at construction
+        time) if no directory is supplied.
+
+        This method will raise TypeError if a normal file is found at the
+        specified path.
+        """
+        name, directory = self.__transformPath(name, directory)
+        return self.__doLookup(Dir, name, directory)
+
+    
 
 # XXX TODO?
 # Annotate with the creator
@@ -94,20 +193,33 @@ def lookup(fsclass, name, directory = Top):
 # linked_targets
 # is_accessible
 
-class Dir(SCons.Node.Node):
+class Dir(Node):
     """A class for directories in a file system.
     """
 
-    def __init__(self, name, directory):
-       self.entries = {}
-       self.entries['.'] = self
-       self.entries['..'] = directory
-       if not directory is None:
-           self.abspath = os.path.join(directory.abspath, name, '')
-           self.path = os.path.join(directory.path, name, '')
+    def __init__(self, name, directory = None):
+        self.entries = PathDict()
+        self.entries['.'] = self
+
+        if directory:
+            self.entries['..'] = directory
+            self.abspath = os.path.join(directory.abspath, name, '')
+            if str(directory.path) == '.':
+                self.path = os.path.join(name, '')
+            else:
+                self.path = os.path.join(directory.path, name, '')
+        else:
+            self.abspath = self.path = name
+            self.entries['..'] = None
 
     def up(self):
-       return self.entries['..']
+        return self.entries['..']
+
+    def root(self):
+        if not self.entries['..']:
+            return self
+        else:
+            return self.entries['..'].root()
 
 
 # XXX TODO?
@@ -130,10 +242,21 @@ class Dir(SCons.Node.Node):
 # is_under
 # relpath
 
-class File(SCons.Node.Node):
+class File(Node):
     """A class for files in a file system.
     """
 
     def __init__(self, name, directory):
-       self.abspath = os.path.join(directory.abspath, name)
-       self.path = os.path.join(directory.path, name)
+        self.abspath = os.path.join(directory.abspath, name)
+        if str(directory.path) == '.':
+            self.path = name
+        else:
+            self.path = os.path.join(directory.path, name)
+        self.parent = directory
+
+    def root(self):
+        return self.parent.root()
+
+
+
+default_fs = FS()
index 24785dacac5d0333d8e3f8ea8bdd96c596309cf8..f95837fbde55a062a09404d05eddcf6cbce60158 100644 (file)
@@ -6,104 +6,103 @@ import unittest
 
 import SCons.Node.FS
 
-
-
 built_it = None
 
 class Builder:
     def execute(self, target = None, source = None):
-       global built_it
-       built_it = 1
-
-
+        global built_it
+        built_it = 1
 
 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.
-       """
-       from TestCmd import TestCmd
-
-       test = TestCmd(workdir = '')
-       test.subdir('sub', ['sub', 'dir'])
-
-       wp = test.workpath('')
-       sub = test.workpath('sub', '')
-       sub_dir = test.workpath('sub', 'dir', '')
-       sub_dir_foo = test.workpath('sub', 'dir', 'foo', '')
-       sub_dir_foo_bar = test.workpath('sub', 'dir', 'foo', 'bar', '')
-       sub_foo = test.workpath('sub', 'foo', '')
-
-       os.chdir(sub_dir)
-
-       SCons.Node.FS.init()
-
-       def Dir_test(lpath, path, abspath, up_path):
-           dir = SCons.Node.FS.lookup(SCons.Node.FS.Dir, lpath)
-           assert(dir.path == path)
-           assert(dir.abspath == abspath)
-           assert(dir.up().path == up_path)
-
-       Dir_test('foo',         'foo/',         sub_dir_foo,            '.')
-       Dir_test('foo/bar',     'foo/bar/',     sub_dir_foo_bar,        'foo/')
-       Dir_test('/foo',        '/foo/',        '/foo/',                '/')
-       Dir_test('/foo/bar',    '/foo/bar/',    '/foo/bar/',            '/foo/')
-       Dir_test('..',          sub,            sub,                    wp)
-       Dir_test('foo/..',      '.',            sub_dir,                sub)
-       Dir_test('../foo',      sub_foo,        sub_foo,                sub)
-       Dir_test('.',           '.',            sub_dir,                sub)
-       Dir_test('./.',         '.',            sub_dir,                sub)
-       Dir_test('foo/./bar',   'foo/bar/',     sub_dir_foo_bar,        'foo/')
-
-       d1 = SCons.Node.FS.lookup(SCons.Node.FS.Dir, 'd1')
-
-       f1 = SCons.Node.FS.lookup(SCons.Node.FS.File, 'f1', directory = d1)
-
-       assert(f1.path == 'd1/f1')
-
-       try:
-           f2 = SCons.Node.FS.lookup(SCons.Node.FS.File, 'f1/f2', directory = d1)
-       except TypeError, x:
-           node = x.args[0]
-           assert(node.path == 'd1/f1')
-           assert(node.__class__.__name__ == 'File')
-       except:
-           raise
-
-       try:
-           dir = SCons.Node.FS.lookup(SCons.Node.FS.Dir, 'd1/f1')
-       except TypeError, x:
-           node = x.args[0]
-           assert(node.path == 'd1/f1')
-           assert(node.__class__.__name__ == 'File')
-       except:
-           raise
-
-       # Test for sub-classing of node building.
-       global built_it
-
-       built_it = None
-       assert not built_it
-       d1.path = "d"           # XXX FAKE SUBCLASS ATTRIBUTE
-       d1.sources = "d"        # XXX FAKE SUBCLASS ATTRIBUTE
-       d1.builder_set(Builder())
-       d1.build()
-       assert built_it
-
-       built_it = None
-       assert not built_it
-       f1.path = "f"           # XXX FAKE SUBCLASS ATTRIBUTE
-       f1.sources = "f"        # XXX FAKE SUBCLASS ATTRIBUTE
-       f1.builder_set(Builder())
-       f1.build()
-       assert built_it
+        """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.
+        """
+        from TestCmd import TestCmd
+
+        test = TestCmd(workdir = '')
+        test.subdir('sub', ['sub', 'dir'])
+
+        wp = test.workpath('')
+        sub = test.workpath('sub', '')
+        sub_dir = test.workpath('sub', 'dir', '')
+        sub_dir_foo = test.workpath('sub', 'dir', 'foo', '')
+        sub_dir_foo_bar = test.workpath('sub', 'dir', 'foo', 'bar', '')
+        sub_foo = test.workpath('sub', 'foo', '')
+
+        os.chdir(sub_dir)
+
+        fs = SCons.Node.FS.FS()
+
+        def Dir_test(lpath, path, abspath, up_path, fileSys=fs):
+            dir = fileSys.Dir(lpath)
+            assert dir.path == path, "Dir.path %s != expected path %s" % \
+                   (dir.path, path)
+            assert dir.abspath == abspath, "Dir.abspath %s != expected abs. path %s" % \
+                   (dir.abspath, path)
+            assert dir.up().path == up_path, "Dir.up().path %s != expected parent path %s" % \
+                   (dir.up().path, up_path)
+
+        Dir_test('foo',         'foo/',         sub_dir_foo,            '.')
+        Dir_test('foo/bar',     'foo/bar/',     sub_dir_foo_bar,        'foo/')
+        Dir_test('/foo',        '/foo/',        '/foo/',                '/')
+        Dir_test('/foo/bar',    '/foo/bar/',    '/foo/bar/',            '/foo/')
+        Dir_test('..',          sub,            sub,                    wp)
+        Dir_test('foo/..',      '.',            sub_dir,                sub)
+        Dir_test('../foo',      sub_foo,        sub_foo,                sub)
+        Dir_test('.',           '.',            sub_dir,                sub)
+        Dir_test('./.',         '.',            sub_dir,                sub)
+        Dir_test('foo/./bar',   'foo/bar/',     sub_dir_foo_bar,        'foo/')
+
+        d1 = fs.Dir('d1')
+
+        f1 = fs.File('f1', directory = d1)
+
+        assert f1.path == 'd1/f1', "f1.path %s != d1/f1" % f1.path
+
+        try:
+            f2 = fs.File('f1/f2', directory = d1)
+        except TypeError, x:
+            node = x.args[0]
+            assert node.path == 'd1/f1', "node.path %s != d1/f1" % node.path
+            assert node.__class__.__name__ == 'File'
+        except:
+            raise
+
+        try:
+            dir = fs.Dir('d1/f1')
+        except TypeError, x:
+            node = x.args[0]
+            assert node.path == 'd1/f1', "node.path %s != d1/f1" % node.path
+            assert node.__class__.__name__ == 'File'
+        except:
+            raise
+
+        # Test for sub-classing of node building.
+        global built_it
+
+        built_it = None
+        assert not built_it
+        d1.path = "d"           # XXX FAKE SUBCLASS ATTRIBUTE
+        d1.sources = "d"        # XXX FAKE SUBCLASS ATTRIBUTE
+        d1.builder_set(Builder())
+        d1.build()
+        assert built_it
+
+        built_it = None
+        assert not built_it
+        f1.path = "f"           # XXX FAKE SUBCLASS ATTRIBUTE
+        f1.sources = "f"        # XXX FAKE SUBCLASS ATTRIBUTE
+        f1.builder_set(Builder())
+        f1.build()
+        assert built_it
 
 
 if __name__ == "__main__":
     suite = unittest.TestSuite()
     suite.addTest(FSTestCase())
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
-       sys.exit(1)
+        sys.exit(1)
index c981737e5bba645e6e93748df2fa169b5fc6b54e..8736787ca1d3445159fe026983b18ada7024b32b 100644 (file)
@@ -516,9 +516,6 @@ def main():
 
     sys.path = include_dirs + sys.path
 
-    # initialize node factory
-    SCons.Node.FS.init()
-
     while Scripts:
         file, Scripts = Scripts[0], Scripts[1:]
        if file == "-":
@@ -538,7 +535,7 @@ def main():
        sys.exit(0)
 
     taskmaster = Taskmaster(map(
-                       lambda x: SCons.Node.FS.lookup(SCons.Node.FS.File, x),
+                       lambda x: SCons.Node.FS.default_fs.File(x),
                        targets))
 
     jobs = SCons.Job.Jobs(num_jobs, taskmaster)