Fix small problems found by PyChecker. (Steve Leblanc)
[scons.git] / src / engine / SCons / Scanner / C.py
1 """SCons.Scanner.C
2
3 This module implements the depenency scanner for C/C++ code. 
4
5 """
6
7 #
8 # Copyright (c) 2001, 2002 Steven Knight
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32
33 import re
34
35 import SCons.Node
36 import SCons.Node.FS
37 import SCons.Scanner
38 import SCons.Util
39 import SCons.Warnings
40
41 include_re = re.compile('^[ \t]*#[ \t]*include[ \t]+(<|")([^>"]+)(>|")', re.M)
42
43 def CScan(fs = SCons.Node.FS.default_fs):
44     """Return a prototype Scanner instance for scanning source files
45     that use the C pre-processor"""
46     cs = SCons.Scanner.Current(scan, "CScan", fs,
47                                [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
48                                 ".h", ".H", ".hxx", ".hpp", ".hh",
49                                 ".F", ".fpp", ".FPP"],
50                                path_function = path,
51                                recursive = 1)
52     return cs
53
54 def path(env, dir, fs = SCons.Node.FS.default_fs):
55     try:
56         cpppath = env['CPPPATH']
57     except KeyError:
58         return ()
59     return tuple(fs.Rsearchall(SCons.Util.mapPaths(cpppath, dir, env),
60                                clazz = SCons.Node.FS.Dir,
61                                must_exist = 0))
62
63 def scan(node, env, cpppath = (), fs = SCons.Node.FS.default_fs):
64     """
65     scan(node, Environment) -> [node]
66
67     the C/C++ dependency scanner function
68
69     This function is intentionally simple. There are two rules it
70     follows:
71     
72     1) #include <foo.h> - search for foo.h in CPPPATH followed by the
73         directory 'filename' is in
74     2) #include \"foo.h\" - search for foo.h in the directory 'filename' is
75        in followed by CPPPATH
76
77     These rules approximate the behaviour of most C/C++ compilers.
78
79     This scanner also ignores #ifdef and other preprocessor conditionals, so
80     it may find more depencies than there really are, but it never misses
81     dependencies.
82     """
83
84     node = node.rfile()
85
86     # This function caches the following information:
87     # node.includes - the result of include_re.findall()
88
89     if not node.exists():
90         return []
91
92     # cache the includes list in node so we only scan it once:
93     if node.includes != None:
94         includes = node.includes
95     else:
96         includes = include_re.findall(node.get_contents())
97         node.includes = includes
98
99     nodes = []
100     source_dir = node.get_dir()
101     for include in includes:
102         if include[0] == '"':
103             n = SCons.Node.FS.find_file(include[1],
104                                         (source_dir,) + cpppath,
105                                         fs.File)
106         else:
107             n = SCons.Node.FS.find_file(include[1],
108                                         cpppath + (source_dir,),
109                                         fs.File)
110
111         if not n is None:
112             nodes.append(n)
113         else:
114             SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
115                                 "No dependency generated for file: %s (included from: %s) -- file not found" % (include[1], node))
116
117     # Schwartzian transform from the Python FAQ Wizard
118     def st(List, Metric):
119         def pairing(element, M = Metric):
120             return (M(element), element)
121         def stripit(pair):
122             return pair[1]
123         paired = map(pairing, List)
124         paired.sort()
125         return map(stripit, paired)
126     
127     def normalize(node):
128         # We don't want the order of includes to be 
129         # modified by case changes on case insensitive OSes, so
130         # normalize the case of the filename here:
131         # (see test/win32pathmadness.py for a test of this)
132         return SCons.Node.FS._my_normcase(str(node))
133
134     return st(nodes, normalize)