OLD | NEW |
(Empty) | |
| 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 S
Cons 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 5134 2010/08/16 23:02:40 bd
eegan" |
| 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 (inclu
ded 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: |
OLD | NEW |