b9f2d8f04ada1e609599fc928d3f7ec74be3b9be
[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 copy
34 import os.path
35 import re
36
37 import SCons.Node
38 import SCons.Node.FS
39 import SCons.Scanner
40 import SCons.Util
41 import SCons.Warnings
42
43 include_re = re.compile('^[ \t]*#[ \t]*include[ \t]+(<|")([^>"]+)(>|")', re.M)
44
45 def CScan(fs = SCons.Node.FS.default_fs):
46     """Return a prototype Scanner instance for scanning source files
47     that use the C pre-processor"""
48     cs = SCons.Scanner.Current(scan, "CScan", fs,
49                                [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
50                                 ".h", ".H", ".hxx", ".hpp", ".hh",
51                                 ".F", ".fpp", ".FPP"],
52                                path_function = path,
53                                recursive = 1)
54     return cs
55
56 def path(env, dir, fs = SCons.Node.FS.default_fs):
57     try:
58         cpppath = env['CPPPATH']
59     except KeyError:
60         return ()
61     return tuple(fs.Rsearchall(SCons.Util.mapPaths(cpppath, dir, env),
62                                clazz = SCons.Node.FS.Dir,
63                                must_exist = 0))
64
65 def scan(node, env, cpppath = (), fs = SCons.Node.FS.default_fs):
66     """
67     scan(node, Environment) -> [node]
68
69     the C/C++ dependency scanner function
70
71     This function is intentionally simple. There are two rules it
72     follows:
73     
74     1) #include <foo.h> - search for foo.h in CPPPATH followed by the
75         directory 'filename' is in
76     2) #include \"foo.h\" - search for foo.h in the directory 'filename' is
77        in followed by CPPPATH
78
79     These rules approximate the behaviour of most C/C++ compilers.
80
81     This scanner also ignores #ifdef and other preprocessor conditionals, so
82     it may find more depencies than there really are, but it never misses
83     dependencies.
84     """
85
86     node = node.rfile()
87
88     # This function caches the following information:
89     # node.includes - the result of include_re.findall()
90
91     if not node.exists():
92         return []
93
94     # cache the includes list in node so we only scan it once:
95     if node.includes != None:
96         includes = node.includes
97     else:
98         includes = include_re.findall(node.get_contents())
99         node.includes = includes
100
101     nodes = []
102     source_dir = node.get_dir()
103     for include in includes:
104         if include[0] == '"':
105             n = SCons.Node.FS.find_file(include[1],
106                                         (source_dir,) + cpppath,
107                                         fs.File)
108         else:
109             n = SCons.Node.FS.find_file(include[1],
110                                         cpppath + (source_dir,),
111                                         fs.File)
112
113         if not n is None:
114             nodes.append(n)
115         else:
116             SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
117                                 "No dependency generated for file: %s (included from: %s) -- file not found" % (include[1], node))
118
119     # Schwartzian transform from the Python FAQ Wizard
120     def st(List, Metric):
121         def pairing(element, M = Metric):
122             return (M(element), element)
123         def stripit(pair):
124             return pair[1]
125         paired = map(pairing, List)
126         paired.sort()
127         return map(stripit, paired)
128     
129     def normalize(node):
130         # We don't want the order of includes to be 
131         # modified by case changes on case insensitive OSes, so
132         # normalize the case of the filename here:
133         # (see test/win32pathmadness.py for a test of this)
134         return SCons.Node.FS._my_normcase(str(node))
135
136     return st(nodes, normalize)