summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Scanner
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2003-04-23 22:27:58 (GMT)
committerSteven Knight <knight@baldmt.com>2003-04-23 22:27:58 (GMT)
commit9587e1d2dad1c532d86f664f5cbd6266ebd77808 (patch)
tree71ab8dbc059c0d16de3f5088427e288716d9dd43 /src/engine/SCons/Scanner
parent9c4ebd90350becd6ff9b1b4e4049546680c849b6 (diff)
downloadSCons-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.py103
-rw-r--r--src/engine/SCons/Scanner/Fortran.py72
-rw-r--r--src/engine/SCons/Scanner/IDL.py43
-rw-r--r--src/engine/SCons/Scanner/IDLTests.py428
-rw-r--r--src/engine/SCons/Scanner/ScannerTests.py178
-rw-r--r--src/engine/SCons/Scanner/__init__.py108
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]