summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--runtest.sh2
-rw-r--r--src/MANIFEST1
-rw-r--r--src/scons/Scanner/C.py87
-rw-r--r--src/scons/Scanner/CTests.py131
4 files changed, 220 insertions, 1 deletions
diff --git a/runtest.sh b/runtest.sh
index 34bf004..571a478 100644
--- a/runtest.sh
+++ b/runtest.sh
@@ -1,3 +1,3 @@
# This script makes it possible to run a test without building first
-export PYTHONPATH=`pwd`/src
+export PYTHONPATH="`pwd`/src:`pwd`/etc"
python $1
diff --git a/src/MANIFEST b/src/MANIFEST
index c09fc4a..64585a1 100644
--- a/src/MANIFEST
+++ b/src/MANIFEST
@@ -7,6 +7,7 @@ scons/Job.py
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
diff --git a/src/scons/Scanner/C.py b/src/scons/Scanner/C.py
new file mode 100644
index 0000000..fcb3a46
--- /dev/null
+++ b/src/scons/Scanner/C.py
@@ -0,0 +1,87 @@
+"""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
+
+
+
+
+
+
diff --git a/src/scons/Scanner/CTests.py b/src/scons/Scanner/CTests.py
new file mode 100644
index 0000000..326e9b8
--- /dev/null
+++ b/src/scons/Scanner/CTests.py
@@ -0,0 +1,131 @@
+__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)