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 - 2015 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.4.1:3453:73fefd3ea0b0 2015/11/09 03:25:05 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 explicitely 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 RemoteFS: 1169 # # Skeleton for the obvious methods we might need from the 1170 # # abstraction layer for a remote filesystem. 1171 # def upload(self, local_src, remote_dst): 1172 # pass 1173 # def download(self, remote_src, local_dst): 1174 # pass 1175 1176 1177 -class FS(LocalFS):
1178
1179 - def __init__(self, path = None):
1180 """Initialize the Node.FS subsystem. 1181 1182 The supplied path is the top of the source tree, where we 1183 expect to find the top-level build file. If no path is 1184 supplied, the current directory is the default. 1185 1186 The path argument must be a valid absolute path. 1187 """ 1188 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS') 1189 1190 self._memo = {} 1191 1192 self.Root = {} 1193 self.SConstruct_dir = None 1194 self.max_drift = default_max_drift 1195 1196 self.Top = None 1197 if path is None: 1198 self.pathTop = os.getcwd() 1199 else: 1200 self.pathTop = path 1201 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0]) 1202 1203 self.Top = self.Dir(self.pathTop) 1204 self.Top._path = '.' 1205 self.Top._tpath = '.' 1206 self._cwd = self.Top 1207 1208 DirNodeInfo.fs = self 1209 FileNodeInfo.fs = self
1210
1211 - def set_SConstruct_dir(self, dir):
1212 self.SConstruct_dir = dir
1213
1214 - def get_max_drift(self):
1215 return self.max_drift
1216
1217 - def set_max_drift(self, max_drift):
1218 self.max_drift = max_drift
1219
1220 - def getcwd(self):
1221 if hasattr(self, "_cwd"): 1222 return self._cwd 1223 else: 1224 return "<no cwd>"
1225
1226 - def chdir(self, dir, change_os_dir=0):
1227 """Change the current working directory for lookups. 1228 If change_os_dir is true, we will also change the "real" cwd 1229 to match. 1230 """ 1231 curr=self._cwd 1232 try: 1233 if dir is not None: 1234 self._cwd = dir 1235 if change_os_dir: 1236 os.chdir(dir.get_abspath()) 1237 except OSError: 1238 self._cwd = curr 1239 raise
1240
1241 - def get_root(self, drive):
1242 """ 1243 Returns the root directory for the specified drive, creating 1244 it if necessary. 1245 """ 1246 drive = _my_normcase(drive) 1247 try: 1248 return self.Root[drive] 1249 except KeyError: 1250 root = RootDir(drive, self) 1251 self.Root[drive] = root 1252 if not drive: 1253 self.Root[self.defaultDrive] = root 1254 elif drive == self.defaultDrive: 1255 self.Root[''] = root 1256 return root
1257
1258 - def _lookup(self, p, directory, fsclass, create=1):
1259 """ 1260 The generic entry point for Node lookup with user-supplied data. 1261 1262 This translates arbitrary input into a canonical Node.FS object 1263 of the specified fsclass. The general approach for strings is 1264 to turn it into a fully normalized absolute path and then call 1265 the root directory's lookup_abs() method for the heavy lifting. 1266 1267 If the path name begins with '#', it is unconditionally 1268 interpreted relative to the top-level directory of this FS. '#' 1269 is treated as a synonym for the top-level SConstruct directory, 1270 much like '~' is treated as a synonym for the user's home 1271 directory in a UNIX shell. So both '#foo' and '#/foo' refer 1272 to the 'foo' subdirectory underneath the top-level SConstruct 1273 directory. 1274 1275 If the path name is relative, then the path is looked up relative 1276 to the specified directory, or the current directory (self._cwd, 1277 typically the SConscript directory) if the specified directory 1278 is None. 1279 """ 1280 if isinstance(p, Base): 1281 # It's already a Node.FS object. Make sure it's the right 1282 # class and return. 1283 p.must_be_same(fsclass) 1284 return p 1285 # str(p) in case it's something like a proxy object 1286 p = str(p) 1287 1288 if not os_sep_is_slash: 1289 p = p.replace(OS_SEP, '/') 1290 1291 if p[0:1] == '#': 1292 # There was an initial '#', so we strip it and override 1293 # whatever directory they may have specified with the 1294 # top-level SConstruct directory. 1295 p = p[1:] 1296 directory = self.Top 1297 1298 # There might be a drive letter following the 1299 # '#'. Although it is not described in the SCons man page, 1300 # the regression test suite explicitly tests for that 1301 # syntax. It seems to mean the following thing: 1302 # 1303 # Assuming the the SCons top dir is in C:/xxx/yyy, 1304 # '#X:/toto' means X:/xxx/yyy/toto. 1305 # 1306 # i.e. it assumes that the X: drive has a directory 1307 # structure similar to the one found on drive C:. 1308 if do_splitdrive: 1309 drive, p = _my_splitdrive(p) 1310 if drive: 1311 root = self.get_root(drive) 1312 else: 1313 root = directory.root 1314 else: 1315 root = directory.root 1316 1317 # We can only strip trailing after splitting the drive 1318 # since the drive might the UNC '//' prefix. 1319 p = p.strip('/') 1320 1321 needs_normpath = needs_normpath_match(p) 1322 1323 # The path is relative to the top-level SCons directory. 1324 if p in ('', '.'): 1325 p = directory.get_labspath() 1326 else: 1327 p = directory.get_labspath() + '/' + p 1328 else: 1329 if do_splitdrive: 1330 drive, p = _my_splitdrive(p) 1331 if drive and not p: 1332 # This causes a naked drive letter to be treated 1333 # as a synonym for the root directory on that 1334 # drive. 1335 p = '/' 1336 else: 1337 drive = '' 1338 1339 # We can only strip trailing '/' since the drive might the 1340 # UNC '//' prefix. 1341 if p != '/': 1342 p = p.rstrip('/') 1343 1344 needs_normpath = needs_normpath_match(p) 1345 1346 if p[0:1] == '/': 1347 # Absolute path 1348 root = self.get_root(drive) 1349 else: 1350 # This is a relative lookup or to the current directory 1351 # (the path name is not absolute). Add the string to the 1352 # appropriate directory lookup path, after which the whole 1353 # thing gets normalized. 1354 if directory: 1355 if not isinstance(directory, Dir): 1356 directory = self.Dir(directory) 1357 else: 1358 directory = self._cwd 1359 1360 if p in ('', '.'): 1361 p = directory.get_labspath() 1362 else: 1363 p = directory.get_labspath() + '/' + p 1364 1365 if drive: 1366 root = self.get_root(drive) 1367 else: 1368 root = directory.root 1369 1370 if needs_normpath is not None: 1371 # Normalize a pathname. Will return the same result for 1372 # equivalent paths. 1373 # 1374 # We take advantage of the fact that we have an absolute 1375 # path here for sure. In addition, we know that the 1376 # components of lookup path are separated by slashes at 1377 # this point. Because of this, this code is about 2X 1378 # faster than calling os.path.normpath() followed by 1379 # replacing os.sep with '/' again. 1380 ins = p.split('/')[1:] 1381 outs = [] 1382 for d in ins: 1383 if d == '..': 1384 try: 1385 outs.pop() 1386 except IndexError: 1387 pass 1388 elif d not in ('', '.'): 1389 outs.append(d) 1390 p = '/' + '/'.join(outs) 1391 1392 return root._lookup_abs(p, fsclass, create)
1393
1394 - def Entry(self, name, directory = None, create = 1):
1395 """Look up or create a generic Entry node with the specified name. 1396 If the name is a relative path (begins with ./, ../, or a file 1397 name), then it is looked up relative to the supplied directory 1398 node, or to the top level directory of the FS (supplied at 1399 construction time) if no directory is supplied. 1400 """ 1401 return self._lookup(name, directory, Entry, create)
1402
1403 - def File(self, name, directory = None, create = 1):
1404 """Look up or create a File node with the specified name. If 1405 the name is a relative path (begins with ./, ../, or a file name), 1406 then it is looked up relative to the supplied directory node, 1407 or to the top level directory of the FS (supplied at construction 1408 time) if no directory is supplied. 1409 1410 This method will raise TypeError if a directory is found at the 1411 specified path. 1412 """ 1413 return self._lookup(name, directory, File, create)
1414
1415 - def Dir(self, name, directory = None, create = True):
1416 """Look up or create a Dir node with the specified name. If 1417 the name is a relative path (begins with ./, ../, or a file name), 1418 then it is looked up relative to the supplied directory node, 1419 or to the top level directory of the FS (supplied at construction 1420 time) if no directory is supplied. 1421 1422 This method will raise TypeError if a normal file is found at the 1423 specified path. 1424 """ 1425 return self._lookup(name, directory, Dir, create)
1426
1427 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1428 """Link the supplied variant directory to the source directory 1429 for purposes of building files.""" 1430 1431 if not isinstance(src_dir, SCons.Node.Node): 1432 src_dir = self.Dir(src_dir) 1433 if not isinstance(variant_dir, SCons.Node.Node): 1434 variant_dir = self.Dir(variant_dir) 1435 if src_dir.is_under(variant_dir): 1436 raise SCons.Errors.UserError("Source directory cannot be under variant directory.") 1437 if variant_dir.srcdir: 1438 if variant_dir.srcdir == src_dir: 1439 return # We already did this. 1440 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)) 1441 variant_dir.link(src_dir, duplicate)
1442
1443 - def Repository(self, *dirs):
1444 """Specify Repository directories to search.""" 1445 for d in dirs: 1446 if not isinstance(d, SCons.Node.Node): 1447 d = self.Dir(d) 1448 self.Top.addRepository(d)
1449
1450 - def variant_dir_target_climb(self, orig, dir, tail):
1451 """Create targets in corresponding variant directories 1452 1453 Climb the directory tree, and look up path names 1454 relative to any linked variant directories we find. 1455 1456 Even though this loops and walks up the tree, we don't memoize 1457 the return value because this is really only used to process 1458 the command-line targets. 1459 """ 1460 targets = [] 1461 message = None 1462 fmt = "building associated VariantDir targets: %s" 1463 start_dir = dir 1464 while dir: 1465 for bd in dir.variant_dirs: 1466 if start_dir.is_under(bd): 1467 # If already in the build-dir location, don't reflect 1468 return [orig], fmt % str(orig) 1469 p = os.path.join(bd._path, *tail) 1470 targets.append(self.Entry(p)) 1471 tail = [dir.name] + tail 1472 dir = dir.up() 1473 if targets: 1474 message = fmt % ' '.join(map(str, targets)) 1475 return targets, message
1476
1477 - def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None):
1478 """ 1479 Globs 1480 1481 This is mainly a shim layer 1482 """ 1483 if cwd is None: 1484 cwd = self.getcwd() 1485 return cwd.glob(pathname, ondisk, source, strings, exclude)
1486
1487 -class DirNodeInfo(SCons.Node.NodeInfoBase):
1488 __slots__ = () 1489 # This should get reset by the FS initialization. 1490 current_version_id = 2 1491 1492 fs = None 1493
1494 - def str_to_node(self, s):
1495 top = self.fs.Top 1496 root = top.root 1497 if do_splitdrive: 1498 drive, s = _my_splitdrive(s) 1499 if drive: 1500 root = self.fs.get_root(drive) 1501 if not os.path.isabs(s): 1502 s = top.get_labspath() + '/' + s 1503 return root._lookup_abs(s, Entry)
1504
1505 -class DirBuildInfo(SCons.Node.BuildInfoBase):
1506 __slots__ = () 1507 current_version_id = 2
1508 1509 glob_magic_check = re.compile('[*?[]')
1510 1511 -def has_glob_magic(s):
1512 return glob_magic_check.search(s) is not None
1513
1514 -class Dir(Base):
1515 """A class for directories in a file system. 1516 """ 1517 1518 __slots__ = ['scanner_paths', 1519 'cachedir_csig', 1520 'cachesig', 1521 'repositories', 1522 'srcdir', 1523 'entries', 1524 'searched', 1525 '_sconsign', 1526 'variant_dirs', 1527 'root', 1528 'dirname', 1529 'on_disk_entries', 1530 'sccs_dir', 1531 'rcs_dir', 1532 'released_target_info', 1533 'contentsig'] 1534 1535 NodeInfo = DirNodeInfo 1536 BuildInfo = DirBuildInfo 1537
1538 - def __init__(self, name, directory, fs):
1539 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Dir') 1540 Base.__init__(self, name, directory, fs) 1541 self._morph()
1542
1543 - def _morph(self):
1544 """Turn a file system Node (either a freshly initialized directory 1545 object or a separate Entry object) into a proper directory object. 1546 1547 Set up this directory's entries and hook it into the file 1548 system tree. Specify that directories (this Node) don't use 1549 signatures for calculating whether they're current. 1550 """ 1551 1552 self.repositories = [] 1553 self.srcdir = None 1554 1555 self.entries = {} 1556 self.entries['.'] = self 1557 self.entries['..'] = self.dir 1558 self.cwd = self 1559 self.searched = 0 1560 self._sconsign = None 1561 self.variant_dirs = [] 1562 self.root = self.dir.root 1563 self.changed_since_last_build = 3 1564 self._func_sconsign = 1 1565 self._func_exists = 2 1566 self._func_get_contents = 2 1567 1568 self._abspath = SCons.Util.silent_intern(self.dir.entry_abspath(self.name)) 1569 self._labspath = SCons.Util.silent_intern(self.dir.entry_labspath(self.name)) 1570 if self.dir._path == '.': 1571 self._path = SCons.Util.silent_intern(self.name) 1572 else: 1573 self._path = SCons.Util.silent_intern(self.dir.entry_path(self.name)) 1574 if self.dir._tpath == '.': 1575 self._tpath = SCons.Util.silent_intern(self.name) 1576 else: 1577 self._tpath = SCons.Util.silent_intern(self.dir.entry_tpath(self.name)) 1578 self._path_elements = self.dir._path_elements + [self] 1579 1580 # For directories, we make a difference between the directory 1581 # 'name' and the directory 'dirname'. The 'name' attribute is 1582 # used when we need to print the 'name' of the directory or 1583 # when we it is used as the last part of a path. The 'dirname' 1584 # is used when the directory is not the last element of the 1585 # path. The main reason for making that distinction is that 1586 # for RoorDir's the dirname can not be easily inferred from 1587 # the name. For example, we have to add a '/' after a drive 1588 # letter but not after a UNC path prefix ('//'). 1589 self.dirname = self.name + OS_SEP 1590 1591 # Don't just reset the executor, replace its action list, 1592 # because it might have some pre-or post-actions that need to 1593 # be preserved. 1594 # 1595 # But don't reset the executor if there is a non-null executor 1596 # attached already. The existing executor might have other 1597 # targets, in which case replacing the action list with a 1598 # Mkdir action is a big mistake. 1599 if not hasattr(self, 'executor'): 1600 self.builder = get_MkdirBuilder() 1601 self.get_executor().set_action_list(self.builder.action) 1602 else: 1603 # Prepend MkdirBuilder action to existing action list 1604 l = self.get_executor().action_list 1605 a = get_MkdirBuilder().action 1606 l.insert(0, a) 1607 self.get_executor().set_action_list(l)
1608
1609 - def diskcheck_match(self):
1610 diskcheck_match(self, self.isfile, 1611 "File %s found where directory expected.")
1612
1613 - def __clearRepositoryCache(self, duplicate=None):
1614 """Called when we change the repository(ies) for a directory. 1615 This clears any cached information that is invalidated by changing 1616 the repository.""" 1617 1618 for node in self.entries.values(): 1619 if node != self.dir: 1620 if node != self and isinstance(node, Dir): 1621 node.__clearRepositoryCache(duplicate) 1622 else: 1623 node.clear() 1624 try: 1625 del node._srcreps 1626 except AttributeError: 1627 pass 1628 if duplicate is not None: 1629 node.duplicate=duplicate
1630
1631 - def __resetDuplicate(self, node):
1632 if node != self: 1633 node.duplicate = node.get_dir().duplicate
1634
1635 - def Entry(self, name):
1636 """ 1637 Looks up or creates an entry node named 'name' relative to 1638 this directory. 1639 """ 1640 return self.fs.Entry(name, self)
1641
1642 - def Dir(self, name, create=True):
1643 """ 1644 Looks up or creates a directory node named 'name' relative to 1645 this directory. 1646 """ 1647 return self.fs.Dir(name, self, create)
1648
1649 - def File(self, name):
1650 """ 1651 Looks up or creates a file node named 'name' relative to 1652 this directory. 1653 """ 1654 return self.fs.File(name, self)
1655 1663
1664 - def getRepositories(self):
1665 """Returns a list of repositories for this directory. 1666 """ 1667 if self.srcdir and not self.duplicate: 1668 return self.srcdir.get_all_rdirs() + self.repositories 1669 return self.repositories
1670 1671 @SCons.Memoize.CountMethodCall
1672 - def get_all_rdirs(self):
1673 try: 1674 return list(self._memo['get_all_rdirs']) 1675 except KeyError: 1676 pass 1677 1678 result = [self] 1679 fname = '.' 1680 dir = self 1681 while dir: 1682 for rep in dir.getRepositories(): 1683 result.append(rep.Dir(fname)) 1684 if fname == '.': 1685 fname = dir.name 1686 else: 1687 fname = dir.name + OS_SEP + fname 1688 dir = dir.up() 1689 1690 self._memo['get_all_rdirs'] = list(result) 1691 1692 return result
1693
1694 - def addRepository(self, dir):
1695 if dir != self and not dir in self.repositories: 1696 self.repositories.append(dir) 1697 dir._tpath = '.' 1698 self.__clearRepositoryCache()
1699
1700 - def up(self):
1701 return self.dir
1702
1703 - def _rel_path_key(self, other):
1704 return str(other)
1705 1706 @SCons.Memoize.CountDictCall(_rel_path_key)
1707 - def rel_path(self, other):
1708 """Return a path to "other" relative to this directory. 1709 """ 1710 1711 # This complicated and expensive method, which constructs relative 1712 # paths between arbitrary Node.FS objects, is no longer used 1713 # by SCons itself. It was introduced to store dependency paths 1714 # in .sconsign files relative to the target, but that ended up 1715 # being significantly inefficient. 1716 # 1717 # We're continuing to support the method because some SConstruct 1718 # files out there started using it when it was available, and 1719 # we're all about backwards compatibility.. 1720 1721 try: 1722 memo_dict = self._memo['rel_path'] 1723 except KeyError: 1724 memo_dict = {} 1725 self._memo['rel_path'] = memo_dict 1726 else: 1727 try: 1728 return memo_dict[other] 1729 except KeyError: 1730 pass 1731 1732 if self is other: 1733 result = '.' 1734 1735 elif not other in self._path_elements: 1736 try: 1737 other_dir = other.get_dir() 1738 except AttributeError: 1739 result = str(other) 1740 else: 1741 if other_dir is None: 1742 result = other.name 1743 else: 1744 dir_rel_path = self.rel_path(other_dir) 1745 if dir_rel_path == '.': 1746 result = other.name 1747 else: 1748 result = dir_rel_path + OS_SEP + other.name 1749 else: 1750 i = self._path_elements.index(other) + 1 1751 1752 path_elems = ['..'] * (len(self._path_elements) - i) \ 1753 + [n.name for n in other._path_elements[i:]] 1754 1755 result = OS_SEP.join(path_elems) 1756 1757 memo_dict[other] = result 1758 1759 return result
1760
1761 - def get_env_scanner(self, env, kw={}):
1762 import SCons.Defaults 1763 return SCons.Defaults.DirEntryScanner
1764
1765 - def get_target_scanner(self):
1766 import SCons.Defaults 1767 return SCons.Defaults.DirEntryScanner
1768
1769 - def get_found_includes(self, env, scanner, path):
1770 """Return this directory's implicit dependencies. 1771 1772 We don't bother caching the results because the scan typically 1773 shouldn't be requested more than once (as opposed to scanning 1774 .h file contents, which can be requested as many times as the 1775 files is #included by other files). 1776 """ 1777 if not scanner: 1778 return [] 1779 # Clear cached info for this Dir. If we already visited this 1780 # directory on our walk down the tree (because we didn't know at 1781 # that point it was being used as the source for another Node) 1782 # then we may have calculated build signature before realizing 1783 # we had to scan the disk. Now that we have to, though, we need 1784 # to invalidate the old calculated signature so that any node 1785 # dependent on our directory structure gets one that includes 1786 # info about everything on disk. 1787 self.clear() 1788 return scanner(self, env, path)
1789 1790 # 1791 # Taskmaster interface subsystem 1792 # 1793
1794 - def prepare(self):
1795 pass
1796
1797 - def build(self, **kw):
1798 """A null "builder" for directories.""" 1799 global MkdirBuilder 1800 if self.builder is not MkdirBuilder: 1801 SCons.Node.Node.build(self, **kw)
1802 1803 # 1804 # 1805 # 1806
1807 - def _create(self):
1808 """Create this directory, silently and without worrying about 1809 whether the builder is the default or not.""" 1810 listDirs = [] 1811 parent = self 1812 while parent: 1813 if parent.exists(): 1814 break 1815 listDirs.append(parent) 1816 p = parent.up() 1817 if p is None: 1818 # Don't use while: - else: for this condition because 1819 # if so, then parent is None and has no .path attribute. 1820 raise SCons.Errors.StopError(parent._path) 1821 parent = p 1822 listDirs.reverse() 1823 for dirnode in listDirs: 1824 try: 1825 # Don't call dirnode.build(), call the base Node method 1826 # directly because we definitely *must* create this 1827 # directory. The dirnode.build() method will suppress 1828 # the build if it's the default builder. 1829 SCons.Node.Node.build(dirnode) 1830 dirnode.get_executor().nullify() 1831 # The build() action may or may not have actually 1832 # created the directory, depending on whether the -n 1833 # option was used or not. Delete the _exists and 1834 # _rexists attributes so they can be reevaluated. 1835 dirnode.clear() 1836 except OSError: 1837 pass
1838
1840 global MkdirBuilder 1841 return self.builder is not MkdirBuilder and self.has_builder()
1842
1843 - def alter_targets(self):
1844 """Return any corresponding targets in a variant directory. 1845 """ 1846 return self.fs.variant_dir_target_climb(self, self, [])
1847
1848 - def scanner_key(self):
1849 """A directory does not get scanned.""" 1850 return None
1851
1852 - def get_text_contents(self):
1853 """We already emit things in text, so just return the binary 1854 version.""" 1855 return self.get_contents()
1856
1857 - def get_contents(self):
1858 """Return content signatures and names of all our children 1859 separated by new-lines. Ensure that the nodes are sorted.""" 1860 return SCons.Node._get_contents_map[self._func_get_contents](self)
1861
1862 - def get_csig(self):
1863 """Compute the content signature for Directory nodes. In 1864 general, this is not needed and the content signature is not 1865 stored in the DirNodeInfo. However, if get_contents on a Dir 1866 node is called which has a child directory, the child 1867 directory should return the hash of its contents.""" 1868 contents = self.get_contents() 1869 return SCons.Util.MD5signature(contents)
1870
1871 - def do_duplicate(self, src):
1872 pass
1873
1874 - def is_up_to_date(self):
1875 """If any child is not up-to-date, then this directory isn't, 1876 either.""" 1877 if self.builder is not MkdirBuilder and not self.exists(): 1878 return 0 1879 up_to_date = SCons.Node.up_to_date 1880 for kid in self.children(): 1881 if kid.get_state() > up_to_date: 1882 return 0 1883 return 1
1884
1885 - def rdir(self):
1886 if not self.exists(): 1887 norm_name = _my_normcase(self.name) 1888 for dir in self.dir.get_all_rdirs(): 1889 try: node = dir.entries[norm_name] 1890 except KeyError: node = dir.dir_on_disk(self.name) 1891 if node and node.exists() and \ 1892 (isinstance(dir, Dir) or isinstance(dir, Entry)): 1893 return node 1894 return self
1895
1896 - def sconsign(self):
1897 """Return the .sconsign file info for this directory. """ 1898 return _sconsign_map[self._func_sconsign](self)
1899
1900 - def srcnode(self):
1901 """Dir has a special need for srcnode()...if we 1902 have a srcdir attribute set, then that *is* our srcnode.""" 1903 if self.srcdir: 1904 return self.srcdir 1905 return Base.srcnode(self)
1906
1907 - def get_timestamp(self):
1908 """Return the latest timestamp from among our children""" 1909 stamp = 0 1910 for kid in self.children(): 1911 if kid.get_timestamp() > stamp: 1912 stamp = kid.get_timestamp() 1913 return stamp
1914
1915 - def get_abspath(self):
1916 """Get the absolute path of the file.""" 1917 return self._abspath
1918
1919 - def get_labspath(self):
1920 """Get the absolute path of the file.""" 1921 return self._labspath
1922
1923 - def get_internal_path(self):
1924 return self._path
1925
1926 - def get_tpath(self):
1927 return self._tpath
1928
1929 - def get_path_elements(self):
1930 return self._path_elements
1931
1932 - def entry_abspath(self, name):
1933 return self._abspath + OS_SEP + name
1934
1935 - def entry_labspath(self, name):
1936 return self._labspath + '/' + name
1937
1938 - def entry_path(self, name):
1939 return self._path + OS_SEP + name
1940
1941 - def entry_tpath(self, name):
1942 return self._tpath + OS_SEP + name
1943
1944 - def entry_exists_on_disk(self, name):
1945 """ Searches through the file/dir entries of the current 1946 directory, and returns True if a physical entry with the given 1947 name could be found. 1948 1949 @see rentry_exists_on_disk 1950 """ 1951 try: 1952 d = self.on_disk_entries 1953 except AttributeError: 1954 d = {} 1955 try: 1956 entries = os.listdir(self._abspath) 1957 except OSError: 1958 pass 1959 else: 1960 for entry in map(_my_normcase, entries): 1961 d[entry] = True 1962 self.on_disk_entries = d 1963 if sys.platform == 'win32' or sys.platform == 'cygwin': 1964 name = _my_normcase(name) 1965 result = d.get(name) 1966 if result is None: 1967 # Belt-and-suspenders for Windows: check directly for 1968 # 8.3 file names that don't show up in os.listdir(). 1969 result = os.path.exists(self._abspath + OS_SEP + name) 1970 d[name] = result 1971 return result 1972 else: 1973 return name in d
1974
1975 - def rentry_exists_on_disk(self, name):
1976 """ Searches through the file/dir entries of the current 1977 *and* all its remote directories (repos), and returns 1978 True if a physical entry with the given name could be found. 1979 The local directory (self) gets searched first, so 1980 repositories take a lower precedence regarding the 1981 searching order. 1982 1983 @see entry_exists_on_disk 1984 """ 1985 1986 rentry_exists = self.entry_exists_on_disk(name) 1987 if not rentry_exists: 1988 # Search through the repository folders 1989 norm_name = _my_normcase(name) 1990 for rdir in self.get_all_rdirs(): 1991 try: 1992 node = rdir.entries[norm_name] 1993 if node: 1994 rentry_exists = True 1995 break 1996 except KeyError: 1997 if rdir.entry_exists_on_disk(name): 1998 rentry_exists = True 1999 break 2000 return rentry_exists
2001 2002 @SCons.Memoize.CountMethodCall
2003 - def srcdir_list(self):
2004 try: 2005 return self._memo['srcdir_list'] 2006 except KeyError: 2007 pass 2008 2009 result = [] 2010 2011 dirname = '.' 2012 dir = self 2013 while dir: 2014 if dir.srcdir: 2015 result.append(dir.srcdir.Dir(dirname)) 2016 dirname = dir.name + OS_SEP + dirname 2017 dir = dir.up() 2018 2019 self._memo['srcdir_list'] = result 2020 2021 return result
2022
2023 - def srcdir_duplicate(self, name):
2024 for dir in self.srcdir_list(): 2025 if self.is_under(dir): 2026 # We shouldn't source from something in the build path; 2027 # variant_dir is probably under src_dir, in which case 2028 # we are reflecting. 2029 break 2030 if dir.entry_exists_on_disk(name): 2031 srcnode = dir.Entry(name).disambiguate() 2032 if self.duplicate: 2033 node = self.Entry(name).disambiguate() 2034 node.do_duplicate(srcnode) 2035 return node 2036 else: 2037 return srcnode 2038 return None
2039
2040 - def _srcdir_find_file_key(self, filename):
2041 return filename
2042 2043 @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
2044 - def srcdir_find_file(self, filename):
2045 try: 2046 memo_dict = self._memo['srcdir_find_file'] 2047 except KeyError: 2048 memo_dict = {} 2049 self._memo['srcdir_find_file'] = memo_dict 2050 else: 2051 try: 2052 return memo_dict[filename] 2053 except KeyError: 2054 pass 2055 2056 def func(node): 2057 if (isinstance(node, File) or isinstance(node, Entry)) and \ 2058 (node.is_derived() or node.exists()): 2059 return node 2060 return None
2061 2062 norm_name = _my_normcase(filename) 2063 2064 for rdir in self.get_all_rdirs(): 2065 try: node = rdir.entries[norm_name] 2066 except KeyError: node = rdir.file_on_disk(filename) 2067 else: node = func(node) 2068 if node: 2069 result = (node, self) 2070 memo_dict[filename] = result 2071 return result 2072 2073 for srcdir in self.srcdir_list(): 2074 for rdir in srcdir.get_all_rdirs(): 2075 try: node = rdir.entries[norm_name] 2076 except KeyError: node = rdir.file_on_disk(filename) 2077 else: node = func(node) 2078 if node: 2079 result = (File(filename, self, self.fs), srcdir) 2080 memo_dict[filename] = result 2081 return result 2082 2083 result = (None, None) 2084 memo_dict[filename] = result 2085 return result
2086
2087 - def dir_on_disk(self, name):
2088 if self.entry_exists_on_disk(name): 2089 try: return self.Dir(name) 2090 except TypeError: pass 2091 node = self.srcdir_duplicate(name) 2092 if isinstance(node, File): 2093 return None 2094 return node
2095
2096 - def file_on_disk(self, name):
2097 if self.entry_exists_on_disk(name) or \ 2098 diskcheck_rcs(self, name) or \ 2099 diskcheck_sccs(self, name): 2100 try: return self.File(name) 2101 except TypeError: pass 2102 node = self.srcdir_duplicate(name) 2103 if isinstance(node, Dir): 2104 return None 2105 return node
2106
2107 - def walk(self, func, arg):
2108 """ 2109 Walk this directory tree by calling the specified function 2110 for each directory in the tree. 2111 2112 This behaves like the os.path.walk() function, but for in-memory 2113 Node.FS.Dir objects. The function takes the same arguments as 2114 the functions passed to os.path.walk(): 2115 2116 func(arg, dirname, fnames) 2117 2118 Except that "dirname" will actually be the directory *Node*, 2119 not the string. The '.' and '..' entries are excluded from 2120 fnames. The fnames list may be modified in-place to filter the 2121 subdirectories visited or otherwise impose a specific order. 2122 The "arg" argument is always passed to func() and may be used 2123 in any way (or ignored, passing None is common). 2124 """ 2125 entries = self.entries 2126 names = list(entries.keys()) 2127 names.remove('.') 2128 names.remove('..') 2129 func(arg, self, names) 2130 for dirname in [n for n in names if isinstance(entries[n], Dir)]: 2131 entries[dirname].walk(func, arg)
2132
2133 - def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None):
2134 """ 2135 Returns a list of Nodes (or strings) matching a specified 2136 pathname pattern. 2137 2138 Pathname patterns follow UNIX shell semantics: * matches 2139 any-length strings of any characters, ? matches any character, 2140 and [] can enclose lists or ranges of characters. Matches do 2141 not span directory separators. 2142 2143 The matches take into account Repositories, returning local 2144 Nodes if a corresponding entry exists in a Repository (either 2145 an in-memory Node or something on disk). 2146 2147 By defafult, the glob() function matches entries that exist 2148 on-disk, in addition to in-memory Nodes. Setting the "ondisk" 2149 argument to False (or some other non-true value) causes the glob() 2150 function to only match in-memory Nodes. The default behavior is 2151 to return both the on-disk and in-memory Nodes. 2152 2153 The "source" argument, when true, specifies that corresponding 2154 source Nodes must be returned if you're globbing in a build 2155 directory (initialized with VariantDir()). The default behavior 2156 is to return Nodes local to the VariantDir(). 2157 2158 The "strings" argument, when true, returns the matches as strings, 2159 not Nodes. The strings are path names relative to this directory. 2160 2161 The "exclude" argument, if not None, must be a pattern or a list 2162 of patterns following the same UNIX shell semantics. 2163 Elements matching a least one pattern of this list will be excluded 2164 from the result. 2165 2166 The underlying algorithm is adapted from the glob.glob() function 2167 in the Python library (but heavily modified), and uses fnmatch() 2168 under the covers. 2169 """ 2170 dirname, basename = os.path.split(pathname) 2171 if not dirname: 2172 result = self._glob1(basename, ondisk, source, strings) 2173 else: 2174 if has_glob_magic(dirname): 2175 list = self.glob(dirname, ondisk, source, False, exclude) 2176 else: 2177 list = [self.Dir(dirname, create=True)] 2178 result = [] 2179 for dir in list: 2180 r = dir._glob1(basename, ondisk, source, strings) 2181 if strings: 2182 r = [os.path.join(str(dir), x) for x in r] 2183 result.extend(r) 2184 if exclude: 2185 excludes = [] 2186 excludeList = SCons.Util.flatten(exclude) 2187 for x in excludeList: 2188 r = self.glob(x, ondisk, source, strings) 2189 excludes.extend(r) 2190 result = filter(lambda x: not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes)), result) 2191 return sorted(result, key=lambda a: str(a))
2192
2193 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2194 """ 2195 Globs for and returns a list of entry names matching a single 2196 pattern in this directory. 2197 2198 This searches any repositories and source directories for 2199 corresponding entries and returns a Node (or string) relative 2200 to the current directory if an entry is found anywhere. 2201 2202 TODO: handle pattern with no wildcard 2203 """ 2204 search_dir_list = self.get_all_rdirs() 2205 for srcdir in self.srcdir_list(): 2206 search_dir_list.extend(srcdir.get_all_rdirs()) 2207 2208 selfEntry = self.Entry 2209 names = [] 2210 for dir in search_dir_list: 2211 # We use the .name attribute from the Node because the keys of 2212 # the dir.entries dictionary are normalized (that is, all upper 2213 # case) on case-insensitive systems like Windows. 2214 node_names = [ v.name for k, v in dir.entries.items() 2215 if k not in ('.', '..') ] 2216 names.extend(node_names) 2217 if not strings: 2218 # Make sure the working directory (self) actually has 2219 # entries for all Nodes in repositories or variant dirs. 2220 for name in node_names: selfEntry(name) 2221 if ondisk: 2222 try: 2223 disk_names = os.listdir(dir._abspath) 2224 except os.error: 2225 continue 2226 names.extend(disk_names) 2227 if not strings: 2228 # We're going to return corresponding Nodes in 2229 # the local directory, so we need to make sure 2230 # those Nodes exist. We only want to create 2231 # Nodes for the entries that will match the 2232 # specified pattern, though, which means we 2233 # need to filter the list here, even though 2234 # the overall list will also be filtered later, 2235 # after we exit this loop. 2236 if pattern[0] != '.': 2237 #disk_names = [ d for d in disk_names if d[0] != '.' ] 2238 disk_names = [x for x in disk_names if x[0] != '.'] 2239 disk_names = fnmatch.filter(disk_names, pattern) 2240 dirEntry = dir.Entry 2241 for name in disk_names: 2242 # Add './' before disk filename so that '#' at 2243 # beginning of filename isn't interpreted. 2244 name = './' + name 2245 node = dirEntry(name).disambiguate() 2246 n = selfEntry(name) 2247 if n.__class__ != node.__class__: 2248 n.__class__ = node.__class__ 2249 n._morph() 2250 2251 names = set(names) 2252 if pattern[0] != '.': 2253 names = [x for x in names if x[0] != '.'] 2254 names = fnmatch.filter(names, pattern) 2255 2256 if strings: 2257 return names 2258 2259 return [self.entries[_my_normcase(n)] for n in names]
2260
2261 -class RootDir(Dir):
2262 """A class for the root directory of a file system. 2263 2264 This is the same as a Dir class, except that the path separator 2265 ('/' or '\\') is actually part of the name, so we don't need to 2266 add a separator when creating the path names of entries within 2267 this directory. 2268 """ 2269 2270 __slots__ = ['_lookupDict'] 2271
2272 - def __init__(self, drive, fs):
2273 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir') 2274 SCons.Node.Node.__init__(self) 2275 2276 # Handle all the types of drives: 2277 if drive == '': 2278 # No drive, regular UNIX root or Windows default drive. 2279 name = OS_SEP 2280 dirname = OS_SEP 2281 elif drive == '//': 2282 # UNC path 2283 name = UNC_PREFIX 2284 dirname = UNC_PREFIX 2285 else: 2286 # Windows drive letter 2287 name = drive 2288 dirname = drive + OS_SEP 2289 2290 #: Filename with extension as it was specified when the object was 2291 #: created; to obtain filesystem path, use Python str() function 2292 self.name = SCons.Util.silent_intern(name) 2293 self.fs = fs #: Reference to parent Node.FS object 2294 2295 self._path_elements = [self] 2296 self.dir = self 2297 self._func_rexists = 2 2298 self._func_target_from_source = 1 2299 self.store_info = 1 2300 2301 # Now set our paths to what we really want them to be. The 2302 # name should already contain any necessary separators, such 2303 # as the initial drive letter (the name) plus the directory 2304 # separator, except for the "lookup abspath," which does not 2305 # have the drive letter. 2306 self._abspath = dirname 2307 self._labspath = '' 2308 self._path = dirname 2309 self._tpath = dirname 2310 self.dirname = dirname 2311 2312 self._morph() 2313 2314 self.duplicate = 0 2315 self._lookupDict = {} 2316 2317 self._lookupDict[''] = self 2318 self._lookupDict['/'] = self 2319 self.root = self 2320 # The // entry is necessary because os.path.normpath() 2321 # preserves double slashes at the beginning of a path on Posix 2322 # platforms. 2323 if not has_unc: 2324 self._lookupDict['//'] = self
2325
2326 - def _morph(self):
2327 """Turn a file system Node (either a freshly initialized directory 2328 object or a separate Entry object) into a proper directory object. 2329 2330 Set up this directory's entries and hook it into the file 2331 system tree. Specify that directories (this Node) don't use 2332 signatures for calculating whether they're current. 2333 """ 2334 2335 self.repositories = [] 2336 self.srcdir = None 2337 2338 self.entries = {} 2339 self.entries['.'] = self 2340 self.entries['..'] = self.dir 2341 self.cwd = self 2342 self.searched = 0 2343 self._sconsign = None 2344 self.variant_dirs = [] 2345 self.changed_since_last_build = 3 2346 self._func_sconsign = 1 2347 self._func_exists = 2 2348 self._func_get_contents = 2 2349 2350 # Don't just reset the executor, replace its action list, 2351 # because it might have some pre-or post-actions that need to 2352 # be preserved. 2353 # 2354 # But don't reset the executor if there is a non-null executor 2355 # attached already. The existing executor might have other 2356 # targets, in which case replacing the action list with a 2357 # Mkdir action is a big mistake. 2358 if not hasattr(self, 'executor'): 2359 self.builder = get_MkdirBuilder() 2360 self.get_executor().set_action_list(self.builder.action) 2361 else: 2362 # Prepend MkdirBuilder action to existing action list 2363 l = self.get_executor().action_list 2364 a = get_MkdirBuilder().action 2365 l.insert(0, a) 2366 self.get_executor().set_action_list(l)
2367 2368
2369 - def must_be_same(self, klass):
2370 if klass is Dir: 2371 return 2372 Base.must_be_same(self, klass)
2373
2374 - def _lookup_abs(self, p, klass, create=1):
2375 """ 2376 Fast (?) lookup of a *normalized* absolute path. 2377 2378 This method is intended for use by internal lookups with 2379 already-normalized path data. For general-purpose lookups, 2380 use the FS.Entry(), FS.Dir() or FS.File() methods. 2381 2382 The caller is responsible for making sure we're passed a 2383 normalized absolute path; we merely let Python's dictionary look 2384 up and return the One True Node.FS object for the path. 2385 2386 If a Node for the specified "p" doesn't already exist, and 2387 "create" is specified, the Node may be created after recursive 2388 invocation to find or create the parent directory or directories. 2389 """ 2390 k = _my_normcase(p) 2391 try: 2392 result = self._lookupDict[k] 2393 except KeyError: 2394 if not create: 2395 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self)) 2396 raise SCons.Errors.UserError(msg) 2397 # There is no Node for this path name, and we're allowed 2398 # to create it. 2399 dir_name, file_name = p.rsplit('/',1) 2400 dir_node = self._lookup_abs(dir_name, Dir) 2401 result = klass(file_name, dir_node, self.fs) 2402 2403 # Double-check on disk (as configured) that the Node we 2404 # created matches whatever is out there in the real world. 2405 result.diskcheck_match() 2406 2407 self._lookupDict[k] = result 2408 dir_node.entries[_my_normcase(file_name)] = result 2409 dir_node.implicit = None 2410 else: 2411 # There is already a Node for this path name. Allow it to 2412 # complain if we were looking for an inappropriate type. 2413 result.must_be_same(klass) 2414 return result
2415
2416 - def __str__(self):
2417 return self._abspath
2418
2419 - def entry_abspath(self, name):
2420 return self._abspath + name
2421
2422 - def entry_labspath(self, name):
2423 return '/' + name
2424
2425 - def entry_path(self, name):
2426 return self._path + name
2427
2428 - def entry_tpath(self, name):
2429 return self._tpath + name
2430
2431 - def is_under(self, dir):
2432 if self is dir: 2433 return 1 2434 else: 2435 return 0
2436
2437 - def up(self):
2438 return None
2439
2440 - def get_dir(self):
2441 return None
2442
2443 - def src_builder(self):
2444 return _null
2445
2446 -class FileNodeInfo(SCons.Node.NodeInfoBase):
2447 __slots__ = ('csig', 'timestamp', 'size') 2448 current_version_id = 2 2449 2450 field_list = ['csig', 'timestamp', 'size'] 2451 2452 # This should get reset by the FS initialization. 2453 fs = None 2454
2455 - def str_to_node(self, s):
2456 top = self.fs.Top 2457 root = top.root 2458 if do_splitdrive: 2459 drive, s = _my_splitdrive(s) 2460 if drive: 2461 root = self.fs.get_root(drive) 2462 if not os.path.isabs(s): 2463 s = top.get_labspath() + '/' + s 2464 return root._lookup_abs(s, Entry)
2465
2466 - def __getstate__(self):
2467 """ 2468 Return all fields that shall be pickled. Walk the slots in the class 2469 hierarchy and add those to the state dictionary. If a '__dict__' slot is 2470 available, copy all entries to the dictionary. Also include the version 2471 id, which is fixed for all instances of a class. 2472 """ 2473 state = getattr(self, '__dict__', {}).copy() 2474 for obj in type(self).mro(): 2475 for name in getattr(obj,'__slots__',()): 2476 if hasattr(self, name): 2477 state[name] = getattr(self, name) 2478 2479 state['_version_id'] = self.current_version_id 2480 try: 2481 del state['__weakref__'] 2482 except KeyError: 2483 pass 2484 2485 return state
2486
2487 - def __setstate__(self, state):
2488 """ 2489 Restore the attributes from a pickled state. 2490 """ 2491 # TODO check or discard version 2492 del state['_version_id'] 2493 for key, value in state.items(): 2494 if key not in ('__weakref__',): 2495 setattr(self, key, value)
2496
2497 -class FileBuildInfo(SCons.Node.BuildInfoBase):
2498 __slots__ = () 2499 current_version_id = 2 2500
2501 - def convert_to_sconsign(self):
2502 """ 2503 Converts this FileBuildInfo object for writing to a .sconsign file 2504 2505 This replaces each Node in our various dependency lists with its 2506 usual string representation: relative to the top-level SConstruct 2507 directory, or an absolute path if it's outside. 2508 """ 2509 if os_sep_is_slash: 2510 node_to_str = str 2511 else: 2512 def node_to_str(n): 2513 try: 2514 s = n.get_internal_path() 2515 except AttributeError: 2516 s = str(n) 2517 else: 2518 s = s.replace(OS_SEP, '/') 2519 return s
2520 for attr in ['bsources', 'bdepends', 'bimplicit']: 2521 try: 2522 val = getattr(self, attr) 2523 except AttributeError: 2524 pass 2525 else: 2526 setattr(self, attr, list(map(node_to_str, val)))
2527 - def convert_from_sconsign(self, dir, name):
2528 """ 2529 Converts a newly-read FileBuildInfo object for in-SCons use 2530 2531 For normal up-to-date checking, we don't have any conversion to 2532 perform--but we're leaving this method here to make that clear. 2533 """ 2534 pass
2535 - def prepare_dependencies(self):
2536 """ 2537 Prepares a FileBuildInfo object for explaining what changed 2538 2539 The bsources, bdepends and bimplicit lists have all been 2540 stored on disk as paths relative to the top-level SConstruct 2541 directory. Convert the strings to actual Nodes (for use by the 2542 --debug=explain code and --implicit-cache). 2543 """ 2544 attrs = [ 2545 ('bsources', 'bsourcesigs'), 2546 ('bdepends', 'bdependsigs'), 2547 ('bimplicit', 'bimplicitsigs'), 2548 ] 2549 for (nattr, sattr) in attrs: 2550 try: 2551 strings = getattr(self, nattr) 2552 nodeinfos = getattr(self, sattr) 2553 except AttributeError: 2554 continue 2555 if strings is None or nodeinfos is None: 2556 continue 2557 nodes = [] 2558 for s, ni in zip(strings, nodeinfos): 2559 if not isinstance(s, SCons.Node.Node): 2560 s = ni.str_to_node(s) 2561 nodes.append(s) 2562 setattr(self, nattr, nodes)
2563 - def format(self, names=0):
2564 result = [] 2565 bkids = self.bsources + self.bdepends + self.bimplicit 2566 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs 2567 for bkid, bkidsig in zip(bkids, bkidsigs): 2568 result.append(str(bkid) + ': ' + 2569 ' '.join(bkidsig.format(names=names))) 2570 if not hasattr(self,'bact'): 2571 self.bact = "none" 2572 result.append('%s [%s]' % (self.bactsig, self.bact)) 2573 return '\n'.join(result)
2574
2575 -class File(Base):
2576 """A class for files in a file system. 2577 """ 2578 2579 __slots__ = ['scanner_paths', 2580 'cachedir_csig', 2581 'cachesig', 2582 'repositories', 2583 'srcdir', 2584 'entries', 2585 'searched', 2586 '_sconsign', 2587 'variant_dirs', 2588 'root', 2589 'dirname', 2590 'on_disk_entries', 2591 'sccs_dir', 2592 'rcs_dir', 2593 'released_target_info', 2594 'contentsig'] 2595 2596 NodeInfo = FileNodeInfo 2597 BuildInfo = FileBuildInfo 2598 2599 md5_chunksize = 64 2600
2601 - def diskcheck_match(self):
2602 diskcheck_match(self, self.isdir, 2603 "Directory %s found where file expected.")
2604
2605 - def __init__(self, name, directory, fs):
2606 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.File') 2607 Base.__init__(self, name, directory, fs) 2608 self._morph()
2609
2610 - def Entry(self, name):
2611 """Create an entry node named 'name' relative to 2612 the directory of this file.""" 2613 return self.dir.Entry(name)
2614
2615 - def Dir(self, name, create=True):
2616 """Create a directory node named 'name' relative to 2617 the directory of this file.""" 2618 return self.dir.Dir(name, create=create)
2619
2620 - def Dirs(self, pathlist):
2621 """Create a list of directories relative to the SConscript 2622 directory of this file.""" 2623 return [self.Dir(p) for p in pathlist]
2624
2625 - def File(self, name):
2626 """Create a file node named 'name' relative to 2627 the directory of this file.""" 2628 return self.dir.File(name)
2629 2630 #def generate_build_dict(self): 2631 # """Return an appropriate dictionary of values for building 2632 # this File.""" 2633 # return {'Dir' : self.Dir, 2634 # 'File' : self.File, 2635 # 'RDirs' : self.RDirs} 2636
2637 - def _morph(self):
2638 """Turn a file system node into a File object.""" 2639 self.scanner_paths = {} 2640 if not hasattr(self, '_local'): 2641 self._local = 0 2642 if not hasattr(self, 'released_target_info'): 2643 self.released_target_info = False 2644 2645 self.store_info = 1 2646 self._func_exists = 4 2647 self._func_get_contents = 3 2648 2649 # Initialize this Node's decider function to decide_source() because 2650 # every file is a source file until it has a Builder attached... 2651 self.changed_since_last_build = 4 2652 2653 # If there was already a Builder set on this entry, then 2654 # we need to make sure we call the target-decider function, 2655 # not the source-decider. Reaching in and doing this by hand 2656 # is a little bogus. We'd prefer to handle this by adding 2657 # an Entry.builder_set() method that disambiguates like the 2658 # other methods, but that starts running into problems with the 2659 # fragile way we initialize Dir Nodes with their Mkdir builders, 2660 # yet still allow them to be overridden by the user. Since it's 2661 # not clear right now how to fix that, stick with what works 2662 # until it becomes clear... 2663 if self.has_builder(): 2664 self.changed_since_last_build = 5
2665
2666 - def scanner_key(self):
2667 return self.get_suffix()
2668
2669 - def get_contents(self):
2671 2672 # This attempts to figure out what the encoding of the text is 2673 # based upon the BOM bytes, and then decodes the contents so that 2674 # it's a valid python string.
2675 - def get_text_contents(self):
2676 contents = self.get_contents() 2677 # The behavior of various decode() methods and functions 2678 # w.r.t. the initial BOM bytes is different for different 2679 # encodings and/or Python versions. ('utf-8' does not strip 2680 # them, but has a 'utf-8-sig' which does; 'utf-16' seems to 2681 # strip them; etc.) Just sidestep all the complication by 2682 # explicitly stripping the BOM before we decode(). 2683 if contents.startswith(codecs.BOM_UTF8): 2684 return contents[len(codecs.BOM_UTF8):].decode('utf-8') 2685 if contents.startswith(codecs.BOM_UTF16_LE): 2686 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le') 2687 if contents.startswith(codecs.BOM_UTF16_BE): 2688 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be') 2689 return contents
2690
2691 - def get_content_hash(self):
2692 """ 2693 Compute and return the MD5 hash for this file. 2694 """ 2695 if not self.rexists(): 2696 return SCons.Util.MD5signature('') 2697 fname = self.rfile().get_abspath() 2698 try: 2699 cs = SCons.Util.MD5filesignature(fname, 2700 chunksize=SCons.Node.FS.File.md5_chunksize*1024) 2701 except EnvironmentError, e: 2702 if not e.filename: 2703 e.filename = fname 2704 raise 2705 return cs
2706 2707 @SCons.Memoize.CountMethodCall
2708 - def get_size(self):
2709 try: 2710 return self._memo['get_size'] 2711 except KeyError: 2712 pass 2713 2714 if self.rexists(): 2715 size = self.rfile().getsize() 2716 else: 2717 size = 0 2718 2719 self._memo['get_size'] = size 2720 2721 return size
2722 2723 @SCons.Memoize.CountMethodCall
2724 - def get_timestamp(self):
2725 try: 2726 return self._memo['get_timestamp'] 2727 except KeyError: 2728 pass 2729 2730 if self.rexists(): 2731 timestamp = self.rfile().getmtime() 2732 else: 2733 timestamp = 0 2734 2735 self._memo['get_timestamp'] = timestamp 2736 2737 return timestamp
2738 2739 convert_copy_attrs = [ 2740 'bsources', 2741 'bimplicit', 2742 'bdepends', 2743 'bact', 2744 'bactsig', 2745 'ninfo', 2746 ] 2747 2748 2749 convert_sig_attrs = [ 2750 'bsourcesigs', 2751 'bimplicitsigs', 2752 'bdependsigs', 2753 ] 2754
2755 - def convert_old_entry(self, old_entry):
2756 # Convert a .sconsign entry from before the Big Signature 2757 # Refactoring, doing what we can to convert its information 2758 # to the new .sconsign entry format. 2759 # 2760 # The old format looked essentially like this: 2761 # 2762 # BuildInfo 2763 # .ninfo (NodeInfo) 2764 # .bsig 2765 # .csig 2766 # .timestamp 2767 # .size 2768 # .bsources 2769 # .bsourcesigs ("signature" list) 2770 # .bdepends 2771 # .bdependsigs ("signature" list) 2772 # .bimplicit 2773 # .bimplicitsigs ("signature" list) 2774 # .bact 2775 # .bactsig 2776 # 2777 # The new format looks like this: 2778 # 2779 # .ninfo (NodeInfo) 2780 # .bsig 2781 # .csig 2782 # .timestamp 2783 # .size 2784 # .binfo (BuildInfo) 2785 # .bsources 2786 # .bsourcesigs (NodeInfo list) 2787 # .bsig 2788 # .csig 2789 # .timestamp 2790 # .size 2791 # .bdepends 2792 # .bdependsigs (NodeInfo list) 2793 # .bsig 2794 # .csig 2795 # .timestamp 2796 # .size 2797 # .bimplicit 2798 # .bimplicitsigs (NodeInfo list) 2799 # .bsig 2800 # .csig 2801 # .timestamp 2802 # .size 2803 # .bact 2804 # .bactsig 2805 # 2806 # The basic idea of the new structure is that a NodeInfo always 2807 # holds all available information about the state of a given Node 2808 # at a certain point in time. The various .b*sigs lists can just 2809 # be a list of pointers to the .ninfo attributes of the different 2810 # dependent nodes, without any copying of information until it's 2811 # time to pickle it for writing out to a .sconsign file. 2812 # 2813 # The complicating issue is that the *old* format only stored one 2814 # "signature" per dependency, based on however the *last* build 2815 # was configured. We don't know from just looking at it whether 2816 # it was a build signature, a content signature, or a timestamp 2817 # "signature". Since we no longer use build signatures, the 2818 # best we can do is look at the length and if it's thirty two, 2819 # assume that it was (or might have been) a content signature. 2820 # If it was actually a build signature, then it will cause a 2821 # rebuild anyway when it doesn't match the new content signature, 2822 # but that's probably the best we can do. 2823 import SCons.SConsign 2824 new_entry = SCons.SConsign.SConsignEntry() 2825 new_entry.binfo = self.new_binfo() 2826 binfo = new_entry.binfo 2827 for attr in self.convert_copy_attrs: 2828 try: 2829 value = getattr(old_entry, attr) 2830 except AttributeError: 2831 continue 2832 setattr(binfo, attr, value) 2833 delattr(old_entry, attr) 2834 for attr in self.convert_sig_attrs: 2835 try: 2836 sig_list = getattr(old_entry, attr) 2837 except AttributeError: 2838 continue 2839 value = [] 2840 for sig in sig_list: 2841 ninfo = self.new_ninfo() 2842 if len(sig) == 32: 2843 ninfo.csig = sig 2844 else: 2845 ninfo.timestamp = sig 2846 value.append(ninfo) 2847 setattr(binfo, attr, value) 2848 delattr(old_entry, attr) 2849 return new_entry
2850 2851 @SCons.Memoize.CountMethodCall
2852 - def get_stored_info(self):
2853 try: 2854 return self._memo['get_stored_info'] 2855 except KeyError: 2856 pass 2857 2858 try: 2859 sconsign_entry = self.dir.sconsign().get_entry(self.name) 2860 except (KeyError, EnvironmentError): 2861 import SCons.SConsign 2862 sconsign_entry = SCons.SConsign.SConsignEntry() 2863 sconsign_entry.binfo = self.new_binfo() 2864 sconsign_entry.ninfo = self.new_ninfo() 2865 else: 2866 if isinstance(sconsign_entry, FileBuildInfo): 2867 # This is a .sconsign file from before the Big Signature 2868 # Refactoring; convert it as best we can. 2869 sconsign_entry = self.convert_old_entry(sconsign_entry) 2870 try: 2871 delattr(sconsign_entry.ninfo, 'bsig') 2872 except AttributeError: 2873 pass 2874 2875 self._memo['get_stored_info'] = sconsign_entry 2876 2877 return sconsign_entry
2878
2879 - def get_stored_implicit(self):
2880 binfo = self.get_stored_info().binfo 2881 binfo.prepare_dependencies() 2882 try: return binfo.bimplicit 2883 except AttributeError: return None
2884
2885 - def rel_path(self, other):
2886 return self.dir.rel_path(other)
2887
2888 - def _get_found_includes_key(self, env, scanner, path):
2889 return (id(env), id(scanner), path)
2890 2891 @SCons.Memoize.CountDictCall(_get_found_includes_key)
2892 - def get_found_includes(self, env, scanner, path):
2893 """Return the included implicit dependencies in this file. 2894 Cache results so we only scan the file once per path 2895 regardless of how many times this information is requested. 2896 """ 2897 memo_key = (id(env), id(scanner), path) 2898 try: 2899 memo_dict = self._memo['get_found_includes'] 2900 except KeyError: 2901 memo_dict = {} 2902 self._memo['get_found_includes'] = memo_dict 2903 else: 2904 try: 2905 return memo_dict[memo_key] 2906 except KeyError: 2907 pass 2908 2909 if scanner: 2910 # result = [n.disambiguate() for n in scanner(self, env, path)] 2911 result = scanner(self, env, path) 2912 result = [N.disambiguate() for N in result] 2913 else: 2914 result = [] 2915 2916 memo_dict[memo_key] = result 2917 2918 return result
2919
2920 - def _createDir(self):
2921 # ensure that the directories for this node are 2922 # created. 2923 self.dir._create()
2924
2925 - def push_to_cache(self):
2926 """Try to push the node into a cache 2927 """ 2928 # This should get called before the Nodes' .built() method is 2929 # called, which would clear the build signature if the file has 2930 # a source scanner. 2931 # 2932 # We have to clear the local memoized values *before* we push 2933 # the node to cache so that the memoization of the self.exists() 2934 # return value doesn't interfere. 2935 if self.nocache: 2936 return 2937 self.clear_memoized_values() 2938 if self.exists(): 2939 self.get_build_env().get_CacheDir().push(self)
2940
2941 - def retrieve_from_cache(self):
2942 """Try to retrieve the node's content from a cache 2943 2944 This method is called from multiple threads in a parallel build, 2945 so only do thread safe stuff here. Do thread unsafe stuff in 2946 built(). 2947 2948 Returns true if the node was successfully retrieved. 2949 """ 2950 if self.nocache: 2951 return None 2952 if not self.is_derived(): 2953 return None 2954 return self.get_build_env().get_CacheDir().retrieve(self)
2955
2956 - def visited(self):
2957 if self.exists() and self.executor is not None: 2958 self.get_build_env().get_CacheDir().push_if_forced(self) 2959 2960 ninfo = self.get_ninfo() 2961 2962 csig = self.get_max_drift_csig() 2963 if csig: 2964 ninfo.csig = csig 2965 2966 ninfo.timestamp = self.get_timestamp() 2967 ninfo.size = self.get_size() 2968 2969 if not self.has_builder(): 2970 # This is a source file, but it might have been a target file 2971 # in another build that included more of the DAG. Copy 2972 # any build information that's stored in the .sconsign file 2973 # into our binfo object so it doesn't get lost. 2974 old = self.get_stored_info() 2975 self.get_binfo().merge(old.binfo) 2976 2977 SCons.Node.store_info_map[self.store_info](self)
2978
2979 - def release_target_info(self):
2980 """Called just after this node has been marked 2981 up-to-date or was built completely. 2982 2983 This is where we try to release as many target node infos 2984 as possible for clean builds and update runs, in order 2985 to minimize the overall memory consumption. 2986 2987 We'd like to remove a lot more attributes like self.sources 2988 and self.sources_set, but they might get used 2989 in a next build step. For example, during configuration 2990 the source files for a built *.o file are used to figure out 2991 which linker to use for the resulting Program (gcc vs. g++)! 2992 That's why we check for the 'keep_targetinfo' attribute, 2993 config Nodes and the Interactive mode just don't allow 2994 an early release of most variables. 2995 2996 In the same manner, we can't simply remove the self.attributes 2997 here. The smart linking relies on the shared flag, and some 2998 parts of the java Tool use it to transport information 2999 about nodes... 3000 3001 @see: built() and Node.release_target_info() 3002 """ 3003 if (self.released_target_info or SCons.Node.interactive): 3004 return 3005 3006 if not hasattr(self.attributes, 'keep_targetinfo'): 3007 # Cache some required values, before releasing 3008 # stuff like env, executor and builder... 3009 self.changed(allowcache=True) 3010 self.get_contents_sig() 3011 self.get_build_env() 3012 # Now purge unneeded stuff to free memory... 3013 self.executor = None 3014 self._memo.pop('rfile', None) 3015 self.prerequisites = None 3016 # Cleanup lists, but only if they're empty 3017 if not len(self.ignore_set): 3018 self.ignore_set = None 3019 if not len(self.implicit_set): 3020 self.implicit_set = None 3021 if not len(self.depends_set): 3022 self.depends_set = None 3023 if not len(self.ignore): 3024 self.ignore = None 3025 if not len(self.depends): 3026 self.depends = None 3027 # Mark this node as done, we only have to release 3028 # the memory once... 3029 self.released_target_info = True
3030
3031 - def find_src_builder(self):
3032 if self.rexists(): 3033 return None 3034 scb = self.dir.src_builder() 3035 if scb is _null: 3036 if diskcheck_sccs(self.dir, self.name): 3037 scb = get_DefaultSCCSBuilder() 3038 elif diskcheck_rcs(self.dir, self.name): 3039 scb = get_DefaultRCSBuilder() 3040 else: 3041 scb = None 3042 if scb is not None: 3043 try: 3044 b = self.builder 3045 except AttributeError: 3046 b = None 3047 if b is None: 3048 self.builder_set(scb) 3049 return scb
3050
3051 - def has_src_builder(self):
3052 """Return whether this Node has a source builder or not. 3053 3054 If this Node doesn't have an explicit source code builder, this 3055 is where we figure out, on the fly, if there's a transparent 3056 source code builder for it. 3057 3058 Note that if we found a source builder, we also set the 3059 self.builder attribute, so that all of the methods that actually 3060 *build* this file don't have to do anything different. 3061 """ 3062 try: 3063 scb = self.sbuilder 3064 except AttributeError: 3065 scb = self.sbuilder = self.find_src_builder() 3066 return scb is not None
3067
3068 - def alter_targets(self):
3069 """Return any corresponding targets in a variant directory. 3070 """ 3071 if self.is_derived(): 3072 return [], None 3073 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
3074
3075 - def _rmv_existing(self):
3076 self.clear_memoized_values() 3077 if SCons.Node.print_duplicate: 3078 print "dup: removing existing target %s"%self 3079 e = Unlink(self, [], None) 3080 if isinstance(e, SCons.Errors.BuildError): 3081 raise e
3082 3083 # 3084 # Taskmaster interface subsystem 3085 # 3086
3087 - def make_ready(self):
3088 self.has_src_builder() 3089 self.get_binfo()
3090
3091 - def prepare(self):
3092 """Prepare for this file to be created.""" 3093 SCons.Node.Node.prepare(self) 3094 3095 if self.get_state() != SCons.Node.up_to_date: 3096 if self.exists(): 3097 if self.is_derived() and not self.precious: 3098 self._rmv_existing() 3099 else: 3100 try: 3101 self._createDir() 3102 except SCons.Errors.StopError, drive: 3103 desc = "No drive `%s' for target `%s'." % (drive, self) 3104 raise SCons.Errors.StopError(desc)
3105 3106 # 3107 # 3108 # 3109
3110 - def remove(self):
3111 """Remove this file.""" 3112 if self.exists() or self.islink(): 3113 self.fs.unlink(self.get_internal_path()) 3114 return 1 3115 return None
3116
3117 - def do_duplicate(self, src):
3118 self._createDir() 3119 if SCons.Node.print_duplicate: 3120 print "dup: relinking variant '%s' from '%s'"%(self, src) 3121 Unlink(self, None, None) 3122 e = Link(self, src, None) 3123 if isinstance(e, SCons.Errors.BuildError): 3124 desc = "Cannot duplicate `%s' in `%s': %s." % (src.get_internal_path(), self.dir._path, e.errstr) 3125 raise SCons.Errors.StopError(desc) 3126 self.linked = 1 3127 # The Link() action may or may not have actually 3128 # created the file, depending on whether the -n 3129 # option was used or not. Delete the _exists and 3130 # _rexists attributes so they can be reevaluated. 3131 self.clear()
3132 3133 @SCons.Memoize.CountMethodCall
3134 - def exists(self):
3135 try: 3136 return self._memo['exists'] 3137 except KeyError: 3138 pass 3139 3140 result = SCons.Node._exists_map[self._func_exists](self) 3141 self._memo['exists'] = result 3142 return result
3143 3144 # 3145 # SIGNATURE SUBSYSTEM 3146 # 3147
3148 - def get_max_drift_csig(self):
3149 """ 3150 Returns the content signature currently stored for this node 3151 if it's been unmodified longer than the max_drift value, or the 3152 max_drift value is 0. Returns None otherwise. 3153 """ 3154 old = self.get_stored_info() 3155 mtime = self.get_timestamp() 3156 3157 max_drift = self.fs.max_drift 3158 if max_drift > 0: 3159 if (time.time() - mtime) > max_drift: 3160 try: 3161 n = old.ninfo 3162 if n.timestamp and n.csig and n.timestamp == mtime: 3163 return n.csig 3164 except AttributeError: 3165 pass 3166 elif max_drift == 0: 3167 try: 3168 return old.ninfo.csig 3169 except AttributeError: 3170 pass 3171 3172 return None
3173
3174 - def get_csig(self):
3175 """ 3176 Generate a node's content signature, the digested signature 3177 of its content. 3178 3179 node - the node 3180 cache - alternate node to use for the signature cache 3181 returns - the content signature 3182 """ 3183 ninfo = self.get_ninfo() 3184 try: 3185 return ninfo.csig 3186 except AttributeError: 3187 pass 3188 3189 csig = self.get_max_drift_csig() 3190 if csig is None: 3191 3192 try: 3193 if self.get_size() < SCons.Node.FS.File.md5_chunksize: 3194 contents = self.get_contents() 3195 else: 3196 csig = self.get_content_hash() 3197 except IOError: 3198 # This can happen if there's actually a directory on-disk, 3199 # which can be the case if they've disabled disk checks, 3200 # or if an action with a File target actually happens to 3201 # create a same-named directory by mistake. 3202 csig = '' 3203 else: 3204 if not csig: 3205 csig = SCons.Util.MD5signature(contents) 3206 3207 ninfo.csig = csig 3208 3209 return csig
3210 3211 # 3212 # DECISION SUBSYSTEM 3213 # 3214
3215 - def builder_set(self, builder):
3218
3219 - def built(self):
3220 """Called just after this File node is successfully built. 3221 3222 Just like for 'release_target_info' we try to release 3223 some more target node attributes in order to minimize the 3224 overall memory consumption. 3225 3226 @see: release_target_info 3227 """ 3228 3229 SCons.Node.Node.built(self) 3230 3231 if (not SCons.Node.interactive and 3232 not hasattr(self.attributes, 'keep_targetinfo')): 3233 # Ensure that the build infos get computed and cached... 3234 SCons.Node.store_info_map[self.store_info](self) 3235 # ... then release some more variables. 3236 self._specific_sources = False 3237 self._labspath = None 3238 self._save_str() 3239 self.cwd = None 3240 3241 self.scanner_paths = None
3242
3243 - def changed(self, node=None, allowcache=False):
3244 """ 3245 Returns if the node is up-to-date with respect to the BuildInfo 3246 stored last time it was built. 3247 3248 For File nodes this is basically a wrapper around Node.changed(), 3249 but we allow the return value to get cached after the reference 3250 to the Executor got released in release_target_info(). 3251 3252 @see: Node.changed() 3253 """ 3254 if node is None: 3255 try: 3256 return self._memo['changed'] 3257 except KeyError: 3258 pass 3259 3260 has_changed = SCons.Node.Node.changed(self, node) 3261 if allowcache: 3262 self._memo['changed'] = has_changed 3263 return has_changed
3264
3265 - def changed_content(self, target, prev_ni):
3266 cur_csig = self.get_csig() 3267 try: 3268 return cur_csig != prev_ni.csig 3269 except AttributeError: 3270 return 1
3271
3272 - def changed_state(self, target, prev_ni):
3273 return self.state != SCons.Node.up_to_date
3274
3275 - def changed_timestamp_then_content(self, target, prev_ni):
3276 if not self.changed_timestamp_match(target, prev_ni): 3277 try: 3278 self.get_ninfo().csig = prev_ni.csig 3279 except AttributeError: 3280 pass 3281 return False 3282 return self.changed_content(target, prev_ni)
3283
3284 - def changed_timestamp_newer(self, target, prev_ni):
3285 try: 3286 return self.get_timestamp() > target.get_timestamp() 3287 except AttributeError: 3288 return 1
3289
3290 - def changed_timestamp_match(self, target, prev_ni):
3291 try: 3292 return self.get_timestamp() != prev_ni.timestamp 3293 except AttributeError: 3294 return 1
3295
3296 - def is_up_to_date(self):
3297 T = 0 3298 if T: Trace('is_up_to_date(%s):' % self) 3299 if not self.exists(): 3300 if T: Trace(' not self.exists():') 3301 # The file doesn't exist locally... 3302 r = self.rfile() 3303 if r != self: 3304 # ...but there is one in a Repository... 3305 if not self.changed(r): 3306 if T: Trace(' changed(%s):' % r) 3307 # ...and it's even up-to-date... 3308 if self._local: 3309 # ...and they'd like a local copy. 3310 e = LocalCopy(self, r, None) 3311 if isinstance(e, SCons.Errors.BuildError): 3312 raise 3313 SCons.Node.store_info_map[self.store_info](self) 3314 if T: Trace(' 1\n') 3315 return 1 3316 self.changed() 3317 if T: Trace(' None\n') 3318 return None 3319 else: 3320 r = self.changed() 3321 if T: Trace(' self.exists(): %s\n' % r) 3322 return not r
3323 3324 @SCons.Memoize.CountMethodCall
3325 - def rfile(self):
3326 try: 3327 return self._memo['rfile'] 3328 except KeyError: 3329 pass 3330 result = self 3331 if not self.exists(): 3332 norm_name = _my_normcase(self.name) 3333 for dir in self.dir.get_all_rdirs(): 3334 try: node = dir.entries[norm_name] 3335 except KeyError: node = dir.file_on_disk(self.name) 3336 if node and node.exists() and \ 3337 (isinstance(node, File) or isinstance(node, Entry) \ 3338 or not node.is_derived()): 3339 result = node 3340 # Copy over our local attributes to the repository 3341 # Node so we identify shared object files in the 3342 # repository and don't assume they're static. 3343 # 3344 # This isn't perfect; the attribute would ideally 3345 # be attached to the object in the repository in 3346 # case it was built statically in the repository 3347 # and we changed it to shared locally, but that's 3348 # rarely the case and would only occur if you 3349 # intentionally used the same suffix for both 3350 # shared and static objects anyway. So this 3351 # should work well in practice. 3352 result.attributes = self.attributes 3353 break 3354 self._memo['rfile'] = result 3355 return result
3356
3357 - def rstr(self):
3358 return str(self.rfile())
3359
3360 - def get_cachedir_csig(self):
3361 """ 3362 Fetch a Node's content signature for purposes of computing 3363 another Node's cachesig. 3364 3365 This is a wrapper around the normal get_csig() method that handles 3366 the somewhat obscure case of using CacheDir with the -n option. 3367 Any files that don't exist would normally be "built" by fetching 3368 them from the cache, but the normal get_csig() method will try 3369 to open up the local file, which doesn't exist because the -n 3370 option meant we didn't actually pull the file from cachedir. 3371 But since the file *does* actually exist in the cachedir, we 3372 can use its contents for the csig. 3373 """ 3374 try: 3375 return self.cachedir_csig 3376 except AttributeError: 3377 pass 3378 3379 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self) 3380 if not self.exists() and cachefile and os.path.exists(cachefile): 3381 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \ 3382 SCons.Node.FS.File.md5_chunksize * 1024) 3383 else: 3384 self.cachedir_csig = self.get_csig() 3385 return self.cachedir_csig
3386
3387 - def get_contents_sig(self):
3388 """ 3389 A helper method for get_cachedir_bsig. 3390 3391 It computes and returns the signature for this 3392 node's contents. 3393 """ 3394 3395 try: 3396 return self.contentsig 3397 except AttributeError: 3398 pass 3399 3400 executor = self.get_executor() 3401 3402 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents()) 3403 return result
3404
3405 - def get_cachedir_bsig(self):
3406 """ 3407 Return the signature for a cached file, including 3408 its children. 3409 3410 It adds the path of the cached file to the cache signature, 3411 because multiple targets built by the same action will all 3412 have the same build signature, and we have to differentiate 3413 them somehow. 3414 """ 3415 try: 3416 return self.cachesig 3417 except AttributeError: 3418 pass 3419 3420 # Collect signatures for all children 3421 children = self.children() 3422 sigs = [n.get_cachedir_csig() for n in children] 3423 # Append this node's signature... 3424 sigs.append(self.get_contents_sig()) 3425 # ...and it's path 3426 sigs.append(self.get_internal_path()) 3427 # Merge this all into a single signature 3428 result = self.cachesig = SCons.Util.MD5collect(sigs) 3429 return result
3430 3431 default_fs = None
3432 3433 -def get_default_fs():
3434 global default_fs 3435 if not default_fs: 3436 default_fs = FS() 3437 return default_fs
3438
3439 -class FileFinder(object):
3440 """ 3441 """ 3442
3443 - def __init__(self):
3444 self._memo = {}
3445
3446 - def filedir_lookup(self, p, fd=None):
3447 """ 3448 A helper method for find_file() that looks up a directory for 3449 a file we're trying to find. This only creates the Dir Node if 3450 it exists on-disk, since if the directory doesn't exist we know 3451 we won't find any files in it... :-) 3452 3453 It would be more compact to just use this as a nested function 3454 with a default keyword argument (see the commented-out version 3455 below), but that doesn't work unless you have nested scopes, 3456 so we define it here just so this work under Python 1.5.2. 3457 """ 3458 if fd is None: 3459 fd = self.default_filedir 3460 dir, name = os.path.split(fd) 3461 drive, d = _my_splitdrive(dir) 3462 if not name and d[:1] in ('/', OS_SEP): 3463 #return p.fs.get_root(drive).dir_on_disk(name) 3464 return p.fs.get_root(drive) 3465 if dir: 3466 p = self.filedir_lookup(p, dir) 3467 if not p: 3468 return None 3469 norm_name = _my_normcase(name) 3470 try: 3471 node = p.entries[norm_name] 3472 except KeyError: 3473 return p.dir_on_disk(name) 3474 if isinstance(node, Dir): 3475 return node 3476 if isinstance(node, Entry): 3477 node.must_be_same(Dir) 3478 return node 3479 return None
3480
3481 - def _find_file_key(self, filename, paths, verbose=None):
3482 return (filename, paths)
3483 3484 @SCons.Memoize.CountDictCall(_find_file_key)
3485 - def find_file(self, filename, paths, verbose=None):
3486 """ 3487 find_file(str, [Dir()]) -> [nodes] 3488 3489 filename - a filename to find 3490 paths - a list of directory path *nodes* to search in. Can be 3491 represented as a list, a tuple, or a callable that is 3492 called with no arguments and returns the list or tuple. 3493 3494 returns - the node created from the found file. 3495 3496 Find a node corresponding to either a derived file or a file 3497 that exists already. 3498 3499 Only the first file found is returned, and none is returned 3500 if no file is found. 3501 """ 3502 memo_key = self._find_file_key(filename, paths) 3503 try: 3504 memo_dict = self._memo['find_file'] 3505 except KeyError: 3506 memo_dict = {} 3507 self._memo['find_file'] = memo_dict 3508 else: 3509 try: 3510 return memo_dict[memo_key] 3511 except KeyError: 3512 pass 3513 3514 if verbose and not callable(verbose): 3515 if not SCons.Util.is_String(verbose): 3516 verbose = "find_file" 3517 _verbose = u' %s: ' % verbose 3518 verbose = lambda s: sys.stdout.write(_verbose + s) 3519 3520 filedir, filename = os.path.split(filename) 3521 if filedir: 3522 # More compact code that we can't use until we drop 3523 # support for Python 1.5.2: 3524 # 3525 #def filedir_lookup(p, fd=filedir): 3526 # """ 3527 # A helper function that looks up a directory for a file 3528 # we're trying to find. This only creates the Dir Node 3529 # if it exists on-disk, since if the directory doesn't 3530 # exist we know we won't find any files in it... :-) 3531 # """ 3532 # dir, name = os.path.split(fd) 3533 # if dir: 3534 # p = filedir_lookup(p, dir) 3535 # if not p: 3536 # return None 3537 # norm_name = _my_normcase(name) 3538 # try: 3539 # node = p.entries[norm_name] 3540 # except KeyError: 3541 # return p.dir_on_disk(name) 3542 # if isinstance(node, Dir): 3543 # return node 3544 # if isinstance(node, Entry): 3545 # node.must_be_same(Dir) 3546 # return node 3547 # if isinstance(node, Dir) or isinstance(node, Entry): 3548 # return node 3549 # return None 3550 #paths = [_f for _f in map(filedir_lookup, paths) if _f] 3551 3552 self.default_filedir = filedir 3553 paths = [_f for _f in map(self.filedir_lookup, paths) if _f] 3554 3555 result = None 3556 for dir in paths: 3557 if verbose: 3558 verbose("looking for '%s' in '%s' ...\n" % (filename, dir)) 3559 node, d = dir.srcdir_find_file(filename) 3560 if node: 3561 if verbose: 3562 verbose("... FOUND '%s' in '%s'\n" % (filename, d)) 3563 result = node 3564 break 3565 3566 memo_dict[memo_key] = result 3567 3568 return result
3569 3570 find_file = FileFinder().find_file
3571 3572 3573 -def invalidate_node_memos(targets):
3574 """ 3575 Invalidate the memoized values of all Nodes (files or directories) 3576 that are associated with the given entries. Has been added to 3577 clear the cache of nodes affected by a direct execution of an 3578 action (e.g. Delete/Copy/Chmod). Existing Node caches become 3579 inconsistent if the action is run through Execute(). The argument 3580 `targets` can be a single Node object or filename, or a sequence 3581 of Nodes/filenames. 3582 """ 3583 from traceback import extract_stack 3584 3585 # First check if the cache really needs to be flushed. Only 3586 # actions run in the SConscript with Execute() seem to be 3587 # affected. XXX The way to check if Execute() is in the stacktrace 3588 # is a very dirty hack and should be replaced by a more sensible 3589 # solution. 3590 for f in extract_stack(): 3591 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py': 3592 break 3593 else: 3594 # Dont have to invalidate, so return 3595 return 3596 3597 if not SCons.Util.is_List(targets): 3598 targets = [targets] 3599 3600 for entry in targets: 3601 # If the target is a Node object, clear the cache. If it is a 3602 # filename, look up potentially existing Node object first. 3603 try: 3604 entry.clear_memoized_values() 3605 except AttributeError: 3606 # Not a Node object, try to look up Node by filename. XXX 3607 # This creates Node objects even for those filenames which 3608 # do not correspond to an existing Node object. 3609 node = get_default_fs().Entry(entry) 3610 if node: 3611 node.clear_memoized_values()
3612 3613 # Local Variables: 3614 # tab-width:4 3615 # indent-tabs-mode:nil 3616 # End: 3617 # vim: set expandtab tabstop=4 shiftwidth=4: 3618