Allow Command() to take directories as sources.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sat, 26 Jul 2003 05:15:18 +0000 (05:15 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sat, 26 Jul 2003 05:15:18 +0000 (05:15 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@749 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/CHANGES.txt
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Node/__init__.py
test/Command.py

index 1b21aa88c65ab3e41d8f648114c5327ea62175db..57b6fa7f0889ea9dc2072feb2140e445fda66a01 100644 (file)
@@ -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
index 47d2e243d7ffc79433020af49b4b002c751b4a96..d0a22b5672129ba983a6273b77e44cc960fcea54 100644 (file)
@@ -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):
index e71ee0df046dd504ac7820eef9e2719c43a64abf..503841989abf6c995f48cfc35854e39578fe69da 100644 (file)
@@ -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)
index 12b88692f47e11c153708c5fddd39ca8a11e5190..09519357f7a035091678a72cbdd1563f6e6e13b6 100644 (file)
@@ -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:
index a80c8f960b242e19a6c6015bc8de0cf3ad8588d5..d651e1cf79872ca5c04f32958963616f04bfb641 100644 (file)
@@ -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())
index 66ffe64da547c207f860f086820b5c1bc7c17fc8..ea61de68e6ec0ef5e85f5b2ff03d6638f24aa3f7 100644 (file)
@@ -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
 
index 21f94323dc9f36a329197ddac238096bbb11702b..18029eec3ff2338bd19a35b1fd8d6779fa0c1d0b 100644 (file)
@@ -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()