diff options
-rw-r--r-- | src/CHANGES.txt | 2 | ||||
-rw-r--r-- | src/engine/SCons/Environment.py | 3 | ||||
-rw-r--r-- | src/engine/SCons/EnvironmentTests.py | 5 | ||||
-rw-r--r-- | src/engine/SCons/Node/FS.py | 174 | ||||
-rw-r--r-- | src/engine/SCons/Node/FSTests.py | 94 | ||||
-rw-r--r-- | src/engine/SCons/Node/__init__.py | 10 | ||||
-rw-r--r-- | test/Command.py | 23 |
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() |