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