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