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 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 3842 2008/12/20 22:59:52 scons" 
 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_class = SCons.Node.FS.Entry, 96 node_factory = None, 97 scan_check = None, 98 recursive = None):
99 """ 100 Construct a new scanner object given a scanner function. 101 102 'function' - a scanner function taking two or three 103 arguments and returning a list of strings. 104 105 'name' - a name for identifying this scanner object. 106 107 'argument' - an optional argument that, if specified, will be 108 passed to both the scanner function and the path_function. 109 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. 113 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). 122 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. 127 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. 131 132 'scan_check' - a function to be called to first check whether 133 this node really needs to be scanned. 134 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). 142 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. 149 150 Examples: 151 152 s = Scanner(my_scanner_function) 153 154 s = Scanner(function = my_scanner_function) 155 156 s = Scanner(function = my_scanner_function, argument = 'foo') 157 158 """ 159 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. 164 165 self.function = function 166 self.path_function = path_function 167 self.name = name 168 self.argument = argument 169 170 if skeys is _null: 171 if SCons.Util.is_Dict(function): 172 skeys = function.keys() 173 else: 174 skeys = [] 175 self.skeys = skeys 176 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 182 elif recursive: 183 self.recurse_nodes = self._recurse_all_nodes 184 else: 185 self.recurse_nodes = self._recurse_no_nodes
186
187 - def path(self, env, dir=None, target=None, source=None):
188 if not self.path_function: 189 return () 190 if not self.argument is _null: 191 return self.path_function(env, dir, target, source, self.argument) 192 else: 193 return self.path_function(env, dir, target, source)
194
195 - def __call__(self, node, env, path = ()):
196 """ 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. 201 """ 202 if self.scan_check and not self.scan_check(node, env): 203 return [] 204 205 self = self.select(node) 206 207 if not self.argument is _null: 208 list = self.function(node, env, path, self.argument) 209 else: 210 list = self.function(node, env, path) 211 212 kw = {} 213 if hasattr(node, 'dir'): 214 kw['directory'] = node.dir 215 node_factory = env.get_factory(self.node_factory) 216 nodes = [] 217 for l in list: 218 if self.node_class and not isinstance(l, self.node_class): 219 l = apply(node_factory, (l,), kw) 220 nodes.append(l) 221 return nodes
222
223 - def __cmp__(self, other):
224 try: 225 return cmp(self.__dict__, other.__dict__) 226 except AttributeError: 227 # other probably doesn't have a __dict__ 228 return cmp(self.__dict__, other)
229
230 - def __hash__(self):
231 return id(self)
232
233 - def __str__(self):
234 return self.name
235
236 - def add_skey(self, skey):
237 """Add a skey to the list of skeys""" 238 self.skeys.append(skey)
239
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] 243 return self.skeys
244
245 - def select(self, node):
246 if SCons.Util.is_Dict(self.function): 247 key = node.scanner_key() 248 try: 249 return self.function[key] 250 except KeyError: 251 return None 252 else: 253 return self
254
255 - def _recurse_all_nodes(self, nodes):
256 return nodes
257
258 - def _recurse_no_nodes(self, nodes):
259 return []
260 261 recurse_nodes = _recurse_no_nodes 262
263 - def add_scanner(self, skey, scanner):
264 self.function[skey] = scanner 265 self.add_skey(skey)
266 267
268 -class Selector(Base):
269 """ 270 A class for selecting a more specific scanner based on the 271 scanner_key() (suffix) for a specific Node. 272 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.) 279 """
280 - def __init__(self, dict, *args, **kw):
281 apply(Base.__init__, (self, None,)+args, kw) 282 self.dict = dict 283 self.skeys = dict.keys()
284
285 - def __call__(self, node, env, path = ()):
286 return self.select(node)(node, env, path)
287
288 - def select(self, node):
289 try: 290 return self.dict[node.scanner_key()] 291 except KeyError: 292 return None
293
294 - def add_scanner(self, skey, scanner):
295 self.dict[skey] = scanner 296 self.add_skey(skey)
297 298
299 -class Current(Base):
300 """ 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). 304 """ 305
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)
311
312 -class Classic(Current):
313 """ 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. 317 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. 322 """ 323
324 - def __init__(self, name, suffixes, path_variable, regex, *args, **kw):
325 326 self.cre = re.compile(regex, re.M) 327 328 def _scan(node, env, path=(), self=self): 329 node = node.rfile() 330 if not node.exists(): 331 return [] 332 return self.scan(node, path)
333 334 kw['function'] = _scan 335 kw['path_function'] = FindPathDirs(path_variable) 336 kw['recursive'] = 1 337 kw['skeys'] = suffixes 338 kw['name'] = name 339 340 apply(Current.__init__, (self,) + args, kw)
341
342 - def find_include(self, include, source_dir, path):
343 n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path)) 344 return n, include
345
346 - def sort_key(self, include):
347 return SCons.Node.FS._my_normcase(include)
348
349 - def find_include_names(self, node):
350 return self.cre.findall(node.get_contents())
351
352 - def scan(self, node, path=()):
353 354 # cache the includes list in node so we only scan it once: 355 if node.includes != None: 356 includes = node.includes 357 else: 358 includes = self.find_include_names (node) 359 node.includes = includes 360 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. 367 nodes = [] 368 source_dir = node.get_dir() 369 if callable(path): 370 path = path() 371 for include in includes: 372 n, i = self.find_include(include, source_dir, path) 373 374 if n is None: 375 SCons.Warnings.warn(SCons.Warnings.DependencyWarning, 376 "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) 377 else: 378 sortkey = self.sort_key(include) 379 nodes.append((sortkey, n)) 380 381 nodes.sort() 382 nodes = map(lambda pair: pair[1], nodes) 383 return 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 return n, include[1]
404
405 - def sort_key(self, include):
406 return SCons.Node.FS._my_normcase(string.join(include))
407