3 The Scanner package for the SCons software construction utility.
8 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
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:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
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.
30 __revision__ = "src/engine/SCons/Scanner/__init__.py 3842 2008/12/20 22:59:52 scons"
42 # This is used instead of None as a default argument value so None can be
43 # used as an actual argument value.
46 def Scanner(function, *args, **kw):
48 Public interface factory function for creating different types
49 of Scanners based on the different types of "functions" that may
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.
58 if SCons.Util.is_Dict(function):
59 return apply(Selector, (function,) + args, kw)
61 return apply(Base, (function,) + args, kw)
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):
73 path = env[self.variable]
77 dir = dir or env.fs._cwd
78 path = SCons.PathList.PathList(path).subst_path(env, target, source)
79 return tuple(dir.Rfindalldirs(path))
85 The base class for dependency scanners. This implements
86 straightforward, single-pass scanning of a single file.
95 node_class = SCons.Node.FS.Entry,
100 Construct a new scanner object given a scanner function.
102 'function' - a scanner function taking two or three
103 arguments and returning a list of strings.
105 'name' - a name for identifying this scanner object.
107 'argument' - an optional argument that, if specified, will be
108 passed to both the scanner function and the path_function.
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.
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).
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.
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.
132 'scan_check' - a function to be called to first check whether
133 this node really needs to be scanned.
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).
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.
152 s = Scanner(my_scanner_function)
154 s = Scanner(function = my_scanner_function)
156 s = Scanner(function = my_scanner_function, argument = 'foo')
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.
165 self.function = function
166 self.path_function = path_function
168 self.argument = argument
171 if SCons.Util.is_Dict(function):
172 skeys = function.keys()
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
183 self.recurse_nodes = self._recurse_all_nodes
185 self.recurse_nodes = self._recurse_no_nodes
187 def path(self, env, dir=None, target=None, source=None):
188 if not self.path_function:
190 if not self.argument is _null:
191 return self.path_function(env, dir, target, source, self.argument)
193 return self.path_function(env, dir, target, source)
195 def __call__(self, node, env, path = ()):
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.
202 if self.scan_check and not self.scan_check(node, env):
205 self = self.select(node)
207 if not self.argument is _null:
208 list = self.function(node, env, path, self.argument)
210 list = self.function(node, env, path)
213 if hasattr(node, 'dir'):
214 kw['directory'] = node.dir
215 node_factory = env.get_factory(self.node_factory)
218 if self.node_class and not isinstance(l, self.node_class):
219 l = apply(node_factory, (l,), kw)
223 def __cmp__(self, other):
225 return cmp(self.__dict__, other.__dict__)
226 except AttributeError:
227 # other probably doesn't have a __dict__
228 return cmp(self.__dict__, other)
236 def add_skey(self, skey):
237 """Add a skey to the list of skeys"""
238 self.skeys.append(skey)
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]
245 def select(self, node):
246 if SCons.Util.is_Dict(self.function):
247 key = node.scanner_key()
249 return self.function[key]
255 def _recurse_all_nodes(self, nodes):
258 def _recurse_no_nodes(self, nodes):
261 recurse_nodes = _recurse_no_nodes
263 def add_scanner(self, skey, scanner):
264 self.function[skey] = scanner
268 class Selector(Base):
270 A class for selecting a more specific scanner based on the
271 scanner_key() (suffix) for a specific Node.
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.)
280 def __init__(self, dict, *args, **kw):
281 apply(Base.__init__, (self, None,)+args, kw)
283 self.skeys = dict.keys()
285 def __call__(self, node, env, path = ()):
286 return self.select(node)(node, env, path)
288 def select(self, node):
290 return self.dict[node.scanner_key()]
294 def add_scanner(self, skey, scanner):
295 self.dict[skey] = scanner
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).
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)
312 class Classic(Current):
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.
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.
324 def __init__(self, name, suffixes, path_variable, regex, *args, **kw):
326 self.cre = re.compile(regex, re.M)
328 def _scan(node, env, path=(), self=self):
330 if not node.exists():
332 return self.scan(node, path)
334 kw['function'] = _scan
335 kw['path_function'] = FindPathDirs(path_variable)
337 kw['skeys'] = suffixes
340 apply(Current.__init__, (self,) + args, kw)
342 def find_include(self, include, source_dir, path):
343 n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path))
346 def sort_key(self, include):
347 return SCons.Node.FS._my_normcase(include)
349 def find_include_names(self, node):
350 return self.cre.findall(node.get_contents())
352 def scan(self, node, path=()):
354 # cache the includes list in node so we only scan it once:
355 if node.includes != None:
356 includes = node.includes
358 includes = self.find_include_names (node)
359 node.includes = includes
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.
368 source_dir = node.get_dir()
371 for include in includes:
372 n, i = self.find_include(include, source_dir, path)
375 SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
376 "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
378 sortkey = self.sort_key(include)
379 nodes.append((sortkey, n))
382 nodes = map(lambda pair: pair[1], nodes)
385 class ClassicCPP(Classic):
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.
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.
395 def find_include(self, include, source_dir, path):
396 if include[0] == '"':
397 paths = (source_dir,) + tuple(path)
399 paths = tuple(path) + (source_dir,)
401 n = SCons.Node.FS.find_file(include[1], paths)
405 def sort_key(self, include):
406 return SCons.Node.FS._my_normcase(string.join(include))