summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Node
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2003-05-06 05:58:31 (GMT)
committerSteven Knight <knight@baldmt.com>2003-05-06 05:58:31 (GMT)
commitaaf2cbb74e00fdc89da432d18e9fe40bb7de3b9d (patch)
treec399116a6a33ae9ad69d04dd43c2f272737c2074 /src/engine/SCons/Node
parente591784eb29edfaf2760024f57ee1462f1bae1f3 (diff)
downloadSCons-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.py95
-rw-r--r--src/engine/SCons/Node/FSTests.py70
-rw-r--r--src/engine/SCons/Node/NodeTests.py40
-rw-r--r--src/engine/SCons/Node/__init__.py43
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