3
The Scanner package for the SCons software construction utility.
8
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 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 5134 2010/08/16 23:02:40 bdeegan"
41
# This is used instead of None as a default argument value so None can be
42
# used as an actual argument value.
45
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
51
TODO: Deprecate this some day. We've moved the functionality
52
inside the Base class and really don't need this factory function
53
any more. It was, however, used by some of our Tool modules, so
54
the call probably ended up in various people's custom modules
55
patterned on SCons code.
57
if SCons.Util.is_Dict(function):
58
return Selector(function, *args, **kw)
60
return Base(function, *args, **kw)
64
class FindPathDirs(object):
65
"""A class to bind a specific *PATH variable name to a function that
66
will return all of the *path directories."""
67
def __init__(self, variable):
68
self.variable = variable
69
def __call__(self, env, dir=None, target=None, source=None, argument=None):
72
path = env[self.variable]
76
dir = dir or env.fs._cwd
77
path = SCons.PathList.PathList(path).subst_path(env, target, source)
78
return tuple(dir.Rfindalldirs(path))
84
The base class for dependency scanners. This implements
85
straightforward, single-pass scanning of a single file.
94
# Node.FS.Base so that, by default, it's okay for a
95
# scanner to return a Dir, File or Entry.
96
node_class = SCons.Node.FS.Base,
101
Construct a new scanner object given a scanner function.
103
'function' - a scanner function taking two or three
104
arguments and returning a list of strings.
106
'name' - a name for identifying this scanner object.
108
'argument' - an optional argument that, if specified, will be
109
passed to both the scanner function and the path_function.
111
'skeys' - an optional list argument that can be used to determine
112
which scanner should be used for a given Node. In the case of File
113
nodes, for example, the 'skeys' would be file suffixes.
115
'path_function' - a function that takes four or five arguments
116
(a construction environment, Node for the directory containing
117
the SConscript file that defined the primary target, list of
118
target nodes, list of source nodes, and optional argument for
119
this instance) and returns a tuple of the directories that can
120
be searched for implicit dependency files. May also return a
121
callable() which is called with no args and returns the tuple
122
(supporting Bindable class).
124
'node_class' - the class of Nodes which this scan will return.
125
If node_class is None, then this scanner will not enforce any
126
Node conversion and will return the raw results from the
127
underlying scanner function.
129
'node_factory' - the factory function to be called to translate
130
the raw results returned by the scanner function into the
131
expected node_class objects.
133
'scan_check' - a function to be called to first check whether
134
this node really needs to be scanned.
136
'recursive' - specifies that this scanner should be invoked
137
recursively on all of the implicit dependencies it returns
138
(the canonical example being #include lines in C source files).
139
May be a callable, which will be called to filter the list
140
of nodes found to select a subset for recursive scanning
141
(the canonical example being only recursively scanning
142
subdirectories within a directory).
144
The scanner function's first argument will be a Node that should
145
be scanned for dependencies, the second argument will be an
146
Environment object, the third argument will be the tuple of paths
147
returned by the path_function, and the fourth argument will be
148
the value passed into 'argument', and the returned list should
149
contain the Nodes for all the direct dependencies of the file.
153
s = Scanner(my_scanner_function)
155
s = Scanner(function = my_scanner_function)
157
s = Scanner(function = my_scanner_function, argument = 'foo')
161
# Note: this class could easily work with scanner functions that take
162
# something other than a filename as an argument (e.g. a database
163
# node) and a dependencies list that aren't file names. All that
164
# would need to be changed is the documentation.
166
self.function = function
167
self.path_function = path_function
169
self.argument = argument
172
if SCons.Util.is_Dict(function):
173
skeys = list(function.keys())
178
self.node_class = node_class
179
self.node_factory = node_factory
180
self.scan_check = scan_check
181
if callable(recursive):
182
self.recurse_nodes = recursive
184
self.recurse_nodes = self._recurse_all_nodes
186
self.recurse_nodes = self._recurse_no_nodes
188
def path(self, env, dir=None, target=None, source=None):
189
if not self.path_function:
191
if not self.argument is _null:
192
return self.path_function(env, dir, target, source, self.argument)
194
return self.path_function(env, dir, target, source)
196
def __call__(self, node, env, path = ()):
198
This method scans a single object. 'node' is the node
199
that will be passed to the scanner function, and 'env' is the
200
environment that will be passed to the scanner function. A list of
201
direct dependency nodes for the specified node will be returned.
203
if self.scan_check and not self.scan_check(node, env):
206
self = self.select(node)
208
if not self.argument is _null:
209
list = self.function(node, env, path, self.argument)
211
list = self.function(node, env, path)
214
if hasattr(node, 'dir'):
215
kw['directory'] = node.dir
216
node_factory = env.get_factory(self.node_factory)
219
if self.node_class and not isinstance(l, self.node_class):
220
l = node_factory(l, **kw)
224
def __cmp__(self, other):
226
return cmp(self.__dict__, other.__dict__)
227
except AttributeError:
228
# other probably doesn't have a __dict__
229
return cmp(self.__dict__, other)
237
def add_skey(self, skey):
238
"""Add a skey to the list of skeys"""
239
self.skeys.append(skey)
241
def get_skeys(self, env=None):
242
if env and SCons.Util.is_String(self.skeys):
243
return env.subst_list(self.skeys)[0]
246
def select(self, node):
247
if SCons.Util.is_Dict(self.function):
248
key = node.scanner_key()
250
return self.function[key]
256
def _recurse_all_nodes(self, nodes):
259
def _recurse_no_nodes(self, nodes):
262
recurse_nodes = _recurse_no_nodes
264
def add_scanner(self, skey, scanner):
265
self.function[skey] = scanner
269
class Selector(Base):
271
A class for selecting a more specific scanner based on the
272
scanner_key() (suffix) for a specific Node.
274
TODO: This functionality has been moved into the inner workings of
275
the Base class, and this class will be deprecated at some point.
276
(It was never exposed directly as part of the public interface,
277
although it is used by the Scanner() factory function that was
278
used by various Tool modules and therefore was likely a template
279
for custom modules that may be out there.)
281
def __init__(self, dict, *args, **kw):
282
Base.__init__(self, None, *args, **kw)
284
self.skeys = list(dict.keys())
286
def __call__(self, node, env, path = ()):
287
return self.select(node)(node, env, path)
289
def select(self, node):
291
return self.dict[node.scanner_key()]
295
def add_scanner(self, skey, scanner):
296
self.dict[skey] = scanner
302
A class for scanning files that are source files (have no builder)
303
or are derived files and are current (which implies that they exist,
304
either locally or in a repository).
307
def __init__(self, *args, **kw):
308
def current_check(node, env):
309
return not node.has_builder() or node.is_up_to_date()
310
kw['scan_check'] = current_check
311
Base.__init__(self, *args, **kw)
313
class Classic(Current):
315
A Scanner subclass to contain the common logic for classic CPP-style
316
include scanning, but which can be customized to use different
317
regular expressions to find the includes.
319
Note that in order for this to work "out of the box" (without
320
overriding the find_include() and sort_key() methods), the regular
321
expression passed to the constructor must return the name of the
322
include file in group 0.
325
def __init__(self, name, suffixes, path_variable, regex, *args, **kw):
327
self.cre = re.compile(regex, re.M)
329
def _scan(node, env, path=(), self=self):
331
if not node.exists():
333
return self.scan(node, path)
335
kw['function'] = _scan
336
kw['path_function'] = FindPathDirs(path_variable)
338
kw['skeys'] = suffixes
341
Current.__init__(self, *args, **kw)
343
def find_include(self, include, source_dir, path):
344
n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path))
347
def sort_key(self, include):
348
return SCons.Node.FS._my_normcase(include)
350
def find_include_names(self, node):
351
return self.cre.findall(node.get_text_contents())
353
def scan(self, node, path=()):
355
# cache the includes list in node so we only scan it once:
356
if node.includes is not None:
357
includes = node.includes
359
includes = self.find_include_names (node)
360
# Intern the names of the include files. Saves some memory
361
# if the same header is included many times.
362
node.includes = list(map(SCons.Util.silent_intern, includes))
364
# This is a hand-coded DSU (decorate-sort-undecorate, or
365
# Schwartzian transform) pattern. The sort key is the raw name
366
# of the file as specifed on the #include line (including the
367
# " or <, since that may affect what file is found), which lets
368
# us keep the sort order constant regardless of whether the file
369
# is actually found in a Repository or locally.
371
source_dir = node.get_dir()
374
for include in includes:
375
n, i = self.find_include(include, source_dir, path)
378
SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
379
"No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
381
nodes.append((self.sort_key(include), n))
383
return [pair[1] for pair in sorted(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)
403
i = SCons.Util.silent_intern(include[1])
406
def sort_key(self, include):
407
return SCons.Node.FS._my_normcase(' '.join(include))
411
# indent-tabs-mode:nil
413
# vim: set expandtab tabstop=4 shiftwidth=4: