Toplevel directory cleanup
[senf.git] / tools / scons-1.2.0 / 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 (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
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__ = "src/engine/SCons/Scanner/__init__.py 3842 2008/12/20 22:59:52 scons"
31
32 import re
33 import string
34
35 import SCons.Node.FS
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     """
48     Public interface factory function for creating different types
49     of Scanners based on the different types of "functions" that may
50     be supplied.
51
52     TODO:  Deprecate this some day.  We've moved the functionality
53     inside the Base class and really don't need this factory function
54     any more.  It was, however, used by some of our Tool modules, so
55     the call probably ended up in various people's custom modules
56     patterned on SCons code.
57     """
58     if SCons.Util.is_Dict(function):
59         return apply(Selector, (function,) + args, kw)
60     else:
61         return apply(Base, (function,) + args, kw)
62
63
64
65 class FindPathDirs:
66     """A class to bind a specific *PATH variable name to a function that
67     will return all of the *path directories."""
68     def __init__(self, variable):
69         self.variable = variable
70     def __call__(self, env, dir=None, target=None, source=None, argument=None):
71         import SCons.PathList
72         try:
73             path = env[self.variable]
74         except KeyError:
75             return ()
76
77         dir = dir or env.fs._cwd
78         path = SCons.PathList.PathList(path).subst_path(env, target, source)
79         return tuple(dir.Rfindalldirs(path))
80
81
82
83 class Base:
84     """
85     The base class for dependency scanners.  This implements
86     straightforward, single-pass scanning of a single file.
87     """
88
89     def __init__(self,
90                  function,
91                  name = "NONE",
92                  argument = _null,
93                  skeys = _null,
94                  path_function = None,
95                  node_class = SCons.Node.FS.Entry,
96                  node_factory = None,
97                  scan_check = None,
98                  recursive = None):
99         """
100         Construct a new scanner object given a scanner function.
101
102         'function' - a scanner function taking two or three
103         arguments and returning a list of strings.
104
105         'name' - a name for identifying this scanner object.
106
107         'argument' - an optional argument that, if specified, will be
108         passed to both the scanner function and the path_function.
109
110         'skeys' - an optional list argument that can be used to determine
111         which scanner should be used for a given Node. In the case of File
112         nodes, for example, the 'skeys' would be file suffixes.
113
114         'path_function' - a function that takes four or five arguments
115         (a construction environment, Node for the directory containing
116         the SConscript file that defined the primary target, list of
117         target nodes, list of source nodes, and optional argument for
118         this instance) and returns a tuple of the directories that can
119         be searched for implicit dependency files.  May also return a
120         callable() which is called with no args and returns the tuple
121         (supporting Bindable class).
122
123         'node_class' - the class of Nodes which this scan will return.
124         If node_class is None, then this scanner will not enforce any
125         Node conversion and will return the raw results from the
126         underlying scanner function.
127
128         'node_factory' - the factory function to be called to translate
129         the raw results returned by the scanner function into the
130         expected node_class objects.
131
132         'scan_check' - a function to be called to first check whether
133         this node really needs to be scanned.
134
135         'recursive' - specifies that this scanner should be invoked
136         recursively on all of the implicit dependencies it returns
137         (the canonical example being #include lines in C source files).
138         May be a callable, which will be called to filter the list
139         of nodes found to select a subset for recursive scanning
140         (the canonical example being only recursively scanning
141         subdirectories within a directory).
142
143         The scanner function's first argument will be a Node that should
144         be scanned for dependencies, the second argument will be an
145         Environment object, the third argument will be the tuple of paths
146         returned by the path_function, and the fourth argument will be
147         the value passed into 'argument', and the returned list should
148         contain the Nodes for all the direct dependencies of the file.
149
150         Examples:
151
152         s = Scanner(my_scanner_function)
153
154         s = Scanner(function = my_scanner_function)
155
156         s = Scanner(function = my_scanner_function, argument = 'foo')
157
158         """
159
160         # Note: this class could easily work with scanner functions that take
161         # something other than a filename as an argument (e.g. a database
162         # node) and a dependencies list that aren't file names. All that
163         # would need to be changed is the documentation.
164
165         self.function = function
166         self.path_function = path_function
167         self.name = name
168         self.argument = argument
169
170         if skeys is _null:
171             if SCons.Util.is_Dict(function):
172                 skeys = function.keys()
173             else:
174                 skeys = []
175         self.skeys = skeys
176
177         self.node_class = node_class
178         self.node_factory = node_factory
179         self.scan_check = scan_check
180         if callable(recursive):
181             self.recurse_nodes = recursive
182         elif recursive:
183             self.recurse_nodes = self._recurse_all_nodes
184         else:
185             self.recurse_nodes = self._recurse_no_nodes
186
187     def path(self, env, dir=None, target=None, source=None):
188         if not self.path_function:
189             return ()
190         if not self.argument is _null:
191             return self.path_function(env, dir, target, source, self.argument)
192         else:
193             return self.path_function(env, dir, target, source)
194
195     def __call__(self, node, env, path = ()):
196         """
197         This method scans a single object. 'node' is the node
198         that will be passed to the scanner function, and 'env' is the
199         environment that will be passed to the scanner function. A list of
200         direct dependency nodes for the specified node will be returned.
201         """
202         if self.scan_check and not self.scan_check(node, env):
203             return []
204
205         self = self.select(node)
206
207         if not self.argument is _null:
208             list = self.function(node, env, path, self.argument)
209         else:
210             list = self.function(node, env, path)
211
212         kw = {}
213         if hasattr(node, 'dir'):
214             kw['directory'] = node.dir
215         node_factory = env.get_factory(self.node_factory)
216         nodes = []
217         for l in list:
218             if self.node_class and not isinstance(l, self.node_class):
219                 l = apply(node_factory, (l,), kw)
220             nodes.append(l)
221         return nodes
222
223     def __cmp__(self, other):
224         try:
225             return cmp(self.__dict__, other.__dict__)
226         except AttributeError:
227             # other probably doesn't have a __dict__
228             return cmp(self.__dict__, other)
229
230     def __hash__(self):
231         return id(self)
232
233     def __str__(self):
234         return self.name
235
236     def add_skey(self, skey):
237         """Add a skey to the list of skeys"""
238         self.skeys.append(skey)
239
240     def get_skeys(self, env=None):
241         if env and SCons.Util.is_String(self.skeys):
242             return env.subst_list(self.skeys)[0]
243         return self.skeys
244
245     def select(self, node):
246         if SCons.Util.is_Dict(self.function):
247             key = node.scanner_key()
248             try:
249                 return self.function[key]
250             except KeyError:
251                 return None
252         else:
253             return self
254
255     def _recurse_all_nodes(self, nodes):
256         return nodes
257
258     def _recurse_no_nodes(self, nodes):
259         return []
260
261     recurse_nodes = _recurse_no_nodes
262
263     def add_scanner(self, skey, scanner):
264         self.function[skey] = scanner
265         self.add_skey(skey)
266
267
268 class Selector(Base):
269     """
270     A class for selecting a more specific scanner based on the
271     scanner_key() (suffix) for a specific Node.
272
273     TODO:  This functionality has been moved into the inner workings of
274     the Base class, and this class will be deprecated at some point.
275     (It was never exposed directly as part of the public interface,
276     although it is used by the Scanner() factory function that was
277     used by various Tool modules and therefore was likely a template
278     for custom modules that may be out there.)
279     """
280     def __init__(self, dict, *args, **kw):
281         apply(Base.__init__, (self, None,)+args, kw)
282         self.dict = dict
283         self.skeys = dict.keys()
284
285     def __call__(self, node, env, path = ()):
286         return self.select(node)(node, env, path)
287
288     def select(self, node):
289         try:
290             return self.dict[node.scanner_key()]
291         except KeyError:
292             return None
293
294     def add_scanner(self, skey, scanner):
295         self.dict[skey] = scanner
296         self.add_skey(skey)
297
298
299 class Current(Base):
300     """
301     A class for scanning files that are source files (have no builder)
302     or are derived files and are current (which implies that they exist,
303     either locally or in a repository).
304     """
305
306     def __init__(self, *args, **kw):
307         def current_check(node, env):
308             return not node.has_builder() or node.is_up_to_date()
309         kw['scan_check'] = current_check
310         apply(Base.__init__, (self,) + args, kw)
311
312 class Classic(Current):
313     """
314     A Scanner subclass to contain the common logic for classic CPP-style
315     include scanning, but which can be customized to use different
316     regular expressions to find the includes.
317
318     Note that in order for this to work "out of the box" (without
319     overriding the find_include() and sort_key() methods), the regular
320     expression passed to the constructor must return the name of the
321     include file in group 0.
322     """
323
324     def __init__(self, name, suffixes, path_variable, regex, *args, **kw):
325
326         self.cre = re.compile(regex, re.M)
327
328         def _scan(node, env, path=(), self=self):
329             node = node.rfile()
330             if not node.exists():
331                 return []
332             return self.scan(node, path)
333
334         kw['function'] = _scan
335         kw['path_function'] = FindPathDirs(path_variable)
336         kw['recursive'] = 1
337         kw['skeys'] = suffixes
338         kw['name'] = name
339
340         apply(Current.__init__, (self,) + args, kw)
341
342     def find_include(self, include, source_dir, path):
343         n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path))
344         return n, include
345
346     def sort_key(self, include):
347         return SCons.Node.FS._my_normcase(include)
348
349     def find_include_names(self, node):
350         return self.cre.findall(node.get_contents())
351
352     def scan(self, node, path=()):
353
354         # cache the includes list in node so we only scan it once:
355         if node.includes != None:
356             includes = node.includes
357         else:
358             includes = self.find_include_names (node)
359             node.includes = includes
360
361         # This is a hand-coded DSU (decorate-sort-undecorate, or
362         # Schwartzian transform) pattern.  The sort key is the raw name
363         # of the file as specifed on the #include line (including the
364         # " or <, since that may affect what file is found), which lets
365         # us keep the sort order constant regardless of whether the file
366         # is actually found in a Repository or locally.
367         nodes = []
368         source_dir = node.get_dir()
369         if callable(path):
370             path = path()
371         for include in includes:
372             n, i = self.find_include(include, source_dir, path)
373
374             if n is None:
375                 SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
376                                     "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
377             else:
378                 sortkey = self.sort_key(include)
379                 nodes.append((sortkey, n))
380
381         nodes.sort()
382         nodes = map(lambda pair: pair[1], nodes)
383         return nodes
384
385 class ClassicCPP(Classic):
386     """
387     A Classic Scanner subclass which takes into account the type of
388     bracketing used to include the file, and uses classic CPP rules
389     for searching for the files based on the bracketing.
390
391     Note that in order for this to work, the regular expression passed
392     to the constructor must return the leading bracket in group 0, and
393     the contained filename in group 1.
394     """
395     def find_include(self, include, source_dir, path):
396         if include[0] == '"':
397             paths = (source_dir,) + tuple(path)
398         else:
399             paths = tuple(path) + (source_dir,)
400
401         n = SCons.Node.FS.find_file(include[1], paths)
402
403         return n, include[1]
404
405     def sort_key(self, include):
406         return SCons.Node.FS._my_normcase(string.join(include))