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