Package SCons :: Package Node :: Module FS
[hide private]
[frames] | no frames]

Source Code for Module SCons.Node.FS

   1  """scons.Node.FS 
   2   
   3  File system nodes. 
   4   
   5  These Nodes represent the canonical external objects that people think 
   6  of when they think of building software: files and directories. 
   7   
   8  This holds a "default_fs" variable that should be initialized with an FS 
   9  that can be used by scripts or modules looking for the canonical default. 
  10   
  11  """ 
  12   
  13  # 
  14  # Copyright (c) 2001 - 2019 The SCons Foundation 
  15  # 
  16  # Permission is hereby granted, free of charge, to any person obtaining 
  17  # a copy of this software and associated documentation files (the 
  18  # "Software"), to deal in the Software without restriction, including 
  19  # without limitation the rights to use, copy, modify, merge, publish, 
  20  # distribute, sublicense, and/or sell copies of the Software, and to 
  21  # permit persons to whom the Software is furnished to do so, subject to 
  22  # the following conditions: 
  23  # 
  24  # The above copyright notice and this permission notice shall be included 
  25  # in all copies or substantial portions of the Software. 
  26  # 
  27  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
  28  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
  29  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  30  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
  31  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
  32  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
  33  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
  34  from __future__ import print_function 
  35   
  36  __revision__ = "src/engine/SCons/Node/FS.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan" 
  37   
  38  import fnmatch 
  39  import os 
  40  import re 
  41  import shutil 
  42  import stat 
  43  import sys 
  44  import time 
  45  import codecs 
  46  from itertools import chain 
  47   
  48  import SCons.Action 
  49  import SCons.Debug 
  50  from SCons.Debug import logInstanceCreation 
  51  import SCons.Errors 
  52  import SCons.Memoize 
  53  import SCons.Node 
  54  import SCons.Node.Alias 
  55  import SCons.Subst 
  56  import SCons.Util 
  57  import SCons.Warnings 
  58   
  59  from SCons.Debug import Trace 
  60   
  61  print_duplicate = 0 
  62   
  63  MD5_TIMESTAMP_DEBUG = False 
