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 - 2015 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 rel_2.4.1:3453:73fefd3ea0b0 2015/11/09 03:25:05 bdbaddog" 
 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 """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):
70 import SCons.PathList 71 try: 72 path = env[self.variable] 73 except KeyError: 74 return () 75 76 dir = dir or env.fs._cwd 77 path = SCons.PathList.PathList(path).subst_path(env, target, source) 78 return tuple(dir.Rfindalldirs(path))
79 80 81
82 -class Base(object):
83 """ 84 The base class for dependency scanners. This implements 85 straightforward, single-pass scanning of a single file. 86 """ 87
88 - def __init__(self, 89 function, 90 name = "NONE", 91 argument = _null, 92 skeys = _null, 93 path_function = None, 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, 97 node_factory = None, 98 scan_check = None, 99 recursive = None):
100 """ 101 Construct a new scanner object given a scanner function. 102 103 'function' - a scanner function taking two or three 104 arguments and returning a list of strings. 105 106 'name' - a name for identifying this scanner object. 107 108 'argument' - an optional argument that, if specified, will be 109 passed to both the scanner function and the path_function. 110 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. 114 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). 123 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. 128 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. 132 133 'scan_check' - a function to be called to first check whether 134 this node really needs to be scanned. 135 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). 143 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. 150 151 Examples: 152 153 s = Scanner(my_scanner_function) 154 155 s = Scanner(function = my_scanner_function) 156 157 s = Scanner(function = my_scanner_function, argument = 'foo') 158 159 """ 160 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. 165 166 self.function = function 167 self.path_function = path_function 168 self.name = name 169 self.argument = argument 170 171 if skeys is _null: 172 if SCons.Util.is_Dict(function): 173 skeys = list(function.keys()) 174 else: 175 skeys = [] 176 self.skeys = skeys 177 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 183 elif recursive: 184 self.recurse_nodes = self._recurse_all_nodes 185 else: 186 self.recurse_nodes = self._recurse_no_nodes
187
188 - def path(self, env, dir=None, target=None, source=None):
189 if not self.path_function: 190 return () 191 if not self.argument is _null: 192 return self.path_function(env, dir, target, source, self.argument) 193 else: 194 return self.path_function(env, dir, target, source)
195
196 - def __call__(self, node, env, path = ()):
197 """ 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. 202 """ 203 if self.scan_check and not self.scan_check(node, env): 204 return [] 205 206 self = self.select(node) 207 208 if not self.argument is _null: 209 list = self.function(node, env, path, self.argument) 210 else: 211 list = self.function(node, env, path) 212 213 kw = {} 214 if hasattr(node, 'dir'): 215 kw['directory'] = node.dir 216 node_factory = env.get_factory(self.node_factory) 217 nodes = [] 218 for l in list: 219 if self.node_class and not isinstance(l, self.node_class): 220 l = node_factory(l, **kw) 221 nodes.append(l) 222 return nodes
223
224 - def __cmp__(self, other):
225 try: 226 return cmp(self.__dict__, other.__dict__) 227 except AttributeError: 228 # other probably doesn't have a __dict__ 229 return cmp(self.__dict__, other)
230
231 - def __hash__(self):
232 return id(self)
233
234 - def __str__(self):
235 return self.name
236
237 - def add_skey(self, skey):
238 """Add a skey to the list of skeys""" 239 self.skeys.append(skey)
240
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] 244 return self.skeys
245
246 - def select(self, node):
247 if SCons.Util.is_Dict(self.function): 248 key = node.scanner_key() 249 try: 250 return self.function[key] 251 except KeyError: 252 return None 253 else: 254 return self
255
256 - def _recurse_all_nodes(self, nodes):
257 return nodes
258
259 - def _recurse_no_nodes(self, nodes):
260 return []
261 262 recurse_nodes = _recurse_no_nodes 263
264 - def add_scanner(self, skey, scanner):
265 self.function[skey] = scanner 266 self.add_skey(skey)
267 268
269 -class Selector(Base):
270 """ 271 A class for selecting a more specific scanner based on the 272 scanner_key() (suffix) for a specific Node. 273 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.) 280 """
281 - def __init__(self, dict, *args, **kw):
282 Base.__init__(self, None, *args, **kw) 283 self.dict = dict 284 self.skeys = list(dict.keys())
285
286 - def __call__(self, node, env, path = ()):
287 return self.select(node)(node, env, path)
288
289 - def select(self, node):
290 try: 291 return self.dict[node.scanner_key()] 292 except KeyError: 293 return None
294
295 - def add_scanner(self, skey, scanner):
296 self.dict[skey] = scanner 297 self.add_skey(skey)
298 299
300 -class Current(Base):
301 """ 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). 305 """ 306
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)
312
313 -class Classic(Current):
314 """ 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. 318 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. 323 """ 324
325 - def __init__(self, name, suffixes, path_variable, regex, *args, **kw):
326 327 self.cre = re.compile(regex, re.M) 328 329 def _scan(node, env, path=(), self=self): 330 node = node.rfile() 331 if not node.exists(): 332 return [] 333 return self.scan(node, path)
334 335 kw['function'] = _scan 336 kw['path_function'] = FindPathDirs(path_variable) 337 kw['recursive'] = 1 338 kw['skeys'] = suffixes 339 kw['name'] = name 340 341 Current.__init__(self, *args, **kw)
342
343 - def find_include(self, include, source_dir, path):
344 n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path)) 345 return n, include
346
347 - def sort_key(self, include):
348 return SCons.Node.FS._my_normcase(include)
349
350 - def find_include_names(self, node):
351 return self.cre.findall(node.get_text_contents())
352
353 - def scan(self, node, path=()):
354 355 # cache the includes list in node so we only scan it once: 356 if node.includes is not None: 357 includes = node.includes 358 else: 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)) 363 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. 370 nodes = [] 371 source_dir = node.get_dir() 372 if callable(path): 373 path = path() 374 for include in includes: 375 n, i = self.find_include(include, source_dir, path) 376 377 if n is None: 378 SCons.Warnings.warn(SCons.Warnings.DependencyWarning, 379 "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) 380 else: 381 nodes.append((self.sort_key(include), n)) 382 383 return [pair[1] for pair in sorted(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 i = SCons.Util.silent_intern(include[1]) 404 return n, i
405
406 - def sort_key(self, include):
407 return SCons.Node.FS._my_normcase(' '.join(include))
408 409 # Local Variables: 410 # tab-width:4 411 # indent-tabs-mode:nil 412 # End: 413 # vim: set expandtab tabstop=4 shiftwidth=4: 414