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.0:3365:9259ea1c13d7 2015/09/21 14:03:43 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 result = filter(lambda x: not any(fnmatch.fnmatch(str(x), e) for e in SCons.Util.flatten(exclude)), result) 2186 return sorted(result, key=lambda a: str(a))
2187
2188 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2189 """ 2190 Globs for and returns a list of entry names matching a single 2191 pattern in this directory. 2192 2193 This searches any repositories and source directories for 2194 corresponding entries and returns a Node (or string) relative 2195 to the current directory if an entry is found anywhere. 2196 2197 TODO: handle pattern with no wildcard 2198 """ 2199 search_dir_list = self.get_all_rdirs() 2200 for srcdir in self.srcdir_list(): 2201 search_dir_list.extend(srcdir.get_all_rdirs()) 2202 2203 selfEntry = self.Entry 2204 names = [] 2205 for dir in search_dir_list: 2206 # We use the .name attribute from the Node because the keys of 2207 # the dir.entries dictionary are normalized (that is, all upper 2208 # case) on case-insensitive systems like Windows. 2209 node_names = [ v.name for k, v in dir.entries.items() 2210 if k not in ('.', '..') ] 2211 names.extend(node_names) 2212 if not strings: 2213 # Make sure the working directory (self) actually has 2214 # entries for all Nodes in repositories or variant dirs. 2215 for name in node_names: selfEntry(name) 2216 if ondisk: 2217 try: 2218 disk_names = os.listdir(dir._abspath) 2219 except os.error: 2220 continue 2221 names.extend(disk_names) 2222 if not strings: 2223 # We're going to return corresponding Nodes in 2224 # the local directory, so we need to make sure 2225 # those Nodes exist. We only want to create 2226 # Nodes for the entries that will match the 2227 # specified pattern, though, which means we 2228 # need to filter the list here, even though 2229 # the overall list will also be filtered later, 2230 # after we exit this loop. 2231 if pattern[0] != '.': 2232 #disk_names = [ d for d in disk_names if d[0] != '.' ] 2233 disk_names = [x for x in disk_names if x[0] != '.'] 2234 disk_names = fnmatch.filter(disk_names, pattern) 2235 dirEntry = dir.Entry 2236 for name in disk_names: 2237 # Add './' before disk filename so that '#' at 2238 # beginning of filename isn't interpreted. 2239 name = './' + name 2240 node = dirEntry(name).disambiguate() 2241 n = selfEntry(name) 2242 if n.__class__ != node.__class__: 2243 n.__class__ = node.__class__ 2244 n._morph() 2245 2246 names = set(names) 2247 if pattern[0] != '.': 2248 names = [x for x in names if x[0] != '.'] 2249 names = fnmatch.filter(names, pattern) 2250 2251 if strings: 2252 return names 2253 2254 return [self.entries[_my_normcase(n)] for n in names]
2255
2256 -class RootDir(Dir):
2257 """A class for the root directory of a file system. 2258 2259 This is the same as a Dir class, except that the path separator 2260 ('/' or '\\') is actually part of the name, so we don't need to 2261 add a separator when creating the path names of entries within 2262 this directory. 2263 """ 2264 2265 __slots__ = ['_lookupDict'] 2266
2267 - def __init__(self, drive, fs):
2268 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir') 2269 SCons.Node.Node.__init__(self) 2270 2271 # Handle all the types of drives: 2272 if drive == '': 2273 # No drive, regular UNIX root or Windows default drive. 2274 name = OS_SEP 2275 dirname = OS_SEP 2276 elif drive == '//': 2277 # UNC path 2278 name = UNC_PREFIX 2279 dirname = UNC_PREFIX 2280 else: 2281 # Windows drive letter 2282 name = drive 2283 dirname = drive + OS_SEP 2284 2285 #: Filename with extension as it was specified when the object was 2286 #: created; to obtain filesystem path, use Python str() function 2287 self.name = SCons.Util.silent_intern(name) 2288 self.fs = fs #: Reference to parent Node.FS object 2289 2290 self._path_elements = [self] 2291 self.dir = self 2292 self._func_rexists = 2 2293 self._func_target_from_source = 1 2294 self.store_info = 1 2295 2296 # Now set our paths to what we really want them to be. The 2297 # name should already contain any necessary separators, such 2298 # as the initial drive letter (the name) plus the directory 2299 # separator, except for the "lookup abspath," which does not 2300 # have the drive letter. 2301 self._abspath = dirname 2302 self._labspath = '' 2303 self._path = dirname 2304 self._tpath = dirname 2305 self.dirname = dirname 2306 2307 self._morph() 2308 2309 self.duplicate = 0 2310 self._lookupDict = {} 2311 2312 self._lookupDict[''] = self 2313 self._lookupDict['/'] = self 2314 self.root = self 2315 # The // entry is necessary because os.path.normpath() 2316 # preserves double slashes at the beginning of a path on Posix 2317 # platforms. 2318 if not has_unc: 2319 self._lookupDict['//'] = self
2320
2321 - def _morph(self):
2322 """Turn a file system Node (either a freshly initialized directory 2323 object or a separate Entry object) into a proper directory object. 2324 2325 Set up this directory's entries and hook it into the file 2326 system tree. Specify that directories (this Node) don't use 2327 signatures for calculating whether they're current. 2328 """ 2329 2330 self.repositories = [] 2331 self.srcdir = None 2332 2333 self.entries = {} 2334 self.entries['.'] = self 2335 self.entries['..'] = self.dir 2336 self.cwd = self 2337 self.searched = 0 2338 self._sconsign = None 2339 self.variant_dirs = [] 2340 self.changed_since_last_build = 3 2341 self._func_sconsign = 1 2342 self._func_exists = 2 2343 self._func_get_contents = 2 2344 2345 # Don't just reset the executor, replace its action list, 2346 # because it might have some pre-or post-actions that need to 2347 # be preserved. 2348 # 2349 # But don't reset the executor if there is a non-null executor 2350 # attached already. The existing executor might have other 2351 # targets, in which case replacing the action list with a 2352 # Mkdir action is a big mistake. 2353 if not hasattr(self, 'executor'): 2354 self.builder = get_MkdirBuilder() 2355 self.get_executor().set_action_list(self.builder.action) 2356 else: 2357 # Prepend MkdirBuilder action to existing action list 2358 l = self.get_executor().action_list 2359 a = get_MkdirBuilder().action 2360 l.insert(0, a) 2361 self.get_executor().set_action_list(l)
2362 2363
2364 - def must_be_same(self, klass):
2365 if klass is Dir: 2366 return 2367 Base.must_be_same(self, klass)
2368
2369 - def _lookup_abs(self, p, klass, create=1):
2370 """ 2371 Fast (?) lookup of a *normalized* absolute path. 2372 2373 This method is intended for use by internal lookups with 2374 already-normalized path data. For general-purpose lookups, 2375 use the FS.Entry(), FS.Dir() or FS.File() methods. 2376 2377 The caller is responsible for making sure we're passed a 2378 normalized absolute path; we merely let Python's dictionary look 2379 up and return the One True Node.FS object for the path. 2380 2381 If a Node for the specified "p" doesn't already exist, and 2382 "create" is specified, the Node may be created after recursive 2383 invocation to find or create the parent directory or directories. 2384 """ 2385 k = _my_normcase(p) 2386 try: 2387 result = self._lookupDict[k] 2388 except KeyError: 2389 if not create: 2390 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self)) 2391 raise SCons.Errors.UserError(msg) 2392 # There is no Node for this path name, and we're allowed 2393 # to create it. 2394 dir_name, file_name = p.rsplit('/',1) 2395 dir_node = self._lookup_abs(dir_name, Dir) 2396 result = klass(file_name, dir_node, self.fs) 2397 2398 # Double-check on disk (as configured) that the Node we 2399 # created matches whatever is out there in the real world. 2400 result.diskcheck_match() 2401 2402 self._lookupDict[k] = result 2403 dir_node.entries[_my_normcase(file_name)] = result 2404 dir_node.implicit = None 2405 else: 2406 # There is already a Node for this path name. Allow it to 2407 # complain if we were looking for an inappropriate type. 2408 result.must_be_same(klass) 2409 return result
2410
2411 - def __str__(self):
2412 return self._abspath
2413
2414 - def entry_abspath(self, name):
2415 return self._abspath + name
2416
2417 - def entry_labspath(self, name):
2418 return '/' + name
2419
2420 - def entry_path(self, name):
2421 return self._path + name
2422
2423 - def entry_tpath(self, name):
2424 return self._tpath + name
2425
2426 - def is_under(self, dir):
2427 if self is dir: 2428 return 1 2429 else: 2430 return 0
2431
2432 - def up(self):
2433 return None
2434
2435 - def get_dir(self):
2436 return None
2437
2438 - def src_builder(self):
2439 return _null
2440
2441 -class FileNodeInfo(SCons.Node.NodeInfoBase):
2442 __slots__ = ('csig', 'timestamp', 'size') 2443 current_version_id = 2 2444 2445 field_list = ['csig', 'timestamp', 'size'] 2446 2447 # This should get reset by the FS initialization. 2448 fs = None 2449
2450 - def str_to_node(self, s):
2451 top = self.fs.Top 2452 root = top.root 2453 if do_splitdrive: 2454 drive, s = _my_splitdrive(s) 2455 if drive: 2456 root = self.fs.get_root(drive) 2457 if not os.path.isabs(s): 2458 s = top.get_labspath() + '/' + s 2459 return root._lookup_abs(s, Entry)
2460
2461 - def __getstate__(self):
2462 """ 2463 Return all fields that shall be pickled. Walk the slots in the class 2464 hierarchy and add those to the state dictionary. If a '__dict__' slot is 2465 available, copy all entries to the dictionary. Also include the version 2466 id, which is fixed for all instances of a class. 2467 """ 2468 state = getattr(self, '__dict__', {}).copy() 2469 for obj in type(self).mro(): 2470 for name in getattr(obj,'__slots__',()): 2471 if hasattr(self, name): 2472 state[name] = getattr(self, name) 2473 2474 state['_version_id'] = self.current_version_id 2475 try: 2476 del state['__weakref__'] 2477 except KeyError: 2478 pass 2479 2480 return state
2481
2482 - def __setstate__(self, state):
2483 """ 2484 Restore the attributes from a pickled state. 2485 """ 2486 # TODO check or discard version 2487 del state['_version_id'] 2488 for key, value in state.items(): 2489 if key not in ('__weakref__',): 2490 setattr(self, key, value)
2491
2492 -class FileBuildInfo(SCons.Node.BuildInfoBase):
2493 __slots__ = () 2494 current_version_id = 2 2495
2496 - def convert_to_sconsign(self):
2497 """ 2498 Converts this FileBuildInfo object for writing to a .sconsign file 2499 2500 This replaces each Node in our various dependency lists with its 2501 usual string representation: relative to the top-level SConstruct 2502 directory, or an absolute path if it's outside. 2503 """ 2504 if os_sep_is_slash: 2505 node_to_str = str 2506 else: 2507 def node_to_str(n): 2508 try: 2509 s = n.get_internal_path() 2510 except AttributeError: 2511 s = str(n) 2512 else: 2513 s = s.replace(OS_SEP, '/') 2514 return s
2515 for attr in ['bsources', 'bdepends', 'bimplicit']: 2516 try: 2517 val = getattr(self, attr) 2518 except AttributeError: 2519 pass 2520 else: 2521 setattr(self, attr, list(map(node_to_str, val)))
2522 - def convert_from_sconsign(self, dir, name):
2523 """ 2524 Converts a newly-read FileBuildInfo object for in-SCons use 2525 2526 For normal up-to-date checking, we don't have any conversion to 2527 perform--but we're leaving this method here to make that clear. 2528 """ 2529 pass
2530 - def prepare_dependencies(self):
2531 """ 2532 Prepares a FileBuildInfo object for explaining what changed 2533 2534 The bsources, bdepends and bimplicit lists have all been 2535 stored on disk as paths relative to the top-level SConstruct 2536 directory. Convert the strings to actual Nodes (for use by the 2537 --debug=explain code and --implicit-cache). 2538 """ 2539 attrs = [ 2540 ('bsources', 'bsourcesigs'), 2541 ('bdepends', 'bdependsigs'), 2542 ('bimplicit', 'bimplicitsigs'), 2543 ] 2544 for (nattr, sattr) in attrs: 2545 try: 2546 strings = getattr(self, nattr) 2547 nodeinfos = getattr(self, sattr) 2548 except AttributeError: 2549 continue 2550 if strings is None or nodeinfos is None: 2551 continue 2552 nodes = [] 2553 for s, ni in zip(strings, nodeinfos): 2554 if not isinstance(s, SCons.Node.Node): 2555 s = ni.str_to_node(s) 2556 nodes.append(s) 2557 setattr(self, nattr, nodes)
2558 - def format(self, names=0):
2559 result = [] 2560 bkids = self.bsources + self.bdepends + self.bimplicit 2561 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs 2562 for bkid, bkidsig in zip(bkids, bkidsigs): 2563 result.append(str(bkid) + ': ' + 2564 ' '.join(bkidsig.format(names=names))) 2565 if not hasattr(self,'bact'): 2566 self.bact = "none" 2567 result.append('%s [%s]' % (self.bactsig, self.bact)) 2568 return '\n'.join(result)
2569
2570 -class File(Base):
2571 """A class for files in a file system. 2572 """ 2573 2574 __slots__ = ['scanner_paths', 2575 'cachedir_csig', 2576 'cachesig', 2577 'repositories', 2578 'srcdir', 2579 'entries', 2580 'searched', 2581 '_sconsign', 2582 'variant_dirs', 2583 'root', 2584 'dirname', 2585 'on_disk_entries', 2586 'sccs_dir', 2587 'rcs_dir', 2588 'released_target_info', 2589 'contentsig'] 2590 2591 NodeInfo = FileNodeInfo 2592 BuildInfo = FileBuildInfo 2593 2594 md5_chunksize = 64 2595
2596 - def diskcheck_match(self):
2597 diskcheck_match(self, self.isdir, 2598 "Directory %s found where file expected.")
2599
2600 - def __init__(self, name, directory, fs):
2601 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.File') 2602 Base.__init__(self, name, directory, fs) 2603 self._morph()
2604
2605 - def Entry(self, name):
2606 """Create an entry node named 'name' relative to 2607 the directory of this file.""" 2608 return self.dir.Entry(name)
2609
2610 - def Dir(self, name, create=True):
2611 """Create a directory node named 'name' relative to 2612 the directory of this file.""" 2613 return self.dir.Dir(name, create=create)
2614
2615 - def Dirs(self, pathlist):
2616 """Create a list of directories relative to the SConscript 2617 directory of this file.""" 2618 return [self.Dir(p) for p in pathlist]
2619
2620 - def File(self, name):
2621 """Create a file node named 'name' relative to 2622 the directory of this file.""" 2623 return self.dir.File(name)
2624 2625 #def generate_build_dict(self): 2626 # """Return an appropriate dictionary of values for building 2627 # this File.""" 2628 # return {'Dir' : self.Dir, 2629 # 'File' : self.File, 2630 # 'RDirs' : self.RDirs} 2631
2632 - def _morph(self):
2633 """Turn a file system node into a File object.""" 2634 self.scanner_paths = {} 2635 if not hasattr(self, '_local'): 2636 self._local = 0 2637 if not hasattr(self, 'released_target_info'): 2638 self.released_target_info = False 2639 2640 self.store_info = 1 2641 self._func_exists = 4 2642 self._func_get_contents = 3 2643 2644 # Initialize this Node's decider function to decide_source() because 2645 # every file is a source file until it has a Builder attached... 2646 self.changed_since_last_build = 4 2647 2648 # If there was already a Builder set on this entry, then 2649 # we need to make sure we call the target-decider function, 2650 # not the source-decider. Reaching in and doing this by hand 2651 # is a little bogus. We'd prefer to handle this by adding 2652 # an Entry.builder_set() method that disambiguates like the 2653 # other methods, but that starts running into problems with the 2654 # fragile way we initialize Dir Nodes with their Mkdir builders, 2655 # yet still allow them to be overridden by the user. Since it's 2656 # not clear right now how to fix that, stick with what works 2657 # until it becomes clear... 2658 if self.has_builder(): 2659 self.changed_since_last_build = 5
2660
2661 - def scanner_key(self):
2662 return self.get_suffix()
2663
2664 - def get_contents(self):
2666 2667 # This attempts to figure out what the encoding of the text is 2668 # based upon the BOM bytes, and then decodes the contents so that 2669 # it's a valid python string.
2670 - def get_text_contents(self):
2671 contents = self.get_contents() 2672 # The behavior of various decode() methods and functions 2673 # w.r.t. the initial BOM bytes is different for different 2674 # encodings and/or Python versions. ('utf-8' does not strip 2675 # them, but has a 'utf-8-sig' which does; 'utf-16' seems to 2676 # strip them; etc.) Just sidestep all the complication by 2677 # explicitly stripping the BOM before we decode(). 2678 if contents.startswith(codecs.BOM_UTF8): 2679 return contents[len(codecs.BOM_UTF8):].decode('utf-8') 2680 if contents.startswith(codecs.BOM_UTF16_LE): 2681 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le') 2682 if contents.startswith(codecs.BOM_UTF16_BE): 2683 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be') 2684 return contents
2685
2686 - def get_content_hash(self):
2687 """ 2688 Compute and return the MD5 hash for this file. 2689 """ 2690 if not self.rexists(): 2691 return SCons.Util.MD5signature('') 2692 fname = self.rfile().get_abspath() 2693 try: 2694 cs = SCons.Util.MD5filesignature(fname, 2695 chunksize=SCons.Node.FS.File.md5_chunksize*1024) 2696 except EnvironmentError, e: 2697 if not e.filename: 2698 e.filename = fname 2699 raise 2700 return cs
2701 2702 @SCons.Memoize.CountMethodCall
2703 - def get_size(self):
2704 try: 2705 return self._memo['get_size'] 2706 except KeyError: 2707 pass 2708 2709 if self.rexists(): 2710 size = self.rfile().getsize() 2711 else: 2712 size = 0 2713 2714 self._memo['get_size'] = size 2715 2716 return size
2717 2718 @SCons.Memoize.CountMethodCall
2719 - def get_timestamp(self):
2720 try: 2721 return self._memo['get_timestamp'] 2722 except KeyError: 2723 pass 2724 2725 if self.rexists(): 2726 timestamp = self.rfile().getmtime() 2727 else: 2728 timestamp = 0 2729 2730 self._memo['get_timestamp'] = timestamp 2731 2732 return timestamp
2733 2734 convert_copy_attrs = [ 2735 'bsources', 2736 'bimplicit', 2737 'bdepends', 2738 'bact', 2739 'bactsig', 2740 'ninfo', 2741 ] 2742 2743 2744 convert_sig_attrs = [ 2745 'bsourcesigs', 2746 'bimplicitsigs', 2747 'bdependsigs', 2748 ] 2749
2750 - def convert_old_entry(self, old_entry):
2751 # Convert a .sconsign entry from before the Big Signature 2752 # Refactoring, doing what we can to convert its information 2753 # to the new .sconsign entry format. 2754 # 2755 # The old format looked essentially like this: 2756 # 2757 # BuildInfo 2758 # .ninfo (NodeInfo) 2759 # .bsig 2760 # .csig 2761 # .timestamp 2762 # .size 2763 # .bsources 2764 # .bsourcesigs ("signature" list) 2765 # .bdepends 2766 # .bdependsigs ("signature" list) 2767 # .bimplicit 2768 # .bimplicitsigs ("signature" list) 2769 # .bact 2770 # .bactsig 2771 # 2772 # The new format looks like this: 2773 # 2774 # .ninfo (NodeInfo) 2775 # .bsig 2776 # .csig 2777 # .timestamp 2778 # .size 2779 # .binfo (BuildInfo) 2780 # .bsources 2781 # .bsourcesigs (NodeInfo list) 2782 # .bsig 2783 # .csig 2784 # .timestamp 2785 # .size 2786 # .bdepends 2787 # .bdependsigs (NodeInfo list) 2788 # .bsig 2789 # .csig 2790 # .timestamp 2791 # .size 2792 # .bimplicit 2793 # .bimplicitsigs (NodeInfo list) 2794 # .bsig 2795 # .csig 2796 # .timestamp 2797 # .size 2798 # .bact 2799 # .bactsig 2800 # 2801 # The basic idea of the new structure is that a NodeInfo always 2802 # holds all available information about the state of a given Node 2803 # at a certain point in time. The various .b*sigs lists can just 2804 # be a list of pointers to the .ninfo attributes of the different 2805 # dependent nodes, without any copying of information until it's 2806 # time to pickle it for writing out to a .sconsign file. 2807 # 2808 # The complicating issue is that the *old* format only stored one 2809 # "signature" per dependency, based on however the *last* build 2810 # was configured. We don't know from just looking at it whether 2811 # it was a build signature, a content signature, or a timestamp 2812 # "signature". Since we no longer use build signatures, the 2813 # best we can do is look at the length and if it's thirty two, 2814 # assume that it was (or might have been) a content signature. 2815 # If it was actually a build signature, then it will cause a 2816 # rebuild anyway when it doesn't match the new content signature, 2817 # but that's probably the best we can do. 2818 import SCons.SConsign 2819 new_entry = SCons.SConsign.SConsignEntry() 2820 new_entry.binfo = self.new_binfo() 2821 binfo = new_entry.binfo 2822 for attr in self.convert_copy_attrs: 2823 try: 2824 value = getattr(old_entry, attr) 2825 except AttributeError: 2826 continue 2827 setattr(binfo, attr, value) 2828 delattr(old_entry, attr) 2829 for attr in self.convert_sig_attrs: 2830 try: 2831 sig_list = getattr(old_entry, attr) 2832 except AttributeError: 2833 continue 2834 value = [] 2835 for sig in sig_list: 2836 ninfo = self.new_ninfo() 2837 if len(sig) == 32: 2838 ninfo.csig = sig 2839 else: 2840 ninfo.timestamp = sig 2841 value.append(ninfo) 2842 setattr(binfo, attr, value) 2843 delattr(old_entry, attr) 2844 return new_entry
2845 2846 @SCons.Memoize.CountMethodCall
2847 - def get_stored_info(self):
2848 try: 2849 return self._memo['get_stored_info'] 2850 except KeyError: 2851 pass 2852 2853 try: 2854 sconsign_entry = self.dir.sconsign().get_entry(self.name) 2855 except (KeyError, EnvironmentError): 2856 import SCons.SConsign 2857 sconsign_entry = SCons.SConsign.SConsignEntry() 2858 sconsign_entry.binfo = self.new_binfo() 2859 sconsign_entry.ninfo = self.new_ninfo() 2860 else: 2861 if isinstance(sconsign_entry, FileBuildInfo): 2862 # This is a .sconsign file from before the Big Signature 2863 # Refactoring; convert it as best we can. 2864 sconsign_entry = self.convert_old_entry(sconsign_entry) 2865 try: 2866 delattr(sconsign_entry.ninfo, 'bsig') 2867 except AttributeError: 2868 pass 2869 2870 self._memo['get_stored_info'] = sconsign_entry 2871 2872 return sconsign_entry
2873
2874 - def get_stored_implicit(self):
2875 binfo = self.get_stored_info().binfo 2876 binfo.prepare_dependencies() 2877 try: return binfo.bimplicit 2878 except AttributeError: return None
2879
2880 - def rel_path(self, other):
2881 return self.dir.rel_path(other)
2882
2883 - def _get_found_includes_key(self, env, scanner, path):
2884 return (id(env), id(scanner), path)
2885 2886 @SCons.Memoize.CountDictCall(_get_found_includes_key)
2887 - def get_found_includes(self, env, scanner, path):
2888 """Return the included implicit dependencies in this file. 2889 Cache results so we only scan the file once per path 2890 regardless of how many times this information is requested. 2891 """ 2892 memo_key = (id(env), id(scanner), path) 2893 try: 2894 memo_dict = self._memo['get_found_includes'] 2895 except KeyError: 2896 memo_dict = {} 2897 self._memo['get_found_includes'] = memo_dict 2898 else: 2899 try: 2900 return memo_dict[memo_key] 2901 except KeyError: 2902 pass 2903 2904 if scanner: 2905 # result = [n.disambiguate() for n in scanner(self, env, path)] 2906 result = scanner(self, env, path) 2907 result = [N.disambiguate() for N in result] 2908 else: 2909 result = [] 2910 2911 memo_dict[memo_key] = result 2912 2913 return result
2914
2915 - def _createDir(self):
2916 # ensure that the directories for this node are 2917 # created. 2918 self.dir._create()
2919
2920 - def push_to_cache(self):
2921 """Try to push the node into a cache 2922 """ 2923 # This should get called before the Nodes' .built() method is 2924 # called, which would clear the build signature if the file has 2925 # a source scanner. 2926 # 2927 # We have to clear the local memoized values *before* we push 2928 # the node to cache so that the memoization of the self.exists() 2929 # return value doesn't interfere. 2930 if self.nocache: 2931 return 2932 self.clear_memoized_values() 2933 if self.exists(): 2934 self.get_build_env().get_CacheDir().push(self)
2935
2936 - def retrieve_from_cache(self):
2937 """Try to retrieve the node's content from a cache 2938 2939 This method is called from multiple threads in a parallel build, 2940 so only do thread safe stuff here. Do thread unsafe stuff in 2941 built(). 2942 2943 Returns true if the node was successfully retrieved. 2944 """ 2945 if self.nocache: 2946 return None 2947 if not self.is_derived(): 2948 return None 2949 return self.get_build_env().get_CacheDir().retrieve(self)
2950
2951 - def visited(self):
2952 if self.exists() and self.executor is not None: 2953 self.get_build_env().get_CacheDir().push_if_forced(self) 2954 2955 ninfo = self.get_ninfo() 2956 2957 csig = self.get_max_drift_csig() 2958 if csig: 2959 ninfo.csig = csig 2960 2961 ninfo.timestamp = self.get_timestamp() 2962 ninfo.size = self.get_size() 2963 2964 if not self.has_builder(): 2965 # This is a source file, but it might have been a target file 2966 # in another build that included more of the DAG. Copy 2967 # any build information that's stored in the .sconsign file 2968 # into our binfo object so it doesn't get lost. 2969 old = self.get_stored_info() 2970 self.get_binfo().merge(old.binfo) 2971 2972 SCons.Node.store_info_map[self.store_info](self)
2973
2974 - def release_target_info(self):
2975 """Called just after this node has been marked 2976 up-to-date or was built completely. 2977 2978 This is where we try to release as many target node infos 2979 as possible for clean builds and update runs, in order 2980 to minimize the overall memory consumption. 2981 2982 We'd like to remove a lot more attributes like self.sources 2983 and self.sources_set, but they might get used 2984 in a next build step. For example, during configuration 2985 the source files for a built *.o file are used to figure out 2986 which linker to use for the resulting Program (gcc vs. g++)! 2987 That's why we check for the 'keep_targetinfo' attribute, 2988 config Nodes and the Interactive mode just don't allow 2989 an early release of most variables. 2990 2991 In the same manner, we can't simply remove the self.attributes 2992 here. The smart linking relies on the shared flag, and some 2993 parts of the java Tool use it to transport information 2994 about nodes... 2995 2996 @see: built() and Node.release_target_info() 2997 """ 2998 if (self.released_target_info or SCons.Node.interactive): 2999 return 3000 3001 if not hasattr(self.attributes, 'keep_targetinfo'): 3002 # Cache some required values, before releasing 3003 # stuff like env, executor and builder... 3004 self.changed(allowcache=True) 3005 self.get_contents_sig() 3006 self.get_build_env() 3007 # Now purge unneeded stuff to free memory... 3008 self.executor = None 3009 self._memo.pop('rfile', None) 3010 self.prerequisites = None 3011 # Cleanup lists, but only if they're empty 3012 if not len(self.ignore_set): 3013 self.ignore_set = None 3014 if not len(self.implicit_set): 3015 self.implicit_set = None 3016 if not len(self.depends_set): 3017 self.depends_set = None 3018 if not len(self.ignore): 3019 self.ignore = None 3020 if not len(self.depends): 3021 self.depends = None 3022 # Mark this node as done, we only have to release 3023 # the memory once... 3024 self.released_target_info = True
3025
3026 - def find_src_builder(self):
3027 if self.rexists(): 3028 return None 3029 scb = self.dir.src_builder() 3030 if scb is _null: 3031 if diskcheck_sccs(self.dir, self.name): 3032 scb = get_DefaultSCCSBuilder() 3033 elif diskcheck_rcs(self.dir, self.name): 3034 scb = get_DefaultRCSBuilder() 3035 else: 3036 scb = None 3037 if scb is not None: 3038 try: 3039 b = self.builder 3040 except AttributeError: 3041 b = None 3042 if b is None: 3043 self.builder_set(scb) 3044 return scb
3045
3046 - def has_src_builder(self):
3047 """Return whether this Node has a source builder or not. 3048 3049 If this Node doesn't have an explicit source code builder, this 3050 is where we figure out, on the fly, if there's a transparent 3051 source code builder for it. 3052 3053 Note that if we found a source builder, we also set the 3054 self.builder attribute, so that all of the methods that actually 3055 *build* this file don't have to do anything different. 3056 """ 3057 try: 3058 scb = self.sbuilder 3059 except AttributeError: 3060 scb = self.sbuilder = self.find_src_builder() 3061 return scb is not None
3062
3063 - def alter_targets(self):
3064 """Return any corresponding targets in a variant directory. 3065 """ 3066 if self.is_derived(): 3067 return [], None 3068 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
3069
3070 - def _rmv_existing(self):
3071 self.clear_memoized_values() 3072 if SCons.Node.print_duplicate: 3073 print "dup: removing existing target %s"%self 3074 e = Unlink(self, [], None) 3075 if isinstance(e, SCons.Errors.BuildError): 3076 raise e
3077 3078 # 3079 # Taskmaster interface subsystem 3080 # 3081
3082 - def make_ready(self):
3083 self.has_src_builder() 3084 self.get_binfo()
3085
3086 - def prepare(self):
3087 """Prepare for this file to be created.""" 3088 SCons.Node.Node.prepare(self) 3089 3090 if self.get_state() != SCons.Node.up_to_date: 3091 if self.exists(): 3092 if self.is_derived() and not self.precious: 3093 self._rmv_existing() 3094 else: 3095 try: 3096 self._createDir() 3097 except SCons.Errors.StopError, drive: 3098 desc = "No drive `%s' for target `%s'." % (drive, self) 3099 raise SCons.Errors.StopError(desc)
3100 3101 # 3102 # 3103 # 3104
3105 - def remove(self):
3106 """Remove this file.""" 3107 if self.exists() or self.islink(): 3108 self.fs.unlink(self.get_internal_path()) 3109 return 1 3110 return None
3111
3112 - def do_duplicate(self, src):
3113 self._createDir() 3114 if SCons.Node.print_duplicate: 3115 print "dup: relinking variant '%s' from '%s'"%(self, src) 3116 Unlink(self, None, None) 3117 e = Link(self, src, None) 3118 if isinstance(e, SCons.Errors.BuildError): 3119 desc = "Cannot duplicate `%s' in `%s': %s." % (src.get_internal_path(), self.dir._path, e.errstr) 3120 raise SCons.Errors.StopError(desc) 3121 self.linked = 1 3122 # The Link() action may or may not have actually 3123 # created the file, depending on whether the -n 3124 # option was used or not. Delete the _exists and 3125 # _rexists attributes so they can be reevaluated. 3126 self.clear()
3127 3128 @SCons.Memoize.CountMethodCall
3129 - def exists(self):
3130 try: 3131 return self._memo['exists'] 3132 except KeyError: 3133 pass 3134 3135 result = SCons.Node._exists_map[self._func_exists](self) 3136 self._memo['exists'] = result 3137 return result
3138 3139 # 3140 # SIGNATURE SUBSYSTEM 3141 # 3142
3143 - def get_max_drift_csig(self):
3144 """ 3145 Returns the content signature currently stored for this node 3146 if it's been unmodified longer than the max_drift value, or the 3147 max_drift value is 0. Returns None otherwise. 3148 """ 3149 old = self.get_stored_info() 3150 mtime = self.get_timestamp() 3151 3152 max_drift = self.fs.max_drift 3153 if max_drift > 0: 3154 if (time.time() - mtime) > max_drift: 3155 try: 3156 n = old.ninfo 3157 if n.timestamp and n.csig and n.timestamp == mtime: 3158 return n.csig 3159 except AttributeError: 3160 pass 3161 elif max_drift == 0: 3162 try: 3163 return old.ninfo.csig 3164 except AttributeError: 3165 pass 3166 3167 return None
3168
3169 - def get_csig(self):
3170 """ 3171 Generate a node's content signature, the digested signature 3172 of its content. 3173 3174 node - the node 3175 cache - alternate node to use for the signature cache 3176 returns - the content signature 3177 """ 3178 ninfo = self.get_ninfo() 3179 try: 3180 return ninfo.csig 3181 except AttributeError: 3182 pass 3183 3184 csig = self.get_max_drift_csig() 3185 if csig is None: 3186 3187 try: 3188 if self.get_size() < SCons.Node.FS.File.md5_chunksize: 3189 contents = self.get_contents() 3190 else: 3191 csig = self.get_content_hash() 3192 except IOError: 3193 # This can happen if there's actually a directory on-disk, 3194 # which can be the case if they've disabled disk checks, 3195 # or if an action with a File target actually happens to 3196 # create a same-named directory by mistake. 3197 csig = '' 3198 else: 3199 if not csig: 3200 csig = SCons.Util.MD5signature(contents) 3201 3202 ninfo.csig = csig 3203 3204 return csig
3205 3206 # 3207 # DECISION SUBSYSTEM 3208 # 3209
3210 - def builder_set(self, builder):
3213
3214 - def built(self):
3215 """Called just after this File node is successfully built. 3216 3217 Just like for 'release_target_info' we try to release 3218 some more target node attributes in order to minimize the 3219 overall memory consumption. 3220 3221 @see: release_target_info 3222 """ 3223 3224 SCons.Node.Node.built(self) 3225 3226 if (not SCons.Node.interactive and 3227 not hasattr(self.attributes, 'keep_targetinfo')): 3228 # Ensure that the build infos get computed and cached... 3229 SCons.Node.store_info_map[self.store_info](self) 3230 # ... then release some more variables. 3231 self._specific_sources = False 3232 self._labspath = None 3233 self._save_str() 3234 self.cwd = None 3235 3236 self.scanner_paths = None
3237
3238 - def changed(self, node=None, allowcache=False):
3239 """ 3240 Returns if the node is up-to-date with respect to the BuildInfo 3241 stored last time it was built. 3242 3243 For File nodes this is basically a wrapper around Node.changed(), 3244 but we allow the return value to get cached after the reference 3245 to the Executor got released in release_target_info(). 3246 3247 @see: Node.changed() 3248 """ 3249 if node is None: 3250 try: 3251 return self._memo['changed'] 3252 except KeyError: 3253 pass 3254 3255 has_changed = SCons.Node.Node.changed(self, node) 3256 if allowcache: 3257 self._memo['changed'] = has_changed 3258 return has_changed
3259
3260 - def changed_content(self, target, prev_ni):
3261 cur_csig = self.get_csig() 3262 try: 3263 return cur_csig != prev_ni.csig 3264 except AttributeError: 3265 return 1
3266
3267 - def changed_state(self, target, prev_ni):
3268 return self.state != SCons.Node.up_to_date
3269
3270 - def changed_timestamp_then_content(self, target, prev_ni):
3271 if not self.changed_timestamp_match(target, prev_ni): 3272 try: 3273 self.get_ninfo().csig = prev_ni.csig 3274 except AttributeError: 3275 pass 3276 return False 3277 return self.changed_content(target, prev_ni)
3278
3279 - def changed_timestamp_newer(self, target, prev_ni):
3280 try: 3281 return self.get_timestamp() > target.get_timestamp() 3282 except AttributeError: 3283 return 1
3284
3285 - def changed_timestamp_match(self, target, prev_ni):
3286 try: 3287 return self.get_timestamp() != prev_ni.timestamp 3288 except AttributeError: 3289 return 1
3290
3291 - def is_up_to_date(self):
3292 T = 0 3293 if T: Trace('is_up_to_date(%s):' % self) 3294 if not self.exists(): 3295 if T: Trace(' not self.exists():') 3296 # The file doesn't exist locally... 3297 r = self.rfile() 3298 if r != self: 3299 # ...but there is one in a Repository... 3300 if not self.changed(r): 3301 if T: Trace(' changed(%s):' % r) 3302 # ...and it's even up-to-date... 3303 if self._local: 3304 # ...and they'd like a local copy. 3305 e = LocalCopy(self, r, None) 3306 if isinstance(e, SCons.Errors.BuildError): 3307 raise 3308 SCons.Node.store_info_map[self.store_info](self) 3309 if T: Trace(' 1\n') 3310 return 1 3311 self.changed() 3312 if T: Trace(' None\n') 3313 return None 3314 else: 3315 r = self.changed() 3316 if T: Trace(' self.exists(): %s\n' % r) 3317 return not r
3318 3319 @SCons.Memoize.CountMethodCall
3320 - def rfile(self):
3321 try: 3322 return self._memo['rfile'] 3323 except KeyError: 3324 pass 3325 result = self 3326 if not self.exists(): 3327 norm_name = _my_normcase(self.name) 3328 for dir in self.dir.get_all_rdirs(): 3329 try: node = dir.entries[norm_name] 3330 except KeyError: node = dir.file_on_disk(self.name) 3331 if node and node.exists() and \ 3332 (isinstance(node, File) or isinstance(node, Entry) \ 3333 or not node.is_derived()): 3334 result = node 3335 # Copy over our local attributes to the repository 3336 # Node so we identify shared object files in the 3337 # repository and don't assume they're static. 3338 # 3339 # This isn't perfect; the attribute would ideally 3340 # be attached to the object in the repository in 3341 # case it was built statically in the repository 3342 # and we changed it to shared locally, but that's 3343 # rarely the case and would only occur if you 3344 # intentionally used the same suffix for both 3345 # shared and static objects anyway. So this 3346 # should work well in practice. 3347 result.attributes = self.attributes 3348 break 3349 self._memo['rfile'] = result 3350 return result
3351
3352 - def rstr(self):
3353 return str(self.rfile())
3354
3355 - def get_cachedir_csig(self):
3356 """ 3357 Fetch a Node's content signature for purposes of computing 3358 another Node's cachesig. 3359 3360 This is a wrapper around the normal get_csig() method that handles 3361 the somewhat obscure case of using CacheDir with the -n option. 3362 Any files that don't exist would normally be "built" by fetching 3363 them from the cache, but the normal get_csig() method will try 3364 to open up the local file, which doesn't exist because the -n 3365 option meant we didn't actually pull the file from cachedir. 3366 But since the file *does* actually exist in the cachedir, we 3367 can use its contents for the csig. 3368 """ 3369 try: 3370 return self.cachedir_csig 3371 except AttributeError: 3372 pass 3373 3374 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self) 3375 if not self.exists() and cachefile and os.path.exists(cachefile): 3376 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \ 3377 SCons.Node.FS.File.md5_chunksize * 1024) 3378 else: 3379 self.cachedir_csig = self.get_csig() 3380 return self.cachedir_csig
3381
3382 - def get_contents_sig(self):
3383 """ 3384 A helper method for get_cachedir_bsig. 3385 3386 It computes and returns the signature for this 3387 node's contents. 3388 """ 3389 3390 try: 3391 return self.contentsig 3392 except AttributeError: 3393 pass 3394 3395 executor = self.get_executor() 3396 3397 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents()) 3398 return result
3399
3400 - def get_cachedir_bsig(self):
3401 """ 3402 Return the signature for a cached file, including 3403 its children. 3404 3405 It adds the path of the cached file to the cache signature, 3406 because multiple targets built by the same action will all 3407 have the same build signature, and we have to differentiate 3408 them somehow. 3409 """ 3410 try: 3411 return self.cachesig 3412 except AttributeError: 3413 pass 3414 3415 # Collect signatures for all children 3416 children = self.children() 3417 sigs = [n.get_cachedir_csig() for n in children] 3418 # Append this node's signature... 3419 sigs.append(self.get_contents_sig()) 3420 # ...and it's path 3421 sigs.append(self.get_internal_path()) 3422 # Merge this all into a single signature 3423 result = self.cachesig = SCons.Util.MD5collect(sigs) 3424 return result
3425 3426 default_fs = None
3427 3428 -def get_default_fs():
3429 global default_fs 3430 if not default_fs: 3431 default_fs = FS() 3432 return default_fs
3433
3434 -class FileFinder(object):
3435 """ 3436 """ 3437
3438 - def __init__(self):
3439 self._memo = {}
3440
3441 - def filedir_lookup(self, p, fd=None):
3442 """ 3443 A helper method for find_file() that looks up a directory for 3444 a file we're trying to find. This only creates the Dir Node if 3445 it exists on-disk, since if the directory doesn't exist we know 3446 we won't find any files in it... :-) 3447 3448 It would be more compact to just use this as a nested function 3449 with a default keyword argument (see the commented-out version 3450 below), but that doesn't work unless you have nested scopes, 3451 so we define it here just so this work under Python 1.5.2. 3452 """ 3453 if fd is None: 3454 fd = self.default_filedir 3455 dir, name = os.path.split(fd) 3456 drive, d = _my_splitdrive(dir) 3457 if not name and d[:1] in ('/', OS_SEP): 3458 #return p.fs.get_root(drive).dir_on_disk(name) 3459 return p.fs.get_root(drive) 3460 if dir: 3461 p = self.filedir_lookup(p, dir) 3462 if not p: 3463 return None 3464 norm_name = _my_normcase(name) 3465 try: 3466 node = p.entries[norm_name] 3467 except KeyError: 3468 return p.dir_on_disk(name) 3469 if isinstance(node, Dir): 3470 return node 3471 if isinstance(node, Entry): 3472 node.must_be_same(Dir) 3473 return node 3474 return None
3475
3476 - def _find_file_key(self, filename, paths, verbose=None):
3477 return (filename, paths)
3478 3479 @SCons.Memoize.CountDictCall(_find_file_key)
3480 - def find_file(self, filename, paths, verbose=None):
3481 """ 3482 find_file(str, [Dir()]) -> [nodes] 3483 3484 filename - a filename to find 3485 paths - a list of directory path *nodes* to search in. Can be 3486 represented as a list, a tuple, or a callable that is 3487 called with no arguments and returns the list or tuple. 3488 3489 returns - the node created from the found file. 3490 3491 Find a node corresponding to either a derived file or a file 3492 that exists already. 3493 3494 Only the first file found is returned, and none is returned 3495 if no file is found. 3496 """ 3497 memo_key = self._find_file_key(filename, paths) 3498 try: 3499 memo_dict = self._memo['find_file'] 3500 except KeyError: 3501 memo_dict = {} 3502 self._memo['find_file'] = memo_dict 3503 else: 3504 try: 3505 return memo_dict[memo_key] 3506 except KeyError: 3507 pass 3508 3509 if verbose and not callable(verbose): 3510 if not SCons.Util.is_String(verbose): 3511 verbose = "find_file" 3512 _verbose = u' %s: ' % verbose 3513 verbose = lambda s: sys.stdout.write(_verbose + s) 3514 3515 filedir, filename = os.path.split(filename) 3516 if filedir: 3517 # More compact code that we can't use until we drop 3518 # support for Python 1.5.2: 3519 # 3520 #def filedir_lookup(p, fd=filedir): 3521 # """ 3522 # A helper function that looks up a directory for a file 3523 # we're trying to find. This only creates the Dir Node 3524 # if it exists on-disk, since if the directory doesn't 3525 # exist we know we won't find any files in it... :-) 3526 # """ 3527 # dir, name = os.path.split(fd) 3528 # if dir: 3529 # p = filedir_lookup(p, dir) 3530 # if not p: 3531 # return None 3532 # norm_name = _my_normcase(name) 3533 # try: 3534 # node = p.entries[norm_name] 3535 # except KeyError: 3536 # return p.dir_on_disk(name) 3537 # if isinstance(node, Dir): 3538 # return node 3539 # if isinstance(node, Entry): 3540 # node.must_be_same(Dir) 3541 # return node 3542 # if isinstance(node, Dir) or isinstance(node, Entry): 3543 # return node 3544 # return None 3545 #paths = [_f for _f in map(filedir_lookup, paths) if _f] 3546 3547 self.default_filedir = filedir 3548 paths = [_f for _f in map(self.filedir_lookup, paths) if _f] 3549 3550 result = None 3551 for dir in paths: 3552 if verbose: 3553 verbose("looking for '%s' in '%s' ...\n" % (filename, dir)) 3554 node, d = dir.srcdir_find_file(filename) 3555 if node: 3556 if verbose: 3557 verbose("... FOUND '%s' in '%s'\n" % (filename, d)) 3558 result = node 3559 break 3560 3561 memo_dict[memo_key] = result 3562 3563 return result
3564 3565 find_file = FileFinder().find_file
3566 3567 3568 -def invalidate_node_memos(targets):
3569 """ 3570 Invalidate the memoized values of all Nodes (files or directories) 3571 that are associated with the given entries. Has been added to 3572 clear the cache of nodes affected by a direct execution of an 3573 action (e.g. Delete/Copy/Chmod). Existing Node caches become 3574 inconsistent if the action is run through Execute(). The argument 3575 `targets` can be a single Node object or filename, or a sequence 3576 of Nodes/filenames. 3577 """ 3578 from traceback import extract_stack 3579 3580 # First check if the cache really needs to be flushed. Only 3581 # actions run in the SConscript with Execute() seem to be 3582 # affected. XXX The way to check if Execute() is in the stacktrace 3583 # is a very dirty hack and should be replaced by a more sensible 3584 # solution. 3585 for f in extract_stack(): 3586 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py': 3587 break 3588 else: 3589 # Dont have to invalidate, so return 3590 return 3591 3592 if not SCons.Util.is_List(targets): 3593 targets = [targets] 3594 3595 for entry in targets: 3596 # If the target is a Node object, clear the cache. If it is a 3597 # filename, look up potentially existing Node object first. 3598 try: 3599 entry.clear_memoized_values() 3600 except AttributeError: 3601 # Not a Node object, try to look up Node by filename. XXX 3602 # This creates Node objects even for those filenames which 3603 # do not correspond to an existing Node object. 3604 node = get_default_fs().Entry(entry) 3605 if node: 3606 node.clear_memoized_values()
3607 3608 # Local Variables: 3609 # tab-width:4 3610 # indent-tabs-mode:nil 3611 # End: 3612 # vim: set expandtab tabstop=4 shiftwidth=4: 3613