Commits

Anonymous committed a32ed91

Crain: Finish LIBS, LIBPATH, CPPPATH

Comments (0)

Files changed (22)

src/engine/MANIFEST.in

 SCons/Node/FS.py
 SCons/Scanner/__init__.py
 SCons/Scanner/C.py
+SCons/Scanner/Prog.py
 SCons/Sig/__init__.py
 SCons/Sig/MD5.py
 SCons/Sig/TimeStamp.py

src/engine/SCons/Builder.py

 			prefix = '',
 			suffix = '',
 			src_suffix = '',
-			node_factory = SCons.Node.FS.default_fs.File):
+                        node_factory = SCons.Node.FS.default_fs.File,
+                        scanner = None):
 	self.name = name
 	self.action = Action(action)
 
 	self.suffix = suffix
 	self.src_suffix = src_suffix
 	self.node_factory = node_factory
+        self.scanner = scanner
         if self.suffix and self.suffix[0] not in '.$':
 	    self.suffix = '.' + self.suffix
         if self.src_suffix and self.src_suffix[0] not in '.$':
 	    if not type(files) is type([]):
 	        files = [files]
 	    for f in files:
-		if type(f) == type(""):
+                if type(f) is types.StringType or isinstance(f, UserString):
 		    if pre and f[:len(pre)] != pre:
-		        f = pre + f
+                        path, fn = os.path.split(os.path.normpath(f))
+                        f = os.path.join(path, pre + fn)
 		    if suf:
 		        if f[-len(suf):] != suf:
 		            f = f + suf
 	    t.builder_set(self)
 	    t.env_set(env)
 	    t.add_source(slist)
+            if self.scanner:
+                t.scanner_set(self.scanner)
 
 	for s in slist:
 	    s.env_set(env, 1)
 			prefix = '',
 			suffix = '',
 			src_suffix = '',
-			node_factory = SCons.Node.FS.default_fs.File):
+                        node_factory = SCons.Node.FS.default_fs.File,
+                        scanner=None):
         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
-                             node_factory)
+                             node_factory, scanner)
         self.src_builder = src_builder
 
     def __call__(self, env, target = None, source = None):

src/engine/SCons/BuilderTests.py

 import TestCmd
 import SCons.Builder
 
-
 # Initial setup of the common environment for all tests,
 # a temporary working directory containing a
 # script for writing arguments to an output file.
 	class Foo:
 	    pass
 	def FooFactory(target):
+            global Foo
 	    return Foo(target)
 	builder = SCons.Builder.Builder(node_factory = FooFactory)
 	assert builder.node_factory is FooFactory
             flag = 1
         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
 
+    def test_build_scanner(self):
+        """Testing ability to set a target scanner through a builder."""
+        class TestScanner:
+            pass
+        scn = TestScanner()
+        builder=SCons.Builder.Builder(scanner=scn)
+        tgt = builder(env, target='foo', source='bar')
+        assert tgt.scanner == scn, tgt.scanner
+
+        builder1 = SCons.Builder.Builder(action='foo',
+                                         src_suffix='.bar',
+                                         suffix='.foo')
+        builder2 = SCons.Builder.Builder(action='foo',
+                                         src_builder = builder1,
+                                         scanner = scn)
+        tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt')
+        assert tgt.scanner == scn, tgt.scanner
 
 if __name__ == "__main__":
     suite = unittest.makeSuite(BuilderTestCase, 'test_')

src/engine/SCons/Defaults.py

 import os
 import SCons.Builder
 import SCons.Scanner.C
