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 72ae09dc35ac2626f8ff711d8c4b30b6138e08e3 2019-08-08 14:50:06 bdeegan" 
  37   
  38  import fnmatch 
  39  import os 
  40  import re 
  41  import shutil 
  42  import stat 
  43  import sys 
  44  import time 
  45  import codecs 
  46  from itertools import chain 
  47   
  48  import SCons.Action 
  49  import SCons.Debug 
  50  from SCons.Debug import logInstanceCreation 
  51  import SCons.Errors 
  52  import SCons.Memoize 
  53  import SCons.Node 
  54  import SCons.Node.Alias 
  55  import SCons.Subst 
  56  import SCons.Util 
  57  import SCons.Warnings 
  58   
  59  from SCons.Debug import Trace 
  60   
  61  print_duplicate = 0 
  62   
  63  MD5_TIMESTAMP_DEBUG = False 
64 65 66 -def sconsign_none(node):
67 raise NotImplementedError
68
69 -def sconsign_dir(node):
70 """Return the .sconsign file info for this directory, 71 creating it first if necessary.""" 72 if not node._sconsign: 73 import SCons.SConsign 74 node._sconsign = SCons.SConsign.ForDirectory(node) 75 return node._sconsign
76 77 _sconsign_map = {0 : sconsign_none, 78 1 : sconsign_dir}
79 80 -class FileBuildInfoFileToCsigMappingError(Exception):
81 pass
82
83 -class EntryProxyAttributeError(AttributeError):
84 """ 85 An AttributeError subclass for recording and displaying the name 86 of the underlying Entry involved in an AttributeError exception. 87 """
88 - def __init__(self, entry_proxy, attribute):
89 AttributeError.__init__(self) 90 self.entry_proxy = entry_proxy 91 self.attribute = attribute
92 - def __str__(self):
93 entry = self.entry_proxy.get() 94 fmt = "%s instance %s has no attribute %s" 95 return fmt % (entry.__class__.__name__, 96 repr(entry.name), 97 repr(self.attribute))
98 99 # The max_drift value: by default, use a cached signature value for 100 # any file that's been untouched for more than two days. 101 default_max_drift = 2*24*60*60 102 103 # 104 # We stringify these file system Nodes a lot. Turning a file system Node 105 # into a string is non-trivial, because the final string representation 106 # can depend on a lot of factors: whether it's a derived target or not, 107 # whether it's linked to a repository or source directory, and whether 108 # there's duplication going on. The normal technique for optimizing 109 # calculations like this is to memoize (cache) the string value, so you 110 # only have to do the calculation once. 111 # 112 # A number of the above factors, however, can be set after we've already 113 # been asked to return a string for a Node, because a Repository() or 114 # VariantDir() call or the like may not occur until later in SConscript 115 # files. So this variable controls whether we bother trying to save 116 # string values for Nodes. The wrapper interface can set this whenever 117 # they're done mucking with Repository and VariantDir and the other stuff, 118 # to let this module know it can start returning saved string values 119 # for Nodes. 120 # 121 Save_Strings = None
122 123 -def save_strings(val):
124 global Save_Strings 125 Save_Strings = val
126 127 # 128 # Avoid unnecessary function calls by recording a Boolean value that 129 # tells us whether or not os.path.splitdrive() actually does anything 130 # on this system, and therefore whether we need to bother calling it 131 # when looking up path names in various methods below. 132 # 133 134 do_splitdrive = None 135 _my_splitdrive =None
136 137 -def initialize_do_splitdrive():
138 global do_splitdrive 139 global has_unc 140 drive, path = os.path.splitdrive('X:/foo') 141 # splitunc is removed from python 3.7 and newer 142 # so we can also just test if splitdrive works with UNC 143 has_unc = (hasattr(os.path, 'splitunc') 144 or os.path.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive') 145 146 do_splitdrive = not not drive or has_unc 147 148 global _my_splitdrive 149 if has_unc: 150 def splitdrive(p): 151 if p[1:2] == ':': 152 return p[:2], p[2:] 153 if p[0:2] == '//': 154 # Note that we leave a leading slash in the path 155 # because UNC paths are always absolute. 156 return '//', p[1:] 157 return '', p
158 else: 159 def splitdrive(p): 160 if p[1:2] == ':': 161 return p[:2], p[2:] 162 return '', p 163 _my_splitdrive = splitdrive 164 165 # Keep some commonly used values in global variables to skip to 166 # module look-up costs. 167 global OS_SEP 168 global UNC_PREFIX 169 global os_sep_is_slash 170 171 OS_SEP = os.sep 172 UNC_PREFIX = OS_SEP + OS_SEP 173 os_sep_is_slash = OS_SEP == '/' 174 175 initialize_do_splitdrive() 176 177 # Used to avoid invoking os.path.normpath if not necessary. 178 needs_normpath_check = re.compile( 179 r''' 180 # We need to renormalize the path if it contains any consecutive 181 # '/' characters. 182 .*// | 183 184 # We need to renormalize the path if it contains a '..' directory. 185 # Note that we check for all the following cases: 186 # 187 # a) The path is a single '..' 188 # b) The path starts with '..'. E.g. '../' or '../moredirs' 189 # but we not match '..abc/'. 190 # c) The path ends with '..'. E.g. '/..' or 'dirs/..' 191 # d) The path contains a '..' in the middle. 192 # E.g. dirs/../moredirs 193 194 (.*/)?\.\.(?:/|$) | 195 196 # We need to renormalize the path if it contains a '.' 197 # directory, but NOT if it is a single '.' '/' characters. We 198 # do not want to match a single '.' because this case is checked 199 # for explicitly since this is common enough case. 200 # 201 # Note that we check for all the following cases: 202 # 203 # a) We don't match a single '.' 204 # b) We match if the path starts with '.'. E.g. './' or 205 # './moredirs' but we not match '.abc/'. 206 # c) We match if the path ends with '.'. E.g. '/.' or 207 # 'dirs/.' 208 # d) We match if the path contains a '.' in the middle. 209 # E.g. dirs/./moredirs 210 211 \./|.*/\.(?:/|$) 212 213 ''', 214 re.VERBOSE 215 ) 216 needs_normpath_match = needs_normpath_check.match 217 218 # 219 # SCons.Action objects for interacting with the outside world. 220 # 221 # The Node.FS methods in this module should use these actions to 222 # create and/or remove files and directories; they should *not* use 223 # os.{link,symlink,unlink,mkdir}(), etc., directly. 224 # 225 # Using these SCons.Action objects ensures that descriptions of these 226 # external activities are properly displayed, that the displays are 227 # suppressed when the -s (silent) option is used, and (most importantly) 228 # the actions are disabled when the the -n option is used, in which case 229 # there should be *no* changes to the external file system(s)... 230 # 231 232 # For Now disable hard & softlinks for win32 233 # PY3 supports them, but the rest of SCons is not ready for this 234 # in some cases user permissions may be required. 235 # TODO: See if theres a reasonable way to enable using links on win32/64 236 237 if hasattr(os, 'link') and sys.platform != 'win32': 250 else: 251 _hardlink_func = None 252 253 if hasattr(os, 'symlink') and sys.platform != 'win32': 256 else: 257 _softlink_func = None
258 259 -def _copy_func(fs, src, dest):
260 shutil.copy2(src, dest) 261 st = fs.stat(src) 262 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
263 264 265 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy', 266 'hard-copy', 'soft-copy', 'copy'] 267 268 Link_Funcs = [] # contains the callables of the specified duplication style
269 270 -def set_duplicate(duplicate):
271 # Fill in the Link_Funcs list according to the argument 272 # (discarding those not available on the platform). 273 274 # Set up the dictionary that maps the argument names to the 275 # underlying implementations. We do this inside this function, 276 # not in the top-level module code, so that we can remap os.link 277 # and os.symlink for testing purposes. 278 link_dict = { 279 'hard' : _hardlink_func, 280 'soft' : _softlink_func, 281 'copy' : _copy_func 282 } 283 284 if duplicate not in Valid_Duplicates: 285 raise SCons.Errors.InternalError("The argument of set_duplicate " 286 "should be in Valid_Duplicates") 287 global Link_Funcs 288 Link_Funcs = [] 289 for func in duplicate.split('-'): 290 if link_dict[func]: 291 Link_Funcs.append(link_dict[func])
292
293 -def LinkFunc(target, source, env):
294 """ 295 Relative paths cause problems with symbolic links, so 296 we use absolute paths, which may be a problem for people 297 who want to move their soft-linked src-trees around. Those 298 people should use the 'hard-copy' mode, softlinks cannot be 299 used for that; at least I have no idea how ... 300 """ 301 src = source[0].get_abspath() 302 dest = target[0].get_abspath() 303 dir, file = os.path.split(dest) 304 if dir and not target[0].fs.isdir(dir): 305 os.makedirs(dir) 306 if not Link_Funcs: 307 # Set a default order of link functions. 308 set_duplicate('hard-soft-copy') 309 fs = source[0].fs 310 # Now link the files with the previously specified order. 311 for func in Link_Funcs: 312 try: 313 func(fs, src, dest) 314 break 315 except (IOError, OSError): 316 # An OSError indicates something happened like a permissions 317 # problem or an attempt to symlink across file-system 318 # boundaries. An IOError indicates something like the file 319 # not existing. In either case, keeping trying additional 320 # functions in the list and only raise an error if the last 321 # one failed. 322 if func == Link_Funcs[-1]: 323 # exception of the last link method (copy) are fatal 324 raise 325 return 0
326 327 Link = SCons.Action.Action(LinkFunc, None)
328 -def LocalString(target, source, env):
329 return 'Local copy of %s from %s' % (target[0], source[0])
330 331 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
332 333 -def UnlinkFunc(target, source, env):
334 t = target[0] 335 t.fs.unlink(t.get_abspath()) 336 return 0
337 338 Unlink = SCons.Action.Action(UnlinkFunc, None)
339 340 -def MkdirFunc(target, source, env):
341 t = target[0] 342 # This os.path.exists test looks redundant, but it's possible 343 # when using Install() to install multiple dirs outside the 344 # source tree to get a case where t.exists() is true but 345 # the path does already exist, so this prevents spurious 346 # build failures in that case. See test/Install/multi-dir. 347 if not t.exists() and not os.path.exists(t.get_abspath()): 348 t.fs.mkdir(t.get_abspath()) 349 return 0
350 351 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None) 352 353 MkdirBuilder = None
354 355 -def get_MkdirBuilder():
356 global MkdirBuilder 357 if MkdirBuilder is None: 358 import SCons.Builder 359 import SCons.Defaults 360 # "env" will get filled in by Executor.get_build_env() 361 # calling SCons.Defaults.DefaultEnvironment() when necessary. 362 MkdirBuilder = SCons.Builder.Builder(action = Mkdir, 363 env = None, 364 explain = None, 365 is_explicit = None, 366 target_scanner = SCons.Defaults.DirEntryScanner, 367 name = "MkdirBuilder") 368 return MkdirBuilder
369
370 -class _Null(object):
371 pass
372 373 _null = _Null() 374 375 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem. 376 _is_cygwin = sys.platform == "cygwin" 377 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
378 - def _my_normcase(x):
379 return x
380 else:
381 - def _my_normcase(x):
382 return x.upper()
383
384 385 386 -class DiskChecker(object):
387 - def __init__(self, type, do, ignore):
388 self.type = type 389 self.do = do 390 self.ignore = ignore 391 self.func = do
392 - def __call__(self, *args, **kw):
393 return self.func(*args, **kw)
394 - def set(self, list):
395 if self.type in list: 396 self.func = self.do 397 else: 398 self.func = self.ignore
399
400 -def do_diskcheck_match(node, predicate, errorfmt):
401 result = predicate() 402 try: 403 # If calling the predicate() cached a None value from stat(), 404 # remove it so it doesn't interfere with later attempts to 405 # build this Node as we walk the DAG. (This isn't a great way 406 # to do this, we're reaching into an interface that doesn't 407 # really belong to us, but it's all about performance, so 408 # for now we'll just document the dependency...) 409 if node._memo['stat'] is None: 410 del node._memo['stat'] 411 except (AttributeError, KeyError): 412 pass 413 if result: 414 raise TypeError(errorfmt % node.get_abspath())
415
416 -def ignore_diskcheck_match(node, predicate, errorfmt):
417 pass
418 419 420 421 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match) 422 423 diskcheckers = [ 424 diskcheck_match, 425 ]
426 427 -def set_diskcheck(list):
428 for dc in diskcheckers: 429 dc.set(list)
430
431 -def diskcheck_types():
432 return [dc.type for dc in diskcheckers]
433
434 435 436 -class EntryProxy(SCons.Util.Proxy):
437 438 __str__ = SCons.Util.Delegate('__str__') 439 440 # In PY3 if a class defines __eq__, then it must explicitly provide 441 # __hash__. Since SCons.Util.Proxy provides __eq__ we need the following 442 # see: https://docs.python.org/3.1/reference/datamodel.html#object.__hash__ 443 __hash__ = SCons.Util.Delegate('__hash__') 444
445 - def __get_abspath(self):
446 entry = self.get() 447 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(), 448 entry.name + "_abspath")
449
450 - def __get_filebase(self):
451 name = self.get().name 452 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0], 453 name + "_filebase")
454
455 - def __get_suffix(self):
456 name = self.get().name 457 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1], 458 name + "_suffix")
459
460 - def __get_file(self):
461 name = self.get().name 462 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
463
464 - def __get_base_path(self):
465 """Return the file's directory and file name, with the 466 suffix stripped.""" 467 entry = self.get() 468 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0], 469 entry.name + "_base")
470
471 - def __get_posix_path(self):
472 """Return the path with / as the path separator, 473 regardless of platform.""" 474 if os_sep_is_slash: 475 return self 476 else: 477 entry = self.get() 478 r = entry.get_path().replace(OS_SEP, '/') 479 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
480
481 - def __get_windows_path(self):
482 r"""Return the path with \ as the path separator, 483 regardless of platform.""" 484 if OS_SEP == '\\': 485 return self 486 else: 487 entry = self.get() 488 r = entry.get_path().replace(OS_SEP, '\\') 489 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
490
491 - def __get_srcnode(self):
492 return EntryProxy(self.get().srcnode())
493
494 - def __get_srcdir(self):
495 """Returns the directory containing the source node linked to this 496 node via VariantDir(), or the directory of this node if not linked.""" 497 return EntryProxy(self.get().srcnode().dir)
498
499 - def __get_rsrcnode(self):
500 return EntryProxy(self.get().srcnode().rfile())
501
502 - def __get_rsrcdir(self):
503 """Returns the directory containing the source node linked to this 504 node via VariantDir(), or the directory of this node if not linked.""" 505 return EntryProxy(self.get().srcnode().rfile().dir)
506
507 - def __get_dir(self):
508 return EntryProxy(self.get().dir)
509 510 dictSpecialAttrs = { "base" : __get_base_path, 511 "posix" : __get_posix_path, 512 "windows" : __get_windows_path, 513 "win32" : __get_windows_path, 514 "srcpath" : __get_srcnode, 515 "srcdir" : __get_srcdir, 516 "dir" : __get_dir, 517 "abspath" : __get_abspath, 518 "filebase" : __get_filebase, 519 "suffix" : __get_suffix, 520 "file" : __get_file, 521 "rsrcpath" : __get_rsrcnode, 522 "rsrcdir" : __get_rsrcdir, 523 } 524
525 - def __getattr__(self, name):
526 # This is how we implement the "special" attributes 527 # such as base, posix, srcdir, etc. 528 try: 529 attr_function = self.dictSpecialAttrs[name] 530 except KeyError: 531 try: 532 attr = SCons.Util.Proxy.__getattr__(self, name) 533 except AttributeError: 534 # Raise our own AttributeError subclass with an 535 # overridden __str__() method that identifies the 536 # name of the entry that caused the exception. 537 raise EntryProxyAttributeError(self, name) 538 return attr 539 else: 540 return attr_function(self)
541
542 543 -class Base(SCons.Node.Node):
544 """A generic class for file system entries. This class is for 545 when we don't know yet whether the entry being looked up is a file 546 or a directory. Instances of this class can morph into either 547 Dir or File objects by a later, more precise lookup. 548 549 Note: this class does not define __cmp__ and __hash__ for 550 efficiency reasons. SCons does a lot of comparing of 551 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be 552 as fast as possible, which means we want to use Python's built-in 553 object identity comparisons. 554 """ 555 556 __slots__ = ['name', 557 'fs', 558 '_abspath', 559 '_labspath', 560 '_path', 561 '_tpath', 562 '_path_elements', 563 'dir', 564 'cwd', 565 'duplicate', 566 '_local', 567 'sbuilder', 568 '_proxy', 569 '_func_sconsign'] 570
571 - def __init__(self, name, directory, fs):
572 """Initialize a generic Node.FS.Base object. 573 574 Call the superclass initialization, take care of setting up 575 our relative and absolute paths, identify our parent 576 directory, and indicate that this node should use 577 signatures.""" 578 579 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Base') 580 SCons.Node.Node.__init__(self) 581 582 # Filenames and paths are probably reused and are intern'ed to save some memory. 583 # Filename with extension as it was specified when the object was 584 # created; to obtain filesystem path, use Python str() function 585 self.name = SCons.Util.silent_intern(name) 586 self.fs = fs #: Reference to parent Node.FS object 587 588 assert directory, "A directory must be provided" 589 590 self._abspath = None 591 self._labspath = None 592 self._path = None 593 self._tpath = None 594 self._path_elements = None 595 596 self.dir = directory 597 self.cwd = None # will hold the SConscript directory for target nodes 598 self.duplicate = directory.duplicate 599 self.changed_since_last_build = 2 600 self._func_sconsign = 0 601 self._func_exists = 2 602 self._func_rexists = 2 603 self._func_get_contents = 0 604 self._func_target_from_source = 1 605 self.store_info = 1
606
607 - def str_for_display(self):
608 return '"' + self.__str__() + '"'
609
610 - def must_be_same(self, klass):
611 """ 612 This node, which already existed, is being looked up as the 613 specified klass. Raise an exception if it isn't. 614 """ 615 if isinstance(self, klass) or klass is Entry: 616 return 617 raise TypeError("Tried to lookup %s '%s' as a %s." %\ 618 (self.__class__.__name__, self.get_internal_path(), klass.__name__))
619
620 - def get_dir(self):
621 return self.dir
622
623 - def get_suffix(self):
624 return SCons.Util.splitext(self.name)[1]
625
626 - def rfile(self):
627 return self
628
629 - def __getattr__(self, attr):
630 """ Together with the node_bwcomp dict defined below, 631 this method provides a simple backward compatibility 632 layer for the Node attributes 'abspath', 'labspath', 633 'path', 'tpath', 'suffix' and 'path_elements'. These Node 634 attributes used to be directly available in v2.3 and earlier, but 635 have been replaced by getter methods that initialize the 636 single variables lazily when required, in order to save memory. 637 The redirection to the getters lets older Tools and 638 SConstruct continue to work without any additional changes, 639 fully transparent to the user. 640 Note, that __getattr__ is only called as fallback when the 641 requested attribute can't be found, so there should be no 642 speed performance penalty involved for standard builds. 643 """ 644 if attr in node_bwcomp: 645 return node_bwcomp[attr](self) 646 647 raise AttributeError("%r object has no attribute %r" % 648 (self.__class__, attr))
649
650 - def __str__(self):
651 """A Node.FS.Base object's string representation is its path 652 name.""" 653 global Save_Strings 654 if Save_Strings: 655 return self._save_str() 656 return self._get_str()
657
658 - def __lt__(self, other):
659 """ less than operator used by sorting on py3""" 660 return str(self) < str(other)
661 662 @SCons.Memoize.CountMethodCall
663 - def _save_str(self):
664 try: 665 return self._memo['_save_str'] 666 except KeyError: 667 pass 668 result = SCons.Util.silent_intern(self._get_str()) 669 self._memo['_save_str'] = result 670 return result
671
672 - def _get_str(self):
673 global Save_Strings 674 if self.duplicate or self.is_derived(): 675 return self.get_path() 676 srcnode = self.srcnode() 677 if srcnode.stat() is None and self.stat() is not None: 678 result = self.get_path() 679 else: 680 result = srcnode.get_path() 681 if not Save_Strings: 682 # We're not at the point where we're saving the string 683 # representations of FS Nodes (because we haven't finished 684 # reading the SConscript files and need to have str() return 685 # things relative to them). That also means we can't yet 686 # cache values returned (or not returned) by stat(), since 687 # Python code in the SConscript files might still create 688 # or otherwise affect the on-disk file. So get rid of the 689 # values that the underlying stat() method saved. 690 try: del self._memo['stat'] 691 except KeyError: pass 692 if self is not srcnode: 693 try: del srcnode._memo['stat'] 694 except KeyError: pass 695 return result
696 697 rstr = __str__ 698 699 @SCons.Memoize.CountMethodCall
700 - def stat(self):
701 try: 702 return self._memo['stat'] 703 except KeyError: 704 pass 705 try: 706 result = self.fs.stat(self.get_abspath()) 707 except os.error: 708 result = None 709 710 self._memo['stat'] = result 711 return result
712
713 - def exists(self):
714 return SCons.Node._exists_map[self._func_exists](self)
715
716 - def rexists(self):
717 return SCons.Node._rexists_map[self._func_rexists](self)
718
719 - def getmtime(self):
720 st = self.stat() 721 if st: 722 return st[stat.ST_MTIME] 723 else: 724 return None
725
726 - def getsize(self):
727 st = self.stat() 728 if st: 729 return st[stat.ST_SIZE] 730 else: 731 return None
732
733 - def isdir(self):
734 st = self.stat() 735 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
736
737 - def isfile(self):
738 st = self.stat() 739 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
740 741 if hasattr(os, 'symlink'): 746 else: 749
750 - def is_under(self, dir):
751 if self is dir: 752 return 1 753 else: 754 return self.dir.is_under(dir)
755
756 - def set_local(self):
757 self._local = 1
758
759 - def srcnode(self):
760 """If this node is in a build path, return the node 761 corresponding to its source file. Otherwise, return 762 ourself. 763 """ 764 srcdir_list = self.dir.srcdir_list() 765 if srcdir_list: 766 srcnode = srcdir_list[0].Entry(self.name) 767 srcnode.must_be_same(self.__class__) 768 return srcnode 769 return self
770
771 - def get_path(self, dir=None):
772 """Return path relative to the current working directory of the 773 Node.FS.Base object that owns us.""" 774 if not dir: 775 dir = self.fs.getcwd() 776 if self == dir: 777 return '.' 778 path_elems = self.get_path_elements() 779 pathname = '' 780 try: i = path_elems.index(dir) 781 except ValueError: 782 for p in path_elems[:-1]: 783 pathname += p.dirname 784 else: 785 for p in path_elems[i+1:-1]: 786 pathname += p.dirname 787 return pathname + path_elems[-1].name
788
789 - def set_src_builder(self, builder):
790 """Set the source code builder for this node.""" 791 self.sbuilder = builder 792 if not self.has_builder(): 793 self.builder_set(builder)
794
795 - def src_builder(self):
796 """Fetch the source code builder for this node. 797 798 If there isn't one, we cache the source code builder specified 799 for the directory (which in turn will cache the value from its 800 parent directory, and so on up to the file system root). 801 """ 802 try: 803 scb = self.sbuilder 804 except AttributeError: 805 scb = self.dir.src_builder() 806 self.sbuilder = scb 807 return scb
808
809 - def get_abspath(self):
810 """Get the absolute path of the file.""" 811 return self.dir.entry_abspath(self.name)
812
813 - def get_labspath(self):
814 """Get the absolute path of the file.""" 815 return self.dir.entry_labspath(self.name)
816
817 - def get_internal_path(self):
818 if self.dir._path == '.': 819 return self.name 820 else: 821 return self.dir.entry_path(self.name)
822
823 - def get_tpath(self):
824 if self.dir._tpath == '.': 825 return self.name 826 else: 827 return self.dir.entry_tpath(self.name)
828
829 - def get_path_elements(self):
830 return self.dir._path_elements + [self]
831
832 - def for_signature(self):
833 # Return just our name. Even an absolute path would not work, 834 # because that can change thanks to symlinks or remapped network 835 # paths. 836 return self.name
837
838 - def get_subst_proxy(self):
839 try: 840 return self._proxy 841 except AttributeError: 842 ret = EntryProxy(self) 843 self._proxy = ret 844 return ret
845
846 - def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
847 """ 848 849 Generates a target entry that corresponds to this entry (usually 850 a source file) with the specified prefix and suffix. 851 852 Note that this method can be overridden dynamically for generated 853 files that need different behavior. See Tool/swig.py for 854 an example. 855 """ 856 return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext)
857
858 - def _Rfindalldirs_key(self, pathlist):
859 return pathlist
860 861 @SCons.Memoize.CountDictCall(_Rfindalldirs_key)
862 - def Rfindalldirs(self, pathlist):
863 """ 864 Return all of the directories for a given path list, including 865 corresponding "backing" directories in any repositories. 866 867 The Node lookups are relative to this Node (typically a 868 directory), so memoizing result saves cycles from looking 869 up the same path for each target in a given directory. 870 """ 871 try: 872 memo_dict = self._memo['Rfindalldirs'] 873 except KeyError: 874 memo_dict = {} 875 self._memo['Rfindalldirs'] = memo_dict 876 else: 877 try: 878 return memo_dict[pathlist] 879 except KeyError: 880 pass 881 882 create_dir_relative_to_self = self.Dir 883 result = [] 884 for path in pathlist: 885 if isinstance(path, SCons.Node.Node): 886 result.append(path) 887 else: 888 dir = create_dir_relative_to_self(path) 889 result.extend(dir.get_all_rdirs()) 890 891 memo_dict[pathlist] = result 892 893 return result
894
895 - def RDirs(self, pathlist):
896 """Search for a list of directories in the Repository list.""" 897 cwd = self.cwd or self.fs._cwd 898 return cwd.Rfindalldirs(pathlist)
899 900 @SCons.Memoize.CountMethodCall
901 - def rentry(self):
902 try: 903 return self._memo['rentry'] 904 except KeyError: 905 pass 906 result = self 907 if not self.exists(): 908 norm_name = _my_normcase(self.name) 909 for dir in self.dir.get_all_rdirs(): 910 try: 911 node = dir.entries[norm_name] 912 except KeyError: 913 if dir.entry_exists_on_disk(self.name): 914 result = dir.Entry(self.name) 915 break 916 self._memo['rentry'] = result 917 return result
918
919 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
920 return []
921 922 # Dict that provides a simple backward compatibility 923 # layer for the Node attributes 'abspath', 'labspath', 924 # 'path', 'tpath' and 'path_elements'. 925 # @see Base.__getattr__ above 926 node_bwcomp = {'abspath' : Base.get_abspath, 927 'labspath' : Base.get_labspath, 928 'path' : Base.get_internal_path, 929 'tpath' : Base.get_tpath, 930 'path_elements' : Base.get_path_elements, 931 'suffix' : Base.get_suffix}
932 933 -class Entry(Base):
934 """This is the class for generic Node.FS entries--that is, things 935 that could be a File or a Dir, but we're just not sure yet. 936 Consequently, the methods in this class really exist just to 937 transform their associated object into the right class when the 938 time comes, and then call the same-named method in the transformed 939 class.""" 940 941 __slots__ = ['scanner_paths', 942 'cachedir_csig', 943 'cachesig', 944 'repositories', 945 'srcdir', 946 'entries', 947 'searched', 948 '_sconsign', 949 'variant_dirs', 950 'root', 951 'dirname', 952 'on_disk_entries', 953 'released_target_info', 954 'contentsig'] 955
956 - def __init__(self, name, directory, fs):
957 Base.__init__(self, name, directory, fs) 958 self._func_exists = 3 959 self._func_get_contents = 1
960
961 - def diskcheck_match(self):
962 pass
963
964 - def disambiguate(self, must_exist=None):
965 """ 966 """ 967 if self.isdir(): 968 self.__class__ = Dir 969 self._morph() 970 elif self.isfile(): 971 self.__class__ = File 972 self._morph() 973 self.clear() 974 else: 975 # There was nothing on-disk at this location, so look in 976 # the src directory. 977 # 978 # We can't just use self.srcnode() straight away because 979 # that would create an actual Node for this file in the src 980 # directory, and there might not be one. Instead, use the 981 # dir_on_disk() method to see if there's something on-disk 982 # with that name, in which case we can go ahead and call 983 # self.srcnode() to create the right type of entry. 984 srcdir = self.dir.srcnode() 985 if srcdir != self.dir and \ 986 srcdir.entry_exists_on_disk(self.name) and \ 987 self.srcnode().isdir(): 988 self.__class__ = Dir 989 self._morph() 990 elif must_exist: 991 msg = "No such file or directory: '%s'" % self.get_abspath() 992 raise SCons.Errors.UserError(msg) 993 else: 994 self.__class__ = File 995 self._morph() 996 self.clear() 997 return self
998
999 - def rfile(self):
1000 """We're a generic Entry, but the caller is actually looking for 1001 a File at this point, so morph into one.""" 1002 self.__class__ = File 1003 self._morph() 1004 self.clear() 1005 return File.rfile(self)
1006
1007 - def scanner_key(self):
1008 return self.get_suffix()
1009
1010 - def get_contents(self):
1011 """Fetch the contents of the entry. Returns the exact binary 1012 contents of the file.""" 1013 return SCons.Node._get_contents_map[self._func_get_contents](self)
1014
1015 - def get_text_contents(self):
1016 """Fetch the decoded text contents of a Unicode encoded Entry. 1017 1018 Since this should return the text contents from the file 1019 system, we check to see into what sort of subclass we should 1020 morph this Entry.""" 1021 try: 1022 self = self.disambiguate(must_exist=1) 1023 except SCons.Errors.UserError: 1024 # There was nothing on disk with which to disambiguate 1025 # this entry. Leave it as an Entry, but return a null 1026 # string so calls to get_text_contents() in emitters and 1027 # the like (e.g. in qt.py) don't have to disambiguate by 1028 # hand or catch the exception. 1029 return '' 1030 else: 1031 return self.get_text_contents()
1032
1033 - def must_be_same(self, klass):
1034 """Called to make sure a Node is a Dir. Since we're an 1035 Entry, we can morph into one.""" 1036 if self.__class__ is not klass: 1037 self.__class__ = klass 1038 self._morph() 1039 self.clear()
1040 1041 # The following methods can get called before the Taskmaster has 1042 # had a chance to call disambiguate() directly to see if this Entry 1043 # should really be a Dir or a File. We therefore use these to call 1044 # disambiguate() transparently (from our caller's point of view). 1045 # 1046 # Right now, this minimal set of methods has been derived by just 1047 # looking at some of the methods that will obviously be called early 1048 # in any of the various Taskmasters' calling sequences, and then 1049 # empirically figuring out which additional methods are necessary 1050 # to make various tests pass. 1051
1052 - def exists(self):
1053 return SCons.Node._exists_map[self._func_exists](self)
1054
1055 - def rel_path(self, other):
1056 d = self.disambiguate() 1057 if d.__class__ is Entry: 1058 raise Exception("rel_path() could not disambiguate File/Dir") 1059 return d.rel_path(other)
1060
1061 - def new_ninfo(self):
1062 return self.disambiguate().new_ninfo()
1063
1064 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1065 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
1066
1067 - def get_subst_proxy(self):
1068 return self.disambiguate().get_subst_proxy()
1069 1070 # This is for later so we can differentiate between Entry the class and Entry 1071 # the method of the FS class. 1072 _classEntry = Entry
1073 1074 1075 -class LocalFS(object):
1076 """ 1077 This class implements an abstraction layer for operations involving 1078 a local file system. Essentially, this wraps any function in 1079 the os, os.path or shutil modules that we use to actually go do 1080 anything with or to the local file system. 1081 1082 Note that there's a very good chance we'll refactor this part of 1083 the architecture in some way as we really implement the interface(s) 1084 for remote file system Nodes. For example, the right architecture 1085 might be to have this be a subclass instead of a base class. 1086 Nevertheless, we're using this as a first step in that direction. 1087 1088 We're not using chdir() yet because the calling subclass method 1089 needs to use os.chdir() directly to avoid recursion. Will we 1090 really need this one? 1091 """ 1092 #def chdir(self, path): 1093 # return os.chdir(path)
1094 - def chmod(self, path, mode):
1095 return os.chmod(path, mode)
1096 - def copy(self, src, dst):
1097 return shutil.copy(src, dst)
1098 - def copy2(self, src, dst):
1099 return shutil.copy2(src, dst)
1100 - def exists(self, path):
1101 return os.path.exists(path)
1102 - def getmtime(self, path):
1103 return os.path.getmtime(path)
1104 - def getsize(self, path):
1105 return os.path.getsize(path)
1106 - def isdir(self, path):
1107 return os.path.isdir(path)
1108 - def isfile(self, path):
1109 return os.path.isfile(path)
1112 - def lstat(self, path):
1113 return os.lstat(path)
1114 - def listdir(self, path):
1115 return os.listdir(path)
1116 - def makedirs(self, path):
1117 return os.makedirs(path)
1118 - def mkdir(self, path):
1119 return os.mkdir(path)
1120 - def rename(self, old, new):
1121 return os.rename(old, new)
1122 - def stat(self, path):
1123 return os.stat(path)
1126 - def open(self, path):
1127 return open(path)
1130 1131 if hasattr(os, 'symlink'): 1134 else: 1137 1138 if hasattr(os, 'readlink'): 1141 else:
1144
1145 1146 -class FS(LocalFS):
1147
1148 - def __init__(self, path = None):
1149 """Initialize the Node.FS subsystem. 1150 1151 The supplied path is the top of the source tree, where we 1152 expect to find the top-level build file. If no path is 1153 supplied, the current directory is the default. 1154 1155 The path argument must be a valid absolute path. 1156 """ 1157 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS') 1158 1159 self._memo = {} 1160 1161 self.Root = {} 1162 self.SConstruct_dir = None 1163 self.max_drift = default_max_drift 1164 1165 self.Top = None 1166 if path is None: 1167 self.pathTop = os.getcwd() 1168 else: 1169 self.pathTop = path 1170 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0]) 1171 1172 self.Top = self.Dir(self.pathTop) 1173 self.Top._path = '.' 1174 self.Top._tpath = '.' 1175 self._cwd = self.Top 1176 1177 DirNodeInfo.fs = self 1178 FileNodeInfo.fs = self
1179
1180 - def set_SConstruct_dir(self, dir):
1181 self.SConstruct_dir = dir
1182
1183 - def get_max_drift(self):
1184 return self.max_drift
1185
1186 - def set_max_drift(self, max_drift):
1187 self.max_drift = max_drift
1188
1189 - def getcwd(self):
1190 if hasattr(self, "_cwd"): 1191 return self._cwd 1192 else: 1193 return "<no cwd>"
1194
1195 - def chdir(self, dir, change_os_dir=0):
1196 """Change the current working directory for lookups. 1197 If change_os_dir is true, we will also change the "real" cwd 1198 to match. 1199 """ 1200 curr=self._cwd 1201 try: 1202 if dir is not None: 1203 self._cwd = dir 1204 if change_os_dir: 1205 os.chdir(dir.get_abspath()) 1206 except OSError: 1207 self._cwd = curr 1208 raise
1209
1210 - def get_root(self, drive):
1211 """ 1212 Returns the root directory for the specified drive, creating 1213 it if necessary. 1214 """ 1215 drive = _my_normcase(drive) 1216 try: 1217 return self.Root[drive] 1218 except KeyError: 1219 root = RootDir(drive, self) 1220 self.Root[drive] = root 1221 if not drive: 1222 self.Root[self.defaultDrive] = root 1223 elif drive == self.defaultDrive: 1224 self.Root[''] = root 1225 return root
1226
1227 - def _lookup(self, p, directory, fsclass, create=1):
1228 """ 1229 The generic entry point for Node lookup with user-supplied data. 1230 1231 This translates arbitrary input into a canonical Node.FS object 1232 of the specified fsclass. The general approach for strings is 1233 to turn it into a fully normalized absolute path and then call 1234 the root directory's lookup_abs() method for the heavy lifting. 1235 1236 If the path name begins with '#', it is unconditionally 1237 interpreted relative to the top-level directory of this FS. '#' 1238 is treated as a synonym for the top-level SConstruct directory, 1239 much like '~' is treated as a synonym for the user's home 1240 directory in a UNIX shell. So both '#foo' and '#/foo' refer 1241 to the 'foo' subdirectory underneath the top-level SConstruct 1242 directory. 1243 1244 If the path name is relative, then the path is looked up relative 1245 to the specified directory, or the current directory (self._cwd, 1246 typically the SConscript directory) if the specified directory 1247 is None. 1248 """ 1249 if isinstance(p, Base): 1250 # It's already a Node.FS object. Make sure it's the right 1251 # class and return. 1252 p.must_be_same(fsclass) 1253 return p 1254 # str(p) in case it's something like a proxy object 1255 p = str(p) 1256 1257 if not os_sep_is_slash: 1258 p = p.replace(OS_SEP, '/') 1259 1260 if p[0:1] == '#': 1261 # There was an initial '#', so we strip it and override 1262 # whatever directory they may have specified with the 1263 # top-level SConstruct directory. 1264 p = p[1:] 1265 directory = self.Top 1266 1267 # There might be a drive letter following the 1268 # '#'. Although it is not described in the SCons man page, 1269 # the regression test suite explicitly tests for that 1270 # syntax. It seems to mean the following thing: 1271 # 1272 # Assuming the the SCons top dir is in C:/xxx/yyy, 1273 # '#X:/toto' means X:/xxx/yyy/toto. 1274 # 1275 # i.e. it assumes that the X: drive has a directory 1276 # structure similar to the one found on drive C:. 1277 if do_splitdrive: 1278 drive, p = _my_splitdrive(p) 1279 if drive: 1280 root = self.get_root(drive) 1281 else: 1282 root = directory.root 1283 else: 1284 root = directory.root 1285 1286 # We can only strip trailing after splitting the drive 1287 # since the drive might the UNC '//' prefix. 1288 p = p.strip('/') 1289 1290 needs_normpath = needs_normpath_match(p) 1291 1292 # The path is relative to the top-level SCons directory. 1293 if p in ('', '.'): 1294 p = directory.get_labspath() 1295 else: 1296 p = directory.get_labspath() + '/' + p 1297 else: 1298 if do_splitdrive: 1299 drive, p = _my_splitdrive(p) 1300 if drive and not p: 1301 # This causes a naked drive letter to be treated 1302 # as a synonym for the root directory on that 1303 # drive. 1304 p = '/' 1305 else: 1306 drive = '' 1307 1308 # We can only strip trailing '/' since the drive might the 1309 # UNC '//' prefix. 1310 if p != '/': 1311 p = p.rstrip('/') 1312 1313 needs_normpath = needs_normpath_match(p) 1314 1315 if p[0:1] == '/': 1316 # Absolute path 1317 root = self.get_root(drive) 1318 else: 1319 # This is a relative lookup or to the current directory 1320 # (the path name is not absolute). Add the string to the 1321 # appropriate directory lookup path, after which the whole 1322 # thing gets normalized. 1323 if directory: 1324 if not isinstance(directory, Dir): 1325 directory = self.Dir(directory) 1326 else: 1327 directory = self._cwd 1328 1329 if p in ('', '.'): 1330 p = directory.get_labspath() 1331 else: 1332 p = directory.get_labspath() + '/' + p 1333 1334 if drive: 1335 root = self.get_root(drive) 1336 else: 1337 root = directory.root 1338 1339 if needs_normpath is not None: 1340 # Normalize a pathname. Will return the same result for 1341 # equivalent paths. 1342 # 1343 # We take advantage of the fact that we have an absolute 1344 # path here for sure. In addition, we know that the 1345 # components of lookup path are separated by slashes at 1346 # this point. Because of this, this code is about 2X 1347 # faster than calling os.path.normpath() followed by 1348 # replacing os.sep with '/' again. 1349 ins = p.split('/')[1:] 1350 outs = [] 1351 for d in ins: 1352 if d == '..': 1353 try: 1354 outs.pop() 1355 except IndexError: 1356 pass 1357 elif d not in ('', '.'): 1358 outs.append(d) 1359 p = '/' + '/'.join(outs) 1360 1361 return root._lookup_abs(p, fsclass, create)
1362
1363 - def Entry(self, name, directory = None, create = 1):
1364 """Look up or create a generic Entry node with the specified name. 1365 If the name is a relative path (begins with ./, ../, or a file 1366 name), then it is looked up relative to the supplied directory 1367 node, or to the top level directory of the FS (supplied at 1368 construction time) if no directory is supplied. 1369 """ 1370 return self._lookup(name, directory, Entry, create)
1371
1372 - def File(self, name, directory = None, create = 1):
1373 """Look up or create a File node with the specified name. If 1374 the name is a relative path (begins with ./, ../, or a file name), 1375 then it is looked up relative to the supplied directory node, 1376 or to the top level directory of the FS (supplied at construction 1377 time) if no directory is supplied. 1378 1379 This method will raise TypeError if a directory is found at the 1380 specified path. 1381 """ 1382 return self._lookup(name, directory, File, create)
1383
1384 - def Dir(self, name, directory = None, create = True):
1385 """Look up or create a Dir node with the specified name. If 1386 the name is a relative path (begins with ./, ../, or a file name), 1387 then it is looked up relative to the supplied directory node, 1388 or to the top level directory of the FS (supplied at construction 1389 time) if no directory is supplied. 1390 1391 This method will raise TypeError if a normal file is found at the 1392 specified path. 1393 """ 1394 return self._lookup(name, directory, Dir, create)
1395
1396 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1397 """Link the supplied variant directory to the source directory 1398 for purposes of building files.""" 1399 1400 if not isinstance(src_dir, SCons.Node.Node): 1401 src_dir = self.Dir(src_dir) 1402 if not isinstance(variant_dir, SCons.Node.Node): 1403 variant_dir = self.Dir(variant_dir) 1404 if src_dir.is_under(variant_dir): 1405 raise SCons.Errors.UserError("Source directory cannot be under variant directory.") 1406 if variant_dir.srcdir: 1407 if variant_dir.srcdir == src_dir: 1408 return # We already did this. 1409 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)) 1410 variant_dir.link(src_dir, duplicate)
1411
1412 - def Repository(self, *dirs):
1413 """Specify Repository directories to search.""" 1414 for d in dirs: 1415 if not isinstance(d, SCons.Node.Node): 1416 d = self.Dir(d) 1417 self.Top.addRepository(d)
1418
1419 - def PyPackageDir(self, modulename):
1420 r"""Locate the directory of a given python module name 1421 1422 For example scons might resolve to 1423 Windows: C:\Python27\Lib\site-packages\scons-2.5.1 1424 Linux: /usr/lib/scons 1425 1426 This can be useful when we want to determine a toolpath based on a python module name""" 1427 1428 dirpath = '' 1429 if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] in (0,1,2,3,4)): 1430 # Python2 Code 1431 import imp 1432 splitname = modulename.split('.') 1433 srchpths = sys.path 1434 for item in splitname: 1435 file, path, desc = imp.find_module(item, srchpths) 1436 if file is not None: 1437 path = os.path.dirname(path) 1438 srchpths = [path] 1439 dirpath = path 1440 else: 1441 # Python3 Code 1442 import importlib.util 1443 modspec = importlib.util.find_spec(modulename) 1444 dirpath = os.path.dirname(modspec.origin) 1445 return self._lookup(dirpath, None, Dir, True)
1446 1447
1448 - def variant_dir_target_climb(self, orig, dir, tail):
1449 """Create targets in corresponding variant directories 1450 1451 Climb the directory tree, and look up path names 1452 relative to any linked variant directories we find. 1453 1454 Even though this loops and walks up the tree, we don't memoize 1455 the return value because this is really only used to process 1456 the command-line targets. 1457 """ 1458 targets = [] 1459 message = None 1460 fmt = "building associated VariantDir targets: %s" 1461 start_dir = dir 1462 while dir: 1463 for bd in dir.variant_dirs: 1464 if start_dir.is_under(bd): 1465 # If already in the build-dir location, don't reflect 1466 return [orig], fmt % str(orig) 1467 p = os.path.join(bd._path, *tail) 1468 targets.append(self.Entry(p)) 1469 tail = [dir.name] + tail 1470 dir = dir.up() 1471 if targets: 1472 message = fmt % ' '.join(map(str, targets)) 1473 return targets, message
1474
1475 - def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None):
1476 """ 1477 Globs 1478 1479 This is mainly a shim layer 1480 """ 1481 if cwd is None: 1482 cwd = self.getcwd() 1483 return cwd.glob(pathname, ondisk, source, strings, exclude)
1484
1485 -class DirNodeInfo(SCons.Node.NodeInfoBase):
1486 __slots__ = () 1487 # This should get reset by the FS initialization. 1488 current_version_id = 2 1489 1490 fs = None 1491
1492 - def str_to_node(self, s):
1493 top = self.fs.Top 1494 root = top.root 1495 if do_splitdrive: 1496 drive, s = _my_splitdrive(s) 1497 if drive: 1498 root = self.fs.get_root(drive) 1499 if not os.path.isabs(s): 1500 s = top.get_labspath() + '/' + s 1501 return root._lookup_abs(s, Entry)
1502
1503 -class DirBuildInfo(SCons.Node.BuildInfoBase):
1504 __slots__ = () 1505 current_version_id = 2
1506 1507 glob_magic_check = re.compile('[*?[]')
1508 1509 -def has_glob_magic(s):
1510 return glob_magic_check.search(s) is not None
1511
1512 -class Dir(Base):
1513 """A class for directories in a file system. 1514 """ 1515 1516 __slots__ = ['scanner_paths', 1517 'cachedir_csig', 1518 'cachesig', 1519 'repositories', 1520 'srcdir', 1521 'entries', 1522 'searched', 1523 '_sconsign', 1524 'variant_dirs', 1525 'root', 1526 'dirname', 1527 'on_disk_entries', 1528 'released_target_info', 1529 'contentsig'] 1530 1531 NodeInfo = DirNodeInfo 1532 BuildInfo = DirBuildInfo 1533
1534 - def __init__(self, name, directory, fs):
1535 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Dir') 1536 Base.__init__(self, name, directory, fs) 1537 self._morph()
1538
1539 - def _morph(self):
1540 """Turn a file system Node (either a freshly initialized directory 1541 object or a separate Entry object) into a proper directory object. 1542 1543 Set up this directory's entries and hook it into the file 1544 system tree. Specify that directories (this Node) don't use 1545 signatures for calculating whether they're current. 1546 """ 1547 1548 self.repositories = [] 1549 self.srcdir = None 1550 1551 self.entries = {} 1552 self.entries['.'] = self 1553 self.entries['..'] = self.dir 1554 self.cwd = self 1555 self.searched = 0 1556 self._sconsign = None 1557 self.variant_dirs = [] 1558 self.root = self.dir.root 1559 self.changed_since_last_build = 3 1560 self._func_sconsign = 1 1561 self._func_exists = 2 1562 self._func_get_contents = 2 1563 1564 self._abspath = SCons.Util.silent_intern(self.dir.entry_abspath(self.name)) 1565 self._labspath = SCons.Util.silent_intern(self.dir.entry_labspath(self.name)) 1566 if self.dir._path == '.': 1567 self._path = SCons.Util.silent_intern(self.name) 1568 else: 1569 self._path = SCons.Util.silent_intern(self.dir.entry_path(self.name)) 1570 if self.dir._tpath == '.': 1571 self._tpath = SCons.Util.silent_intern(self.name) 1572 else: 1573 self._tpath = SCons.Util.silent_intern(self.dir.entry_tpath(self.name)) 1574 self._path_elements = self.dir._path_elements + [self] 1575 1576 # For directories, we make a difference between the directory 1577 # 'name' and the directory 'dirname'. The 'name' attribute is 1578 # used when we need to print the 'name' of the directory or 1579 # when we it is used as the last part of a path. The 'dirname' 1580 # is used when the directory is not the last element of the 1581 # path. The main reason for making that distinction is that 1582 # for RoorDir's the dirname can not be easily inferred from 1583 # the name. For example, we have to add a '/' after a drive 1584 # letter but not after a UNC path prefix ('//'). 1585 self.dirname = self.name + OS_SEP 1586 1587 # Don't just reset the executor, replace its action list, 1588 # because it might have some pre-or post-actions that need to 1589 # be preserved. 1590 # 1591 # But don't reset the executor if there is a non-null executor 1592 # attached already. The existing executor might have other 1593 # targets, in which case replacing the action list with a 1594 # Mkdir action is a big mistake. 1595 if not hasattr(self, 'executor'): 1596 self.builder = get_MkdirBuilder() 1597 self.get_executor().set_action_list(self.builder.action) 1598 else: 1599 # Prepend MkdirBuilder action to existing action list 1600 l = self.get_executor().action_list 1601 a = get_MkdirBuilder().action 1602 l.insert(0, a) 1603 self.get_executor().set_action_list(l)
1604
1605 - def diskcheck_match(self):
1606 diskcheck_match(self, self.isfile, 1607 "File %s found where directory expected.")
1608
1609 - def __clearRepositoryCache(self, duplicate=None):
1610 """Called when we change the repository(ies) for a directory. 1611 This clears any cached information that is invalidated by changing 1612 the repository.""" 1613 1614 for node in list(self.entries.values()): 1615 if node != self.dir: 1616 if node != self and isinstance(node, Dir): 1617 node.__clearRepositoryCache(duplicate) 1618 else: 1619 node.clear() 1620 try: 1621 del node._srcreps 1622 except AttributeError: 1623 pass 1624 if duplicate is not None: 1625 node.duplicate=duplicate
1626
1627 - def __resetDuplicate(self, node):
1628 if node != self: 1629 node.duplicate = node.get_dir().duplicate
1630
1631 - def Entry(self, name):
1632 """ 1633 Looks up or creates an entry node named 'name' relative to 1634 this directory. 1635 """ 1636 return self.fs.Entry(name, self)
1637
1638 - def Dir(self, name, create=True):
1639 """ 1640 Looks up or creates a directory node named 'name' relative to 1641 this directory. 1642 """ 1643 return self.fs.Dir(name, self, create)
1644
1645 - def File(self, name):
1646 """ 1647 Looks up or creates a file node named 'name' relative to 1648 this directory. 1649 """ 1650 return self.fs.File(name, self)
1651 1659
1660 - def getRepositories(self):
1661 """Returns a list of repositories for this directory. 1662 """ 1663 if self.srcdir and not self.duplicate: 1664 return self.srcdir.get_all_rdirs() + self.repositories 1665 return self.repositories
1666 1667 @SCons.Memoize.CountMethodCall
1668 - def get_all_rdirs(self):
1669 try: 1670 return list(self._memo['get_all_rdirs']) 1671 except KeyError: 1672 pass 1673 1674 result = [self] 1675 fname = '.' 1676 dir = self 1677 while dir: 1678 for rep in dir.getRepositories(): 1679 result.append(rep.Dir(fname)) 1680 if fname == '.': 1681 fname = dir.name 1682 else: 1683 fname = dir.name + OS_SEP + fname 1684 dir = dir.up() 1685 1686 self._memo['get_all_rdirs'] = list(result) 1687 1688 return result
1689
1690 - def addRepository(self, dir):
1691 if dir != self and dir not in self.repositories: 1692 self.repositories.append(dir) 1693 dir._tpath = '.' 1694 self.__clearRepositoryCache()
1695
1696 - def up(self):
1697 return self.dir
1698
1699 - def _rel_path_key(self, other):
1700 return str(other)
1701 1702 @SCons.Memoize.CountDictCall(_rel_path_key)
1703 - def rel_path(self, other):
1704 """Return a path to "other" relative to this directory. 1705 """ 1706 1707 # This complicated and expensive method, which constructs relative 1708 # paths between arbitrary Node.FS objects, is no longer used 1709 # by SCons itself. It was introduced to store dependency paths 1710 # in .sconsign files relative to the target, but that ended up 1711 # being significantly inefficient. 1712 # 1713 # We're continuing to support the method because some SConstruct 1714 # files out there started using it when it was available, and 1715 # we're all about backwards compatibility.. 1716 1717 try: 1718 memo_dict = self._memo['rel_path'] 1719 except KeyError: 1720 memo_dict = {} 1721 self._memo['rel_path'] = memo_dict 1722 else: 1723 try: 1724 return memo_dict[other] 1725 except KeyError: 1726 pass 1727 1728 if self is other: 1729 result = '.' 1730 1731 elif other not in self._path_elements: 1732 try: 1733 other_dir = other.get_dir() 1734 except AttributeError: 1735 result = str(other) 1736 else: 1737 if other_dir is None: 1738 result = other.name 1739 else: 1740 dir_rel_path = self.rel_path(other_dir) 1741 if dir_rel_path == '.': 1742 result = other.name 1743 else: 1744 result = dir_rel_path + OS_SEP + other.name 1745 else: 1746 i = self._path_elements.index(other) + 1 1747 1748 path_elems = ['..'] * (len(self._path_elements) - i) \ 1749 + [n.name for n in other._path_elements[i:]] 1750 1751 result = OS_SEP.join(path_elems) 1752 1753 memo_dict[other] = result 1754 1755 return result
1756
1757 - def get_env_scanner(self, env, kw={}):
1758 import SCons.Defaults 1759 return SCons.Defaults.DirEntryScanner
1760
1761 - def get_target_scanner(self):
1762 import SCons.Defaults 1763 return SCons.Defaults.DirEntryScanner
1764
1765 - def get_found_includes(self, env, scanner, path):
1766 """Return this directory's implicit dependencies. 1767 1768 We don't bother caching the results because the scan typically 1769 shouldn't be requested more than once (as opposed to scanning 1770 .h file contents, which can be requested as many times as the 1771 files is #included by other files). 1772 """ 1773 if not scanner: 1774 return [] 1775 # Clear cached info for this Dir. If we already visited this 1776 # directory on our walk down the tree (because we didn't know at 1777 # that point it was being used as the source for another Node) 1778 # then we may have calculated build signature before realizing 1779 # we had to scan the disk. Now that we have to, though, we need 1780 # to invalidate the old calculated signature so that any node 1781 # dependent on our directory structure gets one that includes 1782 # info about everything on disk. 1783 self.clear() 1784 return scanner(self, env, path)
1785 1786 # 1787 # Taskmaster interface subsystem 1788 # 1789
1790 - def prepare(self):
1791 pass
1792
1793 - def build(self, **kw):
1794 """A null "builder" for directories.""" 1795 global MkdirBuilder 1796 if self.builder is not MkdirBuilder: 1797 SCons.Node.Node.build(self, **kw)
1798 1799 # 1800 # 1801 # 1802
1803 - def _create(self):
1804 """Create this directory, silently and without worrying about 1805 whether the builder is the default or not.""" 1806 listDirs = [] 1807 parent = self 1808 while parent: 1809 if parent.exists(): 1810 break 1811 listDirs.append(parent) 1812 p = parent.up() 1813 if p is None: 1814 # Don't use while: - else: for this condition because 1815 # if so, then parent is None and has no .path attribute. 1816 raise SCons.Errors.StopError(parent._path) 1817 parent = p 1818 listDirs.reverse() 1819 for dirnode in listDirs: 1820 try: 1821 # Don't call dirnode.build(), call the base Node method 1822 # directly because we definitely *must* create this 1823 # directory. The dirnode.build() method will suppress 1824 # the build if it's the default builder. 1825 SCons.Node.Node.build(dirnode) 1826 dirnode.get_executor().nullify() 1827 # The build() action may or may not have actually 1828 # created the directory, depending on whether the -n 1829 # option was used or not. Delete the _exists and 1830 # _rexists attributes so they can be reevaluated. 1831 dirnode.clear() 1832 except OSError: 1833 pass
1834
1836 global MkdirBuilder 1837 return self.builder is not MkdirBuilder and self.has_builder()
1838
1839 - def alter_targets(self):
1840 """Return any corresponding targets in a variant directory. 1841 """ 1842 return self.fs.variant_dir_target_climb(self, self, [])
1843
1844 - def scanner_key(self):
1845 """A directory does not get scanned.""" 1846 return None
1847
1848 - def get_text_contents(self):
1849 """We already emit things in text, so just return the binary 1850 version.""" 1851 return self.get_contents()
1852
1853 - def get_contents(self):
1854 """Return content signatures and names of all our children 1855 separated by new-lines. Ensure that the nodes are sorted.""" 1856 return SCons.Node._get_contents_map[self._func_get_contents](self)
1857
1858 - def get_csig(self):
1859 """Compute the content signature for Directory nodes. In 1860 general, this is not needed and the content signature is not 1861 stored in the DirNodeInfo. However, if get_contents on a Dir 1862 node is called which has a child directory, the child 1863 directory should return the hash of its contents.""" 1864 contents = self.get_contents() 1865 return SCons.Util.MD5signature(contents)
1866
1867 - def do_duplicate(self, src):
1868 pass
1869
1870 - def is_up_to_date(self):
1871 """If any child is not up-to-date, then this directory isn't, 1872 either.""" 1873 if self.builder is not MkdirBuilder and not self.exists(): 1874 return 0 1875 up_to_date = SCons.Node.up_to_date 1876 for kid in self.children(): 1877 if kid.get_state() > up_to_date: 1878 return 0 1879 return 1
1880
1881 - def rdir(self):
1882 if not self.exists(): 1883 norm_name = _my_normcase(self.name) 1884 for dir in self.dir.get_all_rdirs(): 1885 try: node = dir.entries[norm_name] 1886 except KeyError: node = dir.dir_on_disk(self.name) 1887 if node and node.exists() and \ 1888 (isinstance(dir, Dir) or isinstance(dir, Entry)): 1889 return node 1890 return self
1891
1892 - def sconsign(self):
1893 """Return the .sconsign file info for this directory. """ 1894 return _sconsign_map[self._func_sconsign](self)
1895
1896 - def srcnode(self):
1897 """Dir has a special need for srcnode()...if we 1898 have a srcdir attribute set, then that *is* our srcnode.""" 1899 if self.srcdir: 1900 return self.srcdir 1901 return Base.srcnode(self)
1902
1903 - def get_timestamp(self):
1904 """Return the latest timestamp from among our children""" 1905 stamp = 0 1906 for kid in self.children(): 1907 if kid.get_timestamp() > stamp: 1908 stamp = kid.get_timestamp() 1909 return stamp
1910
1911 - def get_abspath(self):
1912 """Get the absolute path of the file.""" 1913 return self._abspath
1914
1915 - def get_labspath(self):
1916 """Get the absolute path of the file.""" 1917 return self._labspath
1918
1919 - def get_internal_path(self):
1920 return self._path
1921
1922 - def get_tpath(self):
1923 return self._tpath
1924
1925 - def get_path_elements(self):
1926 return self._path_elements
1927
1928 - def entry_abspath(self, name):
1929 return self._abspath + OS_SEP + name
1930
1931 - def entry_labspath(self, name):
1932 return self._labspath + '/' + name
1933
1934 - def entry_path(self, name):
1935 return self._path + OS_SEP + name
1936
1937 - def entry_tpath(self, name):
1938 return self._tpath + OS_SEP + name
1939
1940 - def entry_exists_on_disk(self, name):
1941 """ Searches through the file/dir entries of the current 1942 directory, and returns True if a physical entry with the given 1943 name could be found. 1944 1945 @see rentry_exists_on_disk 1946 """ 1947 try: 1948 d = self.on_disk_entries 1949 except AttributeError: 1950 d = {} 1951 try: 1952 entries = os.listdir(self._abspath) 1953 except OSError: 1954 pass 1955 else: 1956 for entry in map(_my_normcase, entries): 1957 d[entry] = True 1958 self.on_disk_entries = d 1959 if sys.platform == 'win32' or sys.platform == 'cygwin': 1960 name = _my_normcase(name) 1961 result = d.get(name) 1962 if result is None: 1963 # Belt-and-suspenders for Windows: check directly for 1964 # 8.3 file names that don't show up in os.listdir(). 1965 result = os.path.exists(self._abspath + OS_SEP + name) 1966 d[name] = result 1967 return result 1968 else: 1969 return name in d
1970
1971 - def rentry_exists_on_disk(self, name):
1972 """ Searches through the file/dir entries of the current 1973 *and* all its remote directories (repos), and returns 1974 True if a physical entry with the given name could be found. 1975 The local directory (self) gets searched first, so 1976 repositories take a lower precedence regarding the 1977 searching order. 1978 1979 @see entry_exists_on_disk 1980 """ 1981 1982 rentry_exists = self.entry_exists_on_disk(name) 1983 if not rentry_exists: 1984 # Search through the repository folders 1985 norm_name = _my_normcase(name) 1986 for rdir in self.get_all_rdirs(): 1987 try: 1988 node = rdir.entries[norm_name] 1989 if node: 1990 rentry_exists = True 1991 break 1992 except KeyError: 1993 if rdir.entry_exists_on_disk(name): 1994 rentry_exists = True 1995 break 1996 return rentry_exists
1997 1998 @SCons.Memoize.CountMethodCall
1999 - def srcdir_list(self):
2000 try: 2001 return self._memo['srcdir_list'] 2002 except KeyError: 2003 pass 2004 2005 result = [] 2006 2007 dirname = '.' 2008 dir = self 2009 while dir: 2010 if dir.srcdir: 2011 result.append(dir.srcdir.Dir(dirname)) 2012 dirname = dir.name + OS_SEP + dirname 2013 dir = dir.up() 2014 2015 self._memo['srcdir_list'] = result 2016 2017 return result
2018
2019 - def srcdir_duplicate(self, name):
2020 for dir in self.srcdir_list(): 2021 if self.is_under(dir): 2022 # We shouldn't source from something in the build path; 2023 # variant_dir is probably under src_dir, in which case 2024 # we are reflecting. 2025 break 2026 if dir.entry_exists_on_disk(name): 2027 srcnode = dir.Entry(name).disambiguate() 2028 if self.duplicate: 2029 node = self.Entry(name).disambiguate() 2030 node.do_duplicate(srcnode) 2031 return node 2032 else: 2033 return srcnode 2034 return None
2035
2036 - def _srcdir_find_file_key(self, filename):
2037 return filename
2038 2039 @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
2040 - def srcdir_find_file(self, filename):
2041 try: 2042 memo_dict = self._memo['srcdir_find_file'] 2043 except KeyError: 2044 memo_dict = {} 2045 self._memo['srcdir_find_file'] = memo_dict 2046 else: 2047 try: 2048 return memo_dict[filename] 2049 except KeyError: 2050 pass 2051 2052 def func(node): 2053 if (isinstance(node, File) or isinstance(node, Entry)) and \ 2054 (node.is_derived() or node.exists()): 2055 return node 2056 return None
2057 2058 norm_name = _my_normcase(filename) 2059 2060 for rdir in self.get_all_rdirs(): 2061 try: node = rdir.entries[norm_name] 2062 except KeyError: node = rdir.file_on_disk(filename) 2063 else: node = func(node) 2064 if node: 2065 result = (node, self) 2066 memo_dict[filename] = result 2067 return result 2068 2069 for srcdir in self.srcdir_list(): 2070 for rdir in srcdir.get_all_rdirs(): 2071 try: node = rdir.entries[norm_name] 2072 except KeyError: node = rdir.file_on_disk(filename) 2073 else: node = func(node) 2074 if node: 2075 result = (File(filename, self, self.fs), srcdir) 2076 memo_dict[filename] = result 2077 return result 2078 2079 result = (None, None) 2080 memo_dict[filename] = result 2081 return result
2082
2083 - def dir_on_disk(self, name):
2084 if self.entry_exists_on_disk(name): 2085 try: return self.Dir(name) 2086 except TypeError: pass 2087 node = self.srcdir_duplicate(name) 2088 if isinstance(node, File): 2089 return None 2090 return node
2091
2092 - def file_on_disk(self, name):
2093 if self.entry_exists_on_disk(name): 2094 try: return self.File(name) 2095 except TypeError: pass 2096 node = self.srcdir_duplicate(name) 2097 if isinstance(node, Dir): 2098 return None 2099 return node
2100
2101 - def walk(self, func, arg):
2102 """ 2103 Walk this directory tree by calling the specified function 2104 for each directory in the tree. 2105 2106 This behaves like the os.path.walk() function, but for in-memory 2107 Node.FS.Dir objects. The function takes the same arguments as 2108 the functions passed to os.path.walk(): 2109 2110 func(arg, dirname, fnames) 2111 2112 Except that "dirname" will actually be the directory *Node*, 2113 not the string. The '.' and '..' entries are excluded from 2114 fnames. The fnames list may be modified in-place to filter the 2115 subdirectories visited or otherwise impose a specific order. 2116 The "arg" argument is always passed to func() and may be used 2117 in any way (or ignored, passing None is common). 2118 """ 2119 entries = self.entries 2120 names = list(entries.keys()) 2121 names.remove('.') 2122 names.remove('..') 2123 func(arg, self, names) 2124 for dirname in [n for n in names if isinstance(entries[n], Dir)]: 2125 entries[dirname].walk(func, arg)
2126
2127 - def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None):
2128 """ 2129 Returns a list of Nodes (or strings) matching a specified 2130 pathname pattern. 2131 2132 Pathname patterns follow UNIX shell semantics: * matches 2133 any-length strings of any characters, ? matches any character, 2134 and [] can enclose lists or ranges of characters. Matches do 2135 not span directory separators. 2136 2137 The matches take into account Repositories, returning local 2138 Nodes if a corresponding entry exists in a Repository (either 2139 an in-memory Node or something on disk). 2140 2141 By defafult, the glob() function matches entries that exist 2142 on-disk, in addition to in-memory Nodes. Setting the "ondisk" 2143 argument to False (or some other non-true value) causes the glob() 2144 function to only match in-memory Nodes. The default behavior is 2145 to return both the on-disk and in-memory Nodes. 2146 2147 The "source" argument, when true, specifies that corresponding 2148 source Nodes must be returned if you're globbing in a build 2149 directory (initialized with VariantDir()). The default behavior 2150 is to return Nodes local to the VariantDir(). 2151 2152 The "strings" argument, when true, returns the matches as strings, 2153 not Nodes. The strings are path names relative to this directory. 2154 2155 The "exclude" argument, if not None, must be a pattern or a list 2156 of patterns following the same UNIX shell semantics. 2157 Elements matching a least one pattern of this list will be excluded 2158 from the result. 2159 2160 The underlying algorithm is adapted from the glob.glob() function 2161 in the Python library (but heavily modified), and uses fnmatch() 2162 under the covers. 2163 """ 2164 dirname, basename = os.path.split(pathname) 2165 if not dirname: 2166 result = self._glob1(basename, ondisk, source, strings) 2167 else: 2168 if has_glob_magic(dirname): 2169 list = self.glob(dirname, ondisk, source, False, exclude) 2170 else: 2171 list = [self.Dir(dirname, create=True)] 2172 result = [] 2173 for dir in list: 2174 r = dir._glob1(basename, ondisk, source, strings) 2175 if strings: 2176 r = [os.path.join(str(dir), x) for x in r] 2177 result.extend(r) 2178 if exclude: 2179 excludes = [] 2180 excludeList = SCons.Util.flatten(exclude) 2181 for x in excludeList: 2182 r = self.glob(x, ondisk, source, strings) 2183 excludes.extend(r) 2184 result = [x for x in result if not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes))] 2185 return sorted(result, key=lambda a: str(a))
2186
2187 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2188 """ 2189 Globs for and returns a list of entry names matching a single 2190 pattern in this directory. 2191 2192 This searches any repositories and source directories for 2193 corresponding entries and returns a Node (or string) relative 2194 to the current directory if an entry is found anywhere. 2195 2196 TODO: handle pattern with no wildcard 2197 """ 2198 search_dir_list = self.get_all_rdirs() 2199 for srcdir in self.srcdir_list(): 2200 search_dir_list.extend(srcdir.get_all_rdirs()) 2201 2202 selfEntry = self.Entry 2203 names = [] 2204 for dir in search_dir_list: 2205 # We use the .name attribute from the Node because the keys of 2206 # the dir.entries dictionary are normalized (that is, all upper 2207 # case) on case-insensitive systems like Windows. 2208 node_names = [ v.name for k, v in dir.entries.items() 2209 if k not in ('.', '..') ] 2210 names.extend(node_names) 2211 if not strings: 2212 # Make sure the working directory (self) actually has 2213 # entries for all Nodes in repositories or variant dirs. 2214 for name in node_names: selfEntry(name) 2215 if ondisk: 2216 try: 2217 disk_names = os.listdir(dir._abspath) 2218 except os.error: 2219 continue 2220 names.extend(disk_names) 2221 if not strings: 2222 # We're going to return corresponding Nodes in 2223 # the local directory, so we need to make sure 2224 # those Nodes exist. We only want to create 2225 # Nodes for the entries that will match the 2226 # specified pattern, though, which means we 2227 # need to filter the list here, even though 2228 # the overall list will also be filtered later, 2229 # after we exit this loop. 2230 if pattern[0] != '.': 2231 disk_names = [x for x in disk_names if x[0] != '.'] 2232 disk_names = fnmatch.filter(disk_names, pattern) 2233 dirEntry = dir.Entry 2234 for name in disk_names: 2235 # Add './' before disk filename so that '#' at 2236 # beginning of filename isn't interpreted. 2237 name = './' + name 2238 node = dirEntry(name).disambiguate() 2239 n = selfEntry(name) 2240 if n.__class__ != node.__class__: 2241 n.__class__ = node.__class__ 2242 n._morph() 2243 2244 names = set(names) 2245 if pattern[0] != '.': 2246 names = [x for x in names if x[0] != '.'] 2247 names = fnmatch.filter(names, pattern) 2248 2249 if strings: 2250 return names 2251 2252 return [self.entries[_my_normcase(n)] for n in names]
2253
2254 -class RootDir(Dir):
2255 """A class for the root directory of a file system. 2256 2257 This is the same as a Dir class, except that the path separator 2258 ('/' or '\\') is actually part of the name, so we don't need to 2259 add a separator when creating the path names of entries within 2260 this directory. 2261 """ 2262 2263 __slots__ = ('_lookupDict', ) 2264
2265 - def __init__(self, drive, fs):
2266 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir') 2267 SCons.Node.Node.__init__(self) 2268 2269 # Handle all the types of drives: 2270 if drive == '': 2271 # No drive, regular UNIX root or Windows default drive. 2272 name = OS_SEP 2273 dirname = OS_SEP 2274 elif drive == '//': 2275 # UNC path 2276 name = UNC_PREFIX 2277 dirname = UNC_PREFIX 2278 else: 2279 # Windows drive letter 2280 name = drive 2281 dirname = drive + OS_SEP 2282 2283 # Filename with extension as it was specified when the object was 2284 # created; to obtain filesystem path, use Python str() function 2285 self.name = SCons.Util.silent_intern(name) 2286 self.fs = fs #: Reference to parent Node.FS object 2287 2288 self._path_elements = [self] 2289 self.dir = self 2290 self._func_rexists = 2 2291 self._func_target_from_source = 1 2292 self.store_info = 1 2293 2294 # Now set our paths to what we really want them to be. The 2295 # name should already contain any necessary separators, such 2296 # as the initial drive letter (the name) plus the directory 2297 # separator, except for the "lookup abspath," which does not 2298 # have the drive letter. 2299 self._abspath = dirname 2300 self._labspath = '' 2301 self._path = dirname 2302 self._tpath = dirname 2303 self.dirname = dirname 2304 2305 self._morph() 2306 2307 self.duplicate = 0 2308 self._lookupDict = {} 2309 2310 self._lookupDict[''] = self 2311 self._lookupDict['/'] = self 2312 self.root = self 2313 # The // entry is necessary because os.path.normpath() 2314 # preserves double slashes at the beginning of a path on Posix 2315 # platforms. 2316 if not has_unc: 2317 self._lookupDict['//'] = self
2318
2319 - def _morph(self):
2320 """Turn a file system Node (either a freshly initialized directory 2321 object or a separate Entry object) into a proper directory object. 2322 2323 Set up this directory's entries and hook it into the file 2324 system tree. Specify that directories (this Node) don't use 2325 signatures for calculating whether they're current. 2326 """ 2327 2328 self.repositories = [] 2329 self.srcdir = None 2330 2331 self.entries = {} 2332 self.entries['.'] = self 2333 self.entries['..'] = self.dir 2334 self.cwd = self 2335 self.searched = 0 2336 self._sconsign = None 2337 self.variant_dirs = [] 2338 self.changed_since_last_build = 3 2339 self._func_sconsign = 1 2340 self._func_exists = 2 2341 self._func_get_contents = 2 2342 2343 # Don't just reset the executor, replace its action list, 2344 # because it might have some pre-or post-actions that need to 2345 # be preserved. 2346 # 2347 # But don't reset the executor if there is a non-null executor 2348 # attached already. The existing executor might have other 2349 # targets, in which case replacing the action list with a 2350 # Mkdir action is a big mistake. 2351 if not hasattr(self, 'executor'): 2352 self.builder = get_MkdirBuilder() 2353 self.get_executor().set_action_list(self.builder.action) 2354 else: 2355 # Prepend MkdirBuilder action to existing action list 2356 l = self.get_executor().action_list 2357 a = get_MkdirBuilder().action 2358 l.insert(0, a) 2359 self.get_executor().set_action_list(l)
2360 2361
2362 - def must_be_same(self, klass):
2363 if klass is Dir: 2364 return 2365 Base.must_be_same(self, klass)
2366
2367 - def _lookup_abs(self, p, klass, create=1):
2368 """ 2369 Fast (?) lookup of a *normalized* absolute path. 2370 2371 This method is intended for use by internal lookups with 2372 already-normalized path data. For general-purpose lookups, 2373 use the FS.Entry(), FS.Dir() or FS.File() methods. 2374 2375 The caller is responsible for making sure we're passed a 2376 normalized absolute path; we merely let Python's dictionary look 2377 up and return the One True Node.FS object for the path. 2378 2379 If a Node for the specified "p" doesn't already exist, and 2380 "create" is specified, the Node may be created after recursive 2381 invocation to find or create the parent directory or directories. 2382 """ 2383 k = _my_normcase(p) 2384 try: 2385 result = self._lookupDict[k] 2386 except KeyError: 2387 if not create: 2388 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self)) 2389 raise SCons.Errors.UserError(msg) 2390 # There is no Node for this path name, and we're allowed 2391 # to create it. 2392 dir_name, file_name = p.rsplit('/',1) 2393 dir_node = self._lookup_abs(dir_name, Dir) 2394 result = klass(file_name, dir_node, self.fs) 2395 2396 # Double-check on disk (as configured) that the Node we 2397 # created matches whatever is out there in the real world. 2398 result.diskcheck_match() 2399 2400 self._lookupDict[k] = result 2401 dir_node.entries[_my_normcase(file_name)] = result 2402 dir_node.implicit = None 2403 else: 2404 # There is already a Node for this path name. Allow it to 2405 # complain if we were looking for an inappropriate type. 2406 result.must_be_same(klass) 2407 return result
2408
2409 - def __str__(self):
2410 return self._abspath
2411
2412 - def entry_abspath(self, name):
2413 return self._abspath + name
2414
2415 - def entry_labspath(self, name):
2416 return '/' + name
2417
2418 - def entry_path(self, name):
2419 return self._path + name
2420
2421 - def entry_tpath(self, name):
2422 return self._tpath + name
2423
2424 - def is_under(self, dir):
2425 if self is dir: 2426 return 1 2427 else: 2428 return 0
2429
2430 - def up(self):
2431 return None
2432
2433 - def get_dir(self):
2434 return None
2435
2436 - def src_builder(self):
2437 return _null
2438
2439 2440 -class FileNodeInfo(SCons.Node.NodeInfoBase):
2441 __slots__ = ('csig', 'timestamp', 'size') 2442 current_version_id = 2 2443 2444 field_list = ['csig', 'timestamp', 'size'] 2445 2446 # This should get reset by the FS initialization. 2447 fs = None 2448
2449 - def str_to_node(self, s):
2450 top = self.fs.Top 2451 root = top.root 2452 if do_splitdrive: 2453 drive, s = _my_splitdrive(s) 2454 if drive: 2455 root = self.fs.get_root(drive) 2456 if not os.path.isabs(s): 2457 s = top.get_labspath() + '/' + s 2458 return root._lookup_abs(s, Entry)
2459
2460 - def __getstate__(self):
2461 """ 2462 Return all fields that shall be pickled. Walk the slots in the class 2463 hierarchy and add those to the state dictionary. If a '__dict__' slot is 2464 available, copy all entries to the dictionary. Also include the version 2465 id, which is fixed for all instances of a class. 2466 """ 2467 state = getattr(self, '__dict__', {}).copy() 2468 for obj in type(self).mro(): 2469 for name in getattr(obj, '__slots__', ()): 2470 if hasattr(self, name): 2471 state[name] = getattr(self, name) 2472 2473 state['_version_id'] = self.current_version_id 2474 try: 2475 del state['__weakref__'] 2476 except KeyError: 2477 pass 2478 2479 return state
2480
2481 - def __setstate__(self, state):
2482 """ 2483 Restore the attributes from a pickled state. 2484 """ 2485 # TODO check or discard version 2486 del state['_version_id'] 2487 for key, value in state.items(): 2488 if key not in ('__weakref__',): 2489 setattr(self, key, value)
2490
2491 - def __eq__(self, other):
2492 return self.csig == other.csig and self.timestamp == other.timestamp and self.size == other.size
2493
2494 - def __ne__(self, other):
2495 return not self.__eq__(other)
2496
2497 2498 -class FileBuildInfo(SCons.Node.BuildInfoBase):
2499 """ 2500 This is info loaded from sconsign. 2501 2502 Attributes unique to FileBuildInfo: 2503 dependency_map : Caches file->csig mapping 2504 for all dependencies. Currently this is only used when using 2505 MD5-timestamp decider. 2506 It's used to ensure that we copy the correct 2507 csig from previous build to be written to .sconsign when current build 2508 is done. Previously the matching of csig to file was strictly by order 2509 they appeared in bdepends, bsources, or bimplicit, and so a change in order 2510 or count of any of these could yield writing wrong csig, and then false positive 2511 rebuilds 2512 """ 2513 __slots__ = ['dependency_map', ] 2514 current_version_id = 2 2515
2516 - def __setattr__(self, key, value):
2517 2518 # If any attributes are changed in FileBuildInfo, we need to 2519 # invalidate the cached map of file name to content signature 2520 # heald in dependency_map. Currently only used with 2521 # MD5-timestamp decider 2522 if key != 'dependency_map' and hasattr(self, 'dependency_map'): 2523 del self.dependency_map 2524 2525 return super(FileBuildInfo, self).__setattr__(key, value)
2526
2527 - def convert_to_sconsign(self):
2528 """ 2529 Converts this FileBuildInfo object for writing to a .sconsign file 2530 2531 This replaces each Node in our various dependency lists with its 2532 usual string representation: relative to the top-level SConstruct 2533 directory, or an absolute path if it's outside. 2534 """ 2535 if os_sep_is_slash: 2536 node_to_str = str 2537 else: 2538 def node_to_str(n): 2539 try: 2540 s = n.get_internal_path() 2541 except AttributeError: 2542 s = str(n) 2543 else: 2544 s = s.replace(OS_SEP, '/') 2545 return s
2546 for attr in ['bsources', 'bdepends', 'bimplicit']: 2547 try: 2548 val = getattr(self, attr) 2549 except AttributeError: 2550 pass 2551 else: 2552 setattr(self, attr, list(map(node_to_str, val)))
2553
2554 - def convert_from_sconsign(self, dir, name):
2555 """ 2556 Converts a newly-read FileBuildInfo object for in-SCons use 2557 2558 For normal up-to-date checking, we don't have any conversion to 2559 perform--but we're leaving this method here to make that clear. 2560 """ 2561 pass
2562
2563 - def prepare_dependencies(self):
2564 """ 2565 Prepares a FileBuildInfo object for explaining what changed 2566 2567 The bsources, bdepends and bimplicit lists have all been 2568 stored on disk as paths relative to the top-level SConstruct 2569 directory. Convert the strings to actual Nodes (for use by the 2570 --debug=explain code and --implicit-cache). 2571 """ 2572 attrs = [ 2573 ('bsources', 'bsourcesigs'), 2574 ('bdepends', 'bdependsigs'), 2575 ('bimplicit', 'bimplicitsigs'), 2576 ] 2577 for (nattr, sattr) in attrs: 2578 try: 2579 strings = getattr(self, nattr) 2580 nodeinfos = getattr(self, sattr) 2581 except AttributeError: 2582 continue 2583 if strings is None or nodeinfos is None: 2584 continue 2585 nodes = [] 2586 for s, ni in zip(strings, nodeinfos): 2587 if not isinstance(s, SCons.Node.Node): 2588 s = ni.str_to_node(s) 2589 nodes.append(s) 2590 setattr(self, nattr, nodes)
2591
2592 - def format(self, names=0):
2593 result = [] 2594 bkids = self.bsources + self.bdepends + self.bimplicit 2595 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs 2596 for bkid, bkidsig in zip(bkids, bkidsigs): 2597 result.append(str(bkid) + ': ' + 2598 ' '.join(bkidsig.format(names=names))) 2599 if not hasattr(self,'bact'): 2600 self.bact = "none" 2601 result.append('%s [%s]' % (self.bactsig, self.bact)) 2602 return '\n'.join(result)
2603
2604 2605 -class File(Base):
2606 """A class for files in a file system. 2607 """ 2608 2609 __slots__ = ['scanner_paths', 2610 'cachedir_csig', 2611 'cachesig', 2612 'repositories', 2613 'srcdir', 2614 'entries', 2615 'searched', 2616 '_sconsign', 2617 'variant_dirs', 2618 'root', 2619 'dirname', 2620 'on_disk_entries', 2621 'released_target_info', 2622 'contentsig'] 2623 2624 NodeInfo = FileNodeInfo 2625 BuildInfo = FileBuildInfo 2626 2627 md5_chunksize = 64 2628
2629 - def diskcheck_match(self):
2630 diskcheck_match(self, self.isdir, 2631 "Directory %s found where file expected.")
2632
2633 - def __init__(self, name, directory, fs):
2634 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.File') 2635 Base.__init__(self, name, directory, fs) 2636 self._morph()
2637
2638 - def Entry(self, name):
2639 """Create an entry node named 'name' relative to 2640 the directory of this file.""" 2641 return self.dir.Entry(name)
2642
2643 - def Dir(self, name, create=True):
2644 """Create a directory node named 'name' relative to 2645 the directory of this file.""" 2646 return self.dir.Dir(name, create=create)
2647
2648 - def Dirs(self, pathlist):
2649 """Create a list of directories relative to the SConscript 2650 directory of this file.""" 2651 return [self.Dir(p) for p in pathlist]
2652
2653 - def File(self, name):
2654 """Create a file node named 'name' relative to 2655 the directory of this file.""" 2656 return self.dir.File(name)
2657
2658 - def _morph(self):
2659 """Turn a file system node into a File object.""" 2660 self.scanner_paths = {} 2661 if not hasattr(self, '_local'): 2662 self._local = 0 2663 if not hasattr(self, 'released_target_info'): 2664 self.released_target_info = False 2665 2666 self.store_info = 1 2667 self._func_exists = 4 2668 self._func_get_contents = 3 2669 2670 # Initialize this Node's decider function to decide_source() because 2671 # every file is a source file until it has a Builder attached... 2672 self.changed_since_last_build = 4 2673 2674 # If there was already a Builder set on this entry, then 2675 # we need to make sure we call the target-decider function, 2676 # not the source-decider. Reaching in and doing this by hand 2677 # is a little bogus. We'd prefer to handle this by adding 2678 # an Entry.builder_set() method that disambiguates like the 2679 # other methods, but that starts running into problems with the 2680 # fragile way we initialize Dir Nodes with their Mkdir builders, 2681 # yet still allow them to be overridden by the user. Since it's 2682 # not clear right now how to fix that, stick with what works 2683 # until it becomes clear... 2684 if self.has_builder(): 2685 self.changed_since_last_build = 5
2686
2687 - def scanner_key(self):
2688 return self.get_suffix()
2689
2690 - def get_contents(self):
2692
2693 - def get_text_contents(self):
2694 """ 2695 This attempts to figure out what the encoding of the text is 2696 based upon the BOM bytes, and then decodes the contents so that 2697 it's a valid python string. 2698 """ 2699 contents = self.get_contents() 2700 # The behavior of various decode() methods and functions 2701 # w.r.t. the initial BOM bytes is different for different 2702 # encodings and/or Python versions. ('utf-8' does not strip 2703 # them, but has a 'utf-8-sig' which does; 'utf-16' seems to 2704 # strip them; etc.) Just sidestep all the complication by 2705 # explicitly stripping the BOM before we decode(). 2706 if contents[:len(codecs.BOM_UTF8)] == codecs.BOM_UTF8: 2707 return contents[len(codecs.BOM_UTF8):].decode('utf-8') 2708 if contents[:len(codecs.BOM_UTF16_LE)] == codecs.BOM_UTF16_LE: 2709 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le') 2710 if contents[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE: 2711 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be') 2712 try: 2713 return contents.decode('utf-8') 2714 except UnicodeDecodeError as e: 2715 try: 2716 return contents.decode('latin-1') 2717 except UnicodeDecodeError as e: 2718 return contents.decode('utf-8', error='backslashreplace')
2719 2720
2721 - def get_content_hash(self):
2722 """ 2723 Compute and return the MD5 hash for this file. 2724 """ 2725 if not self.rexists(): 2726 return SCons.Util.MD5signature('') 2727 fname = self.rfile().get_abspath() 2728 try: 2729 cs = SCons.Util.MD5filesignature(fname, 2730 chunksize=SCons.Node.FS.File.md5_chunksize*1024) 2731 except EnvironmentError as e: 2732 if not e.filename: 2733 e.filename = fname 2734 raise 2735 return cs
2736 2737 @SCons.Memoize.CountMethodCall
2738 - def get_size(self):
2739 try: 2740 return self._memo['get_size'] 2741 except KeyError: 2742 pass 2743 2744 if self.rexists(): 2745 size = self.rfile().getsize() 2746 else: 2747 size = 0 2748 2749 self._memo['get_size'] = size 2750 2751 return size
2752 2753 @SCons.Memoize.CountMethodCall
2754 - def get_timestamp(self):
2755 try: 2756 return self._memo['get_timestamp'] 2757 except KeyError: 2758 pass 2759 2760 if self.rexists(): 2761 timestamp = self.rfile().getmtime() 2762 else: 2763 timestamp = 0 2764 2765 self._memo['get_timestamp'] = timestamp 2766 2767 return timestamp
2768 2769 convert_copy_attrs = [ 2770 'bsources', 2771 'bimplicit', 2772 'bdepends', 2773 'bact', 2774 'bactsig', 2775 'ninfo', 2776 ] 2777 2778 2779 convert_sig_attrs = [ 2780 'bsourcesigs', 2781 'bimplicitsigs', 2782 'bdependsigs', 2783 ] 2784
2785 - def convert_old_entry(self, old_entry):
2786 # Convert a .sconsign entry from before the Big Signature 2787 # Refactoring, doing what we can to convert its information 2788 # to the new .sconsign entry format. 2789 # 2790 # The old format looked essentially like this: 2791 # 2792 # BuildInfo 2793 # .ninfo (NodeInfo) 2794 # .bsig 2795 # .csig 2796 # .timestamp 2797 # .size 2798 # .bsources 2799 # .bsourcesigs ("signature" list) 2800 # .bdepends 2801 # .bdependsigs ("signature" list) 2802 # .bimplicit 2803 # .bimplicitsigs ("signature" list) 2804 # .bact 2805 # .bactsig 2806 # 2807 # The new format looks like this: 2808 # 2809 # .ninfo (NodeInfo) 2810 # .bsig 2811 # .csig 2812 # .timestamp 2813 # .size 2814 # .binfo (BuildInfo) 2815 # .bsources 2816 # .bsourcesigs (NodeInfo list) 2817 # .bsig 2818 # .csig 2819 # .timestamp 2820 # .size 2821 # .bdepends 2822 # .bdependsigs (NodeInfo list) 2823 # .bsig 2824 # .csig 2825 # .timestamp 2826 # .size 2827 # .bimplicit 2828 # .bimplicitsigs (NodeInfo list) 2829 # .bsig 2830 # .csig 2831 # .timestamp 2832 # .size 2833 # .bact 2834 # .bactsig 2835 # 2836 # The basic idea of the new structure is that a NodeInfo always 2837 # holds all available information about the state of a given Node 2838 # at a certain point in time. The various .b*sigs lists can just 2839 # be a list of pointers to the .ninfo attributes of the different 2840 # dependent nodes, without any copying of information until it's 2841 # time to pickle it for writing out to a .sconsign file. 2842 # 2843 # The complicating issue is that the *old* format only stored one 2844 # "signature" per dependency, based on however the *last* build 2845 # was configured. We don't know from just looking at it whether 2846 # it was a build signature, a content signature, or a timestamp 2847 # "signature". Since we no longer use build signatures, the 2848 # best we can do is look at the length and if it's thirty two, 2849 # assume that it was (or might have been) a content signature. 2850 # If it was actually a build signature, then it will cause a 2851 # rebuild anyway when it doesn't match the new content signature, 2852 # but that's probably the best we can do. 2853 import SCons.SConsign 2854 new_entry = SCons.SConsign.SConsignEntry() 2855 new_entry.binfo = self.new_binfo() 2856 binfo = new_entry.binfo 2857 for attr in self.convert_copy_attrs: 2858 try: 2859 value = getattr(old_entry, attr) 2860 except AttributeError: 2861 continue 2862 setattr(binfo, attr, value) 2863 delattr(old_entry, attr) 2864 for attr in self.convert_sig_attrs: 2865 try: