summaryrefslogtreecommitdiffstats
path: root/src/engine
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2001-09-19 12:16:13 (GMT)
committerSteven Knight <knight@baldmt.com>2001-09-19 12:16:13 (GMT)
commit7cd353fbcb81398e12a5a6b564eb4288449b4bc6 (patch)
tree0fe333cf1e2379c653b120e0fc416b3c97b66838 /src/engine
parent21d1baa81a1e83002c197c8a49a81417856ce15d (diff)
downloadSCons-7cd353fbcb81398e12a5a6b564eb4288449b4bc6.zip
SCons-7cd353fbcb81398e12a5a6b564eb4288449b4bc6.tar.gz
SCons-7cd353fbcb81398e12a5a6b564eb4288449b4bc6.tar.bz2
Clean up the Node.FS class.
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/SCons/Builder.py6
-rw-r--r--src/engine/SCons/BuilderTests.py10
-rw-r--r--src/engine/SCons/Node/FS.py283
-rw-r--r--src/engine/SCons/Node/FSTests.py175
4 files changed, 299 insertions, 175 deletions
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index 6dda645..53e4bcc 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -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
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
index e19b389..ab7e122 100644
--- a/src/engine/SCons/BuilderTests.py
+++ b/src/engine/SCons/BuilderTests.py
@@ -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
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 2d1c8ea..9cd0607 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -1,89 +1,188 @@
-"""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()
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index 24785da..f95837f 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -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)