-
+import SCons.Scanner.Prog
 
 Object = SCons.Builder.Builder(name = 'Object',
                                action = { '.c'   : '$CCCOM',
                                 prefix = '$PROGPREFIX',
                                 suffix = '$PROGSUFFIX',
                                 src_suffix = '$OBJSUFFIX',
-                                src_builder = Object)
+                                src_builder = Object,
+                                scanner = SCons.Scanner.Prog.ProgScan())
 
 Library = SCons.Builder.Builder(name = 'Library',
                                 action = '$ARCOM',
     ConstructionEnvironment = {
         'CC'         : 'cc',
         'CCFLAGS'    : '',
-        'CCCOM'      : '$CC $CCFLAGS -c -o $TARGET $SOURCES',
+        'CCCOM'      : '$CC $CCFLAGS $_INCFLAGS -c -o $TARGET $SOURCES',
         'CXX'        : 'c++',
         'CXXFLAGS'   : '$CCFLAGS',
-        'CXXCOM'     : '$CXX $CXXFLAGS -c -o $TARGET $SOURCES',
+        'CXXCOM'     : '$CXX $CXXFLAGS $_INCFLAGS -c -o $TARGET $SOURCES',
         'LINK'       : '$CXX',
         'LINKFLAGS'  : '',
-        'LINKCOM'    : '$LINK $LINKFLAGS -o $TARGET $SOURCES',
+        'LINKCOM'    : '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS',
         'AR'         : 'ar',
         'ARFLAGS'    : 'r',
         'ARCOM'      : '$AR $ARFLAGS $TARGET $SOURCES\nranlib $TARGET',
         'LIBDIRSUFFIX'          : '',
         'LIBLINKPREFIX'         : '-l',
         'LIBLINKSUFFIX'         : '',
+        'INCPREFIX'             : '-I',
+        'INCSUFFIX'             : '',
         'ENV'        : { 'PATH' : '/usr/local/bin:/bin:/usr/bin' },
     }
 
     ConstructionEnvironment = {
         'CC'         : 'cl',
         'CCFLAGS'    : '/nologo',
-        'CCCOM'      : '$CC $CCFLAGS /c $SOURCES /Fo$TARGET',
+        'CCCOM'      : '$CC $CCFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET',
         'CXX'        : '$CC',
         'CXXFLAGS'   : '$CCFLAGS',
-        'CXXCOM'     : '$CXX $CXXFLAGS /c $SOURCES /Fo$TARGET',
+        'CXXCOM'     : '$CXX $CXXFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET',
         'LINK'       : 'link',
         'LINKFLAGS'  : '',
-        'LINKCOM'    : '$LINK $LINKFLAGS /out:$TARGET $SOURCES',
+        'LINKCOM'    : '$LINK $LINKFLAGS /out:$TARGET $_LIBDIRFLAGS $_LIBFLAGS $SOURCES',
         'AR'         : 'lib',
         'ARFLAGS'    : '/nologo',
         'ARCOM'      : '$AR $ARFLAGS /out:$TARGET $SOURCES',
         'LIBDIRSUFFIX'          : '',
         'LIBLINKPREFIX'         : '',
         'LIBLINKSUFFIX'         : '$LIBSUFFIX',
+        'INCPREFIX'             : '/I',
+        'INCSUFFIX'             : '',
         'ENV'        : {
                         'PATH'    : r'C:\Python20;C:\WINNT\system32;C:\WINNT;C:\Program Files\Microsoft Visual Studio\VC98\Bin\;',
                         'PATHEXT' : '.COM;.EXE;.BAT;.CMD',

src/engine/SCons/Environment.py

                       ( '_LIBDIRFLAGS',
                         'LIBPATH',
                         'LIBDIRPREFIX',
-                        'LIBDIRSUFFIX' ) )
+                        'LIBDIRSUFFIX' ),
+                      ( '_INCFLAGS',
+                        'CPPPATH',
+                        'INCPREFIX',
+                        'INCSUFFIX' ) )
 
     def __init__(self, **kw):
 	import SCons.Defaults

src/engine/SCons/EnvironmentTests.py

         assert env.Dictionary('_LIBFLAGS')[2] == 'foobazbar', \
                env.Dictionary('_LIBFLAGS')[2]
 
+        env = Environment(CPPPATH = [ 'foo', 'bar', 'baz' ],
+                          INCPREFIX = 'foo',
+                          INCSUFFIX = 'bar')
+        assert len(env.Dictionary('_INCFLAGS')) == 3, env.Dictionary('_INCFLAGS')
+        assert env.Dictionary('_INCFLAGS')[0] == 'foofoobar', \
+               env.Dictionary('_INCFLAGS')[0]
+        assert env.Dictionary('_INCFLAGS')[1] == 'foobarbar', \
+               env.Dictionary('_INCFLAGS')[1]
+        assert env.Dictionary('_INCFLAGS')[2] == 'foobazbar', \
+               env.Dictionary('_INCFLAGS')[2]
+        
+
 if __name__ == "__main__":
     suite = unittest.makeSuite(EnvironmentTestCase, 'test_')
     if not unittest.TextTestRunner().run(suite).wasSuccessful():

