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