diff options
author | Steven Knight <knight@baldmt.com> | 2003-04-23 22:27:58 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2003-04-23 22:27:58 (GMT) |
commit | 9587e1d2dad1c532d86f664f5cbd6266ebd77808 (patch) | |
tree | 71ab8dbc059c0d16de3f5088427e288716d9dd43 /src/engine/SCons/Scanner | |
parent | 9c4ebd90350becd6ff9b1b4e4049546680c849b6 (diff) | |
download | SCons-9587e1d2dad1c532d86f664f5cbd6266ebd77808.zip SCons-9587e1d2dad1c532d86f664f5cbd6266ebd77808.tar.gz SCons-9587e1d2dad1c532d86f664f5cbd6266ebd77808.tar.bz2 |
Add support for MIDL. (Greg Spencer)
Diffstat (limited to 'src/engine/SCons/Scanner')
-rw-r--r-- | src/engine/SCons/Scanner/C.py | 103 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/Fortran.py | 72 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/IDL.py | 43 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/IDLTests.py | 428 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/ScannerTests.py | 178 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/__init__.py | 108 |
6 files changed, 726 insertions, 206 deletions
diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py index c06774d..44007f9 100644 --- a/src/engine/SCons/Scanner/C.py +++ b/src/engine/SCons/Scanner/C.py @@ -29,106 +29,17 @@ This module implements the depenency scanner for C/C++ code. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - -import re - -import SCons.Node import SCons.Node.FS import SCons.Scanner -import SCons.Util -import SCons.Warnings - -include_re = re.compile('^[ \t]*#[ \t]*(?:include|import)[ \t]+(<|")([^>"]+)(>|")', re.M) def CScan(fs = SCons.Node.FS.default_fs): """Return a prototype Scanner instance for scanning source files that use the C pre-processor""" - cs = SCons.Scanner.Current(scan, "CScan", fs, - [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", - ".h", ".H", ".hxx", ".hpp", ".hh", - ".F", ".fpp", ".FPP"], - path_function = path, - recursive = 1) + cs = SCons.Scanner.ClassicCPP("CScan", + [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", + ".h", ".H", ".hxx", ".hpp", ".hh", + ".F", ".fpp", ".FPP"], + "CPPPATH", + '^[ \t]*#[ \t]*(?:include|import)[ \t]+(<|")([^>"]+)(>|")', + fs = fs) return cs - -def path(env, dir, fs = SCons.Node.FS.default_fs): - try: - cpppath = env['CPPPATH'] - except KeyError: - return () - return tuple(fs.Rsearchall(SCons.Util.mapPaths(cpppath, dir, env), - clazz = SCons.Node.FS.Dir, - must_exist = 0)) - -def scan(node, env, cpppath = (), fs = SCons.Node.FS.default_fs): - """ - scan(node, Environment) -> [node] - - 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. - """ - - node = node.rfile() - - # This function caches the following information: - # node.includes - the result of include_re.findall() - - if not node.exists(): - return [] - - # cache the includes list in node so we only scan it once: - if node.includes != None: - includes = node.includes - else: - includes = include_re.findall(node.get_contents()) - node.includes = includes - - nodes = [] - source_dir = node.get_dir() - for include in includes: - if include[0] == '"': - n = SCons.Node.FS.find_file(include[1], - (source_dir,) + cpppath, - fs.File) - else: - n = SCons.Node.FS.find_file(include[1], - cpppath + (source_dir,), - fs.File) - - if not n is None: - nodes.append(n) - else: - SCons.Warnings.warn(SCons.Warnings.DependencyWarning, - "No dependency generated for file: %s (included from: %s) -- file not found" % (include[1], node)) - - # Schwartzian transform from the Python FAQ Wizard - def st(List, Metric): - def pairing(element, M = Metric): - return (M(element), element) - def stripit(pair): - return pair[1] - paired = map(pairing, List) - paired.sort() - return map(stripit, paired) - - def normalize(node): - # We don't want the order of includes to be - # modified by case changes on case insensitive OSes, so - # normalize the case of the filename here: - # (see test/win32pathmadness.py for a test of this) - return SCons.Node.FS._my_normcase(str(node)) - - return st(nodes, normalize) diff --git a/src/engine/SCons/Scanner/Fortran.py b/src/engine/SCons/Scanner/Fortran.py index 10f0836..2e44eb9 100644 --- a/src/engine/SCons/Scanner/Fortran.py +++ b/src/engine/SCons/Scanner/Fortran.py @@ -38,76 +38,12 @@ import SCons.Scanner import SCons.Util import SCons.Warnings -include_re = re.compile("INCLUDE[ \t]+'([\\w./\\\\]+)'", re.M) - def FortranScan(fs = SCons.Node.FS.default_fs): """Return a prototype Scanner instance for scanning source files for Fortran INCLUDE statements""" - scanner = SCons.Scanner.Current(scan, "FortranScan", fs, + scanner = SCons.Scanner.Classic("FortranScan", [".f", ".F", ".for", ".FOR"], - path_function = path, - recursive = 1) + "F77PATH", + "INCLUDE[ \t]+'([\\w./\\\\]+)'", + fs = fs) return scanner - -def path(env, dir, fs = SCons.Node.FS.default_fs): - try: - f77path = env['F77PATH'] - except KeyError: - return () - return tuple(fs.Rsearchall(SCons.Util.mapPaths(f77path, dir, env), - clazz = SCons.Node.FS.Dir, - must_exist = 0)) - -def scan(node, env, f77path = (), fs = SCons.Node.FS.default_fs): - """ - scan(node, Environment) -> [node] - - the Fortran dependency scanner function - """ - - node = node.rfile() - - # This function caches the following information: - # node.includes - the result of include_re.findall() - - if not node.exists(): - return [] - - # cache the includes list in node so we only scan it once: - if node.includes != None: - includes = node.includes - else: - includes = include_re.findall(node.get_contents()) - node.includes = includes - - source_dir = node.get_dir() - - nodes = [] - for include in includes: - n = SCons.Node.FS.find_file(include, - (source_dir,) + f77path, - fs.File) - if not n is None: - nodes.append(n) - else: - SCons.Warnings.warn(SCons.Warnings.DependencyWarning, - "No dependency generated for file: %s (included from: %s) -- file not found" % (include, node)) - - # Schwartzian transform from the Python FAQ Wizard - def st(List, Metric): - def pairing(element, M = Metric): - return (M(element), element) - def stripit(pair): - return pair[1] - paired = map(pairing, List) - paired.sort() - return map(stripit, paired) - - def normalize(node): - # We don't want the order of includes to be - # modified by case changes on case insensitive OSes, so - # normalize the case of the filename here: - # (see test/win32pathmadness.py for a test of this) - return SCons.Node.FS._my_normcase(str(node)) - - return st(nodes, normalize) diff --git a/src/engine/SCons/Scanner/IDL.py b/src/engine/SCons/Scanner/IDL.py new file mode 100644 index 0000000..e4ceac8 --- /dev/null +++ b/src/engine/SCons/Scanner/IDL.py @@ -0,0 +1,43 @@ +"""SCons.Scanner.IDL + +This module implements the depenency scanner for IDL (Interface +Definition Language) files. + +""" + +# +# Copyright (c) 2001, 2002, 2003 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__ = "src/engine/SCons/Scanner/IDL.py 0.D013 2003/03/31 21:46:41 software" + +import SCons.Node.FS +import SCons.Scanner + +def IDLScan(fs = SCons.Node.FS.default_fs): + """Return a prototype Scanner instance for scanning IDL source files""" + cs = SCons.Scanner.ClassicCPP("IDLScan", + [".idl", ".IDL"], + "CPPPATH", + '^[ \t]*(?:#[ \t]*include|[ \t]*import)[ \t]+(<|")([^>"]+)(>|")', + fs = fs) + return cs diff --git a/src/engine/SCons/Scanner/IDLTests.py b/src/engine/SCons/Scanner/IDLTests.py new file mode 100644 index 0000000..09e8427 --- /dev/null +++ b/src/engine/SCons/Scanner/IDLTests.py @@ -0,0 +1,428 @@ +# +# Copyright (c) 2001, 2002, 2003 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__ = "src/engine/SCons/Scanner/IDLTests.py 0.D013 2003/03/31 21:46:41 software" + +import TestCmd +import SCons.Scanner.IDL +import unittest +import sys +import os +import os.path +import SCons.Node.FS +import SCons.Warnings + +test = TestCmd.TestCmd(workdir = '') + +os.chdir(test.workpath('')) + +# create some source files and headers: + +test.write('t1.idl',''' +#include "f1.idl" +#include <f2.idl> +import "f3.idl"; + +[ + object, + uuid(22995106-CE26-4561-AF1B-C71C6934B840), + dual, + helpstring("IBarObject Interface"), + pointer_default(unique) +] +interface IBarObject : IDispatch +{ +}; +''') + +test.write('t2.idl',""" +#include \"d1/f1.idl\" +#include <d2/f1.idl> +#include \"f1.idl\" +import <f3.idl>; + +[ + object, + uuid(22995106-CE26-4561-AF1B-C71C6934B840), + dual, + helpstring(\"IBarObject Interface\"), + pointer_default(unique) +] +interface IBarObject : IDispatch +{ +}; +""") + +test.write('t3.idl',""" +#include \t \"f1.idl\" + \t #include \"f2.idl\" +# \t include \"f3-test.idl\" + +#include \t <d1/f1.idl> + \t #include <d1/f2.idl> +# \t include <d1/f3-test.idl> + +import \t \"d1/f1.idl\" + \t import \"d1/f2.idl\" + +include \t \"never.idl\" + \t include \"never.idl\" + +// #include \"never.idl\" + +const char* x = \"#include <never.idl>\" + +[ + object, + uuid(22995106-CE26-4561-AF1B-C71C6934B840), + dual, + helpstring(\"IBarObject Interface\"), + pointer_default(unique) +] +interface IBarObject : IDispatch +{ +}; +""") + +test.subdir('d1', ['d1', 'd2']) + +headers = ['f1.idl','f2.idl', 'f3.idl', 'f3-test.idl', 'fi.idl', 'fj.idl', 'never.idl', + 'd1/f1.idl', 'd1/f2.idl', 'd1/f3-test.idl', 'd1/fi.idl', 'd1/fj.idl', + 'd1/d2/f1.idl', 'd1/d2/f2.idl', 'd1/d2/f3-test.idl', + 'd1/d2/f4.idl', 'd1/d2/fi.idl', 'd1/d2/fj.idl'] + +for h in headers: + test.write(h, " ") + +test.write('f2.idl',""" +#include "fi.idl" +""") + +test.write('f3-test.idl',""" +#include <fj.idl> +""") + + +test.subdir('include', 'subdir', ['subdir', 'include']) + +test.write('t4.idl',""" +#include \"fa.idl\" +#include <fb.idl> + +[ + object, + uuid(22995106-CE26-4561-AF1B-C71C6934B840), + dual, + helpstring(\"IBarObject Interface\"), + pointer_default(unique) +] +interface IBarObject : IDispatch +{ +}; +""") + +test.write(['include', 'fa.idl'], "\n") +test.write(['include', 'fb.idl'], "\n") +test.write(['subdir', 'include', 'fa.idl'], "\n") +test.write(['subdir', 'include', 'fb.idl'], "\n") + +test.subdir('repository', ['repository', 'include'], + ['repository', 'src' ]) +test.subdir('work', ['work', 'src']) + +test.write(['repository', 'include', 'iii.idl'], "\n") + +test.write(['work', 'src', 'fff.c'], """ +#include <iii.idl> +#include <jjj.idl> + +int main() +{ + return 0; +} +""") + +test.write([ 'work', 'src', 'aaa.c'], """ +#include "bbb.idl" + +int main() +{ + return 0; +} +""") + +test.write([ 'work', 'src', 'bbb.idl'], "\n") + +test.write([ 'repository', 'src', 'ccc.c'], """ +#include "ddd.idl" + +int main() +{ + return 0; +} +""") + +test.write([ 'repository', 'src', 'ddd.idl'], "\n") + +# define some helpers: + +class DummyEnvironment: + 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 subst(self, arg): + return arg + + def has_key(self, key): + return self.Dictionary().has_key(key) + + def __getitem__(self,key): + return self.Dictionary()[key] + + def __setitem__(self,key,value): + self.Dictionary()[key] = value + + def __delitem__(self,key): + del self.Dictionary()[key] + +global my_normpath +my_normpath = os.path.normpath + +if os.path.normcase('foo') == os.path.normcase('FOO'): + my_normpath = os.path.normcase + +def deps_match(self, deps, headers): + scanned = map(my_normpath, map(str, deps)) + expect = map(my_normpath, headers) + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + +def make_node(filename, fs=SCons.Node.FS.default_fs): + return fs.File(test.workpath(filename)) + +# define some tests: + +class IDLScannerTestCase1(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(make_node('t1.idl'), env, path) + headers = ['f1.idl', 'f2.idl', 'f3.idl'] + deps_match(self, deps, map(test.workpath, headers)) + +class IDLScannerTestCase2(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(make_node('t1.idl'), env, path) + headers = ['d1/f2.idl', 'f1.idl', 'f3.idl'] + deps_match(self, deps, map(test.workpath, headers)) + +class IDLScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(make_node('t2.idl'), env, path) + headers = ['d1/d2/f1.idl', 'd1/f1.idl', 'f1.idl', 'f3.idl'] + deps_match(self, deps, map(test.workpath, headers)) + +class IDLScannerTestCase4(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1"), test.workpath("d1/d2")]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(make_node('t2.idl'), env, path) + headers = ['d1/d2/f1.idl', 'd1/f1.idl', 'f1.idl', 'f3.idl'] + deps_match(self, deps, map(test.workpath, headers)) + +class IDLScannerTestCase5(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + + n = make_node('t3.idl') + def my_rexists(s=n): + s.rexists_called = 1 + return s.old_rexists() + setattr(n, 'old_rexists', n.rexists) + setattr(n, 'rexists', my_rexists) + + deps = s(n, env, path) + + # Make sure rexists() got called on the file node being + # scanned, essential for cooperation with BuildDir functionality. + assert n.rexists_called + + headers = ['d1/f1.idl', 'd1/f1.idl', 'd1/f2.idl', 'd1/f2.idl', 'd1/f3-test.idl', + 'f1.idl', 'f2.idl', 'f3-test.idl'] + deps_match(self, deps, map(test.workpath, headers)) + +class IDLScannerTestCase6(unittest.TestCase): + def runTest(self): + env1 = DummyEnvironment([test.workpath("d1")]) + env2 = DummyEnvironment([test.workpath("d1/d2")]) + s = SCons.Scanner.IDL.IDLScan() + path1 = s.path(env1) + path2 = s.path(env2) + deps1 = s(make_node('t1.idl'), env1, path1) + deps2 = s(make_node('t1.idl'), env2, path2) + headers1 = ['d1/f2.idl', 'f1.idl', 'f3.idl'] + headers2 = ['d1/d2/f2.idl', 'f1.idl', 'f3.idl'] + deps_match(self, deps1, map(test.workpath, headers1)) + deps_match(self, deps2, map(test.workpath, headers2)) + +class IDLScannerTestCase7(unittest.TestCase): + def runTest(self): + fs = SCons.Node.FS.FS(test.workpath('')) + env = DummyEnvironment(["include"]) + s = SCons.Scanner.IDL.IDLScan(fs = fs) + path = s.path(env) + deps1 = s(fs.File('t4.idl'), env, path) + fs.chdir(fs.Dir('subdir')) + dir = fs.getcwd() + fs.chdir(fs.Dir('..')) + path = s.path(env, dir) + deps2 = s(fs.File('#t4.idl'), env, path) + headers1 = ['include/fa.idl', 'include/fb.idl'] + headers2 = ['subdir/include/fa.idl', 'subdir/include/fb.idl'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class IDLScannerTestCase8(unittest.TestCase): + def runTest(self): + SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning) + class TestOut: + def __call__(self, x): + self.out = x + + to = TestOut() + to.out = None + SCons.Warnings._warningOut = to + test.write('fa.idl','\n') + fs = SCons.Node.FS.FS(test.workpath('')) + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan(fs=fs) + path = s.path(env) + deps = s(fs.File('t4.idl'), env, path) + + # Did we catch the warning associated with not finding fb.idl? + assert to.out + + deps_match(self, deps, [ 'fa.idl' ]) + test.unlink('fa.idl') + +class IDLScannerTestCase9(unittest.TestCase): + def runTest(self): + fs = SCons.Node.FS.FS(test.workpath('')) + fs.chdir(fs.Dir('include')) + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan(fs=fs) + path = s.path(env) + test.write('include/t4.idl', test.read('t4.idl')) + deps = s(fs.File('#include/t4.idl'), env, path) + fs.chdir(fs.Dir('..')) + deps_match(self, deps, [ 'include/fa.idl', 'include/fb.idl' ]) + test.unlink('include/t4.idl') + +class IDLScannerTestCase10(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.Repository(test.workpath('repository')) + + # Create a derived file in a directory that does not exist yet. + # This was a bug at one time. + f1=fs.File('include2/jjj.idl') + f1.builder=1 + env = DummyEnvironment(['include', 'include2']) + s = SCons.Scanner.IDL.IDLScan(fs=fs) + path = s.path(env) + deps = s(fs.File('src/fff.c'), env, path) + deps_match(self, deps, [ test.workpath('repository/include/iii.idl'), 'include2/jjj.idl' ]) + os.chdir(test.workpath('')) + +class IDLScannerTestCase11(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.BuildDir('build1', 'src', 1) + fs.BuildDir('build2', 'src', 0) + fs.Repository(test.workpath('repository')) + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan(fs = fs) + path = s.path(env) + deps1 = s(fs.File('build1/aaa.c'), env, path) + deps_match(self, deps1, [ 'build1/bbb.idl' ]) + deps2 = s(fs.File('build2/aaa.c'), env, path) + deps_match(self, deps2, [ 'src/bbb.idl' ]) + deps3 = s(fs.File('build1/ccc.c'), env, path) + deps_match(self, deps3, [ 'build1/ddd.idl' ]) + deps4 = s(fs.File('build2/ccc.c'), env, path) + deps_match(self, deps4, [ test.workpath('repository/src/ddd.idl') ]) + os.chdir(test.workpath('')) + +class IDLScannerTestCase12(unittest.TestCase): + def runTest(self): + class SubstEnvironment(DummyEnvironment): + def subst(self, arg, test=test): + return test.workpath("d1") + env = SubstEnvironment(["blah"]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(make_node('t1.idl'), env, path) + headers = ['d1/f2.idl', 'f1.idl', 'f3.idl'] + deps_match(self, deps, map(test.workpath, headers)) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(IDLScannerTestCase1()) + suite.addTest(IDLScannerTestCase2()) + suite.addTest(IDLScannerTestCase3()) + suite.addTest(IDLScannerTestCase4()) + suite.addTest(IDLScannerTestCase5()) + suite.addTest(IDLScannerTestCase6()) + suite.addTest(IDLScannerTestCase7()) + suite.addTest(IDLScannerTestCase8()) + suite.addTest(IDLScannerTestCase9()) + suite.addTest(IDLScannerTestCase10()) + suite.addTest(IDLScannerTestCase11()) + suite.addTest(IDLScannerTestCase12()) + 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 index d9882a6..8c8744d 100644 --- a/src/engine/SCons/Scanner/ScannerTests.py +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -27,7 +27,10 @@ import unittest import SCons.Scanner import sys -class ScannerTestBase: +class DummyEnvironment: + pass + +class ScannerTestCase(unittest.TestCase): def func(self, filename, env, target, *args): self.filename = filename @@ -39,7 +42,6 @@ class ScannerTestBase: return self.deps - def test(self, scanner, env, filename, deps, *args): self.deps = deps path = scanner.path(env) @@ -57,13 +59,8 @@ class ScannerTestBase: 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.Base class using the position argument" - def runTest(self): + def test_positional(self): + """Test the Scanner.Base class using positional arguments""" s = SCons.Scanner.Base(self.func, "Pos") env = DummyEnvironment() env.VARIABLE = "var1" @@ -73,9 +70,8 @@ class ScannerPositionalTestCase(ScannerTestBase, unittest.TestCase): env.VARIABLE = "i1" self.test(s, env, 'i1.cpp', ['i1.h', 'i1.hpp']) -class ScannerKeywordTestCase(ScannerTestBase, unittest.TestCase): - "Test the Scanner.Base class using the keyword argument" - def runTest(self): + def test_keywords(self): + """Test the Scanner.Base class using keyword arguments""" s = SCons.Scanner.Base(function = self.func, name = "Key") env = DummyEnvironment() env.VARIABLE = "var2" @@ -85,9 +81,8 @@ class ScannerKeywordTestCase(ScannerTestBase, unittest.TestCase): env.VARIABLE = "i2" self.test(s, env, 'i2.cpp', ['i2.h', 'i2.hpp']) -class ScannerPositionalArgumentTestCase(ScannerTestBase, unittest.TestCase): - "Test the Scanner.Base class using both position and optional arguments" - def runTest(self): + def test_pos_opt(self): + """Test the Scanner.Base class using both position and optional arguments""" arg = "this is the argument" s = SCons.Scanner.Base(self.func, "PosArg", arg) env = DummyEnvironment() @@ -98,9 +93,8 @@ class ScannerPositionalArgumentTestCase(ScannerTestBase, unittest.TestCase): env.VARIABLE = "i3" self.test(s, env, 'i3.cpp', ['i3.h', 'i3.hpp'], arg) -class ScannerKeywordArgumentTestCase(ScannerTestBase, unittest.TestCase): - "Test the Scanner.Base class using both keyword and optional arguments" - def runTest(self): + def test_key_opt(self): + """Test the Scanner.Base class using both keyword and optional arguments""" arg = "this is another argument" s = SCons.Scanner.Base(function = self.func, name = "KeyArg", argument = arg) @@ -112,20 +106,16 @@ class ScannerKeywordArgumentTestCase(ScannerTestBase, unittest.TestCase): env.VARIABLE = "i4" self.test(s, env, 'i4.cpp', ['i4.h', 'i4.hpp'], arg) -class ScannerHashTestCase(ScannerTestBase, unittest.TestCase): - "Test the Scanner.Base class __hash__() method" - def runTest(self): + def test_hash(self): + """Test the Scanner.Base class __hash__() method""" s = SCons.Scanner.Base(self.func, "Hash") dict = {} dict[s] = 777 self.failUnless(hash(dict.keys()[0]) == hash(repr(s)), "did not hash Scanner base class as expected") -class ScannerCheckTestCase(unittest.TestCase): - "Test the Scanner.Base class scan_check method" - def setUp(self): - self.checked = {} - def runTest(self): + def test_scan_check(self): + """Test the Scanner.Base class scan_check() method""" def my_scan(filename, env, target, *args): return [] def check(node, s=self): @@ -133,14 +123,14 @@ class ScannerCheckTestCase(unittest.TestCase): return 1 env = DummyEnvironment() s = SCons.Scanner.Base(my_scan, "Check", scan_check = check) + self.checked = {} path = s.path(env) scanned = s('x', env, path) self.failUnless(self.checked['x'] == 1, "did not call check function") -class ScannerRecursiveTestCase(ScannerTestBase, unittest.TestCase): - "Test the Scanner.Base class recursive flag" - def runTest(self): + def test_recursive(self): + """Test the Scanner.Base class recursive flag""" s = SCons.Scanner.Base(function = self.func) self.failUnless(s.recursive == None, "incorrect default recursive value") @@ -151,9 +141,9 @@ class ScannerRecursiveTestCase(ScannerTestBase, unittest.TestCase): self.failUnless(s.recursive == 1, "did not set recursive flag to 1") -class CurrentTestCase(ScannerTestBase, unittest.TestCase): - "Test the Scanner.Current class" - def runTest(self): +class CurrentTestCase(unittest.TestCase): + def test_class(self): + """Test the Scanner.Current class""" class MyNode: def __init__(self): self.called_has_builder = None @@ -199,16 +189,122 @@ class CurrentTestCase(ScannerTestBase, unittest.TestCase): self.failUnless(ic.called_current, "did not call current()") self.failUnless(ic.func_called, "did not call func()") +class ClassicTestCase(unittest.TestCase): + def test_find_include(self): + """Test the Scanner.Classic find_include() method""" + env = DummyEnvironment() + s = SCons.Scanner.Classic("t", ['.suf'], 'MYPATH', '^my_inc (\S+)') + + def _find_file(filename, paths, factory): + return paths[0]+'/'+filename + + save = SCons.Node.FS.find_file + SCons.Node.FS.find_file = _find_file + + try: + n, i = s.find_include('aaa', 'foo', ('path',)) + assert n == 'foo/aaa', n + assert i == 'aaa', i + + finally: + SCons.Node.FS.find_file = save + + def test_scan(self): + """Test the Scanner.Classic scan() method""" + class MyNode: + def __init__(self, name): + self.name = name + self._rfile = self + self.includes = None + def rfile(self): + return self._rfile + def exists(self): + return self._exists + def get_contents(self): + return self._contents + def get_dir(self): + return self._dir + + class MyScanner(SCons.Scanner.Classic): + def find_include(self, include, source_dir, path): + return include, include + + env = DummyEnvironment() + s = MyScanner("t", ['.suf'], 'MYPATH', '^my_inc (\S+)') + + # If the node doesn't exist, scanning turns up nothing. + n1 = MyNode("n1") + n1._exists = None + ret = s.scan(n1, env) + assert ret == [], ret + + # Verify that it finds includes from the contents. + n = MyNode("n") + n._exists = 1 + n._dir = MyNode("n._dir") + n._contents = 'my_inc abc\n' + ret = s.scan(n, env) + assert ret == ['abc'], ret + + # Verify that it uses the cached include info. + n._contents = 'my_inc def\n' + ret = s.scan(n, env) + assert ret == ['abc'], ret + + # Verify that if we wipe the cache, it uses the new contents. + n.includes = None + ret = s.scan(n, env) + assert ret == ['def'], ret + + # Verify that it sorts what it finds. + n.includes = ['xyz', 'uvw'] + ret = s.scan(n, env) + assert ret == ['uvw', 'xyz'], ret + + # Verify that we use the rfile() node. + nr = MyNode("nr") + nr._exists = 1 + nr._dir = MyNode("nr._dir") + nr.includes = ['jkl', 'mno'] + n._rfile = nr + ret = s.scan(n, env) + assert ret == ['jkl', 'mno'], ret + +class ClassicCPPTestCase(unittest.TestCase): + def test_find_include(self): + """Test the Scanner.ClassicCPP find_include() method""" + env = DummyEnvironment() + s = SCons.Scanner.ClassicCPP("Test", [], None, "") + + def _find_file(filename, paths, factory): + return paths[0]+'/'+filename + + save = SCons.Node.FS.find_file + SCons.Node.FS.find_file = _find_file + + try: + n, i = s.find_include(('"', 'aaa'), 'foo', ('path',)) + assert n == 'foo/aaa', n + assert i == 'aaa', i + + n, i = s.find_include(('<', 'bbb'), 'foo', ('path',)) + assert n == 'path/bbb', n + assert i == 'bbb', i + + finally: + SCons.Node.FS.find_file = _find_file + def suite(): suite = unittest.TestSuite() - suite.addTest(ScannerPositionalTestCase()) - suite.addTest(ScannerKeywordTestCase()) - suite.addTest(ScannerPositionalArgumentTestCase()) - suite.addTest(ScannerKeywordArgumentTestCase()) - suite.addTest(ScannerHashTestCase()) - suite.addTest(ScannerCheckTestCase()) - suite.addTest(ScannerRecursiveTestCase()) - suite.addTest(CurrentTestCase()) + tclasses = [ + ScannerTestCase, + CurrentTestCase, + ClassicTestCase, + ClassicCPPTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) return suite if __name__ == "__main__": @@ -216,5 +312,3 @@ if __name__ == "__main__": 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 index d4ee523..2146ebe 100644 --- a/src/engine/SCons/Scanner/__init__.py +++ b/src/engine/SCons/Scanner/__init__.py @@ -29,6 +29,7 @@ The Scanner package for the SCons software construction utility. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import re import SCons.Node.FS import SCons.Sig @@ -180,3 +181,110 @@ class Current(Base): return c kw['scan_check'] = current_check apply(Base.__init__, (self,) + args, kw) + +class Classic(Current): + """ + A Scanner subclass to contain the common logic for classic CPP-style + include scanning, but which can be customized to use different + regular expressions to find the includes. + + Note that in order for this to work "out of the box" (without + overriding the find_include() method), the regular expression passed + to the constructor must return the name of the include file in group + 0. + """ + + def __init__(self, name, suffixes, path_variable, regex, + fs=SCons.Node.FS.default_fs, *args, **kw): + + self.cre = re.compile(regex, re.M) + self.fs = fs + + def _path(env, dir, pv=path_variable, fs=fs): + try: + path = env[pv] + except KeyError: + return () + return tuple(fs.Rsearchall(SCons.Util.mapPaths(path, dir, env), + clazz = SCons.Node.FS.Dir, + must_exist = 0)) + + def _scan(node, env, path, self=self, fs=fs): + return self.scan(node, env, path) + + kw['function'] = _scan + kw['path_function'] = _path + kw['recursive'] = 1 + kw['skeys'] = suffixes + + apply(Current.__init__, (self,) + args, kw) + + def find_include(self, include, source_dir, path): + n = SCons.Node.FS.find_file(include, (source_dir,) + path, self.fs.File) + return n, include + + def scan(self, node, env, path=()): + node = node.rfile() + + if not node.exists(): + return [] + + # cache the includes list in node so we only scan it once: + if node.includes != None: + includes = node.includes + else: + includes = self.cre.findall(node.get_contents()) + node.includes = includes + + nodes = [] + source_dir = node.get_dir() + for include in includes: + n, i = self.find_include(include, source_dir, path) + + if not n is None: + nodes.append(n) + else: + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) + + # Schwartzian transform from the Python FAQ Wizard + def st(List, Metric): + def pairing(element, M = Metric): + return (M(element), element) + def stripit(pair): + return pair[1] + paired = map(pairing, List) + paired.sort() + return map(stripit, paired) + + def normalize(node): + # We don't want the order of includes to be + # modified by case changes on case insensitive OSes, so + # normalize the case of the filename here: + # (see test/win32pathmadness.py for a test of this) + return SCons.Node.FS._my_normcase(str(node)) + + transformed = st(nodes, normalize) + # print "Classic: " + str(node) + " => " + str(map(lambda x: str(x),list(transformed))) + return transformed + +class ClassicCPP(Classic): + """ + A Classic Scanner subclass which takes into account the type of + bracketing used to include the file, and uses classic CPP rules + for searching for the files based on the bracketing. + + Note that in order for this to work, the regular expression passed + to the constructor must return the leading bracket in group 0, and + the contained filename in group 1. + """ + def find_include(self, include, source_dir, path): + if include[0] == '"': + n = SCons.Node.FS.find_file(include[1], + (source_dir,) + path, + self.fs.File) + else: + n = SCons.Node.FS.find_file(include[1], + path + (source_dir,), + self.fs.File) + return n, include[1] |