src/engine/SCons/Node/FS.py

 	return self.path
 
     def exists(self):
-        return os.path.exists(self.path)
+        return os.path.exists(self.abspath)
 
     def current(self):
         """If the underlying path doesn't exist, we know the node is
 	kids = map(lambda x, s=self: s.entries[x],
 		   filter(lambda k: k != '.' and k != '..',
 			  self.entries.keys()))
-	kids.sort()
+	def c(one, two):
+            if one.abspath < two.abspath:
+               return -1
+            if one.abspath > two.abspath:
+               return 1
+            return 0
+	kids.sort(c)
 	return kids
 
     def build(self):
                 self.add_dependency(scanner.scan(self.path_, self.env))
             self.scanned = 1
 
+    def __createDir(self):
+        # ensure that the directories for this node are
+        # created.
+
+        listPaths = []
+        strPath = self.abspath
+        while 1:
+            strPath, strFile = os.path.split(strPath)
+            if os.path.exists(strPath):
+                break
+            listPaths.append(strPath)
+            if not strFile:
+                break
+        listPaths.reverse()
+        for strPath in listPaths:
+            try:
+                os.mkdir(strPath)
+            except OSError:
+                pass
+
+    def build(self):
+        self.__createDir()
+        Entry.build(self)
 
 default_fs = FS()

src/engine/SCons/Node/FSTests.py

         f1.scan()
         assert f1.depends[0].path_ == "d1/f1"
 
+        # Test building a file whose directory is not there yet...
+        f1 = fs.File(test.workpath("foo/bar/baz/ack"))
+        assert not f1.dir.exists()
+        f1.build()
+        assert f1.dir.exists()
+        
         #XXX test exists()
 
         #XXX test current() for directories

src/engine/SCons/Node/NodeTests.py

 	nw = SCons.Node.Walker(n1)
 	assert nw.next().name ==  "n4"
 	assert nw.next().name ==  "n5"
+        assert nw.history.has_key(n2)
 	assert nw.next().name ==  "n2"
 	assert nw.next().name ==  "n6"
 	assert nw.next().name ==  "n7"
+        assert nw.history.has_key(n3)
 	assert nw.next().name ==  "n3"
+        assert nw.history.has_key(n1)
 	assert nw.next().name ==  "n1"
 	assert nw.next() == None
 

src/engine/SCons/Node/__init__.py

     returns the next node on the descent with each next() call.
     'kids_func' is an optional function that will be called to
     get the children of a node instead of calling 'children'.
+    
+    This class does not get caught in node cycles caused, for example,
+    by C header file include loops.
     """
     def __init__(self, node, kids_func=get_children):
         self.kids_func = kids_func
         self.stack = [Wrapper(node, self.kids_func)]