64 65 66 -def sconsign_none(node):
67 raise NotImplementedError
68
69 -def sconsign_dir(node):
70 """Return the .sconsign file info for this directory, 71 creating it first if necessary.""" 72 if not node._sconsign: 73 import SCons.SConsign 74 node._sconsign = SCons.SConsign.ForDirectory(node) 75 return node._sconsign
76 77 _sconsign_map = {0 : sconsign_none, 78 1 : sconsign_dir}
79 80 -class FileBuildInfoFileToCsigMappingError(Exception):
81 pass
82
83 -class EntryProxyAttributeError(AttributeError):
84 """ 85 An AttributeError subclass for recording and displaying the name 86 of the underlying Entry involved in an AttributeError exception. 87 """
88 - def __init__(self, entry_proxy, attribute):
89 AttributeError.__init__(self) 90 self.entry_proxy = entry_proxy 91 self.attribute = attribute
92 - def __str__(self):
93 entry = self.entry_proxy.get() 94 fmt = "%s instance %s has no attribute %s" 95 return fmt % (entry.__class__.__name__, 96 repr(entry.name), 97 repr(self.attribute))
98 99 # The max_drift value: by default, use a cached signature value for 100 # any file that's been untouched for more than two days. 101 default_max_drift = 2*24*60*60 102 103 # 104 # We stringify these file system Nodes a lot. Turning a file system Node 105 # into a string is non-trivial, because the final string representation 106 # can depend on a lot of factors: whether it's a derived target or not, 107 # whether it's linked to a repository or source directory, and whether 108 # there's duplication going on. The normal technique for optimizing 109 # calculations like this is to memoize (cache) the string value, so you 110 # only have to do the calculation once. 111 # 112 # A number of the above factors, however, can be set after we've already 113 # been asked to return a string for a Node, because a Repository() or 114 # VariantDir() call or the like may not occur until later in SConscript 115 # files. So this variable controls whether we bother trying to save 116 # string values for Nodes. The wrapper interface can set this whenever 117 # they're done mucking with Repository and VariantDir and the other stuff, 118 # to let this module know it can start returning saved string values 119 # for Nodes. 120 # 121 Save_Strings = None
122 123 -def save_strings(val):
124 global Save_Strings 125 Save_Strings = val
126 127 # 128 # Avoid unnecessary function calls by recording a Boolean value that 129 # tells us whether or not os.path.splitdrive() actually does anything 130 # on this system, and therefore whether we need to bother calling it 131 # when looking up path names in various methods below. 132 # 133 134 do_splitdrive = None 135 _my_splitdrive =None
136 137 -def initialize_do_splitdrive():
138 global do_splitdrive 139 global has_unc 140 drive, path = os.path.splitdrive('X:/foo') 141 # splitunc is removed from python 3.7 and newer 142 # so we can also just test if splitdrive works with UNC 143 has_unc = (hasattr(os.path, 'splitunc') 144 or os.path.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive') 145 146 do_splitdrive = not not drive or has_unc 147 148 global _my_splitdrive 149 if has_unc: 150 def splitdrive(p): 151 if p[1:2] == ':': 152 return p[:2], p[2:] 153 if p[0:2] == '//': 154 # Note that we leave a leading slash in the path 155 # because UNC paths are always absolute. 156 return '//', p[1:] 157 return '', p
158 else: 159 def splitdrive(p): 160 if p[1:2] == ':': 161 return p[:2], p[2:] 162 return '', p 163 _my_splitdrive = splitdrive 164 165 # Keep some commonly used values in global variables to skip to 166 # module look-up costs. 167 global OS_SEP 168 global UNC_PREFIX 169 global os_sep_is_slash 170 171 OS_SEP = os.sep 172 UNC_PREFIX = OS_SEP + OS_SEP 173 os_sep_is_slash = OS_SEP == '/' 174 175 initialize_do_splitdrive() 176 177 # Used to avoid invoking os.path.normpath if not necessary. 178 needs_normpath_check = re.compile( 179 r''' 180 # We need to renormalize the path if it contains any consecutive 181 # '/' characters. 182 .*// | 183 184 # We need to renormalize the path if it contains a '..' directory. 185 # Note that we check for all the following cases: 186 # 187 # a) The path is a single '..' 188 # b) The path starts with '..'. E.g. '../' or '../moredirs' 189 # but we not match '..abc/'. 190 # c) The path ends with '..'. E.g. '/..' or 'dirs/..' 191 # d) The path contains a '..' in the middle. 192 # E.g. dirs/../moredirs 193 194 (.*/)?\.\.(?:/|$) | 195 196 # We need to renormalize the path if it contains a '.' 197 # directory, but NOT if it is a single '.' '/' characters. We 198 # do not want to match a single '.' because this case is checked 199 # for explicitly since this is common enough case. 200 # 201 # Note that we check for all the following cases: 202 # 203 # a) We don't match a single '.' 204 # b) We match if the path starts with '.'. E.g. './' or 205 # './moredirs' but we not match '.abc/'. 206 # c) We match if the path ends with '.'. E.g. '/.' or 207 # 'dirs/.' 208 # d) We match if the path contains a '.' in the middle. 209 # E.g. dirs/./moredirs 210 211 \./|.*/\.(?:/|$) 212 213 ''', 214 re.VERBOSE 215 ) 216 needs_normpath_match = needs_normpath_check.match 217 218 # 219 # SCons.Action objects for interacting with the outside world. 220 # 221 # The Node.FS methods in this module should use these actions to 222 # create and/or remove files and directories; they should *not* use 223 # os.{link,symlink,unlink,mkdir}(), etc., directly. 224 # 225 # Using these SCons.Action objects ensures that descriptions of these 226 # external activities are properly displayed, that the displays are 227 # suppressed when the -s (silent) option is used, and (most importantly) 228 # the actions are disabled when the the -n option is used, in which case 229 # there should be *no* changes to the external file system(s)... 230 # 231 232 # For Now disable hard & softlinks for win32 233 # PY3 supports them, but the rest of SCons is not ready for this 234 # in some cases user permissions may be required. 235 # TODO: See if theres a reasonable way to enable using links on win32/64 236 237 if hasattr(os, 'link') and sys.platform != 'win32': 250 else: 251 _hardlink_func = None 252 253 if hasattr(os, 'symlink') and sys.platform != 'win32': 256 else: 257 _softlink_func = None
258 259 -def _copy_func(fs, src, dest):
260 shutil.copy2(src, dest) 261 st = fs.stat(src) 262 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
263 264 265 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy', 266 'hard-copy', 'soft-copy', 'copy'] 267 268 Link_Funcs = [] # contains the callables of the specified duplication style
269 270 -def set_duplicate(duplicate):
271 # Fill in the Link_Funcs list according to the argument 272 # (discarding those not available on the platform). 273 274 # Set up the dictionary that maps the argument names to the 275 # underlying implementations. We do this inside this function, 276 # not in the top-level module code, so that we can remap os.link 277 # and os.symlink for testing purposes. 278 link_dict = { 279 'hard' : _hardlink_func, 280 'soft' : _softlink_func, 281 'copy' : _copy_func 282 } 283 284 if duplicate not in Valid_Duplicates: 285 raise SCons.Errors.InternalError("The argument of set_duplicate " 286 "should be in Valid_Duplicates") 287 global Link_Funcs 288 Link_Funcs = [] 289 for func in duplicate.split('-'): 290 if link_dict[func]: 291 Link_Funcs.append(link_dict[func])
292
293 -def LinkFunc(target, source, env):
294 """ 295 Relative paths cause problems with symbolic links, so 296 we use absolute paths, which may be a problem for people 297 who want to move their soft-linked src-trees around. Those 298 people should use the 'hard-copy' mode, softlinks cannot be 299 used for that; at least I have no idea how ... 300 """ 301 src = source[0].get_abspath() 302 dest = target[0].get_abspath() 303 dir, file = os.path.split(dest) 304 if dir and not target[0].fs.isdir(dir): 305 os.makedirs(dir) 306 if not Link_Funcs: 307 # Set a default order of link functions. 308 set_duplicate('hard-soft-copy') 309 fs = source[0].fs 310 # Now link the files with the previously specified order. 311 for func in Link_Funcs: 312 try: 313 func(fs, src, dest) 314 break 315 except (IOError, OSError): 316 # An OSError indicates something happened like a permissions 317 # problem or an attempt to symlink across file-system 318 # boundaries. An IOError indicates something like the file 319 # not existing. In either case, keeping trying additional 320 # functions in the list and only raise an error if the last 321 # one failed. 322 if func == Link_Funcs[-1]: 323 # exception of the last link method (copy) are fatal 324 raise 325 return 0
326 327 Link = SCons.Action.Action(LinkFunc, None)
328 -def LocalString(target, source, env):
329 return 'Local copy of %s from %s' % (target[0], source[0])
330 331 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
332 333 -def UnlinkFunc(target, source, env):
334 t = target[0] 335 t.fs.unlink(t.get_abspath()) 336 return 0
337 338 Unlink = SCons.Action.Action(UnlinkFunc, None)
339 340 -def MkdirFunc(target, source, env):
341 t = target[0] 342 # This os.path.exists test looks redundant, but it's possible 343 # when using Install() to install multiple dirs outside the 344 # source tree to get a case where t.exists() is true but 345 # the path does already exist, so this prevents spurious 346 # build failures in that case. See test/Install/multi-dir. 347 if not t.exists() and not os.path.exists(t.get_abspath()): 348 t.fs.mkdir(t.get_abspath()) 349 return 0
350 351 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None) 352 353 MkdirBuilder = None
354 355 -def get_MkdirBuilder():
356 global MkdirBuilder 357 if MkdirBuilder is None: 358 import SCons.Builder 359 import SCons.Defaults 360 # "env" will get filled in by Executor.get_build_env() 361 # calling SCons.Defaults.DefaultEnvironment() when necessary. 362 MkdirBuilder = SCons.Builder.Builder(action = Mkdir, 363 env = None, 364 explain = None, 365 is_explicit = None, 366 target_scanner = SCons.Defaults.DirEntryScanner, 367 name = "MkdirBuilder") 368 return MkdirBuilder
369
370 -class _Null(object):
371 pass
372 373 _null = _Null() 374 375 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem. 376 _is_cygwin = sys.platform == "cygwin" 377 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
378 - def _my_normcase(x):
379 return x
380 else:
381 - def _my_normcase(x):
382 return x.upper()
383
384 385 386 -class DiskChecker(object):
387 - def __init__(self, type, do, ignore):
388 self.type = type 389 self.do = do 390 self.ignore = ignore 391 self.func = do
392 - def __call__(self, *args, **kw):
393 return self.func(*args, **kw)
394 - def set(self, list):
395 if self.type in list: 396 self.func = self.do 397 else: 398 self.func = self.ignore
399
400 -def do_diskcheck_match(node, predicate, errorfmt):
401 result = predicate() 402 try: 403 # If calling the predicate() cached a None value from stat(), 404 # remove it so it doesn't interfere with later attempts to 405 # build this Node as we walk the DAG. (This isn't a great way 406 # to do this, we're reaching into an interface that doesn't 407 # really belong to us, but it's all about performance, so 408 # for now we'll just document the dependency...) 409 if node._memo['stat'] is None: 410 del node._memo['stat'] 411 except (AttributeError, KeyError): 412 pass 413 if result: 414 raise TypeError(errorfmt % node.get_abspath())
415
416 -def ignore_diskcheck_match(node, predicate, errorfmt):
417 pass
418 419 420 421 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match) 422 423 diskcheckers = [ 424 diskcheck_match, 425 ]
426 427 -def set_diskcheck(list):
428 for dc in diskcheckers: 429 dc.set(list)
430
431 -def diskcheck_types():
432 return [dc.type for dc in diskcheckers]
433
434 435 436 -class EntryProxy(SCons.Util.Proxy):
437 438 __str__ = SCons.Util.Delegate('__str__') 439 440 # In PY3 if a class defines __eq__, then it must explicitly provide 441 # __hash__. Since SCons.Util.Proxy provides __eq__ we need the following 442 # see: https://docs.python.org/3.1/reference/datamodel.html#object.__hash__ 443 __hash__ = SCons.Util.Delegate('__hash__') 444
445 - def __get_abspath(self):
446 entry = self.get() 447 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(), 448 entry.name + "_abspath")
449
450 - def __get_filebase(self):
451 name = self.get().name 452 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0], 453 name + "_filebase")
454
455 - def __get_suffix(self):
456 name = self.get().name 457 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1], 458 name + "_suffix")
459
460 - def __get_file(self):
461 name = self.get().name 462 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
463
464 - def __get_base_path(self):
465 """Return the file's directory and file name, with the 466 suffix stripped.""" 467 entry = self.get() 468 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0], 469 entry.name + "_base")
470
471 - def __get_posix_path(self):
472 """Return the path with / as the path separator, 473 regardless of platform.""" 474 if os_sep_is_slash: 475 return self 476 else: 477 entry = self.get() 478 r = entry.get_path().replace(OS_SEP, '/') 479 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
480
481 - def __get_windows_path(self):
482 r"""Return the path with \ as the path separator, 483 regardless of platform.""" 484 if OS_SEP == '\\': 485 return self 486 else: 487 entry = self.get() 488 r = entry.get_path().replace(OS_SEP, '\\') 489 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
490
491 - def __get_srcnode(self):
492 return EntryProxy(self.get().srcnode())
493
494 - def __get_srcdir(self):
495 """Returns the directory containing the source node linked to this 496 node via VariantDir(), or the directory of this node if not linked.""" 497 return EntryProxy(self.get().srcnode().dir)
498
499 - def __get_rsrcnode(self):
500 return EntryProxy(self.get().srcnode().rfile())
501
502 - def __get_rsrcdir(self):
503 """Returns the directory containing the source node linked to this 504 node via VariantDir(), or the directory of this node if not linked.""" 505 return EntryProxy(self.get().srcnode().rfile().dir)
506
507 - def __get_dir(self):
508 return EntryProxy(self.get().dir)
509 510 dictSpecialAttrs = { "base" : __get_base_path, 511 "posix" : __get_posix_path, 512 "windows" : __get_windows_path, 513 "win32" : __get_windows_path, 514 "srcpath" : __get_srcnode, 515 "srcdir" : __get_srcdir, 516 "dir" : __get_dir, 517 "abspath" : __get_abspath, 518 "filebase" : __get_filebase, 519 "suffix" : __get_suffix, 520 "file" : __get_file, 521 "rsrcpath" : __get_rsrcnode, 522 "rsrcdir" : __get_rsrcdir, 523 } 524
525 - def __getattr__(self, name):
526 # This is how we implement the "special" attributes 527 # such as base, posix, srcdir, etc. 528 try: 529 attr_function = self.dictSpecialAttrs[name] 530 except KeyError: 531 try: 532 attr = SCons.Util.Proxy.__getattr__(self, name) 533 except AttributeError: 534 # Raise our own AttributeError subclass with an 535 # overridden __str__() method that identifies the 536 # name of the entry that caused the exception. 537 raise EntryProxyAttributeError(self, name) 538 return attr 539 else: 540 return attr_function(self)
541
542 543 -class Base(SCons.Node.Node):
544 """A generic class for file system entries. This class is for 545 when we don't know yet whether the entry being looked up is a file 546 or a directory. Instances of this class can morph into either 547 Dir or File objects by a later, more precise lookup. 548 549 Note: this class does not define __cmp__ and __hash__ for 550 efficiency reasons. SCons does a lot of comparing of 551 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be 552 as fast as possible, which means we want to use Python's built-in 553 object identity comparisons. 554 """ 555 556 __slots__ = ['name', 557 'fs', 558 '_abspath', 559 '_labspath', 560 '_path', 561 '_tpath', 562 '_path_elements', 563 'dir', 564 'cwd', 565 'duplicate', 566 '_local', 567 'sbuilder', 568 '_proxy', 569 '_func_sconsign'] 570
571 - def __init__(self, name, directory, fs):
572 """Initialize a generic Node.FS.Base object. 573 574 Call the superclass initialization, take care of setting up 575 our relative and absolute paths, identify our parent 576 directory, and indicate that this node should use 577 signatures.""" 578 579 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Base') 580 SCons.Node.Node.__init__(self) 581 582 # Filenames and paths are probably reused and are intern'ed to save some memory. 583 # Filename with extension as it was specified when the object was 584 # created; to obtain filesystem path, use Python str() function 585 self.name = SCons.Util.silent_intern(name) 586 self.fs = fs #: Reference to parent Node.FS object 587 588 assert directory, "A directory must be provided" 589 590 self._abspath = None 591 self._labspath = None 592 self._path = None 593 self._tpath = None 594 self._path_elements = None 595 596 self.dir = directory 597 self.cwd = None # will hold the SConscript directory for target nodes 598 self.duplicate = directory.duplicate 599 self.changed_since_last_build = 2 600 self._func_sconsign = 0 601 self._func_exists = 2 602 self._func_rexists = 2 603 self._func_get_contents = 0 604 self._func_target_from_source = 1 605 self.store_info = 1
606
607 - def str_for_display(self):
608 return '"' + self.__str__() + '"'
609
610 - def must_be_same(self, klass):
611 """ 612 This node, which already existed, is being looked up as the 613 specified klass. Raise an exception if it isn't. 614 """ 615 if isinstance(self, klass) or klass is Entry: 616 return 617 raise TypeError("Tried to lookup %s '%s' as a %s." %\ 618 (self.__class__.__name__, self.get_internal_path(), klass.__name__))
619
620 - def get_dir(self):
621 return self.dir
622
623 - def get_suffix(self):
624 return SCons.Util.splitext(self.name)[1]
625
626 - def rfile(self):
627 return self
628
629 - def __getattr__(self, attr):
630 """ Together with the node_bwcomp dict defined below, 631 this method provides a simple backward compatibility 632 layer for the Node attributes 'abspath', 'labspath', 633 'path', 'tpath', 'suffix' and 'path_elements'. These Node 634 attributes used to be directly available in v2.3 and earlier, but 635 have been replaced by getter methods that initialize the 636 single variables lazily when required, in order to save memory. 637 The redirection to the getters lets older Tools and 638 SConstruct continue to work without any additional changes, 639 fully transparent to the user. 640 Note, that __getattr__ is only called as fallback when the 641 requested attribute can't be found, so there should be no 642 speed performance penalty involved for standard builds. 643 """ 644 if attr in node_bwcomp: 645 return node_bwcomp[attr](self) 646 647 raise AttributeError("%r object has no attribute %r" % 648 (self.__class__, attr))
649
650 - def __str__(self):
651 """A Node.FS.Base object's string representation is its path 652 name.""" 653 global Save_Strings 654 if Save_Strings: 655 return self._save_str() 656 return self._get_str()
657
658 - def __lt__(self, other):
659 """ less than operator used by sorting on py3""" 660 return str(self) < str(other)
661 662 @SCons.Memoize.CountMethodCall
663 - def _save_str(self):
664 try: 665 return self._memo['_save_str'] 666 except KeyError: 667 pass 668 result = SCons.Util.silent_intern(self._get_str()) 669 self._memo['_save_str'] = result 670 return result
671
672 - def _get_str(self):
673 global Save_Strings 674 if self.duplicate or self.is_derived(): 675 return self.get_path() 676 srcnode = self.srcnode() 677 if srcnode.stat() is None and self.stat() is not None: 678 result = self.get_path() 679 else: 680 result = srcnode.get_path() 681 if not Save_Strings: 682 # We're not at the point where we're saving the string 683 # representations of FS Nodes (because we haven't finished 684 # reading the SConscript files and need to have str() return 685 # things relative to them). That also means we can't yet 686 # cache values returned (or not returned) by stat(), since 687 # Python code in the SConscript files might still create 688 # or otherwise affect the on-disk file. So get rid of the 689 # values that the underlying stat() method saved. 690 try: del self._memo['stat'] 691 except KeyError: pass 692 if self is not srcnode: 693 try: del srcnode._memo['stat'] 694 except KeyError: pass 695 return result
696 697 rstr = __str__ 698 699 @SCons.Memoize.CountMethodCall
700 - def stat(self):
701 try: 702 return self._memo['stat'] 703 except KeyError: 704 pass 705 try: 706 result = self.fs.stat(self.get_abspath()) 707 except os.error: 708 result = None 709 710 self._memo['stat'] = result 711 return result
712
713 - def exists(self):
714 return SCons.Node._exists_map[self._func_exists](self)
715
716 - def rexists(self):
717 return SCons.Node._rexists_map[self._func_rexists](self)
718
719 - def getmtime(self):
720 st = self.stat() 721 if st: 722 return st[stat.ST_MTIME] 723 else: 724 return None
725
726 - def getsize(self):
727 st = self.stat() 728 if st: 729 return st[stat.ST_SIZE] 730 else: 731 return None
732
733 - def isdir(self):
734 st = self.stat() 735 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
736
737 - def isfile(self):
738 st = self.stat() 739 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
740 741 if hasattr(os, 'symlink'): 746 else: 749
750 - def is_under(self, dir):
751 if self is dir: 752 return 1 753 else: 754 return self.dir.is_under(dir)
755
756 - def set_local(self):
757 self._local = 1
758
759 - def srcnode(self):
760 """If this node is in a build path, return the node 761 corresponding to its source file. Otherwise, return 762 ourself. 763 """ 764 srcdir_list = self.dir.srcdir_list() 765 if srcdir_list: 766 srcnode = srcdir_list[0].Entry(self.name) 767 srcnode.must_be_same(self.__class__) 768 return srcnode 769 return self
770
771 - def get_path(self, dir=None):
772 """Return path relative to the current working directory of the 773 Node.FS.Base object that owns us.""" 774 if not dir: 775 dir = self.fs.getcwd() 776 if self == dir: 777 return '.' 778 path_elems = self.get_path_elements() 779 pathname = '' 780 try: i = path_elems.index(dir) 781 except ValueError: 782 for p in path_elems[:-1]: 783 pathname += p.dirname 784 else: 785 for p in path_elems[i+1:-1]: 786 pathname += p.dirname 787 return pathname + path_elems[-1].name
788
789 - def set_src_builder(self, builder):
790 """Set the source code builder for this node.""" 791 self.sbuilder = builder 792 if not self.has_builder(): 793 self.builder_set(builder)
794
795 - def src_builder(self):
796 """Fetch the source code builder for this node. 797 798 If there isn't one, we cache the source code builder specified 799 for the directory (which in turn will cache the value from its 800 parent directory, and so on up to the file system root). 801 """ 802 try: 803 scb = self.sbuilder 804 except AttributeError: 805 scb = self.dir.src_builder() 806 self.sbuilder = scb 807 return scb
808
809 - def get_abspath(self):
810 """Get the absolute path of the file.""" 811 return self.dir.entry_abspath(self.name)
812
813 - def get_labspath(self):
814 """Get the absolute path of the file.""" 815 return self.dir.entry_labspath(self.name)
816
817 - def get_internal_path(self):
818 if self.dir._path == '.': 819 return self.name 820 else: 821 return self.dir.entry_path(self.name)
822
823 - def get_tpath(self):
824 if self.dir._tpath == '.': 825 return self.name 826 else: 827 return self.dir.entry_tpath(self.name)
828
829 - def get_path_elements(self):
830 return self.dir._path_elements + [self]
831
832 - def for_signature(self):
833 # Return just our name. Even an absolute path would not work, 834 # because that can change thanks to symlinks or remapped network 835 # paths. 836 return self.name
837
838 - def get_subst_proxy(self):
839 try: 840 return self._proxy 841 except AttributeError: 842 ret = EntryProxy(self) 843 self._proxy = ret 844 return ret
845
846 - def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
847 """ 848 849 Generates a target entry that corresponds to this entry (usually 850 a source file) with the specified prefix and suffix. 851 852 Note that this method can be overridden dynamically for generated 853 files that need different behavior. See Tool/swig.py for 854 an example. 855 """ 856 return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext)
857
858 - def _Rfindalldirs_key(self, pathlist):
859 return pathlist
860 861 @SCons.Memoize.CountDictCall(_Rfindalldirs_key)
862 - def Rfindalldirs(self, pathlist):
863 """ 864 Return all of the directories for a given path list, including 865 corresponding "backing" directories in any repositories. 866 867 The Node lookups are relative to this Node (typically a 868 directory), so memoizing result saves cycles from looking 869 up the same path for each target in a given directory. 870 """ 871 try: 872 memo_dict = self._memo['Rfindalldirs'] 873 except KeyError: 874 memo_dict = {} 875 self._memo['Rfindalldirs'] = memo_dict 876 else: 877 try: 878 return memo_dict[pathlist] 879 except KeyError: 880 pass 881 882 create_dir_relative_to_self = self.Dir 883 result = [] 884 for path in pathlist: 885 if isinstance(path, SCons.Node.Node): 886 result.append(path) 887 else: 888 dir = create_dir_relative_to_self(path) 889 result.extend(dir.get_all_rdirs()) 890 891 memo_dict[pathlist] = result 892 893 return result
894
895 - def RDirs(self, pathlist):
896 """Search for a list of directories in the Repository list.""" 897 cwd = self.cwd or self.fs._cwd 898 return cwd.Rfindalldirs(pathlist)
899 900 @SCons.Memoize.CountMethodCall
901 - def rentry(self):
902 try: 903 return self._memo['rentry'] 904 except KeyError: 905 pass 906 result = self 907 if not self.exists(): 908 norm_name = _my_normcase(self.name) 909 for dir in self.dir.get_all_rdirs(): 910 try: 911 node = dir.entries[norm_name] 912 except KeyError: 913 if dir.entry_exists_on_disk(self.name): 914 result = dir.Entry(self.name) 915 break 916 self._memo['rentry'] = result 917 return result
918
919 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
920 return []
921 922 # Dict that provides a simple backward compatibility 923 # layer for the Node attributes 'abspath', 'labspath', 924 # 'path', 'tpath' and 'path_elements'. 925 # @see Base.__getattr__ above 926 node_bwcomp = {'abspath' : Base.get_abspath, 927 'labspath' : Base.get_labspath, 928 'path' : Base.get_internal_path, 929 'tpath' : Base.get_tpath, 930 'path_elements' : Base.get_path_elements, 931 'suffix' : Base.get_suffix}
932 933 -class Entry(Base):
934 """This is the class for generic Node.FS entries--that is, things 935 that could be a File or a Dir, but we're just not sure yet. 936 Consequently, the methods in this class really exist just to 937 transform their associated object into the right class when the 938 time comes, and then call the same-named method in the transformed 939 class.""" 940 941 __slots__ = ['scanner_paths', 942 'cachedir_csig', 943 'cachesig', 944 'repositories', 945 'srcdir', 946 'entries', 947 'searched', 948 '_sconsign', 949 'variant_dirs', 950 'root', 951 'dirname', 952 'on_disk_entries', 953 'released_target_info', 954 'contentsig'] 955
956 - def __init__(self, name, directory, fs):
957 Base.__init__(self, name, directory, fs) 958 self._func_exists = 3 959 self._func_get_contents = 1
960
961 - def diskcheck_match(self):
962 pass
963
964 - def disambiguate(self, must_exist=None):
965 """ 966 """ 967 if self.isdir(): 968 self.__class__ = Dir 969 self._morph() 970 elif self.isfile(): 971 self.__class__ = File 972 self._morph() 973 self.clear() 974 else: 975 # There was nothing on-disk at this location, so look in 976 # the src directory. 977 # 978 # We can't just use self.srcnode() straight away because 979 # that would create an actual Node for this file in the src 980 # directory, and there might not be one. Instead, use the 981 # dir_on_disk() method to see if there's something on-disk 982 # with that name, in which case we can go ahead and call 983 # self.srcnode() to create the right type of entry. 984 srcdir = self.dir.srcnode() 985 if srcdir != self.dir and \ 986 srcdir.entry_exists_on_disk(self.name) and \ 987 self.srcnode().isdir(): 988 self.__class__ = Dir 989 self._morph() 990 elif must_exist: 991 msg = "No such file or directory: '%s'" % self.get_abspath() 992 raise SCons.Errors.UserError(msg) 993 else: 994 self.__class__ = File 995 self._morph() 996 self.clear() 997 return self
998
999 - def rfile(self):
1000 """We're a generic Entry, but the caller is actually looking for 1001 a File at this point, so morph into one.""" 1002 self.__class__ = File 1003 self._morph() 1004 self.clear() 1005 return File.rfile(self)
1006
1007 - def scanner_key(self):
1008 return self.get_suffix()
1009
1010 - def get_contents(self):
1011 """Fetch the contents of the entry. Returns the exact binary 1012 contents of the file.""" 1013 return SCons.Node._get_contents_map[self._func_get_contents](self)
1014
1015 - def get_text_contents(self):
1016 """Fetch the decoded text contents of a Unicode encoded Entry. 1017 1018 Since this should return the text contents from the file 1019 system, we check to see into what sort of subclass we should 1020 morph this Entry.""" 1021 try: 1022 self = self.disambiguate(must_exist=1) 1023 except SCons.Errors.UserError: 1024 # There was nothing on disk with which to disambiguate 1025 # this entry. Leave it as an Entry, but return a null 1026 # string so calls to get_text_contents() in emitters and 1027 # the like (e.g. in qt.py) don't have to disambiguate by 1028 # hand or catch the exception. 1029 return '' 1030 else: 1031 return self.get_text_contents()
1032
1033 - def must_be_same(self, klass):
1034 """Called to make sure a Node is a Dir. Since we're an 1035 Entry, we can morph into one.""" 1036 if self.__class__ is not klass: 1037 self.__class__ = klass 1038 self._morph() 1039 self.clear()
1040 1041 # The following methods can get called before the Taskmaster has 1042 # had a chance to call disambiguate() directly to see if this Entry 1043 # should really be a Dir or a File. We therefore use these to call 1044 # disambiguate() transparently (from our caller's point of view). 1045 # 1046 # Right now, this minimal set of methods has been derived by just 1047 # looking at some of the methods that will obviously be called early 1048 # in any of the various Taskmasters' calling sequences, and then 1049 # empirically figuring out which additional methods are necessary 1050 # to make various tests pass. 1051
1052 - def exists(self):
1053 return SCons.Node._exists_map[self._func_exists](self)
1054
1055 - def rel_path(self, other):
1056 d = self.disambiguate() 1057 if d.__class__ is Entry: 1058 raise Exception("rel_path() could not disambiguate File/Dir") 1059 return d.rel_path(other)
1060
1061 - def new_ninfo(self):
1062 return self.disambiguate().new_ninfo()
1063
1064 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1065 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
1066
1067 - def get_subst_proxy(self):
1068 return self.disambiguate().get_subst_proxy()
1069 1070 # This is for later so we can differentiate between Entry the class and Entry 1071 # the method of the FS class. 1072 _classEntry = Entry
1073 1074 1075 -class LocalFS(object):
1076 """ 1077 This class implements an abstraction layer for operations involving 1078 a local file system. Essentially, this wraps any function in 1079 the os, os.path or shutil modules that we use to actually go do 1080 anything with or to the local file system. 1081 1082 Note that there's a very good chance we'll refactor this part of 1083 the architecture in some way as we really implement the interface(s) 1084 for remote file system Nodes. For example, the right architecture 1085 might be to have this be a subclass instead of a base class. 1086 Nevertheless, we're using this as a first step in that direction. 1087 1088 We're not using chdir() yet because the calling subclass method 1089 needs to use os.chdir() directly to avoid recursion. Will we 1090 really need this one? 1091 """ 1092 #def chdir(self, path): 1093 # return os.chdir(path)
1094 - def chmod(self, path, mode):
1095 return os.chmod(path, mode)
1096 - def copy(self, src, dst):
1097 return shutil.copy(src, dst)
1098 - def copy2(self, src, dst):
1099 return shutil.copy2(src, dst)
1100 - def exists(self, path):
1101 return os.path.exists(path)
1102 - def getmtime(self, path):
1103 return os.path.getmtime(path)
1104 - def getsize(self, path):
1105 return os.path.getsize(path)
1106 - def isdir(self, path):
1107 return os.path.isdir(path)
1108 - def isfile(self, path):
1109 return os.path.isfile(path)
1112 - def lstat(self, path):
1113 return os.lstat(path)
1114 - def listdir(self, path):
1115 return os.listdir(path)
1116 - def makedirs(self, path):
1117 return os.makedirs(path)
1118 - def mkdir(self, path):
1119 return os.mkdir(path)
1120 - def rename(self, old, new):
1121 return os.rename(old, new)
1122 - def stat(self, path):
1123 return os.stat(path)
1126 - def open(self, path):
1127 return open(path)
1130 1131 if hasattr(os, 'symlink'): 1134 else: 1137 1138 if hasattr(os, 'readlink'): 1141 else:
1144
1145 1146 -class FS(LocalFS):
1147
1148 - def __init__(self, path = None):
1149 """Initialize the Node.FS subsystem. 1150 1151 The supplied path is the top of the source tree, where we 1152 expect to find the top-level build file. If no path is 1153 supplied, the current directory is the default. 1154 1155 The path argument must be a valid absolute path. 1156 """ 1157 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS') 1158 1159 self._memo = {} 1160 1161 self.Root = {} 1162 self.SConstruct_dir = None 1163 self.max_drift = default_max_drift 1164 1165 self.Top = None 1166 if path is None: 1167 self.pathTop = os.getcwd() 1168 else: 1169 self.pathTop = path 1170 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0]) 1171 1172 self.Top = self.Dir(self.pathTop) 1173 self.Top._path = '.' 1174 self.Top._tpath = '.' 1175 self._cwd = self.Top 1176 1177 DirNodeInfo.fs = self 1178 FileNodeInfo.fs = self
1179
1180 - def set_SConstruct_dir(self, dir):
1181 self.SConstruct_dir = dir
1182
1183 - def get_max_drift(self):
1184 return self.max_drift
1185
1186 - def set_max_drift(self, max_drift):
1187 self.max_drift = max_drift
1188
1189 - def getcwd(self):
1190 if hasattr(self, "_cwd"): 1191 return self._cwd 1192 else: 1193 return "<no cwd>"
1194
1195 - def chdir(self, dir, change_os_dir=0):
1196 """Change the current working directory for lookups. 1197 If change_os_dir is true, we will also change the "real" cwd 1198 to match. 1199 """ 1200 curr=self._cwd 1201 try: 1202 if dir is not None: 1203 self._cwd = dir 1204 if change_os_dir: 1205 os.chdir(dir.get_abspath()) 1206 except OSError: 1207 self._cwd = curr 1208 raise
1209
1210 - def get_root(self, drive):
1211 """ 1212 Returns the root directory for the specified drive, creating 1213 it if necessary. 1214 """ 1215 drive = _my_normcase(drive) 1216 try: 1217 return self.Root[drive] 1218 except KeyError: 1219 root = RootDir(drive, self) 1220 self.Root[drive] = root 1221 if not drive: 1222 self.Root[self.defaultDrive] = root 1223 elif drive == self.defaultDrive: 1224 self.Root[''] = root 1225 return root
1226
1227 - def _lookup(self, p, directory, fsclass, create=1):
1228 """ 1229 The generic entry point for Node lookup with user-supplied data. 1230 1231 This translates arbitrary input into a canonical Node.FS object 1232 of the specified fsclass. The general approach for strings is 1233 to turn it into a fully normalized absolute path and then call 1234 the root directory's lookup_abs() method for the heavy lifting. 1235 1236 If the path name begins with '#', it is unconditionally 1237 interpreted relative to the top-level directory of this FS. '#' 1238 is treated as a synonym for the top-level SConstruct directory, 1239 much like '~' is treated as a synonym for the user's home 1240 directory in a UNIX shell. So both '#foo' and '#/foo' refer 1241 to the 'foo' subdirectory underneath the top-level SConstruct 1242 directory. 1243 1244 If the path name is relative, then the path is looked up relative 1245 to the specified directory, or the current directory (self._cwd, 1246 typically the SConscript directory) if the specified directory 1247 is None. 1248 """ 1249 if isinstance(p, Base): 1250 # It's already a Node.FS object. Make sure it's the right 1251 # class and return. 1252 p.must_be_same(fsclass) 1253 return p 1254 # str(p) in case it's something like a proxy object 1255 p = str(p) 1256 1257 if not os_sep_is_slash: 1258 p = p.replace(OS_SEP, '/') 1259 1260 if p[0:1] == '#': 1261 # There was an initial '#', so we strip it and override 1262 # whatever directory they may have specified with the 1263 # top-level SConstruct directory. 1264 p = p[1:] 1265 directory = self.Top 1266 1267 # There might be a drive letter following the 1268 # '#'. Although it is not described in the SCons man page, 1269 # the regression test suite explicitly tests for that 1270 # syntax. It seems to mean the following thing: 1271 # 1272 # Assuming the the SCons top dir is in C:/xxx/yyy, 1273 # '#X:/toto' means X:/xxx/yyy/toto. 1274 # 1275 # i.e. it assumes that the X: drive has a directory 1276 # structure similar to the one found on drive C:. 1277 if do_splitdrive: 1278 drive, p = _my_splitdrive(p) 1279 if drive: 1280 root = self.get_root(drive) 1281 else: 1282 root = directory.root 1283 else: 1284 root = directory.root 1285 1286 # We can only strip trailing after splitting the drive 1287 # since the drive might the UNC '//' prefix. 1288 p = p.strip('/') 1289 1290 needs_normpath = needs_normpath_match(p) 1291 1292 # The path is relative to the top-level SCons directory. 1293 if p in ('', '.'): 1294 p = directory.get_labspath() 1295 else: 1296 p = directory.get_labspath() + '/' + p 1297 else: 1298 if do_splitdrive: 1299 drive, p = _my_splitdrive(p) 1300 if drive and not p: 1301 # This causes a naked drive letter to be treated 1302 # as a synonym for the root directory on that 1303 # drive. 1304 p = '/' 1305 else: 1306 drive = '' 1307 1308 # We can only strip trailing '/' since the drive might the 1309 # UNC '//' prefix. 1310 if p != '/': 1311 p = p.rstrip('/') 1312 1313 needs_normpath = needs_normpath_match(p) 1314 1315 if p[0:1] == '/': 1316 # Absolute path 1317 root = self.get_root(drive) 1318 else: 1319 # This is a relative lookup or to the current directory 1320 # (the path name is not absolute). Add the string to the 1321 # appropriate directory lookup path, after which the whole 1322 # thing gets normalized. 1323 if directory: 1324 if not isinstance(directory, Dir): 1325 directory = self.Dir(directory) 1326 else: 1327 directory = self._cwd 1328 1329 if p in ('', '.'): 1330 p = directory.get_labspath() 1331 else: 1332 p = directory.get_labspath() + '/' + p 1333 1334 if drive: 1335 root = self.get_root(drive) 1336 else: 1337 root = directory.root 1338 1339 if needs_normpath is not None: 1340 # Normalize a pathname. Will return the same result for 1341 # equivalent paths. 1342 # 1343 # We take advantage of the fact that we have an absolute 1344 # path here for sure. In addition, we know that the 1345 # components of lookup path are separated by slashes at 1346 # this point. Because of this, this code is about 2X 1347 # faster than calling os.path.normpath() followed by 1348 # replacing os.sep with '/' again. 1349 ins = p.split('/')[1:] 1350 outs = [] 1351 for d in ins: 1352 if d == '..': 1353 try: 1354 outs.pop() 1355 except IndexError: 1356 pass 1357 elif d not in ('', '.'): 1358 outs.append(d) 1359 p = '/' + '/'.join(outs) 1360 1361 return root._lookup_abs(p, fsclass, create)
1362
1363 - def Entry(self, name, directory = None, create = 1):
1364 """Look up or create a generic Entry node with the specified name. 1365 If the name is a relative path (begins with ./, ../, or a file 1366 name), then it is looked up relative to the supplied directory 1367 node, or to the top level directory of the FS (supplied at 1368 construction time) if no directory is supplied. 1369 """ 1370 return self._lookup(name, directory, Entry, create)
1371
1372 - def File(self, name, directory = None, create = 1):
1373 """Look up or create a File node with the specified name. If 1374 the name is a relative path (begins with ./, ../, or a file name), 1375 then it is looked up relative to the supplied directory node, 1376 or to the top level directory of the FS (supplied at construction 1377 time) if no directory is supplied. 1378 1379 This method will raise TypeError if a directory is found at the 1380 specified path. 1381 """ 1382 return self._lookup(name, directory, File, create)
1383
1384 - def Dir(self, name, directory = None, create = True):
1385 """Look up or create a Dir node with the specified name. If 1386 the name is a relative path (begins with ./, ../, or a file name), 1387 then it is looked up relative to the supplied directory node, 1388 or to the top level directory of the FS (supplied at construction 1389 time) if no directory is supplied. 1390 1391 This method will raise TypeError if a normal file is found at the 1392 specified path. 1393 """ 1394 return self._lookup(name, directory, Dir, create)
1395
1396 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1397 """Link the supplied variant directory to the source directory 1398 for purposes of building files.""" 1399 1400 if not isinstance(src_dir, SCons.Node.Node): 1401 src_dir = self.Dir(src_dir) 1402 if not isinstance(variant_dir, SCons.Node.Node): 1403 variant_dir = self.Dir(variant_dir) 1404 if src_dir.is_under(variant_dir): 1405 raise SCons.Errors.UserError("Source directory cannot be under variant directory.") 1406 if variant_dir.srcdir: 1407 if variant_dir.srcdir == src_dir: 1408 return # We already did this. 1409 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)) 1410 variant_dir.link(src_dir, duplicate)
1411
1412 - def Repository(self, *dirs):
1413 """Specify Repository directories to search.""" 1414 for d in dirs: 1415 if not isinstance(d, SCons.Node.Node): 1416 d = self.Dir(d) 1417 self.Top.addRepository(d)
1418
1419 - def PyPackageDir(self, modulename):
1420 r"""Locate the directory of a given python module name 1421 1422 For example scons might resolve to 1423 Windows: C:\Python27\Lib\site-packages\scons-2.5.1 1424 Linux: /usr/lib/scons 1425 1426 This can be useful when we want to determine a toolpath based on a python module name""" 1427 1428 dirpath = '' 1429 if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] in (0,1,2,3,4)): 1430 # Python2 Code 1431 import imp 1432 splitname = modulename.split('.') 1433 srchpths = sys.path 1434 for item in splitname: 1435 file, path, desc = imp.find_module(item, srchpths) 1436 if file is not None: 1437 path = os.path.dirname(path) 1438 srchpths = [path] 1439 dirpath = path 1440 else: 1441 # Python3 Code 1442 import importlib.util 1443 modspec = importlib.util.find_spec(modulename) 1444 dirpath = os.path.dirname(modspec.origin) 1445 return self._lookup(dirpath, None, Dir, True)
1446 1447
1448 - def variant_dir_target_climb(self, orig, dir, tail):
1449 """Create targets in corresponding variant directories 1450 1451 Climb the directory tree, and look up path names 1452 relative to any linked variant directories we find. 1453 1454 Even though this loops and walks up the tree, we don't memoize 1455 the return value because this is really only used to process 1456 the command-line targets. 1457 """ 1458 targets = [] 1459 message = None 1460 fmt = "building associated VariantDir targets: %s" 1461 start_dir = dir 1462 while dir: 1463 for bd in dir.variant_dirs: 1464 if start_dir.is_under(bd): 1465 # If already in the build-dir location, don't reflect 1466 return [orig], fmt % str(orig) 1467 p = os.path.join(bd._path, *tail) 1468 targets.append(self.Entry(p)) 1469 tail = [dir.name] + tail 1470 dir = dir.up() 1471 if targets: 1472 message = fmt % ' '.join(map(str, targets)) 1473 return targets, message
1474
1475 - def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None):
1476 """ 1477 Globs 1478 1479 This is mainly a shim layer 1480 """ 1481 if cwd is None: 1482 cwd = self.getcwd() 1483 return cwd.glob(pathname, ondisk, source, strings, exclude)
1484
1485 -class DirNodeInfo(SCons.Node.NodeInfoBase):
1486 __slots__ = () 1487 # This should get reset by the FS initialization. 1488 current_version_id = 2 1489 1490 fs = None 1491
1492 - def str_to_node(self, s):
1493 top = self.fs.Top 1494 root = top.root 1495 if do_splitdrive: 1496 drive, s = _my_splitdrive(s) 1497 if drive: 1498 root = self.fs.get_root(drive) 1499 if not os.path.isabs(s): 1500 s = top.get_labspath() + '/' + s 1501 return root._lookup_abs(s, Entry)
1502
1503 -class DirBuildInfo(SCons.Node.BuildInfoBase):
1504 __slots__ = () 1505 current_version_id = 2
1506 1507 glob_magic_check = re.compile('[*?[]')
1508 1509 -def has_glob_magic(s):
1510 return glob_magic_check.search(s) is not None
1511
1512 -class Dir(Base):
1513 """A class for directories in a file system. 1514 """ 1515 1516 __slots__ = ['scanner_paths', 1517 'cachedir_csig', 1518 'cachesig', 1519 'repositories', 1520 'srcdir', 1521 'entries', 1522 'searched', 1523 '_sconsign', 1524 'variant_dirs', 1525 'root', 1526 'dirname', 1527 'on_disk_entries', 1528 'released_target_info', 1529 'contentsig'] 1530 1531 NodeInfo = DirNodeInfo 1532 BuildInfo = DirBuildInfo 1533
1534 - def __init__(self, name, directory, fs):
1535 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Dir') 1536 Base.__init__(self, name, directory, fs) 1537 self._morph()
1538
1539 - def _morph(self):
1540 """Turn a file system Node (either a freshly initialized directory 1541 object or a separate Entry object) into a proper directory object. 1542 1543 Set up this directory's entries and hook it into the file 1544 system tree. Specify that directories (this Node) don't use 1545 signatures for calculating whether they're current. 1546 """ 1547 1548 self.repositories = [] 1549 self.srcdir = None 1550 1551 self.entries = {} 1552 self.entries['.'] = self 1553 self.entries['..'] = self.dir 1554 self.cwd = self 1555 self.searched = 0 1556 self._sconsign = None 1557 self.variant_dirs = [] 1558 self.root = self.dir.root 1559 self.changed_since_last_build = 3 1560 self._func_sconsign = 1 1561 self._func_exists = 2 1562 self._func_get_contents = 2 1563 1564 self._abspath = SCons.Util.silent_intern(self.dir.entry_abspath(self.name)) 1565 self._labspath = SCons.Util.silent_intern(self.dir.entry_labspath(self.name)) 1566 if self.dir._path == '.': 1567 self._path = SCons.Util.silent_intern(self.name) 1568 else: 1569 self._path = SCons.Util.silent_intern(self.dir.entry_path(self.name)) 1570 if self.dir._tpath == '.': 1571 self._tpath = SCons.Util.silent_intern(self.name) 1572 else: 1573 self._tpath = SCons.Util.silent_intern(self.dir.entry_tpath(self.name)) 1574 self._path_elements = self.dir._path_elements + [self] 1575 1576 # For directories, we make a difference between the directory 1577 # 'name' and the directory 'dirname'. The 'name' attribute is 1578 # used when we need to print the 'name' of the directory or 1579 # when we it is used as the last part of a path. The 'dirname' 1580 # is used when the directory is not the last element of the 1581 # path. The main reason for making that distinction is that 1582 # for RoorDir's the dirname can not be easily inferred from 1583 # the name. For example, we have to add a '/' after a drive 1584 # letter but not after a UNC path prefix ('//'). 1585 self.dirname = self.name + OS_SEP 1586 1587 # Don't just reset the executor, replace its action list, 1588 # because it might have some pre-or post-actions that need to 1589 # be preserved. 1590 # 1591 # But don't reset the executor if there is a non-null executor 1592 # attached already. The existing executor might have other 1593 # targets, in which case replacing the action list with a 1594 # Mkdir action is a big mistake. 1595 if not hasattr(self, 'executor'): 1596 self.builder = get_MkdirBuilder() 1597 self.get_executor().set_action_list(self.builder.action) 1598 else: 1599 # Prepend MkdirBuilder action to existing action list 1600 l = self.get_executor().action_list 1601 a = get_MkdirBuilder().action 1602 l.insert(0, a) 1603 self.get_executor().set_action_list(l)
1604
1605 - def diskcheck_match(self):
1606 diskcheck_match(self, self.isfile, 1607 "File %s found where directory expected.")
1608
1609 - def __clearRepositoryCache(self, duplicate=None):
1610 """Called when we change the repository(ies) for a directory. 1611 This clears any cached information that is invalidated by changing 1612 the repository.""" 1613 1614 for node in list(self.entries.values()): 1615 if node != self.dir: 1616 if node != self and isinstance(node, Dir): 1617 node.__clearRepositoryCache(duplicate) 1618 else: 1619 node.clear() 1620 try: 1621 del node._srcreps 1622 except AttributeError: 1623 pass 1624 if duplicate is not None: 1625 node.duplicate=duplicate
1626
1627 - def __resetDuplicate(self, node):
1628 if node != self: 1629 node.duplicate = node.get_dir().duplicate
1630
1631 - def Entry(self, name):
1632 """ 1633 Looks up or creates an entry node named 'name' relative to 1634 this directory. 1635 """ 1636 return self.fs.Entry(name, self)
1637
1638 - def Dir(self, name, create=True):
1639 """ 1640 Looks up or creates a directory node named 'name' relative to 1641 this directory. 1642 """ 1643 return self.fs.Dir(name, self, create)
1644
1645 - def File(self, name):
1646 """ 1647 Looks up or creates a file node named 'name' relative to 1648 this directory. 1649 """ 1650 return self.fs.File(name, self)
1651 1659
1660 - def getRepositories(self):
1661 """Returns a list of repositories for this directory. 1662 """ 1663 if self.srcdir and not self.duplicate: 1664 return self.srcdir.get_all_rdirs() + self.repositories 1665 return self.repositories
1666 1667 @SCons.Memoize.CountMethodCall
1668 - def get_all_rdirs(self):
1669 try: 1670 return list(self._memo['get_all_rdirs']) 1671 except KeyError: 1672 pass 1673 1674 result = [self] 1675 fname = '.' 1676 dir = self 1677 while dir: 1678 for rep in dir.getRepositories(): 1679 result.append(rep.Dir(fname)) 1680 if fname == '.': 1681 fname = dir.name 1682 else: 1683 fname = dir.name + OS_SEP + fname 1684 dir = dir.up() 1685 1686 self._memo['get_all_rdirs'] = list(result) 1687 1688 return result
1689
1690 - def addRepository(self, dir):
1691 if dir != self and dir not in self.repositories: 1692 self.repositories.append(dir) 1693 dir._tpath = '.' 1694 self.__clearRepositoryCache()
1695
1696 - def up(self):
1697 return self.dir
1698
1699 - def _rel_path_key(self, other):
1700 return str(other)
1701 1702 @SCons.Memoize.CountDictCall(_rel_path_key)
1703 - def rel_path(self, other):
1704 """Return a path to "other" relative to this directory. 1705 """ 1706 1707 # This complicated and expensive method, which constructs relative 1708 # paths between arbitrary Node.FS objects, is no longer used 1709 # by SCons itself. It was introduced to store dependency paths 1710 # in .sconsign files relative to the target, but that ended up 1711 # being significantly inefficient. 1712 # 1713 # We're continuing to support the method because some SConstruct 1714 # files out there started using it when it was available, and 1715 # we're all about backwards compatibility.. 1716 1717 try: 1718 memo_dict = self._memo['rel_path'] 1719 except KeyError: 1720 memo_dict = {} 1721 self._memo['rel_path'] = memo_dict 1722 else: 1723 try: 1724 return memo_dict[other] 1725 except KeyError: 1726 pass 1727 1728 if self is other: 1729 result = '.' 1730 1731 elif other not in self._path_elements: 1732 try: 1733 other_dir = other.get_dir() 1734 except AttributeError: 1735 result = str(other) 1736 else: 1737 if other_dir is None: 1738 result = other.name 1739 else: 1740 dir_rel_path = self.rel_path(other_dir) 1741 if dir_rel_path == '.': 1742 result = other.name 1743 else: 1744 result = dir_rel_path + OS_SEP + other.name 1745 else: 1746 i = self._path_elements.index(other) + 1 1747 1748 path_elems = ['..'] * (len(self._path_elements) - i) \ 1749 + [n.name for n in other._path_elements[i:]] 1750 1751 result = OS_SEP.join(path_elems) 1752 1753 memo_dict[other] = result 1754 1755 return result
1756
1757 - def get_env_scanner(self, env, kw={}):
1758 import SCons.Defaults 1759 return SCons.Defaults.DirEntryScanner
1760
1761 - def get_target_scanner(self):
1762 import SCons.Defaults 1763 return SCons.Defaults.DirEntryScanner
1764
1765 - def get_found_includes(self, env, scanner, path):
1766 """Return this directory's implicit dependencies. 1767 1768 We don't bother caching the results because the scan typically 1769 shouldn't be requested more than once (as opposed to scanning 1770 .h file contents, which can be requested as many times as the 1771 files is #included by other files). 1772 """ 1773 if not scanner: 1774 return [] 1775 # Clear cached info for this Dir. If we already visited this 1776 # directory on our walk down the tree (because we didn't know at 1777 # that point it was being used as the source for another Node) 1778 # then we may have calculated build signature before realizing 1779 # we had to scan the disk. Now that we have to, though, we need 1780 # to invalidate the old calculated signature so that any node 1781 # dependent on our directory structure gets one that includes 1782 # info about everything on disk. 1783 self.clear() 1784 return scanner(self, env, path)
1785 1786 # 1787 # Taskmaster interface subsystem 1788 # 1789
1790 - def prepare(self):
1791 pass
1792
1793 - def build(self, **kw):
1794 """A null "builder" for directories.""" 1795 global MkdirBuilder 1796 if self.builder is not MkdirBuilder: 1797 SCons.Node.Node.build(self, **kw)
1798 1799 # 1800 # 1801 # 1802
1803 - def _create(self):
1804 """Create this directory, silently and without worrying about 1805 whether the builder is the default or not.""" 1806 listDirs = [] 1807 parent = self 1808 while parent: 1809 if parent.exists(): 1810 break 1811 listDirs.append(parent) 1812 p = parent.up() 1813 if p is None: 1814 # Don't use while: - else: for this condition because 1815 # if so, then parent is None and has no .path attribute. 1816 raise SCons.Errors.StopError(parent._path) 1817 parent = p 1818 listDirs.reverse() 1819 for dirnode in listDirs: 1820 try: 1821 # Don't call dirnode.build(), call the base Node method 1822 # directly because we definitely *must* create this 1823 # directory. The dirnode.build() method will suppress 1824 # the build if it's the default builder. 1825 SCons.Node.Node.build(dirnode) 1826 dirnode.get_executor().nullify() 1827 # The build() action may or may not have actually 1828 # created the directory, depending on whether the -n 1829 # option was used or not. Delete the _exists and 1830 # _rexists attributes so they can be reevaluated. 1831 dirnode.clear() 1832 except OSError: 1833 pass
1834
1836 global MkdirBuilder 1837 return self.builder is not MkdirBuilder and self.has_builder()
1838
1839 - def alter_targets(self):
1840 """Return any corresponding targets in a variant directory. 1841 """ 1842 return self.fs.variant_dir_target_climb(self, self, [])
1843
1844 - def scanner_key(self):
1845 """A directory does not get scanned.""" 1846 return None
1847
1848 - def get_text_contents(self):
1849 """We already emit things in text, so just return the binary 1850 version.""" 1851 return self.get_contents()
1852
1853 - def get_contents(self):
1854 """Return content signatures and names of all our children 1855 separated by new-lines. Ensure that the nodes are sorted.""" 1856 return SCons.Node._get_contents_map[self._func_get_contents](self)
1857
1858 - def get_csig(self):
1859 """Compute the content signature for Directory nodes. In 1860 general, this is not needed and the content signature is not 1861 stored in the DirNodeInfo. However, if get_contents on a Dir 1862 node is called which has a child directory, the child 1863 directory should return the hash of its contents.""" 1864 contents = self.get_contents() 1865 return SCons.Util.MD5signature(contents)
1866
1867 - def do_duplicate(self, src):
1868 pass
1869
1870 - def is_up_to_date(self):
1871 """If any child is not up-to-date, then this directory isn't, 1872 either.""" 1873 if self.builder is not MkdirBuilder and not self.exists(): 1874 return 0 1875 up_to_date = SCons.Node.up_to_date 1876 for kid in self.children(): 1877 if kid.get_state() > up_to_date: 1878 return 0 1879 return 1
1880
1881 - def rdir(self):
1882 if not self.exists(): 1883 norm_name = _my_normcase(self.name) 1884 for dir in self.dir.get_all_rdirs(): 1885 try: node = dir.entries[norm_name] 1886 except KeyError: node = dir.dir_on_disk(self.name) 1887 if node and node.exists() and \ 1888 (isinstance(dir, Dir) or isinstance(dir, Entry)): 1889 return node 1890 return self
1891
1892 - def sconsign(self):
1893 """Return the .sconsign file info for this directory. """ 1894 return _sconsign_map[self._func_sconsign](self)
1895
1896 - def srcnode(self):
1897 """Dir has a special need for srcnode()...if we 1898 have a srcdir attribute set, then that *is* our srcnode.""" 1899 if self.srcdir: 1900 return self.srcdir 1901 return Base.srcnode(self)
1902
1903 - def get_timestamp(self):
1904 """Return the latest timestamp from among our children""" 1905 stamp = 0 1906 for kid in self.children(): 1907 if kid.get_timestamp() > stamp: 1908 stamp = kid.get_timestamp() 1909 return stamp
1910
1911 - def get_abspath(self):
1912 """Get the absolute path of the file.""" 1913 return self._abspath
1914
1915 - def get_labspath(self):
1916 """Get the absolute path of the file.""" 1917 return self._labspath
1918
1919 - def get_internal_path(self):
1920 return self._path
1921
1922 - def get_tpath(self):
1923 return self._tpath
1924
1925 - def get_path_elements(self):
1926 return self._path_elements
1927
1928 - def entry_abspath(self, name):
1929 return self._abspath + OS_SEP + name
1930
1931 - def entry_labspath(self, name):
1932 return self._labspath + '/' + name
1933
1934 - def entry_path(self, name):
1935 return self._path + OS_SEP + name
1936
1937 - def entry_tpath(self, name):
1938 return self._tpath + OS_SEP + name
1939
1940 - def entry_exists_on_disk(self, name):
1941 """ Searches through the file/dir entries of the current 1942 directory, and returns True if a physical entry with the given 1943 name could be found. 1944 1945 @see rentry_exists_on_disk 1946 """ 1947 try: 1948 d = self.on_disk_entries 1949 except AttributeError: 1950 d = {} 1951 try: 1952 entries = os.listdir(self._abspath) 1953 except OSError: 1954 pass 1955 else: 1956 for entry in map(_my_normcase, entries): 1957 d[entry] = True 1958 self.on_disk_entries = d 1959 if sys.platform == 'win32' or sys.platform == 'cygwin': 1960 name = _my_normcase(name) 1961 result = d.get(name) 1962 if result is None: 1963 # Belt-and-suspenders for Windows: check directly for 1964 # 8.3 file names that don't show up in os.listdir(). 1965 result = os.path.exists(self._abspath + OS_SEP + name) 1966 d[name] = result 1967 return result 1968 else: 1969 return name in d
1970
1971 - def rentry_exists_on_disk(self, name):
1972 """ Searches through the file/dir entries of the current 1973 *and* all its remote directories (repos), and returns 1974 True if a physical entry with the given name could be found. 1975 The local directory (self) gets searched first, so 1976 repositories take a lower precedence regarding the 1977 searching order. 1978 1979 @see entry_exists_on_disk 1980 """ 1981 1982 rentry_exists = self.entry_exists_on_disk(name) 1983 if not rentry_exists: 1984 # Search through the repository folders 1985 norm_name = _my_normcase(name) 1986 for rdir in self.get_all_rdirs(): 1987 try: 1988 node = rdir.entries[norm_name] 1989 if node: 1990 rentry_exists = True 1991 break 1992 except KeyError: 1993 if rdir.entry_exists_on_disk(name): 1994 rentry_exists = True 1995 break 1996 return rentry_exists
1997 1998 @SCons.Memoize.CountMethodCall
1999 - def srcdir_list(self):
2000 try: 2001 return self._memo['srcdir_list'] 2002 except KeyError: 2003 pass 2004 2005 result = [] 2006 2007 dirname = '.' 2008 dir = self 2009 while dir: 2010 if dir.srcdir: 2011 result.append(dir.srcdir.Dir(dirname)) 2012 dirname = dir.name + OS_SEP + dirname 2013 dir = dir.up() 2014 2015 self._memo['srcdir_list'] = result 2016 2017 return result
2018
2019 - def srcdir_duplicate(self, name):
2020 for dir in self.srcdir_list(): 2021 if self.is_under(dir): 2022 # We shouldn't source from something in the build path; 2023 # variant_dir is probably under src_dir, in which case 2024 # we are reflecting. 2025 break 2026 if dir.entry_exists_on_disk(name): 2027 srcnode = dir.Entry(name).disambiguate() 2028 if self.duplicate: 2029 node = self.Entry(name).disambiguate() 2030 node.do_duplicate(srcnode) 2031 return node 2032 else: 2033 return srcnode 2034 return None
2035
2036 - def _srcdir_find_file_key(self, filename):
2037 return filename
2038 2039 @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
2040 - def srcdir_find_file(self, filename):
2041 try: 2042 memo_dict = self._memo['srcdir_find_file'] 2043 except KeyError: 2044 memo_dict = {} 2045 self._memo['srcdir_find_file'] = memo_dict 2046 else: 2047 try: 2048 return memo_dict[filename] 2049 except KeyError: 2050 pass 2051 2052 def func(node): 2053 if (isinstance(node, File) or isinstance(node, Entry)) and \ 2054 (node.is_derived() or node.exists()): 2055 return node 2056 return None
2057 2058 norm_name = _my_normcase(filename) 2059 2060 for rdir in self.get_all_rdirs(): 2061 try: node = rdir.entries[norm_name] 2062 except KeyError: node = rdir.file_on_disk(filename) 2063 else: node = func(node) 2064 if node: 2065 result = (node, self) 2066 memo_dict[filename] = result 2067 return result 2068 2069 for srcdir in self.srcdir_list(): 2070 for rdir in srcdir.get_all_rdirs(): 2071 try: node = rdir.entries[norm_name] 2072 except KeyError: node = rdir.file_on_disk(filename) 2073 else: node = func(node) 2074 if node: 2075 result = (File(filename, self, self.fs), srcdir) 2076 memo_dict[filename] = result 2077 return result 2078 2079 result = (None, None) 2080 memo_dict[filename] = result 2081 return result
2082
2083 - def dir_on_disk(self, name):
2084 if self.entry_exists_on_disk(name): 2085 try: return self.Dir(name) 2086 except TypeError: pass 2087 node = self.srcdir_duplicate(name) 2088 if isinstance(node, File): 2089 return None 2090 return node
2091
2092 - def file_on_disk(self, name):
2093 if self.entry_exists_on_disk(name): 2094 try: return self.File(name) 2095 except TypeError: pass 2096 node = self.srcdir_duplicate(name) 2097 if isinstance(node, Dir): 2098 return None 2099 return node
2100
2101 - def walk(self, func, arg):
2102 """ 2103 Walk this directory tree by calling the specified function 2104 for each directory in the tree. 2105 2106 This behaves like the os.path.walk() function, but for in-memory 2107 Node.FS.Dir objects. The function takes the same arguments as 2108 the functions passed to os.path.walk(): 2109 2110 func(arg, dirname, fnames) 2111 2112 Except that "dirname" will actually be the directory *Node*, 2113 not the string. The '.' and '..' entries are excluded from 2114 fnames. The fnames list may be modified in-place to filter the 2115 subdirectories visited or otherwise impose a specific order. 2116 The "arg" argument is always passed to func() and may be used 2117 in any way (or ignored, passing None is common). 2118 """ 2119 entries = self.entries 2120 names = list(entries.keys()) 2121 names.remove('.') 2122 names.remove('..') 2123 func(arg, self, names) 2124 for dirname in [n for n in names if isinstance(entries[n], Dir)]: 2125 entries[dirname].walk(func, arg)
2126
2127 - def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None):
2128 """ 2129 Returns a list of Nodes (or strings) matching a specified 2130 pathname pattern. 2131 2132 Pathname patterns follow UNIX shell semantics: * matches 2133 any-length strings of any characters, ? matches any character, 2134 and [] can enclose lists or ranges of characters. Matches do 2135 not span directory separators. 2136 2137 The matches take into account Repositories, returning local 2138 Nodes if a corresponding entry exists in a Repository (either 2139 an in-memory Node or something on disk). 2140 2141 By defafult, the glob() function matches entries that exist 2142 on-disk, in addition to in-memory Nodes. Setting the "ondisk" 2143 argument to False (or some other non-true value) causes the glob() 2144 function to only match in-memory Nodes. The default behavior is 2145 to return both the on-disk and in-memory Nodes. 2146 2147 The "source" argument, when true, specifies that corresponding 2148 source Nodes must be returned if you're globbing in a build 2149 directory (initialized with VariantDir()). The default behavior 2150 is to return Nodes local to the VariantDir(). 2151 2152 The "strings" argument, when true, returns the matches as strings, 2153 not Nodes. The strings are path names relative to this directory. 2154 2155 The "exclude" argument, if not None, must be a pattern or a list 2156 of patterns following the same UNIX shell semantics. 2157 Elements matching a least one pattern of this list will be excluded 2158 from the result. 2159 2160 The underlying algorithm is adapted from the glob.glob() function 2161 in the Python library (but heavily modified), and uses fnmatch() 2162 under the covers. 2163 """ 2164 dirname, basename = os.path.split(pathname) 2165 if not dirname: 2166 result = self._glob1(basename, ondisk, source, strings) 2167 else: 2168 if has_glob_magic(dirname): 2169 list = self.glob(dirname, ondisk, source, False, exclude) 2170 else: 2171 list = [self.Dir(dirname, create=True)] 2172 result = [] 2173 for dir in list: 2174 r = dir._glob1(basename, ondisk, source, strings) 2175 if strings: 2176 r = [os.path.join(str(dir), x) for x in r] 2177 result.extend(r) 2178 if exclude: 2179 excludes = [] 2180 excludeList = SCons.Util.flatten(exclude) 2181 for x in excludeList: 2182 r = self.glob(x, ondisk, source, strings) 2183 excludes.extend(r) 2184 result = [x for x in result if not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes))] 2185 return sorted(result, key=lambda a: str(a))
2186
2187 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2188 """ 2189 Globs for and returns a list of entry names matching a single 2190 pattern in this directory. 2191 2192 This searches any repositories and source directories for 2193 corresponding entries and returns a Node (or string) relative 2194 to the current directory if an entry is found anywhere. 2195 2196 TODO: handle pattern with no wildcard 2197 """ 2198 search_dir_list = self.get_all_rdirs() 2199 for srcdir in self.srcdir_list(): 2200 search_dir_list.extend(srcdir.get_all_rdirs()) 2201 2202 selfEntry = self.Entry 2203 names = [] 2204 for dir in search_dir_list: 2205 # We use the .name attribute from the Node because the keys of 2206 # the dir.entries dictionary are normalized (that is, all upper 2207 # case) on case-insensitive systems like Windows. 2208 node_names = [ v.name for k, v in dir.entries.items() 2209 if k not in ('.', '..') ] 2210 names.extend(node_names) 2211 if not strings: 2212 # Make sure the working directory (self) actually has 2213 # entries for all Nodes in repositories or variant dirs. 2214 for name in node_names: selfEntry(name) 2215 if ondisk: 2216 try: 2217 disk_names = os.listdir(dir._abspath) 2218 except os.error: 2219 continue 2220 names.extend(disk_names) 2221 if not strings: 2222 # We're going to return corresponding Nodes in 2223 # the local directory, so we need to make sure 2224 # those Nodes exist. We only want to create 2225 # Nodes for the entries that will match the 2226 # specified pattern, though, which means we 2227 # need to filter the list here, even though 2228 # the overall list will also be filtered later, 2229 # after we exit this loop. 2230 if pattern[0] != '.': 2231 disk_names = [x for x in disk_names if x[0] != '.'] 2232 disk_names = fnmatch.filter(disk_names, pattern) 2233 dirEntry = dir.Entry 2234 for name in disk_names: 2235 # Add './' before disk filename so that '#' at 2236 # beginning of filename isn't interpreted. 2237 name = './' + name 2238 node = dirEntry(name).disambiguate() 2239 n = selfEntry(name) 2240 if n.__class__ != node.__class__: 2241 n.__class__ = node.__class__ 2242 n._morph() 2243 2244 names = set(names) 2245 if pattern[0] != '.': 2246 names = [x for x in names if x[0] != '.'] 2247 names = fnmatch.filter(names, pattern) 2248 2249 if strings: 2250 return names 2251 2252 return [self.entries[_my_normcase(n)] for n in names]
2253
2254 -class RootDir(Dir):
2255 """A class for the root directory of a file system. 2256 2257 This is the same as a Dir class, except that the path separator 2258 ('/' or '\\') is actually part of the name, so we don't need to 2259 add a separator when creating the path names of entries within 2260 this directory. 2261 """ 2262 2263 __slots__ = ('_lookupDict', ) 2264
2265 - def __init__(self, drive, fs):
2266 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir') 2267 SCons.Node.Node.__init__(self) 2268 2269 # Handle all the types of drives: 2270 if drive == '': 2271 # No drive, regular UNIX root or Windows default drive. 2272 name = OS_SEP 2273 dirname = OS_SEP 2274 elif drive == '//': 2275 # UNC path 2276 name = UNC_PREFIX 2277 dirname = UNC_PREFIX 2278 else: 2279 # Windows drive letter 2280 name = drive 2281 dirname = drive + OS_SEP 2282 2283 # Filename with extension as it was specified when the object was 2284 # created; to obtain filesystem path, use Python str() function 2285 self.name = SCons.Util.silent_intern(name) 2286 self.fs = fs #: Reference to parent Node.FS object 2287 2288 self._path_elements = [self] 2289 self.dir = self 2290 self._func_rexists = 2 2291 self._func_target_from_source = 1 2292 self.store_info = 1 2293 2294 # Now set our paths to what we really want them to be. The 2295 # name should already contain any necessary separators, such 2296 # as the initial drive letter (the name) plus the directory 2297 # separator, except for the "lookup abspath," which does not 2298 # have the drive letter. 2299 self._abspath = dirname 2300 self._labspath = '' 2301 self._path = dirname 2302 self._tpath = dirname 2303 self.dirname = dirname 2304 2305 self._morph() 2306 2307 self.duplicate = 0 2308 self._lookupDict = {} 2309 2310 self._lookupDict[''] = self 2311 self._lookupDict['/'] = self 2312 self.root = self 2313 # The // entry is necessary because os.path.normpath() 2314 # preserves double slashes at the beginning of a path on Posix 2315 # platforms. 2316 if not has_unc: 2317 self._lookupDict['//'] = self
2318
2319 - def _morph(self):
2320 """Turn a file system Node (either a freshly initialized directory 2321 object or a separate Entry object) into a proper directory object. 2322 2323 Set up this directory's entries and hook it into the file 2324 system tree. Specify that directories (this Node) don't use 2325 signatures for calculating whether they're current. 2326 """ 2327 2328 self.repositories = [] 2329 self.srcdir = None 2330 2331 self.entries = {} 2332 self.entries['.'] = self 2333 self.entries['..'] = self.dir 2334 self.cwd = self 2335 self.searched = 0 2336 self._sconsign = None 2337 self.variant_dirs = [] 2338 self.changed_since_last_build = 3 2339 self._func_sconsign = 1 2340 self._func_exists = 2 2341 self._func_get_contents = 2 2342 2343 # Don't just reset the executor, replace its action list, 2344 # because it might have some pre-or post-actions that need to 2345 # be preserved. 2346 # 2347 # But don't reset the executor if there is a non-null executor 2348 # attached already. The existing executor might have other 2349 # targets, in which case replacing the action list with a 2350 # Mkdir action is a big mistake. 2351 if not hasattr(self, 'executor'): 2352 self.builder = get_MkdirBuilder() 2353 self.get_executor().set_action_list(self.builder.action) 2354 else: 2355 # Prepend MkdirBuilder action to existing action list 2356 l = self.get_executor().action_list 2357 a = get_MkdirBuilder().action 2358 l.insert(0, a) 2359 self.get_executor().set_action_list(l)
2360 2361
2362 - def must_be_same(self, klass):
2363 if klass is Dir: 2364 return 2365 Base.must_be_same(self, klass)
2366
2367 - def _lookup_abs(self, p, klass, create=1):
2368 """ 2369 Fast (?) lookup of a *normalized* absolute path. 2370 2371 This method is intended for use by internal lookups with 2372 already-normalized path data. For general-purpose lookups, 2373 use the FS.Entry(), FS.Dir() or FS.File() methods. 2374 2375 The caller is responsible for making sure we're passed a 2376 normalized absolute path; we merely let Python's dictionary look 2377 up and return the One True Node.FS object for the path. 2378 2379 If a Node for the specified "p" doesn't already exist, and 2380 "create" is specified, the Node may be created after recursive 2381 invocation to find or create the parent directory or directories. 2382 """ 2383 k = _my_normcase(p) 2384 try: 2385 result = self._lookupDict[k] 2386 except KeyError: 2387 if not create: 2388 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self)) 2389 raise SCons.Errors.UserError(msg) 2390 # There is no Node for this path name, and we're allowed 2391 # to create it. 2392 dir_name, file_name = p.rsplit('/',1) 2393 dir_node = self._lookup_abs(dir_name, Dir) 2394 result = klass(file_name, dir_node, self.fs) 2395 2396 # Double-check on disk (as configured) that the Node we 2397 # created matches whatever is out there in the real world. 2398 result.diskcheck_match() 2399 2400 self._lookupDict[k] = result 2401 dir_node.entries[_my_normcase(file_name)] = result 2402 dir_node.implicit = None 2403 else: 2404 # There is already a Node for this path name. Allow it to 2405 # complain if we were looking for an inappropriate type. 2406 result.must_be_same(klass) 2407 return result
2408
2409 - def __str__(self):
2410 return self._abspath
2411
2412 - def entry_abspath(self, name):
2413 return self._abspath + name
2414
2415 - def entry_labspath(self, name):
2416 return '/' + name
2417
2418 - def entry_path(self, name):
2419 return self._path + name
2420
2421 - def entry_tpath(self, name):
2422 return self._tpath + name
2423
2424 - def is_under(self, dir):
2425 if self is dir: 2426 return 1 2427 else: 2428 return 0
2429
2430 - def up(self):
2431 return None
2432
2433 - def get_dir(self):
2434 return None
2435
2436 - def src_builder(self):
2437 return _null
2438
2439 2440 -class FileNodeInfo(SCons.Node.NodeInfoBase):
2441 __slots__ = ('csig', 'timestamp', 'size') 2442 current_version_id = 2 2443 2444 field_list = ['csig', 'timestamp', 'size'] 2445 2446 # This should get reset by the FS initialization. 2447 fs = None 2448
2449 - def str_to_node(self, s):
2450 top = self.fs.Top 2451 root = top.root 2452 if do_splitdrive: 2453 drive, s = _my_splitdrive(s) 2454 if drive: 2455 root = self.fs.get_root(drive) 2456 if not os.path.isabs(s): 2457 s = top.get_labspath() + '/' + s 2458 return root._lookup_abs(s, Entry)
2459
2460 - def __getstate__(self):
2461 """ 2462 Return all fields that shall be pickled. Walk the slots in the class 2463 hierarchy and add those to the state dictionary. If a '__dict__' slot is 2464 available, copy all entries to the dictionary. Also include the version 2465 id, which is fixed for all instances of a class. 2466 """ 2467 state = getattr(self, '__dict__', {}).copy() 2468 for obj in type(self).mro(): 2469 for name in getattr(obj, '__slots__', ()): 2470 if hasattr(self, name): 2471 state[name] = getattr(self, name) 2472 2473 state['_version_id'] = self.current_version_id 2474 try: 2475 del state['__weakref__'] 2476 except KeyError: 2477 pass 2478 2479 return state
2480
2481 - def __setstate__(self, state):
2482 """ 2483 Restore the attributes from a pickled state. 2484 """ 2485 # TODO check or discard version 2486 del state['_version_id'] 2487 for key, value in state.items(): 2488 if key not in ('__weakref__',): 2489 setattr(self, key, value)
2490
2491 - def __eq__(self, other):
2492 return self.csig == other.csig and self.timestamp == other.timestamp and self.size == other.size
2493
2494 - def __ne__(self, other):
2495 return not self.__eq__(other)
2496
2497 2498 -class FileBuildInfo(SCons.Node.BuildInfoBase):
2499 """ 2500 This is info loaded from sconsign. 2501 2502 Attributes unique to FileBuildInfo: 2503 dependency_map : Caches file->csig mapping 2504 for all dependencies. Currently this is only used when using 2505 MD5-timestamp decider. 2506 It's used to ensure that we copy the correct 2507 csig from previous build to be written to .sconsign when current build 2508 is done. Previously the matching of csig to file was strictly by order 2509 they appeared in bdepends, bsources, or bimplicit, and so a change in order 2510 or count of any of these could yield writing wrong csig, and then false positive 2511 rebuilds 2512 """ 2513 __slots__ = ['dependency_map', ] 2514 current_version_id = 2 2515
2516 - def __setattr__(self, key, value):
2517 2518 # If any attributes are changed in FileBuildInfo, we need to 2519 # invalidate the cached map of file name to content signature 2520 # heald in dependency_map. Currently only used with 2521 # MD5-timestamp decider 2522 if key != 'dependency_map' and hasattr(self, 'dependency_map'): 2523 del self.dependency_map 2524 2525 return super(FileBuildInfo, self).__setattr__(key, value)
2526
2527 - def convert_to_sconsign(self):
2528 """ 2529 Converts this FileBuildInfo object for writing to a .sconsign file 2530 2531 This replaces each Node in our various dependency lists with its 2532 usual string representation: relative to the top-level SConstruct 2533 directory, or an absolute path if it's outside. 2534 """ 2535 if os_sep_is_slash: 2536 node_to_str = str 2537 else: 2538 def node_to_str(n): 2539 try: 2540 s = n.get_internal_path() 2541 except AttributeError: 2542 s = str(n) 2543 else: 2544 s = s.replace(OS_SEP, '/') 2545 return s
2546 for attr in ['bsources', 'bdepends', 'bimplicit']: 2547 try: 2548 val = getattr(self, attr) 2549 except AttributeError: 2550 pass 2551 else: 2552 setattr(self, attr, list(map(node_to_str, val)))
2553
2554 - def convert_from_sconsign(self, dir, name):
2555 """ 2556 Converts a newly-read FileBuildInfo object for in-SCons use 2557 2558 For normal up-to-date checking, we don't have any conversion to 2559 perform--but we're leaving this method here to make that clear. 2560 """ 2561 pass
2562
2563 - def prepare_dependencies(self):
2564 """ 2565 Prepares a FileBuildInfo object for explaining what changed 2566 2567 The bsources, bdepends and bimplicit lists have all been 2568 stored on disk as paths relative to the top-level SConstruct 2569 directory. Convert the strings to actual Nodes (for use by the 2570 --debug=explain code and --implicit-cache). 2571 """ 2572 attrs = [ 2573 ('bsources', 'bsourcesigs'), 2574 ('bdepends', 'bdependsigs'), 2575 ('bimplicit', 'bimplicitsigs'), 2576 ] 2577 for (nattr, sattr) in attrs: 2578 try: 2579 strings = getattr(self, nattr) 2580 nodeinfos = getattr(self, sattr) 2581 except AttributeError: 2582 continue 2583 if strings is None or nodeinfos is None: 2584 continue 2585 nodes = [] 2586 for s, ni in zip(strings, nodeinfos): 2587 if not isinstance(s, SCons.Node.Node): 2588 s = ni.str_to_node(s) 2589 nodes.append(s) 2590 setattr(self, nattr, nodes)
2591
2592 - def format(self, names=0):
2593 result = [] 2594 bkids = self.bsources + self.bdepends + self.bimplicit 2595 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs 2596 for bkid, bkidsig in zip(bkids, bkidsigs): 2597 result.append(str(bkid) + ': ' + 2598 ' '.join(bkidsig.format(names=names))) 2599 if not hasattr(self,'bact'): 2600 self.bact = "none" 2601 result.append('%s [%s]' % (self.bactsig, self.bact)) 2602 return '\n'.join(result)
2603
2604 2605 -class File(Base):
2606 """A class for files in a file system. 2607 """ 2608 2609 __slots__ = ['scanner_paths', 2610 'cachedir_csig', 2611 'cachesig', 2612 'repositories', 2613 'srcdir', 2614 'entries', 2615 'searched', 2616 '_sconsign', 2617 'variant_dirs', 2618 'root', 2619 'dirname', 2620 'on_disk_entries', 2621 'released_target_info', 2622 'contentsig'] 2623 2624 NodeInfo = FileNodeInfo 2625 BuildInfo = FileBuildInfo 2626 2627 md5_chunksize = 64 2628
2629 - def diskcheck_match(self):
2630 diskcheck_match(self, self.isdir, 2631 "Directory %s found where file expected.")
2632
2633 - def __init__(self, name, directory, fs):
2634 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.File') 2635 Base.__init__(self, name, directory, fs) 2636 self._morph()
2637
2638 - def Entry(self, name):
2639 """Create an entry node named 'name' relative to 2640 the directory of this file.""" 2641 return self.dir.Entry(name)
2642
2643 - def Dir(self, name, create=True):
2644 """Create a directory node named 'name' relative to 2645 the directory of this file.""" 2646 return self.dir.Dir(name, create=create)
2647
2648 - def Dirs(self, pathlist):
2649 """Create a list of directories relative to the SConscript 2650 directory of this file.""" 2651 return [self.Dir(p) for p in pathlist]
2652
2653 - def File(self, name):
2654 """Create a file node named 'name' relative to 2655 the directory of this file.""" 2656 return self.dir.File(name)
2657
2658 - def _morph(self):
2659 """Turn a file system node into a File object.""" 2660 self.scanner_paths = {} 2661 if not hasattr(self, '_local'): 2662 self._local = 0 2663 if not hasattr(self, 'released_target_info'): 2664 self.released_target_info = False 2665 2666 self.store_info = 1 2667 self._func_exists = 4 2668 self._func_get_contents = 3 2669 2670 # Initialize this Node's decider function to decide_source() because 2671 # every file is a source file until it has a Builder attached... 2672 self.changed_since_last_build = 4 2673 2674 # If there was already a Builder set on this entry, then 2675 # we need to make sure we call the target-decider function, 2676 # not the source-decider. Reaching in and doing this by hand 2677 # is a little bogus. We'd prefer to handle this by adding 2678 # an Entry.builder_set() method that disambiguates like the 2679 # other methods, but that starts running into problems with the 2680 # fragile way we initialize Dir Nodes with their Mkdir builders, 2681 # yet still allow them to be overridden by the user. Since it's 2682 # not clear right now how to fix that, stick with what works 2683 # until it becomes clear... 2684 if self.has_builder(): 2685 self.changed_since_last_build = 5
2686
2687 - def scanner_key(self):
2688 return self.get_suffix()
2689
2690 - def get_contents(self):
2692
2693 - def get_text_contents(self):
2694 """ 2695 This attempts to figure out what the encoding of the text is 2696 based upon the BOM bytes, and then decodes the contents so that 2697 it's a valid python string. 2698 """ 2699 contents = self.get_contents() 2700 # The behavior of various decode() methods and functions 2701 # w.r.t. the initial BOM bytes is different for different 2702 # encodings and/or Python versions. ('utf-8' does not strip 2703 # them, but has a 'utf-8-sig' which does; 'utf-16' seems to 2704 # strip them; etc.) Just sidestep all the complication by 2705 # explicitly stripping the BOM before we decode(). 2706 if contents[:len(codecs.BOM_UTF8)] == codecs.BOM_UTF8: 2707 return contents[len(codecs.BOM_UTF8):].decode('utf-8') 2708 if contents[:len(codecs.BOM_UTF16_LE)] == codecs.BOM_UTF16_LE: 2709 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le') 2710 if contents[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE: 2711 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be') 2712 try: 2713 return contents.decode('utf-8') 2714 except UnicodeDecodeError as e: 2715 try: 2716 return contents.decode('latin-1') 2717 except UnicodeDecodeError as e: 2718 return contents.decode('utf-8', error='backslashreplace')
2719 2720
2721 - def get_content_hash(self):
2722 """ 2723 Compute and return the MD5 hash for this file. 2724 """ 2725 if not self.rexists(): 2726 return SCons.Util.MD5signature('') 2727 fname = self.rfile().get_abspath() 2728 try: 2729 cs = SCons.Util.MD5filesignature(fname, 2730 chunksize=SCons.Node.FS.File.md5_chunksize*1024) 2731 except EnvironmentError as e: 2732 if not e.filename: 2733 e.filename = fname 2734 raise 2735 return cs
2736 2737 @SCons.Memoize.CountMethodCall
2738 - def get_size(self):
2739 try: 2740 return self._memo['get_size'] 2741 except KeyError: 2742 pass 2743 2744 if self.rexists(): 2745 size = self.rfile().getsize() 2746 else: 2747 size = 0 2748 2749 self._memo['get_size'] = size 2750 2751 return size
2752 2753 @SCons.Memoize.CountMethodCall
2754 - def get_timestamp(self):
2755 try: 2756 return self._memo['get_timestamp'] 2757 except KeyError: 2758 pass 2759 2760 if self.rexists(): 2761 timestamp = self.rfile().getmtime() 2762 else: 2763 timestamp = 0 2764 2765 self._memo['get_timestamp'] = timestamp 2766 2767 return timestamp
2768 2769 convert_copy_attrs = [ 2770 'bsources', 2771 'bimplicit', 2772 'bdepends', 2773 'bact', 2774 'bactsig', 2775 'ninfo', 2776 ] 2777 2778 2779 convert_sig_attrs = [ 2780 'bsourcesigs', 2781 'bimplicitsigs', 2782 'bdependsigs', 2783 ] 2784
2785 - def convert_old_entry(self, old_entry):
2786 # Convert a .sconsign entry from before the Big Signature 2787 # Refactoring, doing what we can to convert its information 2788 # to the new .sconsign entry format. 2789 # 2790 # The old format looked essentially like this: 2791 # 2792 # BuildInfo 2793 # .ninfo (NodeInfo) 2794 # .bsig 2795 # .csig 2796 # .timestamp 2797 # .size 2798 # .bsources 2799 # .bsourcesigs ("signature" list) 2800 # .bdepends 2801 # .bdependsigs ("signature" list) 2802 # .bimplicit 2803 # .bimplicitsigs ("signature" list) 2804 # .bact 2805 # .bactsig 2806 # 2807 # The new format looks like this: 2808 # 2809 # .ninfo (NodeInfo) 2810 # .bsig 2811 # .csig 2812 # .timestamp 2813 # .size 2814 # .binfo (BuildInfo) 2815 # .bsources 2816 # .bsourcesigs (NodeInfo list) 2817 # .bsig 2818 # .csig 2819 # .timestamp 2820 # .size 2821 # .bdepends 2822 # .bdependsigs (NodeInfo list) 2823 # .bsig 2824 # .csig 2825 # .timestamp 2826 # .size 2827 # .bimplicit 2828 # .bimplicitsigs (NodeInfo list) 2829 # .bsig 2830 # .csig 2831 # .timestamp 2832 # .size 2833 # .bact 2834 # .bactsig 2835 # 2836 # The basic idea of the new structure is that a NodeInfo always 2837 # holds all available information about the state of a given Node 2838 # at a certain point in time. The various .b*sigs lists can just 2839 # be a list of pointers to the .ninfo attributes of the different 2840 # dependent nodes, without any copying of information until it's 2841 # time to pickle it for writing out to a .sconsign file. 2842 # 2843 # The complicating issue is that the *old* format only stored one 2844 # "signature" per dependency, based on however the *last* build 2845 # was configured. We don't know from just looking at it whether 2846 # it was a build signature, a content signature, or a timestamp 2847 # "signature". Since we no longer use build signatures, the 2848 # best we can do is look at the length and if it's thirty two, 2849 # assume that it was (or might have been) a content signature. 2850 # If it was actually a build signature, then it will cause a 2851 # rebuild anyway when it doesn't match the new content signature, 2852 # but that's probably the best we can do. 2853 import SCons.SConsign 2854 new_entry = SCons.SConsign.SConsignEntry() 2855 new_entry.binfo = self.new_binfo() 2856 binfo = new_entry.binfo 2857 for attr in self.convert_copy_attrs: 2858 try: 2859 value = getattr(old_entry, attr) 2860 except AttributeError: 2861 continue 2862 setattr(binfo, attr, value) 2863 delattr(old_entry, attr) 2864 for attr in self.convert_sig_attrs: 2865 try: 2866 sig_list = getattr(old_entry, attr) 2867 except AttributeError: 2868 continue 2869 value = [] 2870 for sig in sig_list: 2871 ninfo = self.new_ninfo() 2872 if len(sig) == 32: 2873 ninfo.csig = sig 2874 else: 2875 ninfo.timestamp = sig 2876 value.append(ninfo) 2877 setattr(binfo, attr, value) 2878 delattr(old_entry, attr) 2879 return new_entry
2880 2881 @SCons.Memoize.CountMethodCall
2882 - def get_stored_info(self):
2883 try: 2884 return self._memo['get_stored_info'] 2885 except KeyError: 2886 pass 2887 2888 try: 2889 sconsign_entry = self.dir.sconsign().get_entry(self.name) 2890 except (KeyError, EnvironmentError): 2891 import SCons.SConsign 2892 sconsign_entry = SCons.SConsign.SConsignEntry() 2893 sconsign_entry.binfo = self.new_binfo() 2894 sconsign_entry.ninfo = self.new_ninfo() 2895 else: 2896 if isinstance(sconsign_entry, FileBuildInfo): 2897 # This is a .sconsign file from before the Big Signature 2898 # Refactoring; convert it as best we can. 2899 sconsign_entry = self.convert_old_entry(sconsign_entry) 2900 try: 2901 delattr(sconsign_entry.ninfo, 'bsig') 2902 except AttributeError: 2903 pass 2904 2905 self._memo['get_stored_info'] = sconsign_entry 2906 2907 return sconsign_entry
2908
2909 - def get_stored_implicit(self):
2910 binfo = self.get_stored_info().binfo 2911 binfo.prepare_dependencies() 2912 try: return binfo.bimplicit 2913 except AttributeError: return None
2914
2915 - def rel_path(self, other):
2916 return self.dir.rel_path(other)
2917
2918 - def _get_found_includes_key(self, env, scanner, path):
2919 return (id(env), id(scanner), path)
2920 2921 @SCons.Memoize.CountDictCall(_get_found_includes_key)
2922 - def get_found_includes(self, env, scanner, path):
2923 """Return the included implicit dependencies in this file. 2924 Cache results so we only scan the file once per path 2925 regardless of how many times this information is requested. 2926 """ 2927 memo_key = (id(env), id(scanner), path) 2928 try: 2929 memo_dict = self._memo['get_found_includes'] 2930 except KeyError: 2931 memo_dict = {} 2932 self._memo['get_found_includes'] = memo_dict 2933 else: 2934 try: 2935 return memo_dict[memo_key] 2936 except KeyError: 2937 pass 2938 2939 if scanner: 2940 result = [n.disambiguate() for n in scanner(self, env, path)] 2941 else: 2942 result = [] 2943 2944 memo_dict[memo_key] = result 2945 2946 return result
2947
2948 - def _createDir(self):
2949 # ensure that the directories for this node are 2950 # created. 2951 self.dir._create()
2952
2953 - def push_to_cache(self):
2954 """Try to push the node into a cache 2955 """ 2956 # This should get called before the Nodes' .built() method is 2957 # called, which would clear the build signature if the file has 2958 # a source scanner. 2959 # 2960 # We have to clear the local memoized values *before* we push 2961 # the node to cache so that the memoization of the self.exists() 2962 # return value doesn't interfere. 2963 if self.nocache: 2964 return 2965 self.clear_memoized_values() 2966 if self.exists(): 2967 self.get_build_env().get_CacheDir().push(self)
2968
2969 - def retrieve_from_cache(self):
2970 """Try to retrieve the node's content from a cache 2971 2972 This method is called from multiple threads in a parallel build, 2973 so only do thread safe stuff here. Do thread unsafe stuff in 2974 built(). 2975 2976 Returns true if the node was successfully retrieved. 2977 """ 2978 if self.nocache: 2979 return None 2980 if not self.is_derived(): 2981 return None 2982 return self.get_build_env().get_CacheDir().retrieve(self)
2983
2984 - def visited(self):
2985 if self.exists() and self.executor is not None: 2986 self.get_build_env().get_CacheDir().push_if_forced(self) 2987 2988 ninfo = self.get_ninfo() 2989 2990 csig = self.get_max_drift_csig() 2991 if csig: 2992 ninfo.csig = csig 2993 2994 ninfo.timestamp = self.get_timestamp() 2995 ninfo.size = self.get_size() 2996 2997 if not self.has_builder(): 2998 # This is a source file, but it might have been a target file 2999 # in another build that included more of the DAG. Copy 3000 # any build information that's stored in the .sconsign file 3001 # into our binfo object so it doesn't get lost. 3002 old = self.get_stored_info() 3003 self.get_binfo().merge(old.binfo) 3004 3005 SCons.Node.store_info_map[self.store_info](self)
3006
3007 - def release_target_info(self):
3008 """Called just after this node has been marked 3009 up-to-date or was built completely. 3010 3011 This is where we try to release as many target node infos 3012 as possible for clean builds and update runs, in order 3013 to minimize the overall memory consumption. 3014 3015 We'd like to remove a lot more attributes like self.sources 3016 and self.sources_set, but they might get used 3017 in a next build step. For example, during configuration 3018 the source files for a built E{*}.o file are used to figure out 3019 which linker to use for the resulting Program (gcc vs. g++)! 3020 That's why we check for the 'keep_targetinfo' attribute, 3021 config Nodes and the Interactive mode just don't allow 3022 an early release of most variables. 3023 3024 In the same manner, we can't simply remove the self.attributes 3025 here. The smart linking relies on the shared flag, and some 3026 parts of the java Tool use it to transport information 3027 about nodes... 3028 3029 @see: built() and Node.release_target_info() 3030 """ 3031 if (self.released_target_info or SCons.Node.interactive): 3032 return 3033 3034 if not hasattr(self.attributes, 'keep_targetinfo'): 3035 # Cache some required values, before releasing 3036 # stuff like env, executor and builder... 3037 self.changed(allowcache=True) 3038 self.get_contents_sig() 3039 self.get_build_env() 3040 # Now purge unneeded stuff to free memory... 3041 self.executor = None 3042 self._memo.pop('rfile', None) 3043 self.prerequisites = None 3044 # Cleanup lists, but only if they're empty 3045 if not len(self.ignore_set): 3046 self.ignore_set = None 3047 if not len(self.implicit_set): 3048 self.implicit_set = None 3049 if not len(self.depends_set): 3050 self.depends_set = None 3051 if not len(self.ignore): 3052 self.ignore = None 3053 if not len(self.depends): 3054 self.depends = None 3055 # Mark this node as done, we only have to release 3056 # the memory once... 3057 self.released_target_info = True
3058
3059 - def find_src_builder(self):
3060 if self.rexists(): 3061 return None 3062 scb = self.dir.src_builder() 3063 if scb is _null: 3064 scb = None 3065 if scb is not None: 3066 try: 3067 b = self.builder 3068 except AttributeError: 3069 b = None 3070 if b is None: 3071 self.builder_set(scb) 3072 return scb
3073
3074 - def has_src_builder(self):
3075 """Return whether this Node has a source builder or not. 3076 3077 If this Node doesn't have an explicit source code builder, this 3078 is where we figure out, on the fly, if there's a transparent 3079 source code builder for it. 3080 3081 Note that if we found a source builder, we also set the 3082 self.builder attribute, so that all of the methods that actually 3083 *build* this file don't have to do anything different. 3084 """ 3085 try: 3086 scb = self.sbuilder 3087 except AttributeError: 3088 scb = self.sbuilder = self.find_src_builder() 3089 return scb is not None
3090
3091 - def alter_targets(self):
3092 """Return any corresponding targets in a variant directory. 3093 """ 3094 if self.is_derived(): 3095 return [], None 3096 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
3097
3098 - def _rmv_existing(self):
3099 self.clear_memoized_values() 3100 if SCons.Node.print_duplicate: 3101 print("dup: removing existing target {}".format(self)) 3102 e = Unlink(self, [], None) 3103 if isinstance(e, SCons.Errors.BuildError): 3104 raise e
3105 3106 # 3107 # Taskmaster interface subsystem 3108 # 3109
3110 - def make_ready(self):
3111 self.has_src_builder() 3112 self.get_binfo()
3113
3114 - def prepare(self):
3115 """Prepare for this file to be created.""" 3116 SCons.Node.Node.prepare(self) 3117 3118 if self.get_state() != SCons.Node.up_to_date: 3119 if self.exists(): 3120 if self.is_derived() and not self.precious: 3121 self._rmv_existing() 3122 else: 3123 try: 3124 self._createDir() 3125 except SCons.Errors.StopError as drive: 3126 raise SCons.Errors.StopError("No drive `{}' for target `{}'.".format(drive, self))
3127 3128 # 3129 # 3130 # 3131
3132 - def remove(self):
3133 """Remove this file.""" 3134 if self.exists() or self.islink(): 3135 self.fs.unlink(self.get_internal_path()) 3136 return 1 3137 return None
3138
3139 - def do_duplicate(self, src):
3140 self._createDir() 3141 if SCons.Node.print_duplicate: 3142 print("dup: relinking variant '{}' from '{}'".format(self, src)) 3143 Unlink(self, None, None) 3144 e = Link(self, src, None) 3145 if isinstance(e, SCons.Errors.BuildError): 3146 raise SCons.Errors.StopError("Cannot duplicate `{}' in `{}': {}.".format(src.get_internal_path(), self.dir._path, e.errstr)) 3147 self.linked = 1 3148 # The Link() action may or may not have actually 3149 # created the file, depending on whether the -n 3150 # option was used or not. Delete the _exists and 3151 # _rexists attributes so they can be reevaluated. 3152 self.clear()
3153 3154 @SCons.Memoize.CountMethodCall
3155 - def exists(self):
3156 try: 3157 return self._memo['exists'] 3158 except KeyError: 3159 pass 3160 result = SCons.Node._exists_map[self._func_exists](self) 3161 self._memo['exists'] = result 3162 return result
3163 3164 # 3165 # SIGNATURE SUBSYSTEM 3166 # 3167
3168 - def get_max_drift_csig(self):
3169 """ 3170 Returns the content signature currently stored for this node 3171 if it's been unmodified longer than the max_drift value, or the 3172 max_drift value is 0. Returns None otherwise. 3173 """ 3174 old = self.get_stored_info() 3175 mtime = self.get_timestamp() 3176 3177 max_drift = self.fs.max_drift 3178 if max_drift > 0: 3179 if (time.time() - mtime) > max_drift: 3180 try: 3181 n = old.ninfo 3182 if n.timestamp and n.csig and n.timestamp == mtime: 3183 return n.csig 3184 except AttributeError: 3185 pass 3186 elif max_drift == 0: 3187 try: 3188 return old.ninfo.csig 3189 except AttributeError: 3190 pass 3191 3192 return None
3193
3194 - def get_csig(self):
3195 """ 3196 Generate a node's content signature, the digested signature 3197 of its content. 3198 3199 node - the node 3200 cache - alternate node to use for the signature cache 3201 returns - the content signature 3202 """ 3203 ninfo = self.get_ninfo() 3204 try: 3205 return ninfo.csig 3206 except AttributeError: 3207 pass 3208 3209 csig = self.get_max_drift_csig() 3210 if csig is None: 3211 3212 try: 3213 if self.get_size() < SCons.Node.FS.File.md5_chunksize: 3214 contents = self.get_contents() 3215 else: 3216 csig = self.get_content_hash() 3217 except IOError: 3218 # This can happen if there's actually a directory on-disk, 3219 # which can be the case if they've disabled disk checks, 3220 # or if an action with a File target actually happens to 3221 # create a same-named directory by mistake. 3222 csig = '' 3223 else: 3224 if not csig: 3225 csig = SCons.Util.MD5signature(contents) 3226 3227 ninfo.csig = csig 3228 3229 return csig
3230 3231 # 3232 # DECISION SUBSYSTEM 3233 # 3234
3235 - def builder_set(self, builder):
3238
3239 - def built(self):
3240 """Called just after this File node is successfully built. 3241 3242 Just like for 'release_target_info' we try to release 3243 some more target node attributes in order to minimize the 3244 overall memory consumption. 3245 3246 @see: release_target_info 3247 """ 3248 3249 SCons.Node.Node.built(self) 3250 3251 if (not SCons.Node.interactive and 3252 not hasattr(self.attributes, 'keep_targetinfo')): 3253 # Ensure that the build infos get computed and cached... 3254 SCons.Node.store_info_map[self.store_info](self) 3255 # ... then release some more variables. 3256 self._specific_sources = False 3257 self._labspath = None 3258 self._save_str() 3259 self.cwd = None 3260 3261 self.scanner_paths = None
3262
3263 - def changed(self, node=None, allowcache=False):
3264 """ 3265 Returns if the node is up-to-date with respect to the BuildInfo 3266 stored last time it was built. 3267 3268 For File nodes this is basically a wrapper around Node.changed(), 3269 but we allow the return value to get cached after the reference 3270 to the Executor got released in release_target_info(). 3271 3272 @see: Node.changed() 3273 """ 3274 if node is None: 3275 try: 3276 return self._memo['changed'] 3277 except KeyError: 3278 pass 3279 3280 has_changed = SCons.Node.Node.changed(self, node) 3281 if allowcache: 3282 self._memo['changed'] = has_changed 3283 return has_changed
3284
3285 - def changed_content(self, target, prev_ni, repo_node=None):
3286 cur_csig = self.get_csig() 3287 try: 3288 return cur_csig != prev_ni.csig 3289 except AttributeError: 3290 return 1
3291
3292 - def changed_state(self, target, prev_ni, repo_node=None):
3293 return self.state != SCons.Node.up_to_date
3294 3295 3296 # Caching node -> string mapping for the below method 3297 __dmap_cache = {} 3298 __dmap_sig_cache = {} 3299 3300
3301 - def _build_dependency_map(self, binfo):
3302 """ 3303 Build mapping from file -> signature 3304 3305 Args: 3306 self - self 3307 binfo - buildinfo from node being considered 3308 3309 Returns: 3310 dictionary of file->signature mappings 3311 """ 3312 3313 # For an "empty" binfo properties like bsources 3314 # do not exist: check this to avoid exception. 3315 if (len(binfo.bsourcesigs) + len(binfo.bdependsigs) + \ 3316 len(binfo.bimplicitsigs)) == 0: 3317 return {} 3318 3319 binfo.dependency_map = { child:signature for child, signature in zip(chain(binfo.bsources, binfo.bdepends, binfo.bimplicit), 3320 chain(binfo.bsourcesigs, binfo.bdependsigs, binfo.bimplicitsigs))} 3321 3322 return binfo.dependency_map
3323 3324 # @profile
3325 - def _add_strings_to_dependency_map(self, dmap):
3326 """ 3327 In the case comparing node objects isn't sufficient, we'll add the strings for the nodes to the dependency map 3328 :return: 3329 """ 3330 3331 first_string = str(next(iter(dmap))) 3332 3333 # print("DMAP:%s"%id(dmap)) 3334 if first_string not in dmap: 3335 string_dict = {str(child): signature for child, signature in dmap.items()} 3336 dmap.update(string_dict) 3337 return dmap
3338
3339 - def _get_previous_signatures(self, dmap):
3340 """ 3341 Return a list of corresponding csigs from previous 3342 build in order of the node/files in children. 3343 3344 Args: 3345 self - self 3346 dmap - Dictionary of file -> csig 3347 3348 Returns: 3349 List of csigs for provided list of children 3350 """ 3351 prev = [] 3352 # MD5_TIMESTAMP_DEBUG = False 3353 3354 if len(dmap) == 0: 3355 if MD5_TIMESTAMP_DEBUG: print("Nothing dmap shortcutting") 3356 return None 3357 elif MD5_TIMESTAMP_DEBUG: print("len(dmap):%d"%len(dmap)) 3358 3359 3360 # First try retrieving via Node 3361 if MD5_TIMESTAMP_DEBUG: print("Checking if self is in map:%s id:%s type:%s"%(str(self), id(self), type(self))) 3362 df = dmap.get(self, False) 3363 if df: 3364 return df 3365 3366 # Now check if self's repository file is in map. 3367 rf = self.rfile() 3368 if MD5_TIMESTAMP_DEBUG: print("Checking if self.rfile is in map:%s id:%s type:%s"%(str(rf), id(rf), type(rf))) 3369 rfm = dmap.get(rf, False) 3370 if rfm: 3371 return rfm 3372 3373 # get default string for node and then also string swapping os.altsep for os.sep (/ for \) 3374 c_strs = [str(self)] 3375 3376 if os.altsep: 3377 c_strs.append(c_strs[0].replace(os.sep, os.altsep)) 3378 3379 # In some cases the dependency_maps' keys are already strings check. 3380 # Check if either string is now in dmap. 3381 for s in c_strs: 3382 if MD5_TIMESTAMP_DEBUG: print("Checking if str(self) is in map :%s" % s) 3383 df = dmap.get(s, False) 3384 if df: 3385 return df 3386 3387 # Strings don't exist in map, add them and try again 3388 # If there are no strings in this dmap, then add them. 3389 # This may not be necessary, we could walk the nodes in the dmap and check each string 3390 # rather than adding ALL the strings to dmap. In theory that would be n/2 vs 2n str() calls on node 3391 # if not dmap.has_strings: 3392 dmap = self._add_strings_to_dependency_map(dmap) 3393 3394 # In some cases the dependency_maps' keys are already strings check. 3395 # Check if either string is now in dmap. 3396 for s in c_strs: 3397 if MD5_TIMESTAMP_DEBUG: print("Checking if str(self) is in map (now with strings) :%s" % s) 3398 df = dmap.get(s, False) 3399 if df: 3400 return df 3401 3402 # Lastly use nodes get_path() to generate string and see if that's in dmap 3403 if not df: 3404 try: 3405 # this should yield a path which matches what's in the sconsign 3406 c_str = self.get_path() 3407 if os.altsep: 3408 c_str = c_str.replace(os.sep, os.altsep) 3409 3410 if MD5_TIMESTAMP_DEBUG: print("Checking if self.get_path is in map (now with strings) :%s" % s) 3411 3412 df = dmap.get(c_str, None) 3413 3414 except AttributeError as e: 3415 raise FileBuildInfoFileToCsigMappingError("No mapping from file name to content signature for :%s"%c_str) 3416 3417 return df
3418
3419 - def changed_timestamp_then_content(self, target, prev_ni, node=None):
3420 """ 3421 Used when decider for file is Timestamp-MD5 3422 3423 NOTE: If the timestamp hasn't changed this will skip md5'ing the 3424 file and just copy the prev_ni provided. If the prev_ni 3425 is wrong. It will propagate it. 3426 See: https://github.com/SCons/scons/issues/2980 3427 3428 Args: 3429 self - dependency 3430 target - target 3431 prev_ni - The NodeInfo object loaded from previous builds .sconsign 3432 node - Node instance. Check this node for file existence/timestamp 3433 if specified. 3434 3435 Returns: 3436 Boolean - Indicates if node(File) has changed. 3437 """ 3438 3439 # Now get sconsign name -> csig map and then get proper prev_ni if possible 3440 bi = node.get_stored_info().binfo 3441 rebuilt = False 3442 try: 3443 dependency_map = bi.dependency_map 3444 except AttributeError as e: 3445 dependency_map = self._build_dependency_map(bi) 3446 rebuilt = True 3447 3448 if len(dependency_map) == 0: 3449 # If there's no dependency map, there's no need to find the 3450 # prev_ni as there aren't any 3451 # shortcut the rest of the logic 3452 if MD5_TIMESTAMP_DEBUG: print("Skipping checks len(dmap)=0") 3453 3454 # We still need to get the current file's csig 3455 # This should be slightly faster than calling self.changed_content(target, new_prev_ni) 3456 self.get_csig() 3457 return True 3458 3459 new_prev_ni = self._get_previous_signatures(dependency_map) 3460 new = self.changed_timestamp_match(target, new_prev_ni) 3461 3462 if MD5_TIMESTAMP_DEBUG: 3463 old = self.changed_timestamp_match(target, prev_ni) 3464 3465 if old != new: 3466 print("Mismatch self.changed_timestamp_match(%s, prev_ni) old:%s new:%s"%(str(target), old, new)) 3467 new_prev_ni = self._get_previous_signatures(dependency_map) 3468 3469 if not new: 3470 try: 3471 # NOTE: We're modifying the current node's csig in a query. 3472 self.get_ninfo().csig = new_prev_ni.csig 3473 except AttributeError: 3474 pass 3475 return False 3476 return self.changed_content(target, new_prev_ni)
3477
3478 - def changed_timestamp_newer(self, target, prev_ni, repo_node=None):
3479 try: 3480 return self.get_timestamp() > target.get_timestamp() 3481 except AttributeError: 3482 return 1
3483
3484 - def changed_timestamp_match(self, target, prev_ni, repo_node=None):
3485 """ 3486 Return True if the timestamps don't match or if there is no previous timestamp 3487 :param target: 3488 :param prev_ni: Information about the node from the previous build 3489 :return: 3490 """ 3491 try: 3492 return self.get_timestamp() != prev_ni.timestamp 3493 except AttributeError: 3494 return 1
3495
3496 - def is_up_to_date(self):
3497 """Check for whether the Node is current 3498 In all cases self is the target we're checking to see if it's up to date 3499 """ 3500 3501 T = 0 3502 if T: Trace('is_up_to_date(%s):' % self) 3503 if not self.exists(): 3504 if T: Trace(' not self.exists():') 3505 # The file (always a target) doesn't exist locally... 3506 r = self.rfile() 3507 if r != self: 3508 # ...but there is one (always a target) in a Repository... 3509 if not self.changed(r): 3510 if T: Trace(' changed(%s):' % r) 3511 # ...and it's even up-to-date... 3512 if self._local: 3513 # ...and they'd like a local copy. 3514 e = LocalCopy(self, r, None) 3515 if isinstance(e, SCons.Errors.BuildError): 3516 # Likely this should be re-raising exception e 3517 # (which would be BuildError) 3518 raise e 3519 SCons.Node.store_info_map[self.store_info](self) 3520 if T: Trace(' 1\n') 3521 return 1 3522 self.changed() 3523 if T: Trace(' None\n') 3524 return None 3525 else: 3526 r = self.changed() 3527 if T: Trace(' self.exists(): %s\n' % r) 3528 return not r
3529 3530 @SCons.Memoize.CountMethodCall
3531 - def rfile(self):
3532 try: 3533 return self._memo['rfile'] 3534 except KeyError: 3535 pass 3536 result = self 3537 if not self.exists(): 3538 norm_name = _my_normcase(self.name) 3539 for repo_dir in self.dir.get_all_rdirs(): 3540 try: 3541 node = repo_dir.entries[norm_name] 3542 except KeyError: 3543 node = repo_dir.file_on_disk(self.name) 3544 3545 if node and node.exists() and \ 3546 (isinstance(node, File) or isinstance(node, Entry) 3547 or not node.is_derived()): 3548 result = node 3549 # Copy over our local attributes to the repository 3550 # Node so we identify shared object files in the 3551 # repository and don't assume they're static. 3552 # 3553 # This isn't perfect; the attribute would ideally 3554 # be attached to the object in the repository in 3555 # case it was built statically in the repository 3556 # and we changed it to shared locally, but that's 3557 # rarely the case and would only occur if you 3558 # intentionally used the same suffix for both 3559 # shared and static objects anyway. So this 3560 # should work well in practice. 3561 result.attributes = self.attributes 3562 break 3563 self._memo['rfile'] = result 3564 return result
3565
3566 - def find_repo_file(self):
3567 """ 3568 For this node, find if there exists a corresponding file in one or more repositories 3569 :return: list of corresponding files in repositories 3570 """ 3571 retvals = [] 3572 3573 norm_name = _my_normcase(self.name) 3574 for repo_dir in self.dir.get_all_rdirs(): 3575 try: 3576 node = repo_dir.entries[norm_name] 3577 except KeyError: 3578 node = repo_dir.file_on_disk(self.name) 3579 3580 if node and node.exists() and \ 3581 (isinstance(node, File) or isinstance(node, Entry) \ 3582 or not node.is_derived()): 3583 retvals.append(node) 3584 3585 return retvals
3586 3587
3588 - def rstr(self):
3589 return str(self.rfile())
3590
3591 - def get_cachedir_csig(self):
3592 """ 3593 Fetch a Node's content signature for purposes of computing 3594 another Node's cachesig. 3595 3596 This is a wrapper around the normal get_csig() method that handles 3597 the somewhat obscure case of using CacheDir with the -n option. 3598 Any files that don't exist would normally be "built" by fetching 3599 them from the cache, but the normal get_csig() method will try 3600 to open up the local file, which doesn't exist because the -n 3601 option meant we didn't actually pull the file from cachedir. 3602 But since the file *does* actually exist in the cachedir, we 3603 can use its contents for the csig. 3604 """ 3605 try: 3606 return self.cachedir_csig 3607 except AttributeError: 3608 pass 3609 3610 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self) 3611 if not self.exists() and cachefile and os.path.exists(cachefile): 3612 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \ 3613 SCons.Node.FS.File.md5_chunksize * 1024) 3614 else: 3615 self.cachedir_csig = self.get_csig() 3616 return self.cachedir_csig
3617
3618 - def get_contents_sig(self):
3619 """ 3620 A helper method for get_cachedir_bsig. 3621 3622 It computes and returns the signature for this 3623 node's contents. 3624 """ 3625 3626 try: 3627 return self.contentsig 3628 except AttributeError: 3629 pass 3630 3631 executor = self.get_executor() 3632 3633 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents()) 3634 return result
3635
3636 - def get_cachedir_bsig(self):
3637 """ 3638 Return the signature for a cached file, including 3639 its children. 3640 3641 It adds the path of the cached file to the cache signature, 3642 because multiple targets built by the same action will all 3643 have the same build signature, and we have to differentiate 3644 them somehow. 3645 3646 Signature should normally be string of hex digits. 3647 """ 3648 try: 3649 return self.cachesig 3650 except AttributeError: 3651 pass 3652 3653 # Collect signatures for all children 3654 children = self.children() 3655 sigs = [n.get_cachedir_csig() for n in children] 3656 3657 # Append this node's signature... 3658 sigs.append(self.get_contents_sig()) 3659 3660 # ...and it's path 3661 sigs.append(self.get_internal_path()) 3662 3663 # Merge this all into a single signature 3664 result = self.cachesig = SCons.Util.MD5collect(sigs) 3665 return result
3666 3667 default_fs = None
3668 3669 -def get_default_fs():
3670 global default_fs 3671 if not default_fs: 3672 default_fs = FS() 3673 return default_fs
3674
3675 -class FileFinder(object):
3676 """ 3677 """ 3678
3679 - def __init__(self):
3680 self._memo = {}
3681
3682 - def filedir_lookup(self, p, fd=None):
3683 """ 3684 A helper method for find_file() that looks up a directory for 3685 a file we're trying to find. This only creates the Dir Node if 3686 it exists on-disk, since if the directory doesn't exist we know 3687 we won't find any files in it... :-) 3688 3689 It would be more compact to just use this as a nested function 3690 with a default keyword argument (see the commented-out version 3691 below), but that doesn't work unless you have nested scopes, 3692 so we define it here just so this work under Python 1.5.2. 3693 """ 3694 if fd is None: 3695 fd = self.default_filedir 3696 dir, name = os.path.split(fd) 3697 drive, d = _my_splitdrive(dir) 3698 if not name and d[:1] in ('/', OS_SEP): 3699 #return p.fs.get_root(drive).dir_on_disk(name) 3700 return p.fs.get_root(drive) 3701 if dir: 3702 p = self.filedir_lookup(p, dir) 3703 if not p: 3704 return None 3705 norm_name = _my_normcase(name) 3706 try: 3707 node = p.entries[norm_name] 3708 except KeyError: 3709 return p.dir_on_disk(name) 3710 if isinstance(node, Dir): 3711 return node 3712 if isinstance(node, Entry): 3713 node.must_be_same(Dir) 3714 return node 3715 return None
3716
3717 - def _find_file_key(self, filename, paths, verbose=None):
3718 return (filename, paths)
3719 3720 @SCons.Memoize.CountDictCall(_find_file_key)
3721 - def find_file(self, filename, paths, verbose=None):
3722 """ 3723 Find a node corresponding to either a derived file or a file that exists already. 3724 3725 Only the first file found is returned, and none is returned if no file is found. 3726 3727 filename: A filename to find 3728 paths: A list of directory path *nodes* to search in. Can be represented as a list, a tuple, or a callable that is called with no arguments and returns the list or tuple. 3729 3730 returns The node created from the found file. 3731 3732 """ 3733 memo_key = self._find_file_key(filename, paths) 3734 try: 3735 memo_dict = self._memo['find_file'] 3736 except KeyError: 3737 memo_dict = {} 3738 self._memo['find_file'] = memo_dict 3739 else: 3740 try: 3741 return memo_dict[memo_key] 3742 except KeyError: 3743 pass 3744 3745 if verbose and not callable(verbose): 3746 if not SCons.Util.is_String(verbose): 3747 verbose = "find_file" 3748 _verbose = u' %s: ' % verbose 3749 verbose = lambda s: sys.stdout.write(_verbose + s) 3750 3751 filedir, filename = os.path.split(filename) 3752 if filedir: 3753 self.default_filedir = filedir 3754 paths = [_f for _f in map(self.filedir_lookup, paths) if _f] 3755 3756 result = None 3757 for dir in paths: 3758 if verbose: 3759 verbose("looking for '%s' in '%s' ...\n" % (filename, dir)) 3760 node, d = dir.srcdir_find_file(filename) 3761 if node: 3762 if verbose: 3763 verbose("... FOUND '%s' in '%s'\n" % (filename, d)) 3764 result = node 3765 break 3766 3767 memo_dict[memo_key] = result 3768 3769 return result
3770 3771 find_file = FileFinder().find_file
3772 3773 3774 -def invalidate_node_memos(targets):
3775 """ 3776 Invalidate the memoized values of all Nodes (files or directories) 3777 that are associated with the given entries. Has been added to 3778 clear the cache of nodes affected by a direct execution of an 3779 action (e.g. Delete/Copy/Chmod). Existing Node caches become 3780 inconsistent if the action is run through Execute(). The argument 3781 `targets` can be a single Node object or filename, or a sequence 3782 of Nodes/filenames. 3783 """ 3784 from traceback import extract_stack 3785 3786 # First check if the cache really needs to be flushed. Only 3787 # actions run in the SConscript with Execute() seem to be 3788 # affected. XXX The way to check if Execute() is in the stacktrace 3789 # is a very dirty hack and should be replaced by a more sensible 3790 # solution. 3791 for f in extract_stack(): 3792 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py': 3793 break 3794 else: 3795 # Dont have to invalidate, so return 3796 return 3797 3798 if not SCons.Util.is_List(targets): 3799 targets = [targets] 3800 3801 for entry in targets: 3802 # If the target is a Node object, clear the cache. If it is a 3803 # filename, look up potentially existing Node object first. 3804 try: 3805 entry.clear_memoized_values() 3806 except AttributeError: 3807 # Not a Node object, try to look up Node by filename. XXX 3808 # This creates Node objects even for those filenames which 3809 # do not correspond to an existing Node object. 3810 node = get_default_fs().Entry(entry) 3811 if node: 3812 node.clear_memoized_values()
3813 3814 # Local Variables: 3815 # tab-width:4 3816 # indent-tabs-mode:nil 3817 # End: 3818 # vim: set expandtab tabstop=4 shiftwidth=4: 3819