Commits

Anonymous committed d67fcb7

Clean up the Node.FS class.

  • Participants
  • Parent commits 51db5a3

Comments (0)

Files changed (5)

File src/engine/SCons/Builder.py

 			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] != '.':
 	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

File src/engine/SCons/BuilderTests.py

 	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

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

-"""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
 
-Top = None
-Root = {}
+    def __init__(self, path_name=''):
+        self.data = PathName.convert_path(path_name)
+        self.norm_path = os.path.normcase(self.data)
 
+    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.
 
-def init(path = None):
-    """Initialize the Node.FS subsystem.
+    All keys are implicitly converted to PathName objects before
+    insertion into the dictionary."""
 
-    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 __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
 
-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
 # 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?
 # 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()

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

 
 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 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'])
+        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', '')
+        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)
+        os.chdir(sub_dir)
 
-	SCons.Node.FS.init()
+        fs = SCons.Node.FS.FS()
 
-	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)
+        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/')
+        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')
+        d1 = fs.Dir('d1')
 
-	f1 = SCons.Node.FS.lookup(SCons.Node.FS.File, 'f1', directory = d1)
+        f1 = fs.File('f1', directory = d1)
 
-	assert(f1.path == 'd1/f1')
+        assert f1.path == 'd1/f1', "f1.path %s != d1/f1" % f1.path
 
-	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:
+            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 = 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
+        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
+        # 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
+        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
+        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)

File src/script/scons.py

 
     sys.path = include_dirs + sys.path
 
-    # initialize node factory
-    SCons.Node.FS.init()
-
     while Scripts:
         file, Scripts = Scripts[0], Scripts[1:]
 	if file == "-":
 	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)