+        self.history = {} # used to efficiently detect and avoid cycles
+        self.history[node] = None
 
     def next(self):
 	"""Return the next node for this walk of the tree.
 
 	while self.stack:
 	    if self.stack[-1].kids:
-	    	self.stack.append(Wrapper(self.stack[-1].kids.pop(0),
-                                          self.kids_func))
+                node = self.stack[-1].kids.pop(0)
+                if not self.history.has_key(node):
+                    self.stack.append(Wrapper(node, self.kids_func))
+                    self.history[node] = None
             else:
-                return self.stack.pop().node
+                node = self.stack.pop().node
+                del self.history[node]
+                return node
 
     def is_done(self):
         return not self.stack

src/engine/SCons/Scanner/C.py

 import SCons.Scanner
 import re
 import os.path
+import SCons.Util
 
 angle_re = re.compile('^[ \t]*#[ \t]*include[ \t]+<([\\w./\\\\]+)>', re.M)
 quote_re = re.compile('^[ \t]*#[ \t]*include[ \t]+"([\\w./\\\\]+)"', re.M)
     s.name = "CScan"
     return s
 
-def find_files(filenames, paths):
-    """
-    find_files([str], [str]) -> [str]
-
-    filenames - a list of filenames to find
-    paths - a list of paths to search in
-
-    returns - the fullnames of the files
-
-    Only the first fullname found is returned for each filename, and any
-    file that aren't found are ignored.
-    """
-    fullnames = []
-    for filename in filenames:
-        for path in paths:
-            fullname = os.path.join(path, filename)
-            if os.path.exists(fullname):
-                fullnames.append(fullname)
-                break
-
-    return fullnames
-
 def scan(filename, env, node_factory):
     """
     scan(str, Environment) -> [str]
     dependencies.
     """
 
-    if hasattr(env, "CPPPATH"):
-        paths = env.CPPPATH
-    else:
+    try:
+        paths = env.Dictionary("CPPPATH")
+    except KeyError:
         paths = []
+
+    try:
+        file = open(filename)
+        contents = file.read()
+        file.close()
+
+        angle_includes = angle_re.findall(contents)
+        quote_includes = quote_re.findall(contents)
+
+        source_dir = os.path.dirname(filename)
         
-    file = open(filename)
-    contents = file.read()
-    file.close()
-
-    angle_includes = angle_re.findall(contents)
-    quote_includes = quote_re.findall(contents)
-
-    source_dir = os.path.dirname(filename)
-    
-    deps = (find_files(angle_includes, paths + [source_dir])
-            + find_files(quote_includes, [source_dir] + paths))
-
-    deps = map(node_factory, deps)
-    return deps
+        return (SCons.Util.find_files(angle_includes, paths + [source_dir],
+                                      node_factory)
+                + SCons.Util.find_files(quote_includes, [source_dir] + paths,
+                                        node_factory))
+    except OSError:
+        return []

src/engine/SCons/Scanner/CTests.py

 import SCons.Scanner.C
 import unittest
 import sys
+import os.path
 
 test = TestCmd.TestCmd(workdir = '')
 
 # define some helpers:
 
 class DummyEnvironment:
-    pass
+    def __init__(self, listCppPath):
+        self.path = listCppPath
+        
+    def Dictionary(self, *args):
+        if not args:
+            return { 'CPPPATH': self.path }
+        elif len(args) == 1 and args[0] == 'CPPPATH':
+            return self.path
+        else:
+            raise KeyError, "Dummy environment only has CPPPATH attribute."
 
 def deps_match(deps, headers):
-    return deps.sort() == map(test.workpath, headers).sort()
+    deps = map(str, deps)
+    headers = map(test.workpath, headers)
+    deps.sort()
+    headers.sort()
+    return map(os.path.normpath, deps) == \
+           map(os.path.normpath, headers)
 
 # define some tests:
 
 class CScannerTestCase1(unittest.TestCase):
     def runTest(self):
-        env = DummyEnvironment
+        env = DummyEnvironment([])
         s = SCons.Scanner.C.CScan()
         deps = s.scan(test.workpath('f1.cpp'), env)
-        self.failUnless(deps_match(deps, ['f1.h', 'f2.h']))
+        self.failUnless(deps_match(deps, ['f1.h', 'f2.h']), map(str, deps))
 
 class CScannerTestCase2(unittest.TestCase):
     def runTest(self):
-        env = DummyEnvironment
-        env.CPPPATH = [test.workpath("d1")]
+        env = DummyEnvironment([test.workpath("d1")])
         s = SCons.Scanner.C.CScan()
         deps = s.scan(test.workpath('f1.cpp'), env)
         headers = ['f1.h', 'd1/f2.h']
-        self.failUnless(deps_match(deps, headers)) 
+        self.failUnless(deps_match(deps, headers), map(str, deps))
 
 class CScannerTestCase3(unittest.TestCase):
     def runTest(self):
-        env = DummyEnvironment
-        env.CPPPATH = [test.workpath("d1")]
+        env = DummyEnvironment([test.workpath("d1")])
         s = SCons.Scanner.C.CScan()
         deps = s.scan(test.workpath('f2.cpp'), env)
-        headers = ['f1.h', 'd1/f2.h', 'd1/d2/f1.h']
-        self.failUnless(deps_match(deps, headers))
-                  
+        headers = ['f1.h', 'd1/f1.h', 'd1/d2/f1.h']
+        self.failUnless(deps_match(deps, headers), map(str, deps))
 
 class CScannerTestCase4(unittest.TestCase):
     def runTest(self):
-        env = DummyEnvironment
-        env.CPPPATH = [test.workpath("d1"), test.workpath("d1/d2")]
+        env = DummyEnvironment([test.workpath("d1"), test.workpath("d1/d2")])
         s = SCons.Scanner.C.CScan()
         deps = s.scan(test.workpath('f2.cpp'), env)
-        headers =  ['f1.h', 'd1/f2.h', 'd1/d2/f1.h', 'd1/d2/f4.h']
-        self.failUnless(deps_match(deps, headers))
+        headers =  ['f1.h', 'd1/f1.h', 'd1/d2/f1.h', 'd1/d2/f4.h']
+        self.failUnless(deps_match(deps, headers), map(str, deps))
         
 class CScannerTestCase5(unittest.TestCase):
     def runTest(self):
-        env = DummyEnvironment
+        env = DummyEnvironment([])
         s = SCons.Scanner.C.CScan()
         deps = s.scan(test.workpath('f3.cpp'), env)
         headers =  ['f1.h', 'f2.h', 'f3.h', 'd1/f1.h', 'd1/f2.h', 'd1/f3.h']
-        self.failUnless(deps_match(deps, headers))
+        self.failUnless(deps_match(deps, headers), map(str, deps))
 
 def suite():
     suite = unittest.TestSuite()

src/engine/SCons/Scanner/Prog.py

+#
+# Copyright (c) 2001 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Scanner
+import SCons.Node.FS
+import SCons.Util
+
+def ProgScan():
+    """Return a Scanner instance for scanning executable files
+    for static-lib dependencies"""
+    s = SCons.Scanner.Scanner(scan, SCons.Node.FS.default_fs.File)
+    s.name = "ProgScan"
+    return s
+
+def scan(filename, env, node_factory):
+    """
+    This scanner scans program files for static-library
+    dependencies.  It will search the LIBPATH environment variable
+    for libraries specified in the LIBS variable, returning any
+    files it finds as dependencies.
+    """
+
+    try:
+        paths = env.Dictionary("LIBPATH")
+    except KeyError:
+        paths = []
+
+    try:
+        libs = env.Dictionary("LIBS")
+    except KeyError:
+        libs = []
+
+    try:
+        prefix = env.Dictionary("LIBPREFIX")
+    except KeyError:
+        prefix=''
+
+    try:
+        suffix = env.Dictionary("LIBSUFFIX")
+    except KeyError:
+        suffix=''
+
+    libs = map(lambda x, s=suffix, p=prefix: p + x + s, libs)
+    return SCons.Util.find_files(libs, paths, node_factory)

src/engine/SCons/Scanner/ProgTests.py

+#
+# Copyright (c) 2001 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestCmd
+import SCons.Scanner.Prog
+import unittest
+import sys
+import os.path
+
+test = TestCmd.TestCmd(workdir = '')
+
+test.subdir('d1', ['d1', 'd2'])
+
+libs = [ 'l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib' ]
+
+for h in libs:
+    test.write(h, " ")
+
+# define some helpers:
+
+class DummyEnvironment:
+    def __init__(self, **kw):
+        self._dict = kw
+        self._dict['LIBSUFFIX'] = '.lib'
+        
+    def Dictionary(self, *args):
+        if not args:
+            return self._dict
+        elif len(args) == 1:
+            return self._dict[args[0]]
+        else:
+            return map(lambda x, s=self: s._dict[x], args)
+
+def deps_match(deps, libs):
+    deps=map(str, deps)
+    deps.sort()
+    libs.sort()
+    return map(os.path.normpath, deps) == \
+           map(os.path.normpath,
+               map(test.workpath, libs))
+
+# define some tests:
+
+class ProgScanTestCase1(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment(LIBPATH=[ test.workpath("") ],
+                               LIBS=[ 'l1', 'l2', 'l3' ])
+        s = SCons.Scanner.Prog.ProgScan()
+        deps = s.scan('dummy', env)
+        assert deps_match(deps, ['l1.lib']), map(str, deps)
+
+class ProgScanTestCase2(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment(LIBPATH=map(test.workpath,
+                                           ["", "d1", "d1/d2" ]),
+                               LIBS=[ 'l1', 'l2', 'l3' ])
+        s = SCons.Scanner.Prog.ProgScan()
+        deps = s.scan('dummy', env)
+        assert deps_match(deps, ['l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib' ]), map(str, deps)
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(ProgScanTestCase1())
+    suite.addTest(ProgScanTestCase2())
+    return suite
+
+if __name__ == "__main__":
+    runner = unittest.TextTestRunner()
+    result = runner.run(suite())
+    if not result.wasSuccessful():
+        sys.exit(1)

src/engine/SCons/Sig/MD5Tests.py

 
 import sys
 import unittest
+import string
 
 from SCons.Sig.MD5 import current, collect, signature, to_string, from_string
 
         try:
             signature('string')
         except AttributeError, e:
-            assert str(e) == "unable to fetch contents of 'string': 'string' object has no attribute 'get_contents'", e
+            # the error string should begin with "unable to fetch contents of 'string': "
+            assert string.find(str(e), "unable to fetch contents of 'string':") == 0
         else:
             raise AttributeError, "unexpected get_contents() attribute"
 

src/engine/SCons/Sig/SigTests.py

         self.failUnless(current(calc, nodes[4]))
         self.failUnless(current(calc, nodes[5]))
         self.failUnless(not current(calc, nodes[6]), "modified directly")
-        self.failUnless(current(calc, nodes[7]), "indirect source modified")
+        self.failUnless(not current(calc, nodes[7]), "indirect source modified")
         self.failUnless(not current(calc, nodes[8]), "modified directory")
         self.failUnless(not current(calc, nodes[9]), "direct source modified")
         self.failUnless(not current(calc, nodes[10]), "indirect source modified")

src/engine/SCons/Sig/__init__.py

 
 import os.path
 import string
-
+import SCons.Node
 
 #XXX Get rid of the global array so this becomes re-entrant.
 sig_files = []
             return None
         #XXX If configured, use the content signatures from the
         #XXX .sconsign file if the timestamps match.
-        sigs = map(lambda n,s=self: s.get_signature(n), node.children())
+
+        # Collect the signatures for ALL the nodes that this
+        # node depends on. Just collecting the direct
+        # dependants is not good enough, because
+        # the signature of a non-derived file does
+        # not include the signatures of its psuedo-sources
+        # (e.g. the signature for a .c file does not include
+        # the signatures of the .h files that it includes).
+        walker = SCons.Node.Walker(node)
+        sigs = []
+        while 1:
+            child = walker.next()
+            if child is None: break
+            if child is node: continue # skip the node itself
+            sigs.append(self.get_signature(child))
+
         if node.builder:
             sigs.append(self.module.signature(node.builder_sig_adapter()))
         return self.module.collect(filter(lambda x: not x is None, sigs))

src/engine/SCons/Util.py

     """
     cmd_list = scons_subst_list(strSubst, locals, globals)
     return string.join(map(string.join, cmd_list), '\n')
