8432638e41dd4bb3d36d0374e4c2b9164346c156
[scons.git] / src / engine / SCons / Scanner / __init__.py
1 """SCons.Scanner
2
3 The Scanner package for the SCons software construction utility.
4
5 """
6
7 #
8 # __COPYRIGHT__
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 import re
33
34 import SCons.Node.FS
35 import SCons.Sig
36 import SCons.Util
37
38
39 class _Null:
40     pass
41
42 # This is used instead of None as a default argument value so None can be
43 # used as an actual argument value.
44 _null = _Null
45
46 def Scanner(function, *args, **kw):
47     """Public interface factory function for creating different types
48     of Scanners based on the different types of "functions" that may
49     be supplied."""
50     if SCons.Util.is_Dict(function):
51         return apply(Selector, (function,) + args, kw)
52     else:
53         return apply(Base, (function,) + args, kw)
54
55 class FindPathDirs:
56     """A class to bind a specific *PATH variable name and the fs object
57     to a function that will return all of the *path directories."""
58     def __init__(self, variable, fs):
59         self.variable = variable
60         self.fs = fs
61     def __call__(self, env, dir, argument=None):
62         try:
63             path = env[self.variable]
64         except KeyError:
65             return ()
66
67         return tuple(self.fs.Rsearchall(env.subst_path(path),
68                                         must_exist = 0,
69                                         clazz = SCons.Node.FS.Dir,
70                                         cwd = dir))
71
72 class Base:
73     """
74     The base class for dependency scanners.  This implements
75     straightforward, single-pass scanning of a single file.
76     """
77
78     def __init__(self,
79                  function,
80                  name = "NONE",
81                  argument = _null,
82                  skeys = [],
83                  path_function = None,
84                  node_class = SCons.Node.FS.Entry,
85                  node_factory = SCons.Node.FS.default_fs.File,
86                  scan_check = None,
87                  recursive = None):
88         """
89         Construct a new scanner object given a scanner function.
90
91         'function' - a scanner function taking two or three
92         arguments and returning a list of strings.
93
94         'name' - a name for identifying this scanner object.
95
96         'argument' - an optional argument that, if specified, will be
97         passed to both the scanner function and the path_function.
98
99         'skeys' - an optional list argument that can be used to determine
100         which scanner should be used for a given Node. In the case of File
101         nodes, for example, the 'skeys' would be file suffixes.
102
103         'path_function' - a function that takes one to three arguments
104         (a construction environment, optional directory, and optional
105         argument for this instance) and returns a tuple of the
106         directories that can be searched for implicit dependency files.
107
108         'node_class' - the class of Nodes which this scan will return.
109         If node_class is None, then this scanner will not enforce any
110         Node conversion and will return the raw results from the
111         underlying scanner function.
112
113         'node_factory' - the factory function to be called to translate
114         the raw results returned by the scanner function into the
115         expected node_class objects.
116
117         'scan_check' - a function to be called to first check whether
118         this node really needs to be scanned.
119
120         'recursive' - specifies that this scanner should be invoked
121         recursively on the implicit dependencies it returns (the
122         canonical example being #include lines in C source files).
123
124         The scanner function's first argument will be the name of a file
125         that should be scanned for dependencies, the second argument will
126         be an Environment object, the third argument will be the value
127         passed into 'argument', and the returned list should contain the
128         Nodes for all the direct dependencies of the file.
129
130         Examples:
131
132         s = Scanner(my_scanner_function)
133
134         s = Scanner(function = my_scanner_function)
135
136         s = Scanner(function = my_scanner_function, argument = 'foo')
137
138         """
139
140         # Note: this class could easily work with scanner functions that take
141         # something other than a filename as an argument (e.g. a database
142         # node) and a dependencies list that aren't file names. All that
143         # would need to be changed is the documentation.
144
145         self.function = function
146         self.path_function = path_function
147         self.name = name
148         self.argument = argument
149         self.skeys = skeys
150         self.node_class = node_class
151         self.node_factory = node_factory
152         self.scan_check = scan_check
153         self.recursive = recursive
154
155     def path(self, env, dir = None):
156         if not self.path_function:
157             return ()
158         if not self.argument is _null:
159             return self.path_function(env, dir, self.argument)
160         else:
161             return self.path_function(env, dir)
162
163     def __call__(self, node, env, path = ()):
164         """
165         This method scans a single object. 'node' is the node
166         that will be passed to the scanner function, and 'env' is the
167         environment that will be passed to the scanner function. A list of
168         direct dependency nodes for the specified node will be returned.
169         """
170         if self.scan_check and not self.scan_check(node, env):
171             return []
172
173         if not self.argument is _null:
174             list = self.function(node, env, path, self.argument)
175         else:
176             list = self.function(node, env, path)
177         kw = {}
178         if hasattr(node, 'dir'):
179             kw['directory'] = node.dir
180         nodes = []
181         for l in list:
182             if self.node_class and not isinstance(l, self.node_class):
183                 l = apply(self.node_factory, (l,), kw)
184             nodes.append(l)
185         return nodes
186
187     def __cmp__(self, other):
188         return cmp(self.__dict__, other.__dict__)
189
190     def __hash__(self):
191         return hash(repr(self))
192
193     def add_skey(self, skey):
194         """Add a skey to the list of skeys"""
195         self.skeys.append(skey)
196
197     def get_skeys(self, env=None):
198         if SCons.Util.is_String(self.skeys):
199             return env.subst_list(self.skeys)[0]
200         return self.skeys
201
202     def select(self, node):
203         return self
204
205
206 class Selector(Base):
207     """
208     A class for selecting a more specific scanner based on the
209     scanner_key() (suffix) for a specific Node.
210     """
211     def __init__(self, dict, *args, **kw):
212         Base.__init__(self, (None,)+args, kw)
213         self.dict = dict
214
215     def __call__(self, node, env, path = ()):
216         return self.select(node)(node, env, path)
217
218     def select(self, node):
219         try:
220             return self.dict[node.scanner_key()]
221         except KeyError:
222             return None
223
224     def add_scanner(self, skey, scanner):
225         self.dict[skey] = scanner
226
227
228 class Current(Base):
229     """
230     A class for scanning files that are source files (have no builder)
231     or are derived files and are current (which implies that they exist,
232     either locally or in a repository).
233     """
234
235     def __init__(self, *args, **kw):
236         def current_check(node, env):
237             calc = env.get_calculator()
238             c = not node.has_builder() or node.current(env.get_calculator())
239             return c
240         kw['scan_check'] = current_check
241         apply(Base.__init__, (self,) + args, kw)
242
243 class Classic(Current):
244     """
245     A Scanner subclass to contain the common logic for classic CPP-style
246     include scanning, but which can be customized to use different
247     regular expressions to find the includes.
248
249     Note that in order for this to work "out of the box" (without
250     overriding the find_include() method), the regular expression passed
251     to the constructor must return the name of the include file in group
252     0.
253     """
254
255     def __init__(self, name, suffixes, path_variable, regex,
256                  fs=SCons.Node.FS.default_fs, *args, **kw):
257
258         self.cre = re.compile(regex, re.M)
259         self.fs = fs
260
261         def _scan(node, env, path, self=self, fs=fs):
262             return self.scan(node, env, path)
263
264         kw['function'] = _scan
265         kw['path_function'] = FindPathDirs(path_variable, fs)
266         kw['recursive'] = 1
267         kw['skeys'] = suffixes
268         kw['name'] = name
269
270         apply(Current.__init__, (self,) + args, kw)
271
272     def find_include(self, include, source_dir, path):
273         n = SCons.Node.FS.find_file(include, (source_dir,) + path, self.fs.File)
274         return n, include
275
276     def scan(self, node, env, path=()):
277         node = node.rfile()
278
279         if not node.exists():
280             return []
281
282         # cache the includes list in node so we only scan it once:
283         if node.includes != None:
284             includes = node.includes
285         else:
286             includes = self.cre.findall(node.get_contents())
287             node.includes = includes
288
289         nodes = []
290         source_dir = node.get_dir()
291         for include in includes:
292             n, i = self.find_include(include, source_dir, path)
293
294             if not n is None:
295                 nodes.append(n)
296             else:
297                 SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
298                                     "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
299
300         # Schwartzian transform from the Python FAQ Wizard
301         def st(List, Metric):
302             def pairing(element, M = Metric):
303                 return (M(element), element)
304             def stripit(pair):
305                 return pair[1]
306             paired = map(pairing, List)
307             paired.sort()
308             return map(stripit, paired)
309
310         def normalize(node):
311             # We don't want the order of includes to be
312             # modified by case changes on case insensitive OSes, so
313             # normalize the case of the filename here:
314             # (see test/win32pathmadness.py for a test of this)
315             return SCons.Node.FS._my_normcase(str(node))
316
317         transformed = st(nodes, normalize)
318         # print "Classic: " + str(node) + " => " + str(map(lambda x: str(x),list(transformed)))
319         return transformed
320
321 class ClassicCPP(Classic):
322     """
323     A Classic Scanner subclass which takes into account the type of
324     bracketing used to include the file, and uses classic CPP rules
325     for searching for the files based on the bracketing.
326
327     Note that in order for this to work, the regular expression passed
328     to the constructor must return the leading bracket in group 0, and
329     the contained filename in group 1.
330     """
331     def find_include(self, include, source_dir, path):
332         if include[0] == '"':
333             n = SCons.Node.FS.find_file(include[1],
334                                         (source_dir,) + path,
335                                         self.fs.File)
336         else:
337             n = SCons.Node.FS.find_file(include[1],
338                                         path + (source_dir,),
339                                         self.fs.File)
340         return n, include[1]