Crain: Finish LIBS, LIBPATH, CPPPATH
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 20 Nov 2001 17:58:56 +0000 (17:58 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 20 Nov 2001 17:58:56 +0000 (17:58 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@120 fdb21ef1-2011-0410-befe-b5e4ea1792b1

22 files changed:
src/engine/MANIFEST.in
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Defaults.py
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/NodeTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Scanner/C.py
src/engine/SCons/Scanner/CTests.py
src/engine/SCons/Scanner/Prog.py [new file with mode: 0644]
src/engine/SCons/Scanner/ProgTests.py [new file with mode: 0644]
src/engine/SCons/Sig/MD5Tests.py
src/engine/SCons/Sig/SigTests.py
src/engine/SCons/Sig/__init__.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
test/CPPPATH.py
test/LIBPATH.py
test/Library.py

index a79a0592f1f246dc6b05e6b5c3460077c0324e68..410b013efb135cabb07b79adcb4375fa35ed4b63 100644 (file)
@@ -9,6 +9,7 @@ SCons/Node/__init__.py
 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
index 47199516d20248bc678781ebc5979f81f13cdb73..fc372fa1caa0a82ca9a1baf03f22a7b8b0c5af1e 100644 (file)
@@ -136,7 +136,8 @@ class BuilderBase:
                        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)
 
@@ -144,6 +145,7 @@ class BuilderBase:
        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 '.$':
@@ -158,9 +160,10 @@ class BuilderBase:
            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
@@ -180,6 +183,8 @@ class BuilderBase:
            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)
@@ -216,9 +221,10 @@ class MultiStepBuilder(BuilderBase):
                        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):
index 3910323adac8a4c5aa8b78e40e3047f9ec417a76..cc5b314b7b80c42d34ecb79228b304a96d5b3778 100644 (file)
@@ -36,7 +36,6 @@ import unittest
 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.
@@ -269,6 +268,7 @@ class BuilderTestCase(unittest.TestCase):
        class Foo:
            pass
        def FooFactory(target):
+            global Foo
            return Foo(target)
        builder = SCons.Builder.Builder(node_factory = FooFactory)
        assert builder.node_factory is FooFactory
@@ -347,6 +347,23 @@ class BuilderTestCase(unittest.TestCase):
             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_')
index b330dae49d22419037f45589df673f0baef9f3c7..0bc82ebd46abfb24a04317d46fb31974ec6204cd 100644 (file)
@@ -36,7 +36,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import os
 import SCons.Builder
 import SCons.Scanner.C
-
+import SCons.Scanner.Prog
 
 Object = SCons.Builder.Builder(name = 'Object',
                                action = { '.c'   : '$CCCOM',
@@ -55,7 +55,8 @@ Program = SCons.Builder.Builder(name = 'Program',
                                 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',
@@ -72,13 +73,13 @@ if os.name == 'posix':
     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',
@@ -94,6 +95,8 @@ if os.name == 'posix':
         'LIBDIRSUFFIX'          : '',
         'LIBLINKPREFIX'         : '-l',
         'LIBLINKSUFFIX'         : '',
+        'INCPREFIX'             : '-I',
+        'INCSUFFIX'             : '',
         'ENV'        : { 'PATH' : '/usr/local/bin:/bin:/usr/bin' },
     }
 
@@ -102,13 +105,13 @@ elif os.name == 'nt':
     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',
@@ -124,6 +127,8 @@ elif os.name == 'nt':
         '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',
index e603b43c5d2c2808533e3e5c837ce5a11e946551..812eda71371e16442642e276ffb79670e5c81ca5 100644 (file)
@@ -81,7 +81,11 @@ class Environment:
                       ( '_LIBDIRFLAGS',
                         'LIBPATH',
                         'LIBDIRPREFIX',
-                        'LIBDIRSUFFIX' ) )
+                        'LIBDIRSUFFIX' ),
+                      ( '_INCFLAGS',
+                        'CPPPATH',
+                        'INCPREFIX',
+                        'INCSUFFIX' ) )
 
     def __init__(self, **kw):
        import SCons.Defaults
index 1fbdb8c8fa4929a8364476aac0815a67c415f826..3341c8325501f86a716e201727a9b5c12b6d7083 100644 (file)
@@ -269,6 +269,18 @@ class EnvironmentTestCase(unittest.TestCase):
         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():
index 7ee769efa5a1735ab822f31396b302ae57b0e02e..75f7c5c16bed74a105619fb6452fc88175d6c69e 100644 (file)
@@ -271,7 +271,7 @@ class Entry(SCons.Node.Node):
        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
@@ -338,7 +338,13 @@ class Dir(Entry):
        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):
@@ -451,5 +457,28 @@ class File(Entry):
                 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()
index 4305a32a62176b9e294415c473728fcca3299b9c..fe35d76681924bd8251e887d9c511bb6ac0a6496 100644 (file)
@@ -301,6 +301,12 @@ class FSTestCase(unittest.TestCase):
         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
index 373571b48bb83c36a8af227a5e8f0ab1ac478047..8c51e0ba5c7fecaa4481f25abb4c1345a25a0500 100644 (file)
@@ -296,10 +296,13 @@ class NodeTestCase(unittest.TestCase):
        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
 