+
+def find_files(filenames, paths,
+               node_factory = SCons.Node.FS.default_fs.File):
+    """
+    find_files([str], [str]) -> [nodes]
+
+    filenames - a list of filenames to find
+    paths - a list of paths to search in
+
+    returns - the nodes created from the found files.
+
+    Finds nodes corresponding to either derived files or files
+    that exist already.
+
+    Only the first fullname found is returned for each filename, and any
+    file that aren't found are ignored.
+    """
+    nodes = []
+    for filename in filenames:
+        for path in paths:
+            fullname = os.path.join(path, filename)
+            try:
+                node = node_factory(fullname)
+                # Return true of the node exists or is a derived node.
+                if node.builder or \
+                   (isinstance(node, SCons.Node.FS.Entry) and node.exists()):
+                    nodes.append(node)
+                    break
+            except TypeError:
+                # If we find a directory instead of a file, we
+                # don't care
+                pass
+
+    return nodes

src/engine/SCons/UtilTests.py

 import unittest
 import SCons.Node
 import SCons.Node.FS
-from SCons.Util import scons_str2nodes, scons_subst, PathList, scons_subst_list
-
+from SCons.Util import *
+import TestCmd
 
 class UtilTestCase(unittest.TestCase):
     def test_str2nodes(self):
         assert len(cmd_list) == 2, cmd_list
         assert cmd_list[1][0] == 'after', cmd_list[1][0]
         assert cmd_list[0][2] == cvt('../foo/ack.cbefore'), cmd_list[0][2]
