Remove dead imports and other things found by PyChecker.
[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             c = not node.has_builder() or node.current(env.get_calculator())
238             return c
239         kw['scan_check'] = current_check
240         apply(Base.__init__, (self,) + args, kw)
241
242 class Classic(Current):
243     """
244     A Scanner subclass to contain the common logic for classic CPP-style
245     include scanning, but which can be customized to use different
246     regular expressions to find the includes.
247
248     Note that in order for this to work "out of the box" (without
249     overriding the find_include() method), the regular expression passed
250     to the constructor must return the name of the include file in group
251     0.
252     """
253
254     def __init__(self, name, suffixes, path_variable, regex,
255                  fs=SCons.Node.FS.default_fs, *args, **kw):
256
257         self.cre = re.compile(regex, re.M)
258         self.fs = fs
259
260         def _scan(node, env, path, self=self, fs=fs):
261             return self.scan(node, env, path)
262
263         kw['function'] = _scan
264         kw['path_function'] = FindPathDirs(path_variable, fs)
265         kw['recursive'] = 1
266         kw['skeys'] = suffixes
267         kw['name'] = name
268
269         apply(Current.__init__, (self,) + args, kw)
270
271     def find_include(self, include, source_dir, path):
272         n = SCons.Node.FS.find_file(include, (source_dir,) + path, self.fs.File)
273         return n, include
274
275     def scan(self, node, env, path=()):
276         node = node.rfile()
277
278         if not node.exists():
279             return []
280
281         # cache the includes list in node so we only scan it once:
282         if node.includes != None:
283             includes = node.includes
284         else:
285             includes = self.cre.findall(node.get_contents())
286             node.includes = includes
287
288         nodes = []
289         source_dir = node.get_dir()
290         for include in includes:
291             n, i = self.find_include(include, source_dir, path)
292
293             if not n is None:
294                 nodes.append(n)
295             else:
296                 SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
297                                     "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
298
299         # Schwartzian transform from the Python FAQ Wizard
300         def st(List, Metric):
301             def pairing(element, M = Metric):
302                 return (M(element), element)
303             def stripit(pair):
304                 return pair[1]
305             paired = map(pairing, List)
306             paired.sort()
307             return map(stripit, paired)
308
309         def normalize(node):
310             # We don't want the order of includes to be
311             # modified by case changes on case insensitive OSes, so
312             # normalize the case of the filename here:
313             # (see test/win32pathmadness.py for a test of this)
314             return SCons.Node.FS._my_normcase(str(node))
315
316         transformed = st(nodes, normalize)
317         # print "Classic: " + str(node) + " => " + str(map(lambda x: str(x),list(transformed)))
318         return transformed
319
320 class ClassicCPP(Classic):
321     """
322     A Classic Scanner subclass which takes into account the type of
323     bracketing used to include the file, and uses classic CPP rules
324     for searching for the files based on the bracketing.
325
326     Note that in order for this to work, the regular expression passed
327     to the constructor must return the leading bracket in group 0, and
328     the contained filename in group 1.
329     """
330     def find_include(self, include, source_dir, path):
331         if include[0] == '"':
332             n = SCons.Node.FS.find_file(include[1],
333                                         (source_dir,) + path,
334                                         self.fs.File)
335         else:
336             n = SCons.Node.FS.find_file(include[1],
337                                         path + (source_dir,),
338                                         self.fs.File)
339         return n, include[1]