Package SCons :: Package Scanner
[hide private]
[frames] | no frames]

Source Code for Package SCons.Scanner

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