+
+    def test_find_files(self):
+        """Testing find_files function."""
+        test = TestCmd.TestCmd(workdir = '')
+        test.write('./foo', 'Some file\n')
+        fs = SCons.Node.FS.FS(test.workpath(""))
+        node_derived = fs.File(test.workpath('./bar/baz'))
+        node_derived.builder_set(1) # Any non-zero value.
+        nodes = find_files(['foo', 'baz'],
+                           map(test.workpath, ['./', './bar' ]), fs.File)
+        file_names = map(str, nodes)
+        file_names = map(os.path.normpath, file_names)
+        assert os.path.normpath('./foo') in file_names, file_names
+        assert os.path.normpath('./bar/baz') in file_names, file_names
+        
         
 if __name__ == "__main__":
     suite = unittest.makeSuite(UtilTestCase, 'test_')
 
 test = TestSCons.TestSCons()
 
-test.pass_test()	#XXX Short-circuit until this is implemented.
+test.write('foo.c',
+"""#include "include/foo.h"
+#include <stdio.h>
+
+int main(void)
+{
+    printf(TEST_STRING);
+    return 0;
+}
+""")
+
+test.subdir('include')
+
+test.write('include/foo.h',
+"""
+#define TEST_STRING "Bad news\n"
+""")
 
 test.write('SConstruct', """
+env = Environment()
+env.Program(target='prog', source='foo.c')
+#env.Depends(target='foo.c', dependency='include/foo.h')
 """)
 
