diff options
| author | Steven Knight <knight@baldmt.com> | 2003-05-06 05:58:31 (GMT) |
|---|---|---|
| committer | Steven Knight <knight@baldmt.com> | 2003-05-06 05:58:31 (GMT) |
| commit | aaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d (patch) | |
| tree | c399116a6a33ae9ad69d04dd43c2f272737c2074 /src/engine/SCons/Node | |
| parent | e591784eb29edfaf2760024f57ee1462f1bae1f3 (diff) | |
| download | SCons-aaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d.zip SCons-aaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d.tar.gz SCons-aaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d.tar.bz2 | |
Refactor to use real Nodes for command-line attributes and eliminate PathList. (Charles Crain)
Diffstat (limited to 'src/engine/SCons/Node')
| -rw-r--r-- | src/engine/SCons/Node/FS.py | 95 | ||||
| -rw-r--r-- | src/engine/SCons/Node/FSTests.py | 70 | ||||
| -rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 40 | ||||
| -rw-r--r-- | src/engine/SCons/Node/__init__.py | 43 |
4 files changed, 229 insertions, 19 deletions
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 0a016ad..e64aacc 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -41,11 +41,11 @@ import os.path import shutil import stat import string -from UserDict import UserDict import SCons.Action import SCons.Errors import SCons.Node +import SCons.Util import SCons.Warnings # @@ -190,7 +190,7 @@ class ParentOfRoot: This class is an instance of the Null object pattern. """ def __init__(self): - self.abspath = '' + self.abspath_str = '' self.path = '' self.abspath_ = '' self.path_ = '' @@ -251,14 +251,14 @@ class Entry(SCons.Node.Node): assert directory, "A directory must be provided" - self.abspath = directory.abspath_ + name + self.abspath_str = directory.abspath_ + name if directory.path == '.': self.path = name else: self.path = directory.path_ + name self.path_ = self.path - self.abspath_ = self.abspath + self.abspath_ = self.abspath_str self.dir = directory self.cwd = None # will hold the SConscript directory for target nodes self.duplicate = directory.duplicate @@ -293,11 +293,11 @@ class Entry(SCons.Node.Node): Since this should return the real contents from the file system, we check to see into what sort of subclass we should morph this Entry.""" - if os.path.isfile(self.abspath): + if os.path.isfile(self.abspath_str): self.__class__ = File self._morph() return File.get_contents(self) - if os.path.isdir(self.abspath): + if os.path.isdir(self.abspath_str): self.__class__ = Dir self._morph() return Dir.get_contents(self) @@ -307,7 +307,7 @@ class Entry(SCons.Node.Node): try: return self._exists except AttributeError: - self._exists = _existsp(self.abspath) + self._exists = _existsp(self.abspath_str) return self._exists def rexists(self): @@ -403,6 +403,71 @@ class Entry(SCons.Node.Node): self.sbuilder = scb return scb + def get_base_path(self): + """Return the file's directory and file name, with the + suffix stripped.""" + return os.path.splitext(self.get_path())[0] + + def get_suffix(self): + """Return the file's suffix.""" + return os.path.splitext(self.get_path())[1] + + def get_file_name(self): + """Return the file's name without the path.""" + return self.name + + def get_file_base(self): + """Return the file name with path and suffix stripped.""" + return os.path.splitext(self.name)[0] + + def get_posix_path(self): + """Return the path with / as the path separator, regardless + of platform.""" + if os.sep == '/': + return str(self) + else: + return string.replace(self.get_path(), os.sep, '/') + + def get_abspath(self): + """Get the absolute path of the file.""" + return self.abspath_str + + def get_srcdir(self): + """Returns the directory containing the source node linked to this + node via BuildDir(), or the directory of this node if not linked.""" + return self.srcnode().dir + + dictSpecialAttrs = { "file" : get_file_name, + "base" : get_base_path, + "filebase" : get_file_base, + "suffix" : get_suffix, + "posix" : get_posix_path, + "abspath" : get_abspath, + "srcpath" : srcnode, + "srcdir" : get_srcdir } + + def __getattr__(self, name): + # This is how we implement the "special" attributes + # such as base, suffix, basepath, etc. + # + # Note that we enclose values in a SCons.Util.Literal instance, + # so they will retain special characters during Environment variable + # substitution. + try: + attr = self.dictSpecialAttrs[name](self) + except KeyError: + raise AttributeError, '%s has no attribute: %s' % (self.__class__, name) + if SCons.Util.is_String(attr): + return SCons.Util.SpecialAttrWrapper(attr, self.name + + "_%s" % name) + return attr + + def for_signature(self): + # Return just our name. Even an absolute path would not work, + # because that can change thanks to symlinks or remapped network + # paths. + return self.name + # This is for later so we can differentiate between Entry the class and Entry # the method of the FS class. _classEntry = Entry @@ -491,7 +556,7 @@ class FS: raise SCons.Errors.UserError dir = Dir(drive, ParentOfRoot(), self) dir.path = dir.path + os.sep - dir.abspath = dir.abspath + os.sep + dir.abspath_str = dir.abspath_str + os.sep self.Root[drive] = dir directory = dir path_comp = path_comp[1:] @@ -577,7 +642,7 @@ class FS: if not dir is None: self._cwd = dir if change_os_dir: - os.chdir(dir.abspath) + os.chdir(dir.abspath_str) except: self._cwd = curr raise @@ -785,7 +850,7 @@ class Dir(Entry): node) don't use signatures for currency calculation.""" self.path_ = self.path + os.sep - self.abspath_ = self.abspath + os.sep + self.abspath_ = self.abspath_str + os.sep self.repositories = [] self.srcdir = None @@ -886,9 +951,9 @@ class Dir(Entry): keys = filter(lambda k: k != '.' and k != '..', self.entries.keys()) kids = map(lambda x, s=self: s.entries[x], keys) def c(one, two): - if one.abspath < two.abspath: + if one.abspath_str < two.abspath_str: return -1 - if one.abspath > two.abspath: + if one.abspath_str > two.abspath_str: return 1 return 0 kids.sort(c) @@ -1020,11 +1085,11 @@ class File(Entry): def get_contents(self): if not self.rexists(): return '' - return open(self.rfile().abspath, "rb").read() + return open(self.rfile().abspath_str, "rb").read() def get_timestamp(self): if self.rexists(): - return os.path.getmtime(self.rfile().abspath) + return os.path.getmtime(self.rfile().abspath_str) else: return 0 @@ -1263,7 +1328,7 @@ class File(Entry): # Duplicate from source path if we are set up to do this. if self.duplicate and not self.has_builder() and not self.linked: src=self.srcnode().rfile() - if src.exists() and src.abspath != self.abspath: + if src.exists() and src.abspath_str != self.abspath_str: self._createDir() try: Unlink(self, None, None) diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index f2d8dd9..209e80d 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -193,7 +193,7 @@ class BuildDirTestCase(unittest.TestCase): # Build path does not exist assert not f1.exists() # ...but the actual file is not there... - assert not os.path.exists(f1.abspath) + assert not os.path.exists(f1.get_abspath()) # And duplicate=0 should also work just like a Repository assert f1.rexists() # rfile() should point to the source path @@ -614,9 +614,9 @@ class FSTestCase(unittest.TestCase): assert dir.path_ == path_, \ "dir.path_ %s != expected path_ %s" % \ (dir.path_, path_) - assert dir.abspath == abspath, \ + assert dir.get_abspath() == abspath, \ "dir.abspath %s != expected absolute path %s" % \ - (dir.abspath, abspath) + (dir.get_abspath(), abspath) assert dir.abspath_ == abspath_, \ "dir.abspath_ %s != expected absolute path_ %s" % \ (dir.abspath_, abspath_) @@ -1052,6 +1052,12 @@ class FSTestCase(unittest.TestCase): assert exc_caught, "Should have caught an OSError, r = " + str(r) + f = fs.Entry('foo/bar/baz') + assert f.for_signature() == 'baz', f.for_signature() + assert f.get_string(0) == os.path.normpath('foo/bar/baz'), \ + f.get_string(0) + assert f.get_string(1) == 'baz', f.get_string(1) + class RepositoryTestCase(unittest.TestCase): def runTest(self): @@ -1512,7 +1518,64 @@ class clearTestCase(unittest.TestCase): assert not hasattr(f, '_exists') assert not hasattr(f, '_rexists') +class SpecialAttrTestCase(unittest.TestCase): + def runTest(self): + """Test special attributes of file nodes.""" + test=TestCmd(workdir='') + fs = SCons.Node.FS.FS(test.workpath('')) + f=fs.Entry('foo/bar/baz.blat') + assert str(f.dir) == os.path.normpath('foo/bar'), str(f.dir) + assert f.dir.is_literal() + assert f.dir.for_signature() == 'bar', f.dir.for_signature() + + assert str(f.file) == 'baz.blat', str(f.file) + assert f.file.is_literal() + assert f.file.for_signature() == 'baz.blat_file', \ + f.file.for_signature() + + assert str(f.base) == os.path.normpath('foo/bar/baz'), str(f.base) + assert f.base.is_literal() + assert f.base.for_signature() == 'baz.blat_base', \ + f.base.for_signature() + + assert str(f.filebase) == 'baz', str(f.filebase) + assert f.filebase.is_literal() + assert f.filebase.for_signature() == 'baz.blat_filebase', \ + f.filebase.for_signature() + + assert str(f.suffix) == '.blat', str(f.suffix) + assert f.suffix.is_literal() + assert f.suffix.for_signature() == 'baz.blat_suffix', \ + f.suffix.for_signature() + + assert str(f.abspath) == test.workpath('foo', 'bar', 'baz.blat'), str(f.abspath) + assert f.abspath.is_literal() + assert f.abspath.for_signature() == 'baz.blat_abspath', \ + f.abspath.for_signature() + + assert str(f.posix) == 'foo/bar/baz.blat', str(f.posix) + assert f.posix.is_literal() + if f.posix != f: + assert f.posix.for_signature() == 'baz.blat_posix', \ + f.posix.for_signature() + + fs.BuildDir('foo', 'baz') + + assert str(f.srcpath) == os.path.normpath('baz/bar/baz.blat'), str(f.srcpath) + assert f.srcpath.is_literal() + assert isinstance(f.srcpath, SCons.Node.FS.Entry) + + assert str(f.srcdir) == os.path.normpath('baz/bar'), str(f.srcdir) + assert f.srcdir.is_literal() + assert isinstance(f.srcdir, SCons.Node.FS.Dir) + + # And now, combinations!!! + assert str(f.srcpath.base) == os.path.normpath('baz/bar/baz'), str(f.srcpath.base) + assert str(f.srcpath.dir) == str(f.srcdir), str(f.srcpath.dir) + assert str(f.srcpath.posix) == 'baz/bar/baz.blat', str(f.srcpath.posix) + + if __name__ == "__main__": suite = unittest.TestSuite() @@ -1527,5 +1590,6 @@ if __name__ == "__main__": suite.addTest(SConstruct_dirTestCase()) suite.addTest(CacheDirTestCase()) suite.addTest(clearTestCase()) + suite.addTest(SpecialAttrTestCase()) if not unittest.TextTestRunner().run(suite).wasSuccessful(): sys.exit(1) diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 45c4af9..b188f81 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -294,6 +294,22 @@ class NodeTestCase(unittest.TestCase): c = node.builder_sig_adapter().get_contents() assert c == 7, c + class ListBuilder: + def __init__(self, targets): + self.tgt = targets + def targets(self, t): + return self.tgt + def get_contents(self, target, source, env): + assert target == self.tgt + return 8 + + node1 = SCons.Node.Node() + node2 = SCons.Node.Node() + node.builder_set(ListBuilder([node1, node2])) + node.env_set(Environment()) + c = node.builder_sig_adapter().get_contents() + assert c == 8, c + def test_current(self): """Test the default current() method """ @@ -747,6 +763,30 @@ class NodeTestCase(unittest.TestCase): n1 = MyNode("n1") assert n1.rstr() == 'n1', n1.rstr() + def test_abspath(self): + """Test the get_abspath() method.""" + n = MyNode("foo") + assert n.get_abspath() == str(n), n.get_abspath() + + def test_for_signature(self): + """Test the for_signature() method.""" + n = MyNode("foo") + assert n.for_signature() == str(n), n.get_abspath() + + def test_get_string(self): + """Test the get_string() method.""" + class TestNode(MyNode): + def __init__(self, name, sig): + MyNode.__init__(self, name) + self.sig = sig + + def for_signature(self): + return self.sig + + n = TestNode("foo", "bar") + assert n.get_string(0) == "foo", n.get_string(0) + assert n.get_string(1) == "bar", n.get_string(1) + def test_arg2nodes(self): """Test the arg2nodes function.""" dict = {} diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 1c23684..883d757 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -262,7 +262,7 @@ class Node: def __init__(self, node): self.node = node def get_contents(self): - return self.node.builder.get_contents(self.node, self.node.sources, self.node.generate_build_env()) + return self.node.builder.get_contents(self.node.builder.targets(self.node), self.node.sources, self.node.generate_build_env()) def get_timestamp(self): return None return Adapter(self) @@ -597,6 +597,47 @@ class Node: else: return None + def get_abspath(self): + """ + Return an absolute path to the Node. This will return simply + str(Node) by default, but for Node types that have a concept of + relative path, this might return something different. + """ + return str(self) + + def for_signature(self): + """ + Return a string representation of the Node that will always + be the same for this particular Node, no matter what. This + is by contrast to the __str__() method, which might, for + instance, return a relative path for a file Node. The purpose + of this method is to generate a value to be used in signature + calculation for the command line used to build a target, and + we use this method instead of str() to avoid unnecessary + rebuilds. This method does not need to return something that + would actually work in a command line; it can return any kind of + nonsense, so long as it does not change. + """ + return str(self) + + def get_string(self, for_signature): + """This is a convenience function designed primarily to be + used in command generators (i.e., CommandGeneratorActions or + Environment variables that are callable), which are called + with a for_signature argument that is nonzero if the command + generator is being called to generate a signature for the + command line, which determines if we should rebuild or not. + + Such command generators should use this method in preference + to str(Node) when converting a Node to a string, passing + in the for_signature parameter, such that we will call + Node.for_signature() or str(Node) properly, depending on whether + we are calculating a signature or actually constructing a + command line.""" + if for_signature: + return self.for_signature() + return str(self) + def get_children(node, parent): return node.children() def ignore_cycle(node, stack): pass def do_nothing(node, parent): pass |
