Commits

Steven Knight  committed f3fc325

add the C scanner

  • Participants
  • Parent commits efb7f3d

Comments (0)

Files changed (4)

 # This script makes it possible to run a test without building first
-export PYTHONPATH=`pwd`/src
+export PYTHONPATH="`pwd`/src:`pwd`/etc"
 python $1

File src/MANIFEST

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

File src/scons/Scanner/C.py

+"""scons.Scanner.C
+
+This module implements the depenency scanner for C/C++ code. 
+
+"""
+
+__revision__ = "Scanner/C.py __REVISION__ __DATE__ __DEVELOPER__"
+
+
+import scons.Scanner
+import re
+import os.path
+
+angle_re = re.compile('^[ \t]*#[ \t]*include[ \t]+<([\\w./\\\\]+)>', re.M)
+quote_re = re.compile('^[ \t]*#[ \t]*include[ \t]+"([\\w./\\\\]+)"', re.M)
+
+def CScan():
+    "Return a Scanner instance for scanning C/C++ source files"
+    return scons.Scanner.Scanner(scan)
+
+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):
+    """
+    scan(str, Environment) -> [str]
+
+    the C/C++ dependency scanner function
+
+    This function is intentionally simple. There are two rules it
+    follows:
+    
+    1) #include <foo.h> - search for foo.h in CPPPATH followed by the
+        directory 'filename' is in
+    2) #include \"foo.h\" - search for foo.h in the directory 'filename' is
+       in followed by CPPPATH
+
+    These rules approximate the behaviour of most C/C++ compilers.
+
+    This scanner also ignores #ifdef and other preprocessor conditionals, so
+    it may find more depencies than there really are, but it never misses
+    dependencies.
+    """
+
+    if hasattr(env, "CPPPATH"):
+        paths = env.CPPPATH
+    else:
+        paths = []
+        
+    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))
+
+    return deps
+
+    
+    
+    
+
+    

File src/scons/Scanner/CTests.py

+__revision__ = "Scanner/CTests.py __REVISION__ __DATE__ __DEVELOPER__"
+
+from TestCmd import TestCmd
+import scons.Scanner.C
+import unittest
+import sys
+
+test = TestCmd(workdir = '')
+
+# create some source files and headers:
+
+test.write('f1.cpp',"""
+#include \"f1.h\"
+#include <f2.h>
+
+int main()
+{
+   return 0;
+}
+""")
+
+test.write('f2.cpp',"""
+#include \"d1/f1.h\"
+#include <d2/f1.h>
+#include \"f1.h\"
+#include <f4.h>
+
+int main()
+{
+   return 0;
+}
+""")
+
+test.write('f3.cpp',"""
+#include \t "f1.h"
+   \t #include "f2.h"
+#   \t include "f3.h"
+
+#include \t <d1/f1.h>
+   \t #include <d1/f2.h>
+#   \t include <d1/f3.h>
+
+// #include "never.h"
+
+const char* x = "#include <never.h>"
+
+int main()
+{
+   return 0;
+}
+""")
+
+
+# for Emacs -> "
+
+test.subdir('d1', ['d1', 'd2'])
+
+headers = ['f1.h','f2.h', 'f3.h', 'never.h',
+           'd1/f1.h', 'd1/f2.h', 'd1/f3.h',
+           'd1/d2/f1.h', 'd1/d2/f2.h', 'd1/d2/f3.h', 'd1/d2/f4.h']
+
+for h in headers:
+    test.write(h, " ")
+
+# define some helpers:
+
+class DummyEnvironment:
+    pass
+
+def deps_match(deps, headers):
+    return deps.sort() == map(test.workpath, headers).sort()
+
+# define some tests:
+
+class CScannerTestCase1(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment
+        s = scons.Scanner.C.CScan()
+        deps = s.scan(test.workpath('f1.cpp'), env)
+        self.failUnless(deps_match(deps, ['f1.h', 'f2.h']))
+
+class CScannerTestCase2(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment
+        env.CPPPATH = [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)) 
+
+class CScannerTestCase3(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment
+        env.CPPPATH = [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))
+                  
+
+class CScannerTestCase4(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment
+        env.CPPPATH = [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))
+        
+class CScannerTestCase5(unittest.TestCase):
+    def runTest(self):
+        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))
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(CScannerTestCase1())
+    suite.addTest(CScannerTestCase2())
+    suite.addTest(CScannerTestCase3())
+    suite.addTest(CScannerTestCase4())
+    suite.addTest(CScannerTestCase5())
+    return suite
+
+if __name__ == "__main__":
+    runner = unittest.TextTestRunner()
+    result = runner.run(suite())
+    if not result.wasSuccessful():
+        sys.exit(1)