-test.run(arguments = '.')
+test.run(arguments = 'prog')
+
+test.run(program = test.workpath('prog'),
+         stdout = "Bad news\n")
+
+test.unlink('include/foo.h')
+test.write('include/foo.h',
+"""
+#define TEST_STRING "Good news\n"
+""")
+
+test.run(arguments = 'prog')
+
+test.run(program = test.workpath('prog'),
+         stdout = "Good news\n")
 
 test.pass_test()
 
 test = TestSCons.TestSCons()
 
-test.pass_test()	#XXX Short-circuit until this is implemented.
-
 test.write('SConstruct', """
+env = Environment(LIBS = [ 'foo1' ],
+                  LIBPATH = [ './libs' ])
+env.Program(target = 'prog', source = 'prog.c')
+env.Library(target = './libs/foo1', source = 'f1.c')
 """)
 
-test.run(arguments = '.')
+test.write('f1.c', """
+void
+f1(void)
+{
+	printf("f1.c\n");
+}
+""")
+
+test.write('prog.c', """
+void f1(void);
+int
+main(int argc, char *argv[])
+{
+	argv[argc++] = "--";
+	f1();
+	printf("prog.c\n");
+        return 0;
+}
+""")
+
+test.run(arguments = 'prog')
+
+test.run(program = test.workpath('prog'),
+         stdout = "f1.c\nprog.c\n")
 
 test.pass_test()
 
 test = TestSCons.TestSCons()
 
-#XXX Need to switch TestBld to Program() when LIBS variable is working.
 test.write('SConstruct', """
-TestBld = Builder(name='TestBld',
-                  action='cc -o $TARGET $SOURCES -L./ -lfoo1 -lfoo2 -lfoo3')
-env = Environment(BUILDERS=[ TestBld, Library ])
+env = Environment(LIBS = [ 'foo1', 'foo2', 'foo3' ],
+                  LIBPATH = [ './' ])
 env.Library(target = 'foo1', source = 'f1.c')
 env.Library(target = 'foo2', source = 'f2a.c f2b.c f2c.c')
 env.Library(target = 'foo3', source = ['f3a.c', 'f3b.c', 'f3c.c'])
-env.TestBld(target = 'prog', source = 'prog.c')
-env.Depends(target = 'prog', dependency = 'libfoo1.a libfoo2.a libfoo3.a')
+env.Program(target = 'prog', source = 'prog.c')
 """)
 
 test.write('f1.c', """
 }
 """)
 
-test.run(arguments = 'libfoo1.a libfoo2.a libfoo3.a prog')
+test.run(arguments = 'prog')
 
 test.run(program = test.workpath('prog'),
          stdout = "f1.c\nf2a.c\nf2b.c\nf2c.c\nf3a.c\nf3b.c\nf3c.c\nprog.c\n")