diff options
Diffstat (limited to 'src/engine/SCons/Scanner')
-rw-r--r-- | src/engine/SCons/Scanner/C.py | 132 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/CTests.py | 468 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/D.py | 74 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/Dir.py | 111 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/DirTests.py | 140 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/Fortran.py | 320 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/FortranTests.py | 543 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/IDL.py | 48 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/IDLTests.py | 453 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/LaTeX.py | 345 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/LaTeXTests.py | 162 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/Prog.py | 103 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/ProgTests.py | 262 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/RC.py | 55 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/RCTests.py | 168 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/ScannerTests.py | 611 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/__init__.py | 415 |
17 files changed, 4410 insertions, 0 deletions
diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py new file mode 100644 index 0000000..68ba74b --- /dev/null +++ b/src/engine/SCons/Scanner/C.py @@ -0,0 +1,132 @@ +"""SCons.Scanner.C + +This module implements the depenency scanner for C/C++ code. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/C.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Node.FS +import SCons.Scanner +import SCons.Util + +import SCons.cpp + +class SConsCPPScanner(SCons.cpp.PreProcessor): + """ + SCons-specific subclass of the cpp.py module's processing. + + We subclass this so that: 1) we can deal with files represented + by Nodes, not strings; 2) we can keep track of the files that are + missing. + """ + def __init__(self, *args, **kw): + apply(SCons.cpp.PreProcessor.__init__, (self,)+args, kw) + self.missing = [] + def initialize_result(self, fname): + self.result = SCons.Util.UniqueList([fname]) + def finalize_result(self, fname): + return self.result[1:] + def find_include_file(self, t): + keyword, quote, fname = t + result = SCons.Node.FS.find_file(fname, self.searchpath[quote]) + if not result: + self.missing.append((fname, self.current_file)) + return result + def read_file(self, file): + try: + fp = open(str(file.rfile())) + except EnvironmentError, e: + self.missing.append((file, self.current_file)) + return '' + else: + return fp.read() + +def dictify_CPPDEFINES(env): + cppdefines = env.get('CPPDEFINES', {}) + if cppdefines is None: + return {} + if SCons.Util.is_Sequence(cppdefines): + result = {} + for c in cppdefines: + if SCons.Util.is_Sequence(c): + result[c[0]] = c[1] + else: + result[c] = None + return result + if not SCons.Util.is_Dict(cppdefines): + return {cppdefines : None} + return cppdefines + +class SConsCPPScannerWrapper: + """ + The SCons wrapper around a cpp.py scanner. + + This is the actual glue between the calling conventions of generic + SCons scanners, and the (subclass of) cpp.py class that knows how + to look for #include lines with reasonably real C-preprocessor-like + evaluation of #if/#ifdef/#else/#elif lines. + """ + def __init__(self, name, variable): + self.name = name + self.path = SCons.Scanner.FindPathDirs(variable) + def __call__(self, node, env, path = ()): + cpp = SConsCPPScanner(current = node.get_dir(), + cpppath = path, + dict = dictify_CPPDEFINES(env)) + result = cpp(node) + for included, includer in cpp.missing: + fmt = "No dependency generated for file: %s (included from: %s) -- file not found" + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + fmt % (included, includer)) + return result + + def recurse_nodes(self, nodes): + return nodes + def select(self, node): + return self + +def CScanner(): + """Return a prototype Scanner instance for scanning source files + that use the C pre-processor""" + + # Here's how we would (or might) use the CPP scanner code above that + # knows how to evaluate #if/#ifdef/#else/#elif lines when searching + # for #includes. This is commented out for now until we add the + # right configurability to let users pick between the scanners. + #return SConsCPPScannerWrapper("CScanner", "CPPPATH") + + cs = SCons.Scanner.ClassicCPP("CScanner", + "$CPPSUFFIXES", + "CPPPATH", + '^[ \t]*#[ \t]*(?:include|import)[ \t]*(<|")([^>"]+)(>|")') + return cs + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py new file mode 100644 index 0000000..6d7f8a2 --- /dev/null +++ b/src/engine/SCons/Scanner/CTests.py @@ -0,0 +1,468 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/CTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import sys +import TestCmd +import unittest +import UserDict + +import SCons.Node.FS +import SCons.Warnings + +import SCons.Scanner.C + +test = TestCmd.TestCmd(workdir = '') + +os.chdir(test.workpath('')) + +# 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\" +#import <f4.h> + +int main() +{ + return 0; +} +""") + +test.write('f3.cpp',""" +#include \t "f1.h" + \t #include "f2.h" +# \t include "f3-test.h" + +#include \t <d1/f1.h> + \t #include <d1/f2.h> +# \t include <d1/f3-test.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-test.h', 'fi.h', 'fj.h', 'never.h', + 'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h', 'd1/fi.h', 'd1/fj.h', + 'd1/d2/f1.h', 'd1/d2/f2.h', 'd1/d2/f3-test.h', + 'd1/d2/f4.h', 'd1/d2/fi.h', 'd1/d2/fj.h'] + +for h in headers: + test.write(h, " ") + +test.write('f2.h',""" +#include "fi.h" +""") + +test.write('f3-test.h',""" +#include <fj.h> +""") + + +test.subdir('include', 'subdir', ['subdir', 'include']) + +test.write('fa.cpp',""" +#include \"fa.h\" +#include <fb.h> + +int main() +{ + return 0; +} +""") + +test.write(['include', 'fa.h'], "\n") +test.write(['include', 'fb.h'], "\n") +test.write(['subdir', 'include', 'fa.h'], "\n") +test.write(['subdir', 'include', 'fb.h'], "\n") + + +test.subdir('repository', ['repository', 'include'], + ['repository', 'src' ]) +test.subdir('work', ['work', 'src']) + +test.write(['repository', 'include', 'iii.h'], "\n") + +test.write(['work', 'src', 'fff.c'], """ +#include <iii.h> +#include <jjj.h> + +int main() +{ + return 0; +} +""") + +test.write([ 'work', 'src', 'aaa.c'], """ +#include "bbb.h" + +int main() +{ + return 0; +} +""") + +test.write([ 'work', 'src', 'bbb.h'], "\n") + +test.write([ 'repository', 'src', 'ccc.c'], """ +#include "ddd.h" + +int main() +{ + return 0; +} +""") + +test.write([ 'repository', 'src', 'ddd.h'], "\n") + +test.write('f5.c', """\ +#include\"f5a.h\" +#include<f5b.h> +""") + +test.write("f5a.h", "\n") +test.write("f5b.h", "\n") + +# define some helpers: + +class DummyEnvironment(UserDict.UserDict): + def __init__(self, **kw): + UserDict.UserDict.__init__(self) + self.data.update(kw) + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + return self.data + + def subst(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return self.data[strSubst[1:]] + return strSubst + + def subst_list(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return [self.data[strSubst[1:]]] + return [[strSubst]] + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +if os.path.normcase('foo') == os.path.normcase('FOO'): + my_normpath = os.path.normcase +else: + my_normpath = os.path.normpath + +def deps_match(self, deps, headers): + global my_normpath + scanned = map(my_normpath, map(str, deps)) + expect = map(my_normpath, headers) + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + +# define some tests: + +class CScannerTestCase1(unittest.TestCase): + def runTest(self): + """Find local files with no CPPPATH""" + env = DummyEnvironment(CPPPATH=[]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f1.cpp'), env, path) + headers = ['f1.h', 'f2.h'] + deps_match(self, deps, headers) + +class CScannerTestCase2(unittest.TestCase): + def runTest(self): + """Find a file in a CPPPATH directory""" + env = DummyEnvironment(CPPPATH=[test.workpath("d1")]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f1.cpp'), env, path) + headers = ['f1.h', 'd1/f2.h'] + deps_match(self, deps, headers) + +class CScannerTestCase3(unittest.TestCase): + def runTest(self): + """Find files in explicit subdirectories, ignore missing file""" + env = DummyEnvironment(CPPPATH=[test.workpath("d1")]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f2.cpp'), env, path) + headers = ['d1/f1.h', 'f1.h', 'd1/d2/f1.h'] + deps_match(self, deps, headers) + +class CScannerTestCase4(unittest.TestCase): + def runTest(self): + """Find files in explicit subdirectories""" + env = DummyEnvironment(CPPPATH=[test.workpath("d1"), test.workpath("d1/d2")]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f2.cpp'), env, path) + headers = ['d1/f1.h', 'f1.h', 'd1/d2/f1.h', 'd1/d2/f4.h'] + deps_match(self, deps, headers) + +class CScannerTestCase5(unittest.TestCase): + def runTest(self): + """Make sure files in repositories will get scanned""" + env = DummyEnvironment(CPPPATH=[]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + + n = env.File('f3.cpp') + 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 VariantDir functionality. + assert n.rexists_called + + headers = ['f1.h', 'f2.h', 'f3-test.h', + 'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h'] + deps_match(self, deps, headers) + +class CScannerTestCase6(unittest.TestCase): + def runTest(self): + """Find a same-named file in different directories when CPPPATH changes""" + env1 = DummyEnvironment(CPPPATH=[test.workpath("d1")]) + env2 = DummyEnvironment(CPPPATH=[test.workpath("d1/d2")]) + s = SCons.Scanner.C.CScanner() + path1 = s.path(env1) + path2 = s.path(env2) + deps1 = s(env1.File('f1.cpp'), env1, path1) + deps2 = s(env2.File('f1.cpp'), env2, path2) + headers1 = ['f1.h', 'd1/f2.h'] + headers2 = ['f1.h', 'd1/d2/f2.h'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class CScannerTestCase8(unittest.TestCase): + def runTest(self): + """Find files in a subdirectory relative to the current directory""" + env = DummyEnvironment(CPPPATH=["include"]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps1 = s(env.File('fa.cpp'), env, path) + env.fs.chdir(env.Dir('subdir')) + dir = env.fs.getcwd() + env.fs.chdir(env.Dir('')) + path = s.path(env, dir) + deps2 = s(env.File('#fa.cpp'), env, path) + headers1 = map(test.workpath, ['include/fa.h', 'include/fb.h']) + headers2 = ['include/fa.h', 'include/fb.h'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class CScannerTestCase9(unittest.TestCase): + def runTest(self): + """Generate a warning when we can't find a #included file""" + 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.h','\n') + fs = SCons.Node.FS.FS(test.workpath('')) + env = DummyEnvironment(CPPPATH=[]) + env.fs = fs + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(fs.File('fa.cpp'), env, path) + + # Did we catch the warning associated with not finding fb.h? + assert to.out + + deps_match(self, deps, [ 'fa.h' ]) + test.unlink('fa.h') + +class CScannerTestCase10(unittest.TestCase): + def runTest(self): + """Find files in the local directory when the scanned file is elsewhere""" + fs = SCons.Node.FS.FS(test.workpath('')) + fs.chdir(fs.Dir('include')) + env = DummyEnvironment(CPPPATH=[]) + env.fs = fs + s = SCons.Scanner.C.CScanner() + path = s.path(env) + test.write('include/fa.cpp', test.read('fa.cpp')) + fs.chdir(fs.Dir('..')) + deps = s(fs.File('#include/fa.cpp'), env, path) + deps_match(self, deps, [ 'include/fa.h', 'include/fb.h' ]) + test.unlink('include/fa.cpp') + +class CScannerTestCase11(unittest.TestCase): + def runTest(self): + """Handle dependencies on a derived .h file in a non-existent directory""" + 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.h') + f1.builder=1 + env = DummyEnvironment(CPPPATH=['include', 'include2']) + env.fs = fs + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(fs.File('src/fff.c'), env, path) + deps_match(self, deps, [ test.workpath('repository/include/iii.h'), + 'include2/jjj.h' ]) + os.chdir(test.workpath('')) + +class CScannerTestCase12(unittest.TestCase): + def runTest(self): + """Find files in VariantDir() directories""" + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.VariantDir('build1', 'src', 1) + fs.VariantDir('build2', 'src', 0) + fs.Repository(test.workpath('repository')) + env = DummyEnvironment(CPPPATH=[]) + env.fs = fs + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps1 = s(fs.File('build1/aaa.c'), env, path) + deps_match(self, deps1, [ 'build1/bbb.h' ]) + deps2 = s(fs.File('build2/aaa.c'), env, path) + deps_match(self, deps2, [ 'src/bbb.h' ]) + deps3 = s(fs.File('build1/ccc.c'), env, path) + deps_match(self, deps3, [ 'build1/ddd.h' ]) + deps4 = s(fs.File('build2/ccc.c'), env, path) + deps_match(self, deps4, [ test.workpath('repository/src/ddd.h') ]) + os.chdir(test.workpath('')) + +class CScannerTestCase13(unittest.TestCase): + def runTest(self): + """Find files in directories named in a substituted environment variable""" + class SubstEnvironment(DummyEnvironment): + def subst(self, arg, target=None, source=None, conv=None, test=test): + if arg == "$blah": + return test.workpath("d1") + else: + return arg + env = SubstEnvironment(CPPPATH=["$blah"]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f1.cpp'), env, path) + headers = ['f1.h', 'd1/f2.h'] + deps_match(self, deps, headers) + +class CScannerTestCase14(unittest.TestCase): + def runTest(self): + """Find files when there's no space between "#include" and the name""" + env = DummyEnvironment(CPPPATH=[]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f5.c'), env, path) + headers = ['f5a.h', 'f5b.h'] + deps_match(self, deps, headers) + +class CScannerTestCase15(unittest.TestCase): + def runTest(self): + """Verify scanner initialization with the suffixes in $CPPSUFFIXES""" + suffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", + ".h", ".H", ".hxx", ".hpp", ".hh", + ".F", ".fpp", ".FPP", + ".S", ".spp", ".SPP"] + env = DummyEnvironment(CPPSUFFIXES = suffixes) + s = SCons.Scanner.C.CScanner() + for suffix in suffixes: + assert suffix in s.get_skeys(env), "%s not in skeys" % suffix + + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(CScannerTestCase1()) + suite.addTest(CScannerTestCase2()) + suite.addTest(CScannerTestCase3()) + suite.addTest(CScannerTestCase4()) + suite.addTest(CScannerTestCase5()) + suite.addTest(CScannerTestCase6()) + suite.addTest(CScannerTestCase8()) + suite.addTest(CScannerTestCase9()) + suite.addTest(CScannerTestCase10()) + suite.addTest(CScannerTestCase11()) + suite.addTest(CScannerTestCase12()) + suite.addTest(CScannerTestCase13()) + suite.addTest(CScannerTestCase14()) + suite.addTest(CScannerTestCase15()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/D.py b/src/engine/SCons/Scanner/D.py new file mode 100644 index 0000000..e0637c0 --- /dev/null +++ b/src/engine/SCons/Scanner/D.py @@ -0,0 +1,74 @@ +"""SCons.Scanner.D + +Scanner for the Digital Mars "D" programming language. + +Coded by Andy Friesen +17 Nov 2003 + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/D.py 4577 2009/12/27 19:44:43 scons" + +import re +import string + +import SCons.Scanner + +def DScanner(): + """Return a prototype Scanner instance for scanning D source files""" + ds = D() + return ds + +class D(SCons.Scanner.Classic): + def __init__ (self): + SCons.Scanner.Classic.__init__ (self, + name = "DScanner", + suffixes = '$DSUFFIXES', + path_variable = 'DPATH', + regex = 'import\s+(?:[a-zA-Z0-9_.]+)\s*(?:,\s*(?:[a-zA-Z0-9_.]+)\s*)*;') + + self.cre2 = re.compile ('(?:import\s)?\s*([a-zA-Z0-9_.]+)\s*(?:,|;)', re.M) + + def find_include(self, include, source_dir, path): + # translate dots (package separators) to slashes + inc = string.replace(include, '.', '/') + + i = SCons.Node.FS.find_file(inc + '.d', (source_dir,) + path) + if i is None: + i = SCons.Node.FS.find_file (inc + '.di', (source_dir,) + path) + return i, include + + def find_include_names(self, node): + includes = [] + for i in self.cre.findall(node.get_text_contents()): + includes = includes + self.cre2.findall(i) + return includes + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/Dir.py b/src/engine/SCons/Scanner/Dir.py new file mode 100644 index 0000000..eacd855 --- /dev/null +++ b/src/engine/SCons/Scanner/Dir.py @@ -0,0 +1,111 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/Dir.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Node.FS +import SCons.Scanner + +def only_dirs(nodes): + is_Dir = lambda n: isinstance(n.disambiguate(), SCons.Node.FS.Dir) + return filter(is_Dir, nodes) + +def DirScanner(**kw): + """Return a prototype Scanner instance for scanning + directories for on-disk files""" + kw['node_factory'] = SCons.Node.FS.Entry + kw['recursive'] = only_dirs + return apply(SCons.Scanner.Base, (scan_on_disk, "DirScanner"), kw) + +def DirEntryScanner(**kw): + """Return a prototype Scanner instance for "scanning" + directory Nodes for their in-memory entries""" + kw['node_factory'] = SCons.Node.FS.Entry + kw['recursive'] = None + return apply(SCons.Scanner.Base, (scan_in_memory, "DirEntryScanner"), kw) + +skip_entry = {} + +skip_entry_list = [ + '.', + '..', + '.sconsign', + # Used by the native dblite.py module. + '.sconsign.dblite', + # Used by dbm and dumbdbm. + '.sconsign.dir', + # Used by dbm. + '.sconsign.pag', + # Used by dumbdbm. + '.sconsign.dat', + '.sconsign.bak', + # Used by some dbm emulations using Berkeley DB. + '.sconsign.db', +] + +for skip in skip_entry_list: + skip_entry[skip] = 1 + skip_entry[SCons.Node.FS._my_normcase(skip)] = 1 + +do_not_scan = lambda k: not skip_entry.has_key(k) + +def scan_on_disk(node, env, path=()): + """ + Scans a directory for on-disk files and directories therein. + + Looking up the entries will add these to the in-memory Node tree + representation of the file system, so all we have to do is just + that and then call the in-memory scanning function. + """ + try: + flist = node.fs.listdir(node.abspath) + except (IOError, OSError): + return [] + e = node.Entry + for f in filter(do_not_scan, flist): + # Add ./ to the beginning of the file name so if it begins with a + # '#' we don't look it up relative to the top-level directory. + e('./' + f) + return scan_in_memory(node, env, path) + +def scan_in_memory(node, env, path=()): + """ + "Scans" a Node.FS.Dir for its in-memory entries. + """ + try: + entries = node.entries + except AttributeError: + # It's not a Node.FS.Dir (or doesn't look enough like one for + # our purposes), which can happen if a target list containing + # mixed Node types (Dirs and Files, for example) has a Dir as + # the first entry. + return [] + entry_list = filter(do_not_scan, entries.keys()) + entry_list.sort() + return map(lambda n, e=entries: e[n], entry_list) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/DirTests.py b/src/engine/SCons/Scanner/DirTests.py new file mode 100644 index 0000000..d244c5c --- /dev/null +++ b/src/engine/SCons/Scanner/DirTests.py @@ -0,0 +1,140 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/DirTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import sys +import types +import unittest + +import TestCmd +import SCons.Node.FS +import SCons.Scanner.Dir + +#class DummyNode: +# def __init__(self, name, fs): +# self.name = name +# self.abspath = test.workpath(name) +# self.fs = fs +# def __str__(self): +# return self.name +# def Entry(self, name): +# return self.fs.Entry(name) + +class DummyEnvironment: + def __init__(self, root): + self.fs = SCons.Node.FS.FS(root) + def Dir(self, name): + return self.fs.Dir(name) + def Entry(self, name): + return self.fs.Entry(name) + def File(self, name): + return self.fs.File(name) + def get_factory(self, factory): + return factory or self.fs.Entry + +class DirScannerTestBase(unittest.TestCase): + def setUp(self): + self.test = TestCmd.TestCmd(workdir = '') + + self.test.subdir('dir', ['dir', 'sub']) + + self.test.write(['dir', 'f1'], "dir/f1\n") + self.test.write(['dir', 'f2'], "dir/f2\n") + self.test.write(['dir', '.sconsign'], "dir/.sconsign\n") + self.test.write(['dir', '.sconsign.bak'], "dir/.sconsign.bak\n") + self.test.write(['dir', '.sconsign.dat'], "dir/.sconsign.dat\n") + self.test.write(['dir', '.sconsign.db'], "dir/.sconsign.db\n") + self.test.write(['dir', '.sconsign.dblite'], "dir/.sconsign.dblite\n") + self.test.write(['dir', '.sconsign.dir'], "dir/.sconsign.dir\n") + self.test.write(['dir', '.sconsign.pag'], "dir/.sconsign.pag\n") + self.test.write(['dir', 'sub', 'f3'], "dir/sub/f3\n") + self.test.write(['dir', 'sub', 'f4'], "dir/sub/f4\n") + self.test.write(['dir', 'sub', '.sconsign'], "dir/.sconsign\n") + self.test.write(['dir', 'sub', '.sconsign.bak'], "dir/.sconsign.bak\n") + self.test.write(['dir', 'sub', '.sconsign.dat'], "dir/.sconsign.dat\n") + self.test.write(['dir', 'sub', '.sconsign.dblite'], "dir/.sconsign.dblite\n") + self.test.write(['dir', 'sub', '.sconsign.dir'], "dir/.sconsign.dir\n") + self.test.write(['dir', 'sub', '.sconsign.pag'], "dir/.sconsign.pag\n") + +class DirScannerTestCase(DirScannerTestBase): + def runTest(self): + env = DummyEnvironment(self.test.workpath()) + + s = SCons.Scanner.Dir.DirScanner() + + expect = [ + os.path.join('dir', 'f1'), + os.path.join('dir', 'f2'), + os.path.join('dir', 'sub'), + ] + deps = s(env.Dir('dir'), env, ()) + sss = map(str, deps) + assert sss == expect, sss + + expect = [ + os.path.join('dir', 'sub', 'f3'), + os.path.join('dir', 'sub', 'f4'), + ] + deps = s(env.Dir('dir/sub'), env, ()) + sss = map(str, deps) + assert sss == expect, sss + +class DirEntryScannerTestCase(DirScannerTestBase): + def runTest(self): + env = DummyEnvironment(self.test.workpath()) + + s = SCons.Scanner.Dir.DirEntryScanner() + + deps = s(env.Dir('dir'), env, ()) + sss = map(str, deps) + assert sss == [], sss + + deps = s(env.Dir('dir/sub'), env, ()) + sss = map(str, deps) + assert sss == [], sss + + # Make sure we don't blow up if handed a non-Dir node. + deps = s(env.File('dir/f1'), env, ()) + sss = map(str, deps) + assert sss == [], sss + +def suite(): + suite = unittest.TestSuite() + suite.addTest(DirScannerTestCase()) + suite.addTest(DirEntryScannerTestCase()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/Fortran.py b/src/engine/SCons/Scanner/Fortran.py new file mode 100644 index 0000000..7c011a6 --- /dev/null +++ b/src/engine/SCons/Scanner/Fortran.py @@ -0,0 +1,320 @@ +"""SCons.Scanner.Fortran + +This module implements the dependency scanner for Fortran code. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/Fortran.py 4577 2009/12/27 19:44:43 scons" + +import re +import string + +import SCons.Node +import SCons.Node.FS +import SCons.Scanner +import SCons.Util +import SCons.Warnings + +class F90Scanner(SCons.Scanner.Classic): + """ + A Classic Scanner subclass for Fortran source files which takes + into account both USE and INCLUDE statements. This scanner will + work for both F77 and F90 (and beyond) compilers. + + Currently, this scanner assumes that the include files do not contain + USE statements. To enable the ability to deal with USE statements + in include files, add logic right after the module names are found + to loop over each include file, search for and locate each USE + statement, and append each module name to the list of dependencies. + Caching the search results in a common dictionary somewhere so that + the same include file is not searched multiple times would be a + smart thing to do. + """ + + def __init__(self, name, suffixes, path_variable, + use_regex, incl_regex, def_regex, *args, **kw): + + self.cre_use = re.compile(use_regex, re.M) + self.cre_incl = re.compile(incl_regex, re.M) + self.cre_def = re.compile(def_regex, re.M) + + def _scan(node, env, path, self=self): + node = node.rfile() + + if not node.exists(): + return [] + + return self.scan(node, env, path) + + kw['function'] = _scan + kw['path_function'] = SCons.Scanner.FindPathDirs(path_variable) + kw['recursive'] = 1 + kw['skeys'] = suffixes + kw['name'] = name + + apply(SCons.Scanner.Current.__init__, (self,) + args, kw) + + def scan(self, node, env, path=()): + + # cache the includes list in node so we only scan it once: + if node.includes != None: + mods_and_includes = node.includes + else: + # retrieve all included filenames + includes = self.cre_incl.findall(node.get_text_contents()) + # retrieve all USE'd module names + modules = self.cre_use.findall(node.get_text_contents()) + # retrieve all defined module names + defmodules = self.cre_def.findall(node.get_text_contents()) + + # Remove all USE'd module names that are defined in the same file + d = {} + for m in defmodules: + d[m] = 1 + modules = filter(lambda m, d=d: not d.has_key(m), modules) + #modules = self.undefinedModules(modules, defmodules) + + # Convert module name to a .mod filename + suffix = env.subst('$FORTRANMODSUFFIX') + modules = map(lambda x, s=suffix: string.lower(x) + s, modules) + # Remove unique items from the list + mods_and_includes = SCons.Util.unique(includes+modules) + node.includes = mods_and_includes + + # This is a hand-coded DSU (decorate-sort-undecorate, or + # Schwartzian transform) pattern. The sort key is the raw name + # of the file as specifed on the USE or INCLUDE line, which lets + # us keep the sort order constant regardless of whether the file + # is actually found in a Repository or locally. + nodes = [] + source_dir = node.get_dir() + if callable(path): + path = path() + for dep in mods_and_includes: + n, i = self.find_include(dep, source_dir, path) + + if n is None: + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "No dependency generated for file: %s (referenced by: %s) -- file not found" % (i, node)) + else: + sortkey = self.sort_key(dep) + nodes.append((sortkey, n)) + + nodes.sort() + nodes = map(lambda pair: pair[1], nodes) + return nodes + +def FortranScan(path_variable="FORTRANPATH"): + """Return a prototype Scanner instance for scanning source files + for Fortran USE & INCLUDE statements""" + +# The USE statement regex matches the following: +# +# USE module_name +# USE :: module_name +# USE, INTRINSIC :: module_name +# USE, NON_INTRINSIC :: module_name +# +# Limitations +# +# -- While the regex can handle multiple USE statements on one line, +# it cannot properly handle them if they are commented out. +# In either of the following cases: +# +# ! USE mod_a ; USE mod_b [entire line is commented out] +# USE mod_a ! ; USE mod_b [in-line comment of second USE statement] +# +# the second module name (mod_b) will be picked up as a dependency +# even though it should be ignored. The only way I can see +# to rectify this would be to modify the scanner to eliminate +# the call to re.findall, read in the contents of the file, +# treating the comment character as an end-of-line character +# in addition to the normal linefeed, loop over each line, +# weeding out the comments, and looking for the USE statements. +# One advantage to this is that the regex passed to the scanner +# would no longer need to match a semicolon. +# +# -- I question whether or not we need to detect dependencies to +# INTRINSIC modules because these are built-in to the compiler. +# If we consider them a dependency, will SCons look for them, not +# find them, and kill the build? Or will we there be standard +# compiler-specific directories we will need to point to so the +# compiler and SCons can locate the proper object and mod files? + +# Here is a breakdown of the regex: +# +# (?i) : regex is case insensitive +# ^ : start of line +# (?: : group a collection of regex symbols without saving the match as a "group" +# ^|; : matches either the start of the line or a semicolon - semicolon +# ) : end the unsaved grouping +# \s* : any amount of white space +# USE : match the string USE, case insensitive +# (?: : group a collection of regex symbols without saving the match as a "group" +# \s+| : match one or more whitespace OR .... (the next entire grouped set of regex symbols) +# (?: : group a collection of regex symbols without saving the match as a "group" +# (?: : establish another unsaved grouping of regex symbols +# \s* : any amount of white space +# , : match a comma +# \s* : any amount of white space +# (?:NON_)? : optionally match the prefix NON_, case insensitive +# INTRINSIC : match the string INTRINSIC, case insensitive +# )? : optionally match the ", INTRINSIC/NON_INTRINSIC" grouped expression +# \s* : any amount of white space +# :: : match a double colon that must appear after the INTRINSIC/NON_INTRINSIC attribute +# ) : end the unsaved grouping +# ) : end the unsaved grouping +# \s* : match any amount of white space +# (\w+) : match the module name that is being USE'd +# +# + use_regex = "(?i)(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)" + + +# The INCLUDE statement regex matches the following: +# +# INCLUDE 'some_Text' +# INCLUDE "some_Text" +# INCLUDE "some_Text" ; INCLUDE "some_Text" +# INCLUDE kind_"some_Text" +# INCLUDE kind_'some_Text" +# +# where some_Text can include any alphanumeric and/or special character +# as defined by the Fortran 2003 standard. +# +# Limitations: +# +# -- The Fortran standard dictates that a " or ' in the INCLUDE'd +# string must be represented as a "" or '', if the quotes that wrap +# the entire string are either a ' or ", respectively. While the +# regular expression below can detect the ' or " characters just fine, +# the scanning logic, presently is unable to detect them and reduce +# them to a single instance. This probably isn't an issue since, +# in practice, ' or " are not generally used in filenames. +# +# -- This regex will not properly deal with multiple INCLUDE statements +# when the entire line has been commented out, ala +# +# ! INCLUDE 'some_file' ; INCLUDE 'some_file' +# +# In such cases, it will properly ignore the first INCLUDE file, +# but will actually still pick up the second. Interestingly enough, +# the regex will properly deal with these cases: +# +# INCLUDE 'some_file' +# INCLUDE 'some_file' !; INCLUDE 'some_file' +# +# To get around the above limitation, the FORTRAN programmer could +# simply comment each INCLUDE statement separately, like this +# +# ! INCLUDE 'some_file' !; INCLUDE 'some_file' +# +# The way I see it, the only way to get around this limitation would +# be to modify the scanning logic to replace the calls to re.findall +# with a custom loop that processes each line separately, throwing +# away fully commented out lines before attempting to match against +# the INCLUDE syntax. +# +# Here is a breakdown of the regex: +# +# (?i) : regex is case insensitive +# (?: : begin a non-saving group that matches the following: +# ^ : either the start of the line +# | : or +# ['">]\s*; : a semicolon that follows a single quote, +# double quote or greater than symbol (with any +# amount of whitespace in between). This will +# allow the regex to match multiple INCLUDE +# statements per line (although it also requires +# the positive lookahead assertion that is +# used below). It will even properly deal with +# (i.e. ignore) cases in which the additional +# INCLUDES are part of an in-line comment, ala +# " INCLUDE 'someFile' ! ; INCLUDE 'someFile2' " +# ) : end of non-saving group +# \s* : any amount of white space +# INCLUDE : match the string INCLUDE, case insensitive +# \s+ : match one or more white space characters +# (?\w+_)? : match the optional "kind-param _" prefix allowed by the standard +# [<"'] : match the include delimiter - an apostrophe, double quote, or less than symbol +# (.+?) : match one or more characters that make up +# the included path and file name and save it +# in a group. The Fortran standard allows for +# any non-control character to be used. The dot +# operator will pick up any character, including +# control codes, but I can't conceive of anyone +# putting control codes in their file names. +# The question mark indicates it is non-greedy so +# that regex will match only up to the next quote, +# double quote, or greater than symbol +# (?=["'>]) : positive lookahead assertion to match the include +# delimiter - an apostrophe, double quote, or +# greater than symbol. This level of complexity +# is required so that the include delimiter is +# not consumed by the match, thus allowing the +# sub-regex discussed above to uniquely match a +# set of semicolon-separated INCLUDE statements +# (as allowed by the F2003 standard) + + include_regex = """(?i)(?:^|['">]\s*;)\s*INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])""" + +# The MODULE statement regex finds module definitions by matching +# the following: +# +# MODULE module_name +# +# but *not* the following: +# +# MODULE PROCEDURE procedure_name +# +# Here is a breakdown of the regex: +# +# (?i) : regex is case insensitive +# ^\s* : any amount of white space +# MODULE : match the string MODULE, case insensitive +# \s+ : match one or more white space characters +# (?!PROCEDURE) : but *don't* match if the next word matches +# PROCEDURE (negative lookahead assertion), +# case insensitive +# (\w+) : match one or more alphanumeric characters +# that make up the defined module name and +# save it in a group + + def_regex = """(?i)^\s*MODULE\s+(?!PROCEDURE)(\w+)""" + + scanner = F90Scanner("FortranScan", + "$FORTRANSUFFIXES", + path_variable, + use_regex, + include_regex, + def_regex) + return scanner + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/FortranTests.py b/src/engine/SCons/Scanner/FortranTests.py new file mode 100644 index 0000000..d3f3041 --- /dev/null +++ b/src/engine/SCons/Scanner/FortranTests.py @@ -0,0 +1,543 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/FortranTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import sys +import unittest + +import SCons.Scanner.Fortran +import SCons.Node.FS +import SCons.Warnings + +import TestCmd + +original = os.getcwd() + +test = TestCmd.TestCmd(workdir = '') + +os.chdir(test.workpath('')) + +# create some source files and headers: + +test.write('fff1.f',""" + PROGRAM FOO + INCLUDE 'f1.f' + include 'f2.f' + STOP + END +""") + +test.write('fff2.f',""" + PROGRAM FOO + INCLUDE 'f2.f' + include 'd1/f2.f' + INCLUDE 'd2/f2.f' + STOP + END +""") + +test.write('fff3.f',""" + PROGRAM FOO + INCLUDE 'f3.f' ; INCLUDE\t'd1/f3.f' + STOP + END +""") + + +# for Emacs -> " + +test.subdir('d1', ['d1', 'd2']) + +headers = ['fi.f', 'never.f', + 'd1/f1.f', 'd1/f2.f', 'd1/f3.f', 'd1/fi.f', + 'd1/d2/f1.f', 'd1/d2/f2.f', 'd1/d2/f3.f', + 'd1/d2/f4.f', 'd1/d2/fi.f'] + +for h in headers: + test.write(h, "\n") + + +test.subdir('include', 'subdir', ['subdir', 'include']) + +test.write('fff4.f',""" + PROGRAM FOO + INCLUDE 'f4.f' + STOP + END +""") + +test.write('include/f4.f', "\n") +test.write('subdir/include/f4.f', "\n") + +test.write('fff5.f',""" + PROGRAM FOO + INCLUDE 'f5.f' + INCLUDE 'not_there.f' + STOP + END +""") + +test.write('f5.f', "\n") + +test.subdir('repository', ['repository', 'include'], + [ 'repository', 'src' ]) +test.subdir('work', ['work', 'src']) + +test.write(['repository', 'include', 'iii.f'], "\n") + +test.write(['work', 'src', 'fff.f'], """ + PROGRAM FOO + INCLUDE 'iii.f' + INCLUDE 'jjj.f' + STOP + END +""") + +test.write([ 'work', 'src', 'aaa.f'], """ + PROGRAM FOO + INCLUDE 'bbb.f' + STOP + END +""") + +test.write([ 'work', 'src', 'bbb.f'], "\n") + +test.write([ 'repository', 'src', 'ccc.f'], """ + PROGRAM FOO + INCLUDE 'ddd.f' + STOP + END +""") + +test.write([ 'repository', 'src', 'ddd.f'], "\n") + + +test.write('fff90a.f90',""" + PROGRAM FOO + +! Test comments - these includes should NOT be picked up +C INCLUDE 'fi.f' +# INCLUDE 'fi.f' + ! INCLUDE 'fi.f' + + INCLUDE 'f1.f' ! in-line comments are valid syntax + INCLUDE"fi.f" ! space is significant - this should be ignored + INCLUDE <f2.f> ! Absoft compiler allows greater than/less than delimiters +! +! Allow kind type parameters + INCLUDE kindType_"f3.f" + INCLUDE kind_Type_"f4.f" +! +! Test multiple statements per line - use various spacings between semicolons + incLUDE 'f5.f';include "f6.f" ; include <f7.f>; include 'f8.f' ;include kindType_'f9.f' +! +! Test various USE statement syntaxes +! + USE Mod01 + use mod02 + use use + USE mOD03, ONLY : someVar + USE MOD04 ,only:someVar + USE Mod05 , ONLY: someVar ! in-line comment + USE Mod06,ONLY :someVar,someOtherVar + + USE mod07;USE mod08; USE mod09 ;USE mod10 ; USE mod11 ! Test various semicolon placements + use mod12 ;use mod13! Test comment at end of line + +! USE modi +! USE modia ; use modib ! Scanner regexp will only ignore the first - this is a deficiency in the regexp + ! USE modic ; ! use modid ! Scanner regexp should ignore both modules + USE mod14 !; USE modi ! Only ignore the second + USE mod15!;USE modi + USE mod16 ! ; USE modi + +! Test semicolon syntax - use various spacings + USE :: mod17 + USE::mod18 + USE ::mod19 ; USE:: mod20 + + use, non_intrinsic :: mod21, ONLY : someVar ; use,intrinsic:: mod22 + USE, NON_INTRINSIC::mod23 ; USE ,INTRINSIC ::mod24 + +USE mod25 ! Test USE statement at the beginning of line + + +; USE modi ! Scanner should ignore this since it isn't valid syntax + USEmodi ! No space in between USE and module name - ignore it + USE mod01 ! This one is a duplicate - there should only be one dependency to it. + + STOP + END +""") + +modules = ['mod01.mod', 'mod02.mod', 'mod03.mod', 'mod04.mod', 'mod05.mod', + 'mod06.mod', 'mod07.mod', 'mod08.mod', 'mod09.mod', 'mod10.mod', + 'mod11.mod', 'mod12.mod', 'mod13.mod', 'mod14.mod', 'mod15.mod', + 'mod16.mod', 'mod17.mod', 'mod18.mod', 'mod19.mod', 'mod20.mod', + 'mod21.mod', 'mod22.mod', 'mod23.mod', 'mod24.mod', 'mod25.mod'] + +for m in modules: + test.write(m, "\n") + +test.subdir('modules') +test.write(['modules', 'use.mod'], "\n") + +# define some helpers: + +class DummyEnvironment: + def __init__(self, listCppPath): + self.path = listCppPath + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + if not args: + return { 'FORTRANPATH': self.path, 'FORTRANMODSUFFIX' : ".mod" } + elif len(args) == 1 and args[0] == 'FORTRANPATH': + return self.path + else: + raise KeyError, "Dummy environment only has FORTRANPATH attribute." + + 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] + + def subst(self, arg, target=None, source=None, conv=None): + if arg[0] == '$': + return self[arg[1:]] + return arg + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +def deps_match(self, deps, headers): + scanned = map(os.path.normpath, map(str, deps)) + expect = map(os.path.normpath, headers) + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + +# define some tests: + +class FortranScannerTestCase1(unittest.TestCase): + def runTest(self): + test.write('f1.f', "\n") + test.write('f2.f', " INCLUDE 'fi.f'\n") + env = DummyEnvironment([]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['f1.f', 'f2.f'] + deps_match(self, deps, headers) + test.unlink('f1.f') + test.unlink('f2.f') + +class FortranScannerTestCase2(unittest.TestCase): + def runTest(self): + test.write('f1.f', "\n") + test.write('f2.f', " INCLUDE 'fi.f'\n") + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['f1.f', 'f2.f'] + deps_match(self, deps, headers) + test.unlink('f1.f') + test.unlink('f2.f') + +class FortranScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['d1/f1.f', 'd1/f2.f'] + deps_match(self, deps, headers) + +class FortranScannerTestCase4(unittest.TestCase): + def runTest(self): + test.write(['d1', 'f2.f'], " INCLUDE 'fi.f'\n") + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['d1/f1.f', 'd1/f2.f'] + deps_match(self, deps, headers) + test.write(['d1', 'f2.f'], "\n") + +class FortranScannerTestCase5(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff2.f'), env, path) + headers = ['d1/f2.f', 'd1/d2/f2.f', 'd1/f2.f'] + deps_match(self, deps, headers) + +class FortranScannerTestCase6(unittest.TestCase): + def runTest(self): + test.write('f2.f', "\n") + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff2.f'), env, path) + headers = ['d1/f2.f', 'd1/d2/f2.f', 'f2.f'] + deps_match(self, deps, headers) + test.unlink('f2.f') + +class FortranScannerTestCase7(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1/d2"), test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff2.f'), env, path) + headers = ['d1/f2.f', 'd1/d2/f2.f', 'd1/d2/f2.f'] + deps_match(self, deps, headers) + +class FortranScannerTestCase8(unittest.TestCase): + def runTest(self): + test.write('f2.f', "\n") + env = DummyEnvironment([test.workpath("d1/d2"), test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff2.f'), env, path) + headers = ['d1/f2.f', 'd1/d2/f2.f', 'f2.f'] + deps_match(self, deps, headers) + test.unlink('f2.f') + +class FortranScannerTestCase9(unittest.TestCase): + def runTest(self): + test.write('f3.f', "\n") + env = DummyEnvironment([]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + + n = env.File('fff3.f') + 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 VariantDir functionality. + assert n.rexists_called + + headers = ['d1/f3.f', 'f3.f'] + deps_match(self, deps, headers) + test.unlink('f3.f') + +class FortranScannerTestCase10(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(["include"]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps1 = s(env.File('fff4.f'), env, path) + env.fs.chdir(env.Dir('subdir')) + dir = env.fs.getcwd() + env.fs.chdir(env.Dir('')) + path = s.path(env, dir) + deps2 = s(env.File('#fff4.f'), env, path) + headers1 = map(test.workpath, ['include/f4.f']) + headers2 = ['include/f4.f'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class FortranScannerTestCase11(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 + env = DummyEnvironment([]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff5.f'), env, path) + + # Did we catch the warning from not finding not_there.f? + assert to.out + + deps_match(self, deps, [ 'f5.f' ]) + +class FortranScannerTestCase12(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + env.fs.chdir(env.Dir('include')) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + test.write('include/fff4.f', test.read('fff4.f')) + deps = s(env.File('#include/fff4.f'), env, path) + env.fs.chdir(env.Dir('')) + deps_match(self, deps, ['f4.f']) + test.unlink('include/fff4.f') + +class FortranScannerTestCase13(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.f') + f1.builder=1 + env = DummyEnvironment(['include','include2']) + env.fs = fs + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(fs.File('src/fff.f'), env, path) + deps_match(self, deps, [test.workpath('repository/include/iii.f'), 'include2/jjj.f']) + os.chdir(test.workpath('')) + +class FortranScannerTestCase14(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.VariantDir('build1', 'src', 1) + fs.VariantDir('build2', 'src', 0) + fs.Repository(test.workpath('repository')) + env = DummyEnvironment([]) + env.fs = fs + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps1 = s(fs.File('build1/aaa.f'), env, path) + deps_match(self, deps1, [ 'build1/bbb.f' ]) + deps2 = s(fs.File('build2/aaa.f'), env, path) + deps_match(self, deps2, [ 'src/bbb.f' ]) + deps3 = s(fs.File('build1/ccc.f'), env, path) + deps_match(self, deps3, [ 'build1/ddd.f' ]) + deps4 = s(fs.File('build2/ccc.f'), env, path) + deps_match(self, deps4, [ test.workpath('repository/src/ddd.f') ]) + os.chdir(test.workpath('')) + +class FortranScannerTestCase15(unittest.TestCase): + def runTest(self): + class SubstEnvironment(DummyEnvironment): + def subst(self, arg, target=None, source=None, conv=None, test=test): + if arg == "$junk": + return test.workpath("d1") + else: + return arg + test.write(['d1', 'f2.f'], " INCLUDE 'fi.f'\n") + env = SubstEnvironment(["$junk"]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['d1/f1.f', 'd1/f2.f'] + deps_match(self, deps, headers) + test.write(['d1', 'f2.f'], "\n") + +class FortranScannerTestCase16(unittest.TestCase): + def runTest(self): + test.write('f1.f', "\n") + test.write('f2.f', "\n") + test.write('f3.f', "\n") + test.write('f4.f', "\n") + test.write('f5.f', "\n") + test.write('f6.f', "\n") + test.write('f7.f', "\n") + test.write('f8.f', "\n") + test.write('f9.f', "\n") + test.write('f10.f', "\n") + env = DummyEnvironment([test.workpath('modules')]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff90a.f90'), env, path) + headers = ['f1.f', 'f2.f', 'f3.f', 'f4.f', 'f5.f', 'f6.f', 'f7.f', 'f8.f', 'f9.f'] + modules = ['mod01.mod', 'mod02.mod', 'mod03.mod', 'mod04.mod', 'mod05.mod', + 'mod06.mod', 'mod07.mod', 'mod08.mod', 'mod09.mod', 'mod10.mod', + 'mod11.mod', 'mod12.mod', 'mod13.mod', 'mod14.mod', 'mod15.mod', + 'mod16.mod', 'mod17.mod', 'mod18.mod', 'mod19.mod', 'mod20.mod', + 'mod21.mod', 'mod22.mod', 'mod23.mod', 'mod24.mod', 'mod25.mod', 'modules/use.mod'] + deps_expected = headers + modules + deps_match(self, deps, deps_expected) + test.unlink('f1.f') + test.unlink('f2.f') + test.unlink('f3.f') + test.unlink('f4.f') + test.unlink('f5.f') + test.unlink('f6.f') + test.unlink('f7.f') + test.unlink('f8.f') + test.unlink('f9.f') + test.unlink('f10.f') + +def suite(): + suite = unittest.TestSuite() + suite.addTest(FortranScannerTestCase1()) + suite.addTest(FortranScannerTestCase2()) + suite.addTest(FortranScannerTestCase3()) + suite.addTest(FortranScannerTestCase4()) + suite.addTest(FortranScannerTestCase5()) + suite.addTest(FortranScannerTestCase6()) + suite.addTest(FortranScannerTestCase7()) + suite.addTest(FortranScannerTestCase8()) + suite.addTest(FortranScannerTestCase9()) + suite.addTest(FortranScannerTestCase10()) + suite.addTest(FortranScannerTestCase11()) + suite.addTest(FortranScannerTestCase12()) + suite.addTest(FortranScannerTestCase13()) + suite.addTest(FortranScannerTestCase14()) + suite.addTest(FortranScannerTestCase15()) + suite.addTest(FortranScannerTestCase16()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/IDL.py b/src/engine/SCons/Scanner/IDL.py new file mode 100644 index 0000000..d12f827 --- /dev/null +++ b/src/engine/SCons/Scanner/IDL.py @@ -0,0 +1,48 @@ +"""SCons.Scanner.IDL + +This module implements the depenency scanner for IDL (Interface +Definition Language) files. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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 4577 2009/12/27 19:44:43 scons" + +import SCons.Node.FS +import SCons.Scanner + +def IDLScan(): + """Return a prototype Scanner instance for scanning IDL source files""" + cs = SCons.Scanner.ClassicCPP("IDLScan", + "$IDLSUFFIXES", + "CPPPATH", + '^[ \t]*(?:#[ \t]*include|[ \t]*import)[ \t]+(<|")([^>"]+)(>|")') + return cs + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/IDLTests.py b/src/engine/SCons/Scanner/IDLTests.py new file mode 100644 index 0000000..9a53e38 --- /dev/null +++ b/src/engine/SCons/Scanner/IDLTests.py @@ -0,0 +1,453 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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 4577 2009/12/27 19:44:43 scons" + +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 + self.fs = SCons.Node.FS.FS(test.workpath('')) + + 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, target=None, source=None, conv=None): + return arg + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + 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] + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +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)) + +# define some tests: + +class IDLScannerTestCase1(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t1.idl'), env, path) + headers = ['f1.idl', 'f3.idl', 'f2.idl'] + deps_match(self, deps, headers) + +class IDLScannerTestCase2(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t1.idl'), env, path) + headers = ['f1.idl', 'f3.idl', 'd1/f2.idl'] + deps_match(self, deps, headers) + +class IDLScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t2.idl'), env, path) + headers = ['d1/f1.idl', 'f1.idl', 'd1/d2/f1.idl', 'f3.idl'] + deps_match(self, deps, 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(env.File('t2.idl'), env, path) + headers = ['d1/f1.idl', 'f1.idl', 'd1/d2/f1.idl', 'f3.idl'] + deps_match(self, deps, headers) + +class IDLScannerTestCase5(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + + n = env.File('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 VariantDir functionality. + assert n.rexists_called + + headers = ['d1/f1.idl', 'd1/f2.idl', + 'f1.idl', 'f2.idl', 'f3-test.idl', + 'd1/f1.idl', 'd1/f2.idl', 'd1/f3-test.idl'] + deps_match(self, deps, 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(env1.File('t1.idl'), env1, path1) + deps2 = s(env2.File('t1.idl'), env2, path2) + headers1 = ['f1.idl', 'f3.idl', 'd1/f2.idl'] + headers2 = ['f1.idl', 'f3.idl', 'd1/d2/f2.idl'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class IDLScannerTestCase7(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(["include"]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps1 = s(env.File('t4.idl'), env, path) + env.fs.chdir(env.Dir('subdir')) + dir = env.fs.getcwd() + env.fs.chdir(env.Dir('')) + path = s.path(env, dir) + deps2 = s(env.File('#t4.idl'), env, path) + headers1 = map(test.workpath, ['include/fa.idl', 'include/fb.idl']) + headers2 = ['include/fa.idl', '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') + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.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): + env = DummyEnvironment([]) + env.fs.chdir(env.Dir('include')) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + test.write('include/t4.idl', test.read('t4.idl')) + deps = s(env.File('#include/t4.idl'), env, path) + env.fs.chdir(env.Dir('')) + deps_match(self, deps, [ 'fa.idl', '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. + env = DummyEnvironment(['include', 'include2']) + env.fs = fs + f1 = fs.File('include2/jjj.idl') + f1.builder = 1 + s = SCons.Scanner.IDL.IDLScan() + 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.VariantDir('build1', 'src', 1) + fs.VariantDir('build2', 'src', 0) + fs.Repository(test.workpath('repository')) + env = DummyEnvironment([]) + env.fs = fs + s = SCons.Scanner.IDL.IDLScan() + 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, target=None, source=None, conv=None, test=test): + if arg == "$blah": + return test.workpath("d1") + else: + return arg + env = SubstEnvironment(["$blah"]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t1.idl'), env, path) + headers = ['f1.idl', 'f3.idl', 'd1/f2.idl'] + deps_match(self, deps, 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) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/LaTeX.py b/src/engine/SCons/Scanner/LaTeX.py new file mode 100644 index 0000000..0aca69d --- /dev/null +++ b/src/engine/SCons/Scanner/LaTeX.py @@ -0,0 +1,345 @@ +"""SCons.Scanner.LaTeX + +This module implements the dependency scanner for LaTeX code. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/LaTeX.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import re + +import SCons.Scanner +import SCons.Util + +# list of graphics file extensions for TeX and LaTeX +TexGraphics = ['.eps', '.ps'] +LatexGraphics = ['.pdf', '.png', '.jpg', '.gif', '.tif'] + +# Used as a return value of modify_env_var if the variable is not set. +class _Null: + pass +_null = _Null + +# The user specifies the paths in env[variable], similar to other builders. +# They may be relative and must be converted to absolute, as expected +# by LaTeX and Co. The environment may already have some paths in +# env['ENV'][var]. These paths are honored, but the env[var] paths have +# higher precedence. All changes are un-done on exit. +def modify_env_var(env, var, abspath): + try: + save = env['ENV'][var] + except KeyError: + save = _null + env.PrependENVPath(var, abspath) + try: + if SCons.Util.is_List(env[var]): + #TODO(1.5) + #env.PrependENVPath(var, [os.path.abspath(str(p)) for p in env[var]]) + env.PrependENVPath(var, map(lambda p: os.path.abspath(str(p)), env[var])) + else: + # Split at os.pathsep to convert into absolute path + #TODO(1.5) env.PrependENVPath(var, [os.path.abspath(p) for p in str(env[var]).split(os.pathsep)]) + env.PrependENVPath(var, map(lambda p: os.path.abspath(p), string.split(str(env[var]), os.pathsep))) + except KeyError: + pass + + # Convert into a string explicitly to append ":" (without which it won't search system + # paths as well). The problem is that env.AppendENVPath(var, ":") + # does not work, refuses to append ":" (os.pathsep). + + if SCons.Util.is_List(env['ENV'][var]): + # TODO(1.5) + #env['ENV'][var] = os.pathsep.join(env['ENV'][var]) + env['ENV'][var] = string.join(env['ENV'][var], os.pathsep) + # Append the trailing os.pathsep character here to catch the case with no env[var] + env['ENV'][var] = env['ENV'][var] + os.pathsep + + return save + +class FindENVPathDirs: + """A class to bind a specific *PATH variable name to a function that + will return all of the *path directories.""" + def __init__(self, variable): + self.variable = variable + def __call__(self, env, dir=None, target=None, source=None, argument=None): + import SCons.PathList + try: + path = env['ENV'][self.variable] + except KeyError: + return () + + dir = dir or env.fs._cwd + path = SCons.PathList.PathList(path).subst_path(env, target, source) + return tuple(dir.Rfindalldirs(path)) + + + +def LaTeXScanner(): + """Return a prototype Scanner instance for scanning LaTeX source files + when built with latex. + """ + ds = LaTeX(name = "LaTeXScanner", + suffixes = '$LATEXSUFFIXES', + # in the search order, see below in LaTeX class docstring + graphics_extensions = TexGraphics, + recursive = 0) + return ds + +def PDFLaTeXScanner(): + """Return a prototype Scanner instance for scanning LaTeX source files + when built with pdflatex. + """ + ds = LaTeX(name = "PDFLaTeXScanner", + suffixes = '$LATEXSUFFIXES', + # in the search order, see below in LaTeX class docstring + graphics_extensions = LatexGraphics, + recursive = 0) + return ds + +class LaTeX(SCons.Scanner.Base): + """Class for scanning LaTeX files for included files. + + Unlike most scanners, which use regular expressions that just + return the included file name, this returns a tuple consisting + of the keyword for the inclusion ("include", "includegraphics", + "input", or "bibliography"), and then the file name itself. + Based on a quick look at LaTeX documentation, it seems that we + should append .tex suffix for the "include" keywords, append .tex if + there is no extension for the "input" keyword, and need to add .bib + for the "bibliography" keyword that does not accept extensions by itself. + + Finally, if there is no extension for an "includegraphics" keyword + latex will append .ps or .eps to find the file, while pdftex may use .pdf, + .jpg, .tif, .mps, or .png. + + The actual subset and search order may be altered by + DeclareGraphicsExtensions command. This complication is ignored. + The default order corresponds to experimentation with teTeX + $ latex --version + pdfeTeX 3.141592-1.21a-2.2 (Web2C 7.5.4) + kpathsea version 3.5.4 + The order is: + ['.eps', '.ps'] for latex + ['.png', '.pdf', '.jpg', '.tif']. + + Another difference is that the search path is determined by the type + of the file being searched: + env['TEXINPUTS'] for "input" and "include" keywords + env['TEXINPUTS'] for "includegraphics" keyword + env['TEXINPUTS'] for "lstinputlisting" keyword + env['BIBINPUTS'] for "bibliography" keyword + env['BSTINPUTS'] for "bibliographystyle" keyword + + FIXME: also look for the class or style in document[class|style]{} + FIXME: also look for the argument of bibliographystyle{} + """ + keyword_paths = {'include': 'TEXINPUTS', + 'input': 'TEXINPUTS', + 'includegraphics': 'TEXINPUTS', + 'bibliography': 'BIBINPUTS', + 'bibliographystyle': 'BSTINPUTS', + 'usepackage': 'TEXINPUTS', + 'lstinputlisting': 'TEXINPUTS'} + env_variables = SCons.Util.unique(keyword_paths.values()) + + def __init__(self, name, suffixes, graphics_extensions, *args, **kw): + + # We have to include \n with the % we exclude from the first part + # part of the regex because the expression is compiled with re.M. + # Without the \n, the ^ could match the beginning of a *previous* + # line followed by one or more newline characters (i.e. blank + # lines), interfering with a match on the next line. + regex = r'^[^%\n]*\\(include|includegraphics(?:\[[^\]]+\])?|lstinputlisting(?:\[[^\]]+\])?|input|bibliography|usepackage){([^}]*)}' + self.cre = re.compile(regex, re.M) + self.graphics_extensions = graphics_extensions + + def _scan(node, env, path=(), self=self): + node = node.rfile() + if not node.exists(): + return [] + return self.scan(node, path) + + class FindMultiPathDirs: + """The stock FindPathDirs function has the wrong granularity: + it is called once per target, while we need the path that depends + on what kind of included files is being searched. This wrapper + hides multiple instances of FindPathDirs, one per the LaTeX path + variable in the environment. When invoked, the function calculates + and returns all the required paths as a dictionary (converted into + a tuple to become hashable). Then the scan function converts it + back and uses a dictionary of tuples rather than a single tuple + of paths. + """ + def __init__(self, dictionary): + self.dictionary = {} + for k,n in dictionary.items(): + self.dictionary[k] = ( SCons.Scanner.FindPathDirs(n), + FindENVPathDirs(n) ) + + def __call__(self, env, dir=None, target=None, source=None, + argument=None): + di = {} + for k,(c,cENV) in self.dictionary.items(): + di[k] = ( c(env, dir=None, target=None, source=None, + argument=None) , + cENV(env, dir=None, target=None, source=None, + argument=None) ) + # To prevent "dict is not hashable error" + return tuple(di.items()) + + class LaTeXScanCheck: + """Skip all but LaTeX source files, i.e., do not scan *.eps, + *.pdf, *.jpg, etc. + """ + def __init__(self, suffixes): + self.suffixes = suffixes + def __call__(self, node, env): + current = not node.has_builder() or node.is_up_to_date() + scannable = node.get_suffix() in env.subst_list(self.suffixes)[0] + # Returning false means that the file is not scanned. + return scannable and current + + kw['function'] = _scan + kw['path_function'] = FindMultiPathDirs(LaTeX.keyword_paths) + kw['recursive'] = 1 + kw['skeys'] = suffixes + kw['scan_check'] = LaTeXScanCheck(suffixes) + kw['name'] = name + + apply(SCons.Scanner.Base.__init__, (self,) + args, kw) + + def _latex_names(self, include): + filename = include[1] + if include[0] == 'input': + base, ext = os.path.splitext( filename ) + if ext == "": + return [filename + '.tex'] + if (include[0] == 'include'): + return [filename + '.tex'] + if include[0] == 'bibliography': + base, ext = os.path.splitext( filename ) + if ext == "": + return [filename + '.bib'] + if include[0] == 'usepackage': + base, ext = os.path.splitext( filename ) + if ext == "": + return [filename + '.sty'] + if include[0] == 'includegraphics': + base, ext = os.path.splitext( filename ) + if ext == "": + #TODO(1.5) return [filename + e for e in self.graphics_extensions] + #return map(lambda e, f=filename: f+e, self.graphics_extensions + TexGraphics) + # use the line above to find dependency for PDF builder when only .eps figure is present + # Since it will be found if the user tell scons how to make the pdf figure leave it out for now. + return map(lambda e, f=filename: f+e, self.graphics_extensions) + return [filename] + + def sort_key(self, include): + return SCons.Node.FS._my_normcase(str(include)) + + def find_include(self, include, source_dir, path): + try: + sub_path = path[include[0]] + except (IndexError, KeyError): + sub_path = () + try_names = self._latex_names(include) + for n in try_names: + # see if we find it using the path in env[var] + i = SCons.Node.FS.find_file(n, (source_dir,) + sub_path[0]) + if i: + return i, include + # see if we find it using the path in env['ENV'][var] + i = SCons.Node.FS.find_file(n, (source_dir,) + sub_path[1]) + if i: + return i, include + return i, include + + def scan(self, node, path=()): + # Modify the default scan function to allow for the regular + # expression to return a comma separated list of file names + # as can be the case with the bibliography keyword. + + # Cache the includes list in node so we only scan it once: + path_dict = dict(list(path)) + noopt_cre = re.compile('\[.*$') + if node.includes != None: + includes = node.includes + else: + includes = self.cre.findall(node.get_text_contents()) + # 1. Split comma-separated lines, e.g. + # ('bibliography', 'phys,comp') + # should become two entries + # ('bibliography', 'phys') + # ('bibliography', 'comp') + # 2. Remove the options, e.g., such as + # ('includegraphics[clip,width=0.7\\linewidth]', 'picture.eps') + # should become + # ('includegraphics', 'picture.eps') + split_includes = [] + for include in includes: + inc_type = noopt_cre.sub('', include[0]) + inc_list = string.split(include[1],',') + for j in range(len(inc_list)): + split_includes.append( (inc_type, inc_list[j]) ) + # + includes = split_includes + node.includes = includes + + # This is a hand-coded DSU (decorate-sort-undecorate, or + # Schwartzian transform) pattern. The sort key is the raw name + # of the file as specifed on the \include, \input, etc. line. + # TODO: what about the comment in the original Classic scanner: + # """which lets + # us keep the sort order constant regardless of whether the file + # is actually found in a Repository or locally.""" + nodes = [] + source_dir = node.get_dir() + for include in includes: + # + # Handle multiple filenames in include[1] + # + n, i = self.find_include(include, source_dir, path_dict) + if n is None: + # Do not bother with 'usepackage' warnings, as they most + # likely refer to system-level files + if include[0] != 'usepackage': + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) + else: + sortkey = self.sort_key(n) + nodes.append((sortkey, n)) + # + nodes.sort() + nodes = map(lambda pair: pair[1], nodes) + return nodes + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/LaTeXTests.py b/src/engine/SCons/Scanner/LaTeXTests.py new file mode 100644 index 0000000..012ad4b --- /dev/null +++ b/src/engine/SCons/Scanner/LaTeXTests.py @@ -0,0 +1,162 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/LaTeXTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import sys +import types +import unittest +import UserDict + +import TestCmd +import SCons.Node.FS +import SCons.Scanner.LaTeX + +test = TestCmd.TestCmd(workdir = '') + +test.write('test1.latex',""" +\include{inc1} +\input{inc2} +include{incNO} +%\include{incNO} +xyzzy \include{inc6} +""") + +test.write('test2.latex',""" +\include{inc1} +\include{inc3} +""") + +test.write('test3.latex',""" +\includegraphics{inc4.eps} +\includegraphics[width=60mm]{inc5.xyz} +""") + +test.subdir('subdir') + +test.write('inc1.tex',"\n") +test.write('inc2.tex',"\n") +test.write(['subdir', 'inc3.tex'], "\n") +test.write(['subdir', 'inc4.eps'], "\n") +test.write('inc5.xyz', "\n") +test.write('inc6.tex', "\n") +test.write('incNO.tex', "\n") + +# define some helpers: +# copied from CTest.py +class DummyEnvironment(UserDict.UserDict): + def __init__(self, **kw): + UserDict.UserDict.__init__(self) + self.data.update(kw) + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + return self.data + + def subst(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return self.data[strSubst[1:]] + return strSubst + + def subst_list(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return [self.data[strSubst[1:]]] + return [[strSubst]] + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +if os.path.normcase('foo') == os.path.normcase('FOO'): + my_normpath = os.path.normcase +else: + my_normpath = os.path.normpath + +def deps_match(self, deps, headers): + global my_normpath + scanned = map(my_normpath, map(str, deps)) + expect = map(my_normpath, headers) + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + + +class LaTeXScannerTestCase1(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LATEXSUFFIXES = [".tex", ".ltx", ".latex"]) + s = SCons.Scanner.LaTeX.LaTeXScanner() + path = s.path(env) + deps = s(env.File('test1.latex'), env, path) + headers = ['inc1.tex', 'inc2.tex', 'inc6.tex'] + deps_match(self, deps, headers) + +class LaTeXScannerTestCase2(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(TEXINPUTS=[test.workpath("subdir")],LATEXSUFFIXES = [".tex", ".ltx", ".latex"]) + s = SCons.Scanner.LaTeX.LaTeXScanner() + path = s.path(env) + deps = s(env.File('test2.latex'), env, path) + headers = ['inc1.tex', 'subdir/inc3.tex'] + deps_match(self, deps, headers) + +class LaTeXScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(TEXINPUTS=[test.workpath("subdir")],LATEXSUFFIXES = [".tex", ".ltx", ".latex"]) + s = SCons.Scanner.LaTeX.LaTeXScanner() + path = s.path(env) + deps = s(env.File('test3.latex'), env, path) + files = ['inc5.xyz', 'subdir/inc4.eps'] + deps_match(self, deps, files) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(LaTeXScannerTestCase1()) + suite.addTest(LaTeXScannerTestCase2()) + suite.addTest(LaTeXScannerTestCase3()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/Prog.py b/src/engine/SCons/Scanner/Prog.py new file mode 100644 index 0000000..bdc11c0 --- /dev/null +++ b/src/engine/SCons/Scanner/Prog.py @@ -0,0 +1,103 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/Prog.py 4577 2009/12/27 19:44:43 scons" + +import string + +import SCons.Node +import SCons.Node.FS +import SCons.Scanner +import SCons.Util + +# global, set by --debug=findlibs +print_find_libs = None + +def ProgramScanner(**kw): + """Return a prototype Scanner instance for scanning executable + files for static-lib dependencies""" + kw['path_function'] = SCons.Scanner.FindPathDirs('LIBPATH') + ps = apply(SCons.Scanner.Base, [scan, "ProgramScanner"], kw) + return ps + +def scan(node, env, libpath = ()): + """ + This scanner scans program files for static-library + dependencies. It will search the LIBPATH environment variable + for libraries specified in the LIBS variable, returning any + files it finds as dependencies. + """ + try: + libs = env['LIBS'] + except KeyError: + # There are no LIBS in this environment, so just return a null list: + return [] + if SCons.Util.is_String(libs): + libs = string.split(libs) + else: + libs = SCons.Util.flatten(libs) + + try: + prefix = env['LIBPREFIXES'] + if not SCons.Util.is_List(prefix): + prefix = [ prefix ] + except KeyError: + prefix = [ '' ] + + try: + suffix = env['LIBSUFFIXES'] + if not SCons.Util.is_List(suffix): + suffix = [ suffix ] + except KeyError: + suffix = [ '' ] + + pairs = [] + for suf in map(env.subst, suffix): + for pref in map(env.subst, prefix): + pairs.append((pref, suf)) + + result = [] + + if callable(libpath): + libpath = libpath() + + find_file = SCons.Node.FS.find_file + adjustixes = SCons.Util.adjustixes + for lib in libs: + if SCons.Util.is_String(lib): + lib = env.subst(lib) + for pref, suf in pairs: + l = adjustixes(lib, pref, suf) + l = find_file(l, libpath, verbose=print_find_libs) + if l: + result.append(l) + else: + result.append(lib) + + return result + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/ProgTests.py b/src/engine/SCons/Scanner/ProgTests.py new file mode 100644 index 0000000..2a5761e --- /dev/null +++ b/src/engine/SCons/Scanner/ProgTests.py @@ -0,0 +1,262 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/ProgTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import sys +import types +import unittest + +import TestCmd +import SCons.Node.FS +import SCons.Scanner.Prog + +test = TestCmd.TestCmd(workdir = '') + +test.subdir('d1', ['d1', 'd2'], 'dir', ['dir', 'sub']) + +libs = [ 'l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib', + 'dir/libfoo.a', 'dir/sub/libbar.a', 'dir/libxyz.other'] + +for h in libs: + test.write(h, "\n") + +# define some helpers: + +class DummyEnvironment: + def __init__(self, **kw): + self._dict = {'LIBSUFFIXES' : '.lib'} + self._dict.update(kw) + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + if not args: + return self._dict + elif len(args) == 1: + return self._dict[args[0]] + else: + return map(lambda x, s=self: s._dict[x], args) + + 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] + + def subst(self, s, target=None, source=None, conv=None): + try: + if s[0] == '$': + return self._dict[s[1:]] + except IndexError: + return '' + return s + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(test.workpath(filename)) + + def File(self, filename): + return self.fs.File(test.workpath(filename)) + +class DummyNode: + def __init__(self, name): + self.name = name + def rexists(self): + return 1 + def __str__(self): + return self.name + +def deps_match(deps, libs): + deps=map(str, deps) + deps.sort() + libs.sort() + return map(os.path.normpath, deps) == map(os.path.normpath, libs) + +# define some tests: + +class ProgramScannerTestCase1(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS=[ 'l1', 'l2', 'l3' ]) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['l1.lib']), map(str, deps) + + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS='l1') + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['l1.lib']), map(str, deps) + + f1 = env.fs.File(test.workpath('f1')) + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS=[f1]) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps[0] is f1, deps + + f2 = env.fs.File(test.workpath('f1')) + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS=f2) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps[0] is f2, deps + + +class ProgramScannerTestCase2(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=map(test.workpath, + ["", "d1", "d1/d2" ]), + LIBS=[ 'l1', 'l2', 'l3' ]) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib' ]), map(str, deps) + +class ProgramScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[test.workpath("d1/d2"), + test.workpath("d1")], + LIBS=string.split('l2 l3')) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['d1/l2.lib', 'd1/d2/l3.lib']), map(str, deps) + +class ProgramScannerTestCase5(unittest.TestCase): + def runTest(self): + class SubstEnvironment(DummyEnvironment): + def subst(self, arg, target=None, source=None, conv=None, path=test.workpath("d1")): + if arg == "$blah": + return test.workpath("d1") + else: + return arg + env = SubstEnvironment(LIBPATH=[ "$blah" ], + LIBS=string.split('l2 l3')) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, [ 'd1/l2.lib' ]), map(str, deps) + +class ProgramScannerTestCase6(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[ test.workpath("dir") ], + LIBS=['foo', 'sub/libbar', 'xyz.other'], + LIBPREFIXES=['lib'], + LIBSUFFIXES=['.a']) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['dir/libfoo.a', 'dir/sub/libbar.a', 'dir/libxyz.other']), map(str, deps) + +class ProgramScannerTestCase7(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[ test.workpath("dir") ], + LIBS=['foo', '$LIBBAR', '$XYZ'], + LIBPREFIXES=['lib'], + LIBSUFFIXES=['.a'], + LIBBAR='sub/libbar', + XYZ='xyz.other') + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['dir/libfoo.a', 'dir/sub/libbar.a', 'dir/libxyz.other']), map(str, deps) + +class ProgramScannerTestCase8(unittest.TestCase): + def runTest(self): + + n1 = DummyNode('n1') + env = DummyEnvironment(LIBPATH=[ test.workpath("dir") ], + LIBS=[n1], + LIBPREFIXES=['p1-', 'p2-'], + LIBSUFFIXES=['.1', '2']) + s = SCons.Scanner.Prog.ProgramScanner(node_class = DummyNode) + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps == [n1], deps + + n2 = DummyNode('n2') + env = DummyEnvironment(LIBPATH=[ test.workpath("dir") ], + LIBS=[n1, [n2]], + LIBPREFIXES=['p1-', 'p2-'], + LIBSUFFIXES=['.1', '2']) + s = SCons.Scanner.Prog.ProgramScanner(node_class = DummyNode) + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps == [n1, n2], deps + +def suite(): + suite = unittest.TestSuite() + suite.addTest(ProgramScannerTestCase1()) + suite.addTest(ProgramScannerTestCase2()) + suite.addTest(ProgramScannerTestCase3()) + suite.addTest(ProgramScannerTestCase5()) + suite.addTest(ProgramScannerTestCase6()) + suite.addTest(ProgramScannerTestCase7()) + suite.addTest(ProgramScannerTestCase8()) + if hasattr(types, 'UnicodeType'): + code = """if 1: + class ProgramScannerTestCase4(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[test.workpath("d1/d2"), + test.workpath("d1")], + LIBS=string.split(u'l2 l3')) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['d1/l2.lib', 'd1/d2/l3.lib']), map(str, deps) + suite.addTest(ProgramScannerTestCase4()) + \n""" + exec code + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/RC.py b/src/engine/SCons/Scanner/RC.py new file mode 100644 index 0000000..7d73125 --- /dev/null +++ b/src/engine/SCons/Scanner/RC.py @@ -0,0 +1,55 @@ +"""SCons.Scanner.RC + +This module implements the depenency scanner for RC (Interface +Definition Language) files. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/RC.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Node.FS +import SCons.Scanner +import re + +def RCScan(): + """Return a prototype Scanner instance for scanning RC source files""" + + res_re= r'^(?:\s*#\s*(?:include)|' \ + '.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)' \ + '\s*.*?)' \ + '\s*(<|"| )([^>"\s]+)(?:[>" ])*$' + resScanner = SCons.Scanner.ClassicCPP( "ResourceScanner", + "$RCSUFFIXES", + "CPPPATH", + res_re ) + + return resScanner + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/RCTests.py b/src/engine/SCons/Scanner/RCTests.py new file mode 100644 index 0000000..ebf298b --- /dev/null +++ b/src/engine/SCons/Scanner/RCTests.py @@ -0,0 +1,168 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/RCTests.py 4577 2009/12/27 19:44:43 scons" + +import TestCmd +import SCons.Scanner.RC +import unittest +import sys +import os +import os.path +import SCons.Node.FS +import SCons.Warnings +import UserDict + +test = TestCmd.TestCmd(workdir = '') + +os.chdir(test.workpath('')) + +# create some source files and headers: + +test.write('t1.rc',''' +#include "t1.h" +''') + +test.write('t2.rc',""" +#include "t1.h" +ICO_TEST ICON DISCARDABLE "abc.ico" +BMP_TEST BITMAP DISCARDABLE "def.bmp" +cursor1 CURSOR "bullseye.cur" +ID_RESPONSE_ERROR_PAGE HTML "responseerrorpage.htm" +5 FONT "cmroman.fnt" +1 MESSAGETABLE "MSG00409.bin" +1 MESSAGETABLE MSG00410.bin +1 TYPELIB "testtypelib.tlb" +TEST_REGIS REGISTRY MOVEABLE PURE "testregis.rgs" +TEST_D3DFX D3DFX DISCARDABLE "testEffect.fx" + +""") + + +# Create dummy include files +headers = ['t1.h', + 'abc.ico','def.bmp','bullseye.cur','responseerrorpage.htm','cmroman.fnt', + 'testEffect.fx', + 'MSG00409.bin','MSG00410.bin','testtypelib.tlb','testregis.rgs'] + +for h in headers: + test.write(h, " ") + + +# define some helpers: + +class DummyEnvironment(UserDict.UserDict): + def __init__(self,**kw): + UserDict.UserDict.__init__(self) + self.data.update(kw) + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + return self.data + + def subst(self, arg, target=None, source=None, conv=None): + if strSubst[0] == '$': + return self.data[strSubst[1:]] + return strSubst + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def has_key(self, key): + return self.Dictionary().has_key(key) + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +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) + scanned.sort() + expect.sort() + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + +# define some tests: + +class RCScannerTestCase1(unittest.TestCase): + def runTest(self): + path = [] + env = DummyEnvironment(RCSUFFIXES=['.rc','.rc2'], + CPPPATH=path) + s = SCons.Scanner.RC.RCScan() + deps = s(env.File('t1.rc'), env, path) + headers = ['t1.h'] + deps_match(self, deps, headers) + +class RCScannerTestCase2(unittest.TestCase): + def runTest(self): + path = [] + env = DummyEnvironment(RCSUFFIXES=['.rc','.rc2'], + CPPPATH=path) + s = SCons.Scanner.RC.RCScan() + deps = s(env.File('t2.rc'), env, path) + headers = ['MSG00410.bin', + 'abc.ico','bullseye.cur', + 'cmroman.fnt','def.bmp', + 'MSG00409.bin', + 'responseerrorpage.htm', + 't1.h', + 'testEffect.fx', + 'testregis.rgs','testtypelib.tlb'] + deps_match(self, deps, headers) + + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(RCScannerTestCase1()) + suite.addTest(RCScannerTestCase2()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py new file mode 100644 index 0000000..ae6cb79 --- /dev/null +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -0,0 +1,611 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/ScannerTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest +import UserDict + +import SCons.Scanner + +class DummyFS: + def File(self, name): + return DummyNode(name) + +class DummyEnvironment(UserDict.UserDict): + def __init__(self, dict=None, **kw): + UserDict.UserDict.__init__(self, dict) + self.data.update(kw) + self.fs = DummyFS() + def subst(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return self.data[strSubst[1:]] + return strSubst + def subst_list(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return [self.data[strSubst[1:]]] + return [[strSubst]] + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + def get_factory(self, factory): + return factory or self.fs.File + +class DummyNode: + def __init__(self, name, search_result=()): + self.name = name + self.search_result = tuple(search_result) + def rexists(self): + return 1 + def __str__(self): + return self.name + def Rfindalldirs(self, pathlist): + return self.search_result + pathlist + +class FindPathDirsTestCase(unittest.TestCase): + def test_FindPathDirs(self): + """Test the FindPathDirs callable class""" + + env = DummyEnvironment(LIBPATH = [ 'foo' ]) + env.fs = DummyFS() + env.fs._cwd = DummyNode('cwd') + + dir = DummyNode('dir', ['xxx']) + fpd = SCons.Scanner.FindPathDirs('LIBPATH') + result = fpd(env) + assert str(result) == "('foo',)", result + result = fpd(env, dir) + assert str(result) == "('xxx', 'foo')", result + +class ScannerTestCase(unittest.TestCase): + + def test_creation(self): + """Test creation of Scanner objects""" + def func(self): + pass + s = SCons.Scanner.Base(func) + assert isinstance(s, SCons.Scanner.Base), s + s = SCons.Scanner.Base({}) + assert isinstance(s, SCons.Scanner.Base), s + + s = SCons.Scanner.Base(func, name='fooscan') + assert str(s) == 'fooscan', str(s) + s = SCons.Scanner.Base({}, name='barscan') + assert str(s) == 'barscan', str(s) + + s = SCons.Scanner.Base(func, name='fooscan', argument=9) + assert str(s) == 'fooscan', str(s) + assert s.argument == 9, s.argument + s = SCons.Scanner.Base({}, name='fooscan', argument=888) + assert str(s) == 'fooscan', str(s) + assert s.argument == 888, s.argument + + +class BaseTestCase(unittest.TestCase): + + class skey_node: + def __init__(self, key): + self.key = key + def scanner_key(self): + return self.key + def rexists(self): + return 1 + + def func(self, filename, env, target, *args): + self.filename = filename + self.env = env + self.target = target + + if len(args) > 0: + self.arg = args[0] + + return self.deps + + def test(self, scanner, env, filename, deps, *args): + self.deps = deps + path = scanner.path(env) + scanned = scanner(filename, env, path) + scanned_strs = map(lambda x: str(x), scanned) + + self.failUnless(self.filename == filename, "the filename was passed incorrectly") + self.failUnless(self.env == env, "the environment was passed incorrectly") + self.failUnless(scanned_strs == deps, "the dependencies were returned incorrectly") + for d in scanned: + self.failUnless(type(d) != type(""), "got a string in the dependencies") + + 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") + + def test___call__dict(self): + """Test calling Scanner.Base objects with a dictionary""" + called = [] + def s1func(node, env, path, called=called): + called.append('s1func') + called.append(node) + return [] + def s2func(node, env, path, called=called): + called.append('s2func') + called.append(node) + return [] + s1 = SCons.Scanner.Base(s1func) + s2 = SCons.Scanner.Base(s2func) + selector = SCons.Scanner.Base({'.x' : s1, '.y' : s2}) + nx = self.skey_node('.x') + env = DummyEnvironment() + selector(nx, env, []) + assert called == ['s1func', nx], called + del called[:] + ny = self.skey_node('.y') + selector(ny, env, []) + assert called == ['s2func', ny], called + + def test_path(self): + """Test the Scanner.Base path() method""" + def pf(env, cwd, target, source, argument=None): + return "pf: %s %s %s %s %s" % \ + (env.VARIABLE, cwd, target[0], source[0], argument) + + env = DummyEnvironment() + env.VARIABLE = 'v1' + target = DummyNode('target') + source = DummyNode('source') + + s = SCons.Scanner.Base(self.func, path_function=pf) + p = s.path(env, 'here', [target], [source]) + assert p == "pf: v1 here target source None", p + + s = SCons.Scanner.Base(self.func, path_function=pf, argument="xyz") + p = s.path(env, 'here', [target], [source]) + assert p == "pf: v1 here target source xyz", p + + def test_positional(self): + """Test the Scanner.Base class using positional arguments""" + s = SCons.Scanner.Base(self.func, "Pos") + env = DummyEnvironment() + env.VARIABLE = "var1" + self.test(s, env, DummyNode('f1.cpp'), ['f1.h', 'f1.hpp']) + + env = DummyEnvironment() + env.VARIABLE = "i1" + self.test(s, env, DummyNode('i1.cpp'), ['i1.h', 'i1.hpp']) + + 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" + self.test(s, env, DummyNode('f2.cpp'), ['f2.h', 'f2.hpp']) + + env = DummyEnvironment() + env.VARIABLE = "i2" + + self.test(s, env, DummyNode('i2.cpp'), ['i2.h', 'i2.hpp']) + + 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() + env.VARIABLE = "var3" + self.test(s, env, DummyNode('f3.cpp'), ['f3.h', 'f3.hpp'], arg) + + env = DummyEnvironment() + env.VARIABLE = "i3" + self.test(s, env, DummyNode('i3.cpp'), ['i3.h', 'i3.hpp'], arg) + + 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) + env = DummyEnvironment() + env.VARIABLE = "var4" + self.test(s, env, DummyNode('f4.cpp'), ['f4.h', 'f4.hpp'], arg) + + env = DummyEnvironment() + env.VARIABLE = "i4" + self.test(s, env, DummyNode('i4.cpp'), ['i4.h', 'i4.hpp'], arg) + + def test___cmp__(self): + """Test the Scanner.Base class __cmp__() method""" + s = SCons.Scanner.Base(self.func, "Cmp") + assert cmp(s, None) + + def test_hash(self): + """Test the Scanner.Base class __hash__() method""" + s = SCons.Scanner.Base(self.func, "Hash") + dict = {} + dict[s] = 777 + i = hash(id(s)) + h = hash(dict.keys()[0]) + self.failUnless(h == i, + "hash Scanner base class expected %s, got %s" % (i, h)) + + def test_scan_check(self): + """Test the Scanner.Base class scan_check() method""" + def my_scan(filename, env, target, *args): + return [] + def check(node, env, s=self): + s.checked[str(node)] = 1 + return 1 + env = DummyEnvironment() + s = SCons.Scanner.Base(my_scan, "Check", scan_check = check) + self.checked = {} + path = s.path(env) + scanned = s(DummyNode('x'), env, path) + self.failUnless(self.checked['x'] == 1, + "did not call check function") + + def test_recursive(self): + """Test the Scanner.Base class recursive flag""" + nodes = [1, 2, 3, 4] + + s = SCons.Scanner.Base(function = self.func) + n = s.recurse_nodes(nodes) + self.failUnless(n == [], + "default behavior returned nodes: %s" % n) + + s = SCons.Scanner.Base(function = self.func, recursive = None) + n = s.recurse_nodes(nodes) + self.failUnless(n == [], + "recursive = None returned nodes: %s" % n) + + s = SCons.Scanner.Base(function = self.func, recursive = 1) + n = s.recurse_nodes(nodes) + self.failUnless(n == n, + "recursive = 1 didn't return all nodes: %s" % n) + + def odd_only(nodes): + return filter(lambda n: n % 2, nodes) + s = SCons.Scanner.Base(function = self.func, recursive = odd_only) + n = s.recurse_nodes(nodes) + self.failUnless(n == [1, 3], + "recursive = 1 didn't return all nodes: %s" % n) + + def test_get_skeys(self): + """Test the Scanner.Base get_skeys() method""" + s = SCons.Scanner.Base(function = self.func) + sk = s.get_skeys() + self.failUnless(sk == [], + "did not initialize to expected []") + + s = SCons.Scanner.Base(function = self.func, skeys = ['.1', '.2']) + sk = s.get_skeys() + self.failUnless(sk == ['.1', '.2'], + "sk was %s, not ['.1', '.2']") + + s = SCons.Scanner.Base(function = self.func, skeys = '$LIST') + env = DummyEnvironment(LIST = ['.3', '.4']) + sk = s.get_skeys(env) + self.failUnless(sk == ['.3', '.4'], + "sk was %s, not ['.3', '.4']") + + def test_select(self): + """Test the Scanner.Base select() method""" + scanner = SCons.Scanner.Base(function = self.func) + s = scanner.select('.x') + assert s is scanner, s + + selector = SCons.Scanner.Base({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.x')) + assert s == 1, s + s = selector.select(self.skey_node('.y')) + assert s == 2, s + s = selector.select(self.skey_node('.z')) + assert s is None, s + + def test_add_scanner(self): + """Test the Scanner.Base add_scanner() method""" + selector = SCons.Scanner.Base({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.z')) + assert s is None, s + selector.add_scanner('.z', 3) + s = selector.select(self.skey_node('.z')) + assert s == 3, s + + def test___str__(self): + """Test the Scanner.Base __str__() method""" + scanner = SCons.Scanner.Base(function = self.func) + s = str(scanner) + assert s == 'NONE', s + scanner = SCons.Scanner.Base(function = self.func, name = 'xyzzy') + s = str(scanner) + assert s == 'xyzzy', s + +class SelectorTestCase(unittest.TestCase): + class skey_node: + def __init__(self, key): + self.key = key + def scanner_key(self): + return self.key + def rexists(self): + return 1 + + def test___init__(self): + """Test creation of Scanner.Selector object""" + s = SCons.Scanner.Selector({}) + assert isinstance(s, SCons.Scanner.Selector), s + assert s.dict == {}, s.dict + + def test___call__(self): + """Test calling Scanner.Selector objects""" + called = [] + def s1func(node, env, path, called=called): + called.append('s1func') + called.append(node) + return [] + def s2func(node, env, path, called=called): + called.append('s2func') + called.append(node) + return [] + s1 = SCons.Scanner.Base(s1func) + s2 = SCons.Scanner.Base(s2func) + selector = SCons.Scanner.Selector({'.x' : s1, '.y' : s2}) + nx = self.skey_node('.x') + env = DummyEnvironment() + selector(nx, env, []) + assert called == ['s1func', nx], called + del called[:] + ny = self.skey_node('.y') + selector(ny, env, []) + assert called == ['s2func', ny], called + + def test_select(self): + """Test the Scanner.Selector select() method""" + selector = SCons.Scanner.Selector({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.x')) + assert s == 1, s + s = selector.select(self.skey_node('.y')) + assert s == 2, s + s = selector.select(self.skey_node('.z')) + assert s is None, s + + def test_add_scanner(self): + """Test the Scanner.Selector add_scanner() method""" + selector = SCons.Scanner.Selector({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.z')) + assert s is None, s + selector.add_scanner('.z', 3) + s = selector.select(self.skey_node('.z')) + assert s == 3, s + +class CurrentTestCase(unittest.TestCase): + def test_class(self): + """Test the Scanner.Current class""" + class MyNode: + def __init__(self): + self.called_has_builder = None + self.called_is_up_to_date = None + self.func_called = None + def rexists(self): + return 1 + class HasNoBuilder(MyNode): + def has_builder(self): + self.called_has_builder = 1 + return None + class IsNotCurrent(MyNode): + def has_builder(self): + self.called_has_builder = 1 + return 1 + def is_up_to_date(self): + self.called_is_up_to_date = 1 + return None + class IsCurrent(MyNode): + def has_builder(self): + self.called_has_builder = 1 + return 1 + def is_up_to_date(self): + self.called_is_up_to_date = 1 + return 1 + def func(node, env, path): + node.func_called = 1 + return [] + env = DummyEnvironment() + s = SCons.Scanner.Current(func) + path = s.path(env) + hnb = HasNoBuilder() + s(hnb, env, path) + self.failUnless(hnb.called_has_builder, "did not call has_builder()") + self.failUnless(not hnb.called_is_up_to_date, "did call is_up_to_date()") + self.failUnless(hnb.func_called, "did not call func()") + inc = IsNotCurrent() + s(inc, env, path) + self.failUnless(inc.called_has_builder, "did not call has_builder()") + self.failUnless(inc.called_is_up_to_date, "did not call is_up_to_date()") + self.failUnless(not inc.func_called, "did call func()") + ic = IsCurrent() + s(ic, env, path) + self.failUnless(ic.called_has_builder, "did not call has_builder()") + self.failUnless(ic.called_is_up_to_date, "did not call is_up_to_date()") + 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): + 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_name(self): + """Test setting the Scanner.Classic name""" + s = SCons.Scanner.Classic("my_name", ['.s'], 'MYPATH', '^my_inc (\S+)') + assert s.name == "my_name", s.name + + 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_text_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+)') + + # This set of tests is intended to test the scanning operation + # of the Classic scanner. + + # Note that caching has been added for not just the includes + # but the entire scan call. The caching is based on the + # arguments, so we will fiddle with the path parameter to + # defeat this caching for the purposes of these tests. + + # If the node doesn't exist, scanning turns up nothing. + n1 = MyNode("n1") + n1._exists = None + ret = s.function(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.function(n, env, ('foo',)) + assert ret == ['abc'], ret + + # Verify that it uses the cached include info. + n._contents = 'my_inc def\n' + ret = s.function(n, env, ('foo2',)) + assert ret == ['abc'], ret + + # Verify that if we wipe the cache, it uses the new contents. + n.includes = None + ret = s.function(n, env, ('foo3',)) + assert ret == ['def'], ret + + # We no longer cache overall scan results, which would be returned + # if individual results are de-cached. If we ever restore that + # functionality, this test goes back here. + #ret = s.function(n, env, ('foo2',)) + #assert ret == ['abc'], 'caching inactive; got: %s'%ret + + # Verify that it sorts what it finds. + n.includes = ['xyz', 'uvw'] + ret = s.function(n, env, ('foo4',)) + 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.function(n, env, ('foo5',)) + 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): + 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 + + # TODO(1.5): remove when 2.2 is minimal; replace ccc + # variable in find_include() call below with in-line u'ccc'. + try: + ccc = eval("u'ccc'") + except SyntaxError: + ccc = 'ccc' + + n, i = s.find_include(('<', ccc), 'foo', ('path',)) + assert n == 'path/ccc', n + assert i == 'ccc', i + + finally: + SCons.Node.FS.find_file = save + +def suite(): + suite = unittest.TestSuite() + tclasses = [ + FindPathDirsTestCase, + ScannerTestCase, + BaseTestCase, + SelectorTestCase, + CurrentTestCase, + ClassicTestCase, + ClassicCPPTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py new file mode 100644 index 0000000..76cd536 --- /dev/null +++ b/src/engine/SCons/Scanner/__init__.py @@ -0,0 +1,415 @@ +"""SCons.Scanner + +The Scanner package for the SCons software construction utility. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# 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/__init__.py 4577 2009/12/27 19:44:43 scons" + +import re +import string + +import SCons.Node.FS +import SCons.Util + + +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 + +def Scanner(function, *args, **kw): + """ + Public interface factory function for creating different types + of Scanners based on the different types of "functions" that may + be supplied. + + TODO: Deprecate this some day. We've moved the functionality + inside the Base class and really don't need this factory function + any more. It was, however, used by some of our Tool modules, so + the call probably ended up in various people's custom modules + patterned on SCons code. + """ + if SCons.Util.is_Dict(function): + return apply(Selector, (function,) + args, kw) + else: + return apply(Base, (function,) + args, kw) + + + +class FindPathDirs: + """A class to bind a specific *PATH variable name to a function that + will return all of the *path directories.""" + def __init__(self, variable): + self.variable = variable + def __call__(self, env, dir=None, target=None, source=None, argument=None): + import SCons.PathList + try: + path = env[self.variable] + except KeyError: + return () + + dir = dir or env.fs._cwd + path = SCons.PathList.PathList(path).subst_path(env, target, source) + return tuple(dir.Rfindalldirs(path)) + + + +class Base: + """ + The base class for dependency scanners. This implements + straightforward, single-pass scanning of a single file. + """ + + def __init__(self, + function, + name = "NONE", + argument = _null, + skeys = _null, + path_function = None, + node_class = SCons.Node.FS.Entry, + node_factory = None, + scan_check = None, + recursive = None): + """ + Construct a new scanner object given a scanner function. + + 'function' - a scanner function taking two or three + arguments and returning a list of strings. + + 'name' - a name for identifying this scanner object. + + 'argument' - an optional argument that, if specified, will be + passed to both the scanner function and the path_function. + + 'skeys' - an optional list argument that can be used to determine + which scanner should be used for a given Node. In the case of File + nodes, for example, the 'skeys' would be file suffixes. + + 'path_function' - a function that takes four or five arguments + (a construction environment, Node for the directory containing + the SConscript file that defined the primary target, list of + target nodes, list of source nodes, and optional argument for + this instance) and returns a tuple of the directories that can + be searched for implicit dependency files. May also return a + callable() which is called with no args and returns the tuple + (supporting Bindable class). + + 'node_class' - the class of Nodes which this scan will return. + If node_class is None, then this scanner will not enforce any + Node conversion and will return the raw results from the + underlying scanner function. + + 'node_factory' - the factory function to be called to translate + the raw results returned by the scanner function into the + expected node_class objects. + + 'scan_check' - a function to be called to first check whether + this node really needs to be scanned. + + 'recursive' - specifies that this scanner should be invoked + recursively on all of the implicit dependencies it returns + (the canonical example being #include lines in C source files). + May be a callable, which will be called to filter the list + of nodes found to select a subset for recursive scanning + (the canonical example being only recursively scanning + subdirectories within a directory). + + The scanner function's first argument will be a Node that should + be scanned for dependencies, the second argument will be an + Environment object, the third argument will be the tuple of paths + returned by the path_function, and the fourth argument will be + the value passed into 'argument', and the returned list should + contain the Nodes for 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.path_function = path_function + self.name = name + self.argument = argument + + if skeys is _null: + if SCons.Util.is_Dict(function): + skeys = function.keys() + else: + skeys = [] + self.skeys = skeys + + self.node_class = node_class + self.node_factory = node_factory + self.scan_check = scan_check + if callable(recursive): + self.recurse_nodes = recursive + elif recursive: + self.recurse_nodes = self._recurse_all_nodes + else: + self.recurse_nodes = self._recurse_no_nodes + + def path(self, env, dir=None, target=None, source=None): + if not self.path_function: + return () + if not self.argument is _null: + return self.path_function(env, dir, target, source, self.argument) + else: + return self.path_function(env, dir, target, source) + + def __call__(self, node, env, path = ()): + """ + This method scans a single object. 'node' is the node + that will be passed to the scanner function, and 'env' is the + environment that will be passed to the scanner function. A list of + direct dependency nodes for the specified node will be returned. + """ + if self.scan_check and not self.scan_check(node, env): + return [] + + self = self.select(node) + + if not self.argument is _null: + list = self.function(node, env, path, self.argument) + else: + list = self.function(node, env, path) + + kw = {} + if hasattr(node, 'dir'): + kw['directory'] = node.dir + node_factory = env.get_factory(self.node_factory) + nodes = [] + for l in list: + if self.node_class and not isinstance(l, self.node_class): + l = apply(node_factory, (l,), kw) + nodes.append(l) + return nodes + + def __cmp__(self, other): + try: + return cmp(self.__dict__, other.__dict__) + except AttributeError: + # other probably doesn't have a __dict__ + return cmp(self.__dict__, other) + + def __hash__(self): + return id(self) + + def __str__(self): + return self.name + + def add_skey(self, skey): + """Add a skey to the list of skeys""" + self.skeys.append(skey) + + def get_skeys(self, env=None): + if env and SCons.Util.is_String(self.skeys): + return env.subst_list(self.skeys)[0] + return self.skeys + + def select(self, node): + if SCons.Util.is_Dict(self.function): + key = node.scanner_key() + try: + return self.function[key] + except KeyError: + return None + else: + return self + + def _recurse_all_nodes(self, nodes): + return nodes + + def _recurse_no_nodes(self, nodes): + return [] + + recurse_nodes = _recurse_no_nodes + + def add_scanner(self, skey, scanner): + self.function[skey] = scanner + self.add_skey(skey) + + +class Selector(Base): + """ + A class for selecting a more specific scanner based on the + scanner_key() (suffix) for a specific Node. + + TODO: This functionality has been moved into the inner workings of + the Base class, and this class will be deprecated at some point. + (It was never exposed directly as part of the public interface, + although it is used by the Scanner() factory function that was + used by various Tool modules and therefore was likely a template + for custom modules that may be out there.) + """ + def __init__(self, dict, *args, **kw): + apply(Base.__init__, (self, None,)+args, kw) + self.dict = dict + self.skeys = dict.keys() + + def __call__(self, node, env, path = ()): + return self.select(node)(node, env, path) + + def select(self, node): + try: + return self.dict[node.scanner_key()] + except KeyError: + return None + + def add_scanner(self, skey, scanner): + self.dict[skey] = scanner + self.add_skey(skey) + + +class Current(Base): + """ + A class for scanning files that are source files (have no builder) + or are derived files and are current (which implies that they exist, + either locally or in a repository). + """ + + def __init__(self, *args, **kw): + def current_check(node, env): + return not node.has_builder() or node.is_up_to_date() + 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() and sort_key() methods), 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, *args, **kw): + + self.cre = re.compile(regex, re.M) + + def _scan(node, env, path=(), self=self): + node = node.rfile() + if not node.exists(): + return [] + return self.scan(node, path) + + kw['function'] = _scan + kw['path_function'] = FindPathDirs(path_variable) + kw['recursive'] = 1 + kw['skeys'] = suffixes + kw['name'] = name + + apply(Current.__init__, (self,) + args, kw) + + def find_include(self, include, source_dir, path): + n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path)) + return n, include + + def sort_key(self, include): + return SCons.Node.FS._my_normcase(include) + + def find_include_names(self, node): + return self.cre.findall(node.get_text_contents()) + + def scan(self, node, path=()): + + # cache the includes list in node so we only scan it once: + if node.includes is not None: + includes = node.includes + else: + includes = self.find_include_names (node) + # Intern the names of the include files. Saves some memory + # if the same header is included many times. + node.includes = map(SCons.Util.silent_intern, includes) + + # This is a hand-coded DSU (decorate-sort-undecorate, or + # Schwartzian transform) pattern. The sort key is the raw name + # of the file as specifed on the #include line (including the + # " or <, since that may affect what file is found), which lets + # us keep the sort order constant regardless of whether the file + # is actually found in a Repository or locally. + nodes = [] + source_dir = node.get_dir() + if callable(path): + path = path() + for include in includes: + n, i = self.find_include(include, source_dir, path) + + if n is None: + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) + else: + sortkey = self.sort_key(include) + nodes.append((sortkey, n)) + + nodes.sort() + nodes = map(lambda pair: pair[1], nodes) + return nodes + +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] == '"': + paths = (source_dir,) + tuple(path) + else: + paths = tuple(path) + (source_dir,) + + n = SCons.Node.FS.find_file(include[1], paths) + + i = SCons.Util.silent_intern(include[1]) + return n, i + + def sort_key(self, include): + return SCons.Node.FS._my_normcase(string.join(include)) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: |