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