summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Scanner
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2001-09-17 04:57:00 (GMT)
committerSteven Knight <knight@baldmt.com>2001-09-17 04:57:00 (GMT)
commit3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab (patch)
tree1a08c189644909cdadc489cce0eaa487e2c6f578 /src/engine/SCons/Scanner
parente2faf9c21bc7712fcdc547b7df0f12a6b2177601 (diff)
downloadSCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.zip
SCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.tar.gz
SCons-3c81bb2bd0e009c0ee81570e17b0f87ad8d034ab.tar.bz2
Run setup.py on the unpacked .tar.gz for testing.
Diffstat (limited to 'src/engine/SCons/Scanner')
-rw-r--r--src/engine/SCons/Scanner/.aeignore4
-rw-r--r--src/engine/SCons/Scanner/C.py87
-rw-r--r--src/engine/SCons/Scanner/CTests.py131
-rw-r--r--src/engine/SCons/Scanner/ScannerTests.py84
-rw-r--r--src/engine/SCons/Scanner/__init__.py69
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)
+
+
+
+