diff options
Diffstat (limited to 'src/engine/SCons/Scanner')
| -rw-r--r-- | src/engine/SCons/Scanner/.aeignore | 4 | ||||
| -rw-r--r-- | src/engine/SCons/Scanner/C.py | 87 | ||||
| -rw-r--r-- | src/engine/SCons/Scanner/CTests.py | 131 | ||||
| -rw-r--r-- | src/engine/SCons/Scanner/ScannerTests.py | 84 | ||||
| -rw-r--r-- | src/engine/SCons/Scanner/__init__.py | 69 |
5 files changed, 375 insertions, 0 deletions
diff --git a/src/engine/SCons/Scanner/.aeignore b/src/engine/SCons/Scanner/.aeignore new file mode 100644 index 0000000..43fe851 --- /dev/null +++ b/src/engine/SCons/Scanner/.aeignore @@ -0,0 +1,4 @@ +*,D +*.pyc +.*.swp +.consign diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py new file mode 100644 index 0000000..ce6f9c6 --- /dev/null +++ b/src/engine/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/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py new file mode 100644 index 0000000..6a85120 --- /dev/null +++ b/src/engine/SCons/Scanner/CTests.py @@ -0,0 +1,131 @@ +__revision__ = "Scanner/CTests.py __REVISION__ __DATE__ __DEVELOPER__" + +import TestCmd +import SCons.Scanner.C +import unittest +import sys + +test = TestCmd.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) diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py new file mode 100644 index 0000000..9c7163d --- /dev/null +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -0,0 +1,84 @@ +__revision__ = "Scanner/ScannerTests.py __REVISION__ __DATE__ __DEVELOPER__" + +import unittest +import SCons.Scanner +import sys + +class ScannerTestBase: + + def func(self, filename, env, *args): + self.filename = filename + self.env = env + + if len(args) > 0: + self.arg = args[0] + + return self.deps + + + def test(self, scanner, env, filename, deps, *args): + self.deps = deps + deps = scanner.scan(filename, env) + + self.failUnless(self.filename == filename, "the filename was passed incorrectly") + self.failUnless(self.env == env, "the environment was passed incorrectly") + self.failUnless(self.deps == deps, "the dependencies were returned incorrectly") + + if len(args) > 0: + self.failUnless(self.arg == args[0], "the argument was passed incorrectly") + else: + self.failIf(hasattr(self, "arg"), "an argument was given when it shouldn't have been") + +class DummyEnvironment: + pass + + +class ScannerPositionalTestCase(ScannerTestBase, unittest.TestCase): + "Test the Scanner class using the position argument" + def runTest(self): + s = SCons.Scanner.Scanner(self.func) + env = DummyEnvironment() + env.VARIABLE = "var1" + self.test(s, env, 'f1.cpp', ['f1.h', 'f1.hpp']) + +class ScannerKeywordTestCase(ScannerTestBase, unittest.TestCase): + "Test the Scanner class using the keyword argument" + def runTest(self): + s = SCons.Scanner.Scanner(function = self.func) + env = DummyEnvironment() + env.VARIABLE = "var2" + self.test(s, env, 'f2.cpp', ['f2.h', 'f2.hpp']) + +class ScannerPositionalArgumentTestCase(ScannerTestBase, unittest.TestCase): + "Test the Scanner class using the position argument and optional argument" + def runTest(self): + arg = "this is the argument" + s = SCons.Scanner.Scanner(self.func, arg) + env = DummyEnvironment() + env.VARIABLE = "var3" + self.test(s, env, 'f3.cpp', ['f3.h', 'f3.hpp'], arg) + +class ScannerKeywordArgumentTestCase(ScannerTestBase, unittest.TestCase): + "Test the Scanner class using the keyword argument and optional argument" + def runTest(self): + arg = "this is another argument" + s = SCons.Scanner.Scanner(function = self.func, argument = arg) + env = DummyEnvironment() + env.VARIABLE = "var4" + self.test(s, env, 'f4.cpp', ['f4.h', 'f4.hpp'], arg) + +def suite(): + suite = unittest.TestSuite() + suite.addTest(ScannerPositionalTestCase()) + suite.addTest(ScannerKeywordTestCase()) + suite.addTest(ScannerPositionalArgumentTestCase()) + suite.addTest(ScannerKeywordArgumentTestCase()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + + diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py new file mode 100644 index 0000000..225c7f3 --- /dev/null +++ b/src/engine/SCons/Scanner/__init__.py @@ -0,0 +1,69 @@ +"""SCons.Scanner + +The Scanner package for the SCons software construction utility. + +""" + +__revision__ = "Scanner/__init__.py __REVISION__ __DATE__ __DEVELOPER__" + +__version__ = "__VERSION__" + +class _Null: + pass + +# This is used instead of None as a default argument value so None can be +# used as an actual argument value. +_null = _Null + +class Scanner: + + def __init__(self, function, argument=_null): + """ + Construct a new scanner object given a scanner function. + + 'function' - a scanner function taking two or three arguments and + returning a list of strings. + + 'argument' - an optional argument that will be passed to the + scanner function if it is given. + + The scanner function's first argument will be the name of a file + that should be scanned for dependencies, the second argument will + be an Environment object, the third argument will be the value + passed into 'argument', and the returned list should contain the + file names of all the direct dependencies of the file. + + Examples: + + s = Scanner(my_scanner_function) + + s = Scanner(function = my_scanner_function) + + s = Scanner(function = my_scanner_function, argument = 'foo') + + """ + + # Note: this class could easily work with scanner functions that take + # something other than a filename as an argument (e.g. a database + # node) and a dependencies list that aren't file names. All that + # would need to be changed is the documentation. + + self.function = function + self.argument = argument + + def scan(self, filename, env): + """ + This method does the actually scanning. 'filename' is the filename + that will be passed to the scanner function, and 'env' is the + environment that will be passed to the scanner function. A list of + dependencies will be returned. + """ + + if not self.argument is _null: + return self.function(filename, env, self.argument) + else: + return self.function(filename, env) + + + + |
