summaryrefslogtreecommitdiff
path: root/src/engine/SCons/Scanner
diff options
context:
space:
mode:
authorLuca Falavigna <dktrkranz@debian.org>2010-01-02 20:56:35 +0100
committerLuca Falavigna <dktrkranz@debian.org>2010-01-02 20:56:35 +0100
commit64c458487151933ee0ba093cf4ac69e177d9be37 (patch)
treef6e3755704f53406eea85532e4ffe5d5ef50b7f0 /src/engine/SCons/Scanner
parent2aec9cc58398cac1376509a7d75edb83b41f984e (diff)
parent72c578fd4b0b4a5a43e18594339ac4ff26c376dc (diff)
Merge commit 'upstream/1.2.0.d20091224'
Diffstat (limited to 'src/engine/SCons/Scanner')
-rw-r--r--src/engine/SCons/Scanner/C.py132
-rw-r--r--src/engine/SCons/Scanner/CTests.py468
-rw-r--r--src/engine/SCons/Scanner/D.py74
-rw-r--r--src/engine/SCons/Scanner/Dir.py111
-rw-r--r--src/engine/SCons/Scanner/DirTests.py140
-rw-r--r--src/engine/SCons/Scanner/Fortran.py320
-rw-r--r--src/engine/SCons/Scanner/FortranTests.py543
-rw-r--r--src/engine/SCons/Scanner/IDL.py48
-rw-r--r--src/engine/SCons/Scanner/IDLTests.py453
-rw-r--r--src/engine/SCons/Scanner/LaTeX.py345
-rw-r--r--src/engine/SCons/Scanner/LaTeXTests.py162
-rw-r--r--src/engine/SCons/Scanner/Prog.py103
-rw-r--r--src/engine/SCons/Scanner/ProgTests.py262
-rw-r--r--src/engine/SCons/Scanner/RC.py55
-rw-r--r--src/engine/SCons/Scanner/RCTests.py168
-rw-r--r--src/engine/SCons/Scanner/ScannerTests.py611
-rw-r--r--src/engine/SCons/Scanner/__init__.py415
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: