summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/CHANGES.txt2
-rw-r--r--src/engine/SCons/Environment.py3
-rw-r--r--src/engine/SCons/EnvironmentTests.py5
-rw-r--r--src/engine/SCons/Node/FS.py174
-rw-r--r--src/engine/SCons/Node/FSTests.py94
-rw-r--r--src/engine/SCons/Node/__init__.py10
-rw-r--r--test/Command.py23
7 files changed, 225 insertions, 86 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 1b21aa8..57b6fa7 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -50,6 +50,8 @@ RELEASE 0.XX - XXX
(or using any other arbitrary action) by making sure all Action
instances have strfunction() methods.
+ - Allow the source of Command() to be a directory.
+
From Gary Oberbrunner:
- Report the target being built in error messages when building
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 47d2e24..d0a22b5 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -409,7 +409,8 @@ class Environment:
source files using the supplied action. Action may
be any type that the Builder constructor will accept
for an action."""
- bld = SCons.Builder.Builder(action=action)
+ bld = SCons.Builder.Builder(action=action,
+ source_factory=SCons.Node.FS.default_fs.Entry)
return bld(self, target, source)
def Install(self, dir, source):
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index e71ee0d..5038419 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -635,6 +635,11 @@ class EnvironmentTestCase(unittest.TestCase):
assert 'foo1.in' in map(lambda x: x.path, t.sources)
assert 'foo2.in' in map(lambda x: x.path, t.sources)
+ sub = SCons.Node.FS.default_fs.Dir('sub')
+ t = env.Command(target='bar.out', source='sub',
+ action='buildbar $target $source')
+ assert 'sub' in map(lambda x: x.path, t.sources)
+
def testFunc(env, target, source):
assert str(target[0]) == 'foo.out'
assert 'foo1.in' in map(str, source) and 'foo2.in' in map(str, source), map(str, source)
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 12b8869..0951935 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -291,20 +291,21 @@ class EntryProxy(SCons.Util.Proxy):
except KeyError:
return SCons.Util.Proxy.__getattr__(self, name)
-class Entry(SCons.Node.Node):
+class Base(SCons.Node.Node):
"""A generic class for file system entries. This class is for
when we don't know yet whether the entry being looked up is a file
or a directory. Instances of this class can morph into either
Dir or File objects by a later, more precise lookup.
- Note: this class does not define __cmp__ and __hash__ for efficiency
- reasons. SCons does a lot of comparing of Entry objects, and so that
- operation must be as fast as possible, which means we want to use
- Python's built-in object identity comparison.
+ Note: this class does not define __cmp__ and __hash__ for
+ efficiency reasons. SCons does a lot of comparing of
+ Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
+ as fast as possible, which means we want to use Python's built-in
+ object identity comparisons.
"""
def __init__(self, name, directory, fs):
- """Initialize a generic file system Entry.
+ """Initialize a generic Node.FS.Base object.
Call the superclass initialization, take care of setting up
our relative and absolute paths, identify our parent
@@ -331,9 +332,9 @@ class Entry(SCons.Node.Node):
self.duplicate = directory.duplicate
def clear(self):
- """Completely clear an Entry of all its cached state (so that it
- can be re-evaluated by interfaces that do continuous integration
- builds).
+ """Completely clear a Node.FS.Base object of all its cached
+ state (so that it can be re-evaluated by interfaces that do
+ continuous integration builds).
"""
SCons.Node.Node.clear(self)
try:
@@ -349,27 +350,12 @@ class Entry(SCons.Node.Node):
return self.dir
def __str__(self):
- """A FS node's string representation is its path name."""
+ """A Node.FS.Base object's string representation is its path
+ name."""
if self.duplicate or self.is_derived():
return self.get_path()
return self.srcnode().get_path()
- def get_contents(self):
- """Fetch the contents of the entry.
-
- 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):
- self.__class__ = File
- self._morph()
- return File.get_contents(self)
- if os.path.isdir(self.abspath):
- self.__class__ = Dir
- self._morph()
- return Dir.get_contents(self)
- raise AttributeError
-
def exists(self):
try:
return self._exists
@@ -438,7 +424,7 @@ class Entry(SCons.Node.Node):
def get_path(self, dir=None):
"""Return path relative to the current working directory of the
- FS object that owns us."""
+ Node.FS.Base object that owns us."""
if not dir:
dir = self.fs.getcwd()
try:
@@ -490,6 +476,75 @@ class Entry(SCons.Node.Node):
self._proxy = ret
return ret
+class Entry(Base):
+ """This is the class for generic Node.FS entries--that is, things
+ that could be a File or a Dir, but we're just not sure yet.
+ Consequently, the methods in this class really exist just to
+ transform their associated object into the right class when the
+ time comes, and then call the same-named method in the transformed
+ class."""
+
+ def rfile(self):
+ """We're a generic Entry, but the caller is actually looking for
+ a File at this point, so morph into one."""
+ self.__class__ = File
+ self._morph()
+ self.clear()
+ return File.rfile(self)
+
+ def get_found_includes(self, env, scanner, target):
+ """If we're looking for included files, it's because this Entry
+ is really supposed to be a File itself."""
+ node = self.rfile()
+ return node.get_found_includes(env, scanner, target)
+
+ def scanner_key(self):
+ return os.path.splitext(self.name)[1]
+
+ def get_contents(self):
+ """Fetch the contents of the entry.
+
+ 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):
+ self.__class__ = File
+ self._morph()
+ return File.get_contents(self)
+ if os.path.isdir(self.abspath):
+ self.__class__ = Dir
+ self._morph()
+ return Dir.get_contents(self)
+ raise AttributeError
+
+ def exists(self):
+ """Return if the Entry exists. Check the file system to see
+ what we should turn into first. Assume a file if there's no
+ directory."""
+ if os.path.isdir(self.abspath):
+ self.__class__ = Dir
+ self._morph()
+ return Dir.exists(self)
+ else:
+ self.__class__ = File
+ self._morph()
+ self.clear()
+ return File.exists(self)
+
+ def calc_signature(self, calc):
+ """Return the Entry's calculated signature. Check the file
+ system to see what we should turn into first. Assume a file if
+ there's no directory."""
+ if os.path.isdir(self.abspath):
+ self.__class__ = Dir
+ self._morph()
+ return Dir.calc_signature(self, calc)
+ else:
+ self.__class__ = File
+ self._morph()
+ self.clear()
+ return File.calc_signature(self, calc)
+
# This is for later so we can differentiate between Entry the class and Entry
# the method of the FS class.
_classEntry = Entry
@@ -680,7 +735,7 @@ class FS:
if not klass:
klass = Entry
- if isinstance(name, Entry):
+ if isinstance(name, Base):
return self.__checkClass(name, klass)
else:
if directory and not isinstance(directory, Dir):
@@ -847,18 +902,12 @@ class FS:
message = "building associated BuildDir targets: %s" % string.join(map(str, targets))
return targets, message
-# XXX TODO?
-# Annotate with the creator
-# rel_path
-# linked_targets
-# is_accessible
-
-class Dir(Entry):
+class Dir(Base):
"""A class for directories in a file system.
"""
def __init__(self, name, directory, fs):
- Entry.__init__(self, name, directory, fs)
+ Base.__init__(self, name, directory, fs)
self._morph()
def _morph(self):
@@ -883,7 +932,7 @@ class Dir(Entry):
self.builder = 1
self._sconsign = None
self.build_dirs = []
-
+
def __clearRepositoryCache(self, duplicate=None):
"""Called when we change the repository(ies) for a directory.
This clears any cached information that is invalidated by changing
@@ -998,6 +1047,10 @@ class Dir(Entry):
"""
return self.fs.build_dir_target_climb(self, [])
+ def scanner_key(self):
+ """A directory does not get scanned."""
+ return None
+
def calc_signature(self, calc):
"""A directory has no signature."""
return None
@@ -1055,20 +1108,13 @@ class Dir(Entry):
have a srcdir attribute set, then that *is* our srcnode."""
if self.srcdir:
return self.srcdir
- return Entry.srcnode(self)
+ return Base.srcnode(self)
-# XXX TODO?
-# base_suf
-# suffix
-# addsuffix
-# accessible
-# relpath
-
-class File(Entry):
+class File(Base):
"""A class for files in a file system.
"""
def __init__(self, name, directory, fs):
- Entry.__init__(self, name, directory, fs)
+ Base.__init__(self, name, directory, fs)
self._morph()
def Entry(self, name):
@@ -1107,6 +1153,9 @@ class File(Entry):
def root(self):
return self.dir.root()
+ def scanner_key(self):
+ return os.path.splitext(self.name)[1]
+
def get_contents(self):
if not self.rexists():
return ''
@@ -1118,29 +1167,6 @@ class File(Entry):
else:
return 0
- def calc_signature(self, calc):
- """
- Select and calculate the appropriate build signature for a File.
-
- self - the File node
- calc - the signature calculation module
- returns - the signature
- """
- try:
- return self._calculated_sig
- except AttributeError:
- if self.is_derived():
- if SCons.Sig.build_signature:
- sig = self.rfile().calc_bsig(calc, self)
- else:
- sig = self.rfile().calc_csig(calc, self)
- elif not self.rexists():
- sig = None
- else:
- sig = self.rfile().calc_csig(calc, self)
- self._calculated_sig = sig
- return sig
-
def store_csig(self):
self.dir.sconsign().set_csig(self.name, self.get_csig())
@@ -1192,9 +1218,6 @@ class File(Entry):
return includes
- def scanner_key(self):
- return os.path.splitext(self.name)[1]
-
def _createDir(self):
# ensure that the directories for this node are
# created.
@@ -1328,7 +1351,6 @@ class File(Entry):
def prepare(self):
"""Prepare for this file to be created."""
-
SCons.Node.Node.prepare(self)
if self.get_state() != SCons.Node.up_to_date:
@@ -1385,7 +1407,7 @@ class File(Entry):
delattr(self, '_rexists')
except AttributeError:
pass
- return Entry.exists(self)
+ return Base.exists(self)
def current(self, calc):
bsig = calc.bsig(self)
@@ -1457,7 +1479,7 @@ def find_file(filename, paths, node_factory = default_fs.File):
node = node_factory(filename, dir)
# Return true of the node exists or is a derived node.
if node.is_derived() or \
- (isinstance(node, SCons.Node.FS.Entry) and node.exists()):
+ (isinstance(node, SCons.Node.FS.Base) and node.exists()):
retval = node
break
except TypeError:
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index a80c8f9..d651e1c 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -987,8 +987,18 @@ class FSTestCase(unittest.TestCase):
#XXX test get_prevsiginfo()
- assert fs.File('foo.x').scanner_key() == '.x'
- assert fs.File('foo.xyz').scanner_key() == '.xyz'
+ skey = fs.Entry('eee.x').scanner_key()
+ assert skey == '.x', skey
+ skey = fs.Entry('eee.xyz').scanner_key()
+ assert skey == '.xyz', skey
+
+ skey = fs.File('fff.x').scanner_key()
+ assert skey == '.x', skey
+ skey = fs.File('fff.xyz').scanner_key()
+ assert skey == '.xyz', skey
+
+ skey = fs.Dir('ddd.x').scanner_key()
+ assert skey is None, skey
d1 = fs.Dir('dir')
f1 = fs.File('dir/file')
@@ -1064,6 +1074,85 @@ class FSTestCase(unittest.TestCase):
f.get_string(0)
assert f.get_string(1) == 'baz', f.get_string(1)
+class EntryTestCase(unittest.TestCase):
+ def runTest(self):
+ """Test methods specific to the Entry sub-class.
+ """
+ test = TestCmd(workdir='')
+ # FS doesn't like the cwd to be something other than its root.
+ os.chdir(test.workpath(""))
+
+ fs = SCons.Node.FS.FS()
+
+ e1 = fs.Entry('e1')
+ e1.rfile()
+ assert e1.__class__ is SCons.Node.FS.File, e1.__class__
+
+ e2 = fs.Entry('e2')
+ e2.get_found_includes(None, None, None)
+ assert e2.__class__ is SCons.Node.FS.File, e2.__class__
+
+ test.subdir('e3d')
+ test.write('e3f', "e3f\n")
+
+ e3d = fs.Entry('e3d')
+ e3d.get_contents()
+ assert e3d.__class__ is SCons.Node.FS.Dir, e3d.__class__
+
+ e3f = fs.Entry('e3f')
+ e3f.get_contents()
+ assert e3f.__class__ is SCons.Node.FS.File, e3f.__class__
+
+ e3n = fs.Entry('e3n')
+ exc_caught = None
+ try:
+ e3n.get_contents()
+ except AttributeError:
+ exc_caught = 1
+ assert exc_caught, "did not catch expected AttributeError"
+
+ test.subdir('e4d')
+ test.write('e4f', "e4f\n")
+
+ e4d = fs.Entry('e4d')
+ exists = e4d.exists()
+ assert e4d.__class__ is SCons.Node.FS.Dir, e4d.__class__
+ assert exists, "e4d does not exist?"
+
+ e4f = fs.Entry('e4f')
+ exists = e4f.exists()
+ assert e4f.__class__ is SCons.Node.FS.File, e4f.__class__
+ assert exists, "e4f does not exist?"
+
+ e4n = fs.Entry('e4n')
+ exists = e4n.exists()
+ assert e4n.__class__ is SCons.Node.FS.File, e4n.__class__
+ assert not exists, "e4n exists?"
+
+ class MyCalc:
+ def __init__(self, val):
+ self.val = val
+ def csig(self, node, cache):
+ return self.val
+ test.subdir('e5d')
+ test.write('e5f', "e5f\n")
+
+ e5d = fs.Entry('e5d')
+ sig = e5d.calc_signature(MyCalc(555))
+ assert e5d.__class__ is SCons.Node.FS.Dir, e5d.__class__
+ assert sig is None, sig
+
+ e5f = fs.Entry('e5f')
+ sig = e5f.calc_signature(MyCalc(666))
+ assert e5f.__class__ is SCons.Node.FS.File, e5f.__class__
+ assert sig == 666, sig
+
+ e5n = fs.Entry('e5n')
+ sig = e5n.calc_signature(MyCalc(777))
+ assert e5n.__class__ is SCons.Node.FS.File, e5n.__class__
+ assert sig is None, sig
+
+
class RepositoryTestCase(unittest.TestCase):
def runTest(self):
@@ -1613,6 +1702,7 @@ if __name__ == "__main__":
suite = unittest.TestSuite()
suite.addTest(FSTestCase())
suite.addTest(BuildDirTestCase())
+ suite.addTest(EntryTestCase())
suite.addTest(RepositoryTestCase())
suite.addTest(find_fileTestCase())
suite.addTest(StringDirTestCase())
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 66ffe64..ea61de6 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -419,15 +419,15 @@ class Node:
try:
return self._calculated_sig
except AttributeError:
- if self.has_builder():
+ if self.is_derived():
if SCons.Sig.build_signature:
- sig = self.calc_bsig(calc)
+ sig = self.rfile().calc_bsig(calc, self)
else:
- sig = self.calc_csig(calc)
- elif not self.exists():
+ sig = self.rfile().calc_csig(calc, self)
+ elif not self.rexists():
sig = None
else:
- sig = self.calc_csig(calc)
+ sig = self.rfile().calc_csig(calc, self)
self._calculated_sig = sig
return sig
diff --git a/test/Command.py b/test/Command.py
index 21f9432..18029ee 100644
--- a/test/Command.py
+++ b/test/Command.py
@@ -31,6 +31,8 @@ python = TestSCons.python
test = TestSCons.TestSCons()
+test.subdir('sub')
+
test.write('build.py', r"""
import sys
contents = open(sys.argv[2], 'rb').read()
@@ -40,6 +42,8 @@ file.close()
""")
test.write('SConstruct', """
+import os
+
def buildIt(env, target, source):
contents = open(str(source[0]), 'rb').read()
file = open(str(target[0]), 'wb')
@@ -47,26 +51,41 @@ def buildIt(env, target, source):
file.close()
return 0
+def sub(env, target, source):
+ target = str(target[0])
+ source = str(source[0])
+ t = open(target, 'wb')
+ files = os.listdir(source)
+ files.sort()
+ for f in files:
+ t.write(open(os.path.join(source, f), 'rb').read())
+ t.close()
+ return 0
+
env = Environment()
env.Command(target = 'f1.out', source = 'f1.in',
action = buildIt)
env.Command(target = 'f2.out', source = 'f2.in',
action = r'%s' + " build.py temp2 $SOURCES\\n" + r'%s' + " build.py $TARGET temp2")
+
env.Command(target = 'f3.out', source = 'f3.in',
action = [ [ r'%s', 'build.py', 'temp3', '$SOURCES' ],
[ r'%s', 'build.py', '$TARGET', 'temp3'] ])
+env.Command(target = 'f4.out', source = 'sub', action = sub)
""" % (python, python, python, python))
test.write('f1.in', "f1.in\n")
-
test.write('f2.in', "f2.in\n")
-
test.write('f3.in', "f3.in\n")
+test.write(['sub', 'f4a'], "sub/f4a\n")
+test.write(['sub', 'f4b'], "sub/f4b\n")
+test.write(['sub', 'f4c'], "sub/f4c\n")
test.run(arguments = '.')
test.fail_test(test.read('f1.out') != "f1.in\n")
test.fail_test(test.read('f2.out') != "f2.in\n")
test.fail_test(test.read('f3.out') != "f3.in\n")
+test.fail_test(test.read('f4.out') != "sub/f4a\nsub/f4b\nsub/f4c\n")
test.pass_test()