index 6364bf8d6e4f75e5ca3e2b4aab4097ae4c97ef89..a6606a95f44f08fcc87146289579f305058ff257 100644 (file)
@@ -192,10 +192,15 @@ class Walker:
     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.
@@ -206,10 +211,14 @@ class Walker:
 
        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
index 0d74dc535121a4cdf52d1bb7368e68f92fc44984..e2842e15fd00cfb8ec0ba8e62ac1806bb63fe63f 100644 (file)
@@ -33,6 +33,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 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)
@@ -44,28 +45,6 @@ def CScan():
     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]
@@ -87,22 +66,24 @@ def scan(filename, env, node_factory):
     dependencies.
     """
 
-    if hasattr(env, "CPPPATH"):
-        paths = env.CPPPATH
-    else:
+    try:
+        paths = env.Dictionary("CPPPATH")
+    except KeyError:
         paths = []
-        
-    file = open(filename)
-    contents = file.read()
-    file.close()
 
-    angle_includes = angle_re.findall(contents)
-    quote_includes = quote_re.findall(contents)
+    try:
+        file = open(filename)
+        contents = file.read()
+        file.close()
 
-    source_dir = os.path.dirname(filename)
-    
-    deps = (find_files(angle_includes, paths + [source_dir])
-            + find_files(quote_includes, [source_dir] + paths))
+        angle_includes = angle_re.findall(contents)
+        quote_includes = quote_re.findall(contents)
 
-    deps = map(node_factory, deps)
-    return deps
+        source_dir = os.path.dirname(filename)
+        
+        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 []
index 8b8aeb5c4e4f14899dbf9dda43a6d9fcc14910f7..e6bd29e9f5aca5ac3daa567add1b6ac46e2b0830 100644 (file)
@@ -27,6 +27,7 @@ import TestCmd
 import SCons.Scanner.C
 import unittest
 import sys
+import os.path
 
 test = TestCmd.TestCmd(workdir = '')
 
@@ -88,55 +89,65 @@ for h in headers:
 # 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()
diff --git a/src/engine/SCons/Scanner/Prog.py b/src/engine/SCons/Scanner/Prog.py
new file mode 100644 (file)
index 0000000..f9d352c
--- /dev/null
@@ -0,0 +1,66 @@
+#
+# 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)
diff --git a/src/engine/SCons/Scanner/ProgTests.py b/src/engine/SCons/Scanner/ProgTests.py
new file mode 100644 (file)
index 0000000..6c3f5e4
--- /dev/null
@@ -0,0 +1,93 @@
+#
+# 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)
index 2d42fc0a5d238556b91d6c3c03b9c377b8113614..e74d339a2bfa0ebca4ddd6e992184e5ee4728e8c 100644 (file)
@@ -25,6 +25,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import sys
 import unittest
+import string
 
 from SCons.Sig.MD5 import current, collect, signature, to_string, from_string
 
@@ -83,7 +84,8 @@ class MD5TestCase(unittest.TestCase):
         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"
 
index 0870c939f51ba78c40c78642f41e7b4c5dd42da2..19816d53f44b61c596c30e1f4ddd6322c24ff9e3 100644 (file)
@@ -211,7 +211,7 @@ class SigTestBase:
         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")
index 43e3b8aba4565e3b9a044213a1bf5fe78b963c3c..fb34fa3c1709b7b73ea13b16c8674d892635d630 100644 (file)
@@ -31,7 +31,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os.path
 import string
-
+import SCons.Node
 
 #XXX Get rid of the global array so this becomes re-entrant.
 sig_files = []
@@ -154,7 +154,22 @@ class Calculator:
             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))
index daeb243dbc28063f875657b46ff6f27fea6aea13..1d42d1800ee4b0a656bf791f9263f7a9bec21b87 100644 (file)
@@ -221,3 +221,37 @@ def scons_subst(strSubst, locals, globals):
     """
     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
index 01d3031146f3e479b2353197397d181e9e87677d..e8a6ecade011b1f42758b4abf8e2c68df045ceb4 100644 (file)
@@ -30,8 +30,8 @@ import sys
 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):
@@ -159,6 +159,21 @@ class UtilTestCase(unittest.TestCase):
         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_')
index dec523df6b4829bb6e563326332c07bf8b12f830..32c00b6477b8163db01a811b10fd7426cbc6a992 100644 (file)
@@ -28,11 +28,44 @@ import TestSCons
 
 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()
index dec523df6b4829bb6e563326332c07bf8b12f830..2efd236d23b58ab57d335db93ef2058e61b3798c 100644 (file)
@@ -28,11 +28,36 @@ import TestSCons
 
 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.write('f1.c', """
+void
+f1(void)
+{
+       printf("f1.c\n");
+}
 """)
 
-test.run(arguments = '.')
+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()
index 3c50f3dcb5dd8f1a21af51dae7cfb7840d8f2fcc..f5e9edb42ff8f5f3eca7986204eebfcc61a2bc73 100644 (file)
@@ -28,16 +28,13 @@ import TestSCons
 
 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', """
@@ -119,7 +116,7 @@ main(int argc, char *argv[])
 }
 """)
 
-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")