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, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 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  2014/03/02 14:18:15 garyo" 
  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  do_store_info = True 
  60  print_duplicate = 0 
  61   
  62   
63 -class EntryProxyAttributeError(AttributeError):
64 """ 65 An AttributeError subclass for recording and displaying the name 66 of the underlying Entry involved in an AttributeError exception. 67 """
68 - def __init__(self, entry_proxy, attribute):
69 AttributeError.__init__(self) 70 self.entry_proxy = entry_proxy 71 self.attribute = attribute
72 - def __str__(self):
73 entry = self.entry_proxy.get() 74 fmt = "%s instance %s has no attribute %s" 75 return fmt % (entry.__class__.__name__, 76 repr(entry.name), 77 repr(self.attribute))
78 79 # The max_drift value: by default, use a cached signature value for 80 # any file that's been untouched for more than two days. 81 default_max_drift = 2*24*60*60 82 83 # 84 # We stringify these file system Nodes a lot. Turning a file system Node 85 # into a string is non-trivial, because the final string representation 86 # can depend on a lot of factors: whether it's a derived target or not, 87 # whether it's linked to a repository or source directory, and whether 88 # there's duplication going on. The normal technique for optimizing 89 # calculations like this is to memoize (cache) the string value, so you 90 # only have to do the calculation once. 91 # 92 # A number of the above factors, however, can be set after we've already 93 # been asked to return a string for a Node, because a Repository() or 94 # VariantDir() call or the like may not occur until later in SConscript 95 # files. So this variable controls whether we bother trying to save 96 # string values for Nodes. The wrapper interface can set this whenever 97 # they're done mucking with Repository and VariantDir and the other stuff, 98 # to let this module know it can start returning saved string values 99 # for Nodes. 100 # 101 Save_Strings = None 102
103 -def save_strings(val):
104 global Save_Strings 105 Save_Strings = val
106 107 # 108 # Avoid unnecessary function calls by recording a Boolean value that 109 # tells us whether or not os.path.splitdrive() actually does anything 110 # on this system, and therefore whether we need to bother calling it 111 # when looking up path names in various methods below. 112 # 113 114 do_splitdrive = None 115 _my_splitdrive =None 116
117 -def initialize_do_splitdrive():
118 global do_splitdrive 119 global has_unc 120 drive, path = os.path.splitdrive('X:/foo') 121 has_unc = hasattr(os.path, 'splitunc') 122 123 do_splitdrive = not not drive or has_unc 124 125 global _my_splitdrive 126 if has_unc: 127 def splitdrive(p): 128 if p[1:2] == ':': 129 return p[:2], p[2:] 130 if p[0:2] == '//': 131 # Note that we leave a leading slash in the path 132 # because UNC paths are always absolute. 133 return '//', p[1:] 134 return '', p
135 else: 136 def splitdrive(p): 137 if p[1:2] == ':': 138 return p[:2], p[2:] 139 return '', p 140 _my_splitdrive = splitdrive 141 142 # Keep some commonly used values in global variables to skip to 143 # module look-up costs. 144 global OS_SEP 145 global UNC_PREFIX 146 global os_sep_is_slash 147 148 OS_SEP = os.sep 149 UNC_PREFIX = OS_SEP + OS_SEP 150 os_sep_is_slash = OS_SEP == '/' 151 152 initialize_do_splitdrive() 153 154 # Used to avoid invoking os.path.normpath if not necessary. 155 needs_normpath_check = re.compile( 156 r''' 157 # We need to renormalize the path if it contains any consecutive 158 # '/' characters. 159 .*// | 160 161 # We need to renormalize the path if it contains a '..' directory. 162 # Note that we check for all the following cases: 163 # 164 # a) The path is a single '..' 165 # b) The path starts with '..'. E.g. '../' or '../moredirs' 166 # but we not match '..abc/'. 167 # c) The path ends with '..'. E.g. '/..' or 'dirs/..' 168 # d) The path contains a '..' in the middle. 169 # E.g. dirs/../moredirs 170 171 (.*/)?\.\.(?:/|$) | 172 173 # We need to renormalize the path if it contains a '.' 174 # directory, but NOT if it is a single '.' '/' characters. We 175 # do not want to match a single '.' because this case is checked 176 # for explicitely since this is common enough case. 177 # 178 # Note that we check for all the following cases: 179 # 180 # a) We don't match a single '.' 181 # b) We match if the path starts with '.'. E.g. './' or 182 # './moredirs' but we not match '.abc/'. 183 # c) We match if the path ends with '.'. E.g. '/.' or 184 # 'dirs/.' 185 # d) We match if the path contains a '.' in the middle. 186 # E.g. dirs/./moredirs 187 188 \./|.*/\.(?:/|$) 189 190 ''', 191 re.VERBOSE 192 ) 193 needs_normpath_match = needs_normpath_check.match 194 195 # 196 # SCons.Action objects for interacting with the outside world. 197 # 198 # The Node.FS methods in this module should use these actions to 199 # create and/or remove files and directories; they should *not* use 200 # os.{link,symlink,unlink,mkdir}(), etc., directly. 201 # 202 # Using these SCons.Action objects ensures that descriptions of these 203 # external activities are properly displayed, that the displays are 204 # suppressed when the -s (silent) option is used, and (most importantly) 205 # the actions are disabled when the the -n option is used, in which case 206 # there should be *no* changes to the external file system(s)... 207 # 208 209 if hasattr(os, 'link'): 222 else: 223 _hardlink_func = None 224 225 if hasattr(os, 'symlink'): 228 else: 229 _softlink_func = None 230
231 -def _copy_func(fs, src, dest):
232 shutil.copy2(src, dest) 233 st = fs.stat(src) 234 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
235 236 237 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy', 238 'hard-copy', 'soft-copy', 'copy'] 239 240 Link_Funcs = [] # contains the callables of the specified duplication style 241
242 -def set_duplicate(duplicate):
243 # Fill in the Link_Funcs list according to the argument 244 # (discarding those not available on the platform). 245 246 # Set up the dictionary that maps the argument names to the 247 # underlying implementations. We do this inside this function, 248 # not in the top-level module code, so that we can remap os.link 249 # and os.symlink for testing purposes. 250 link_dict = { 251 'hard' : _hardlink_func, 252 'soft' : _softlink_func, 253 'copy' : _copy_func 254 } 255 256 if not duplicate in Valid_Duplicates: 257 raise SCons.Errors.InternalError("The argument of set_duplicate " 258 "should be in Valid_Duplicates") 259 global Link_Funcs 260 Link_Funcs = [] 261 for func in duplicate.split('-'): 262 if link_dict[func]: 263 Link_Funcs.append(link_dict[func])
264
265 -def LinkFunc(target, source, env):
266 # Relative paths cause problems with symbolic links, so 267 # we use absolute paths, which may be a problem for people 268 # who want to move their soft-linked src-trees around. Those 269 # people should use the 'hard-copy' mode, softlinks cannot be 270 # used for that; at least I have no idea how ... 271 src = source[0].abspath 272 dest = target[0].abspath 273 dir, file = os.path.split(dest) 274 if dir and not target[0].fs.isdir(dir): 275 os.makedirs(dir) 276 if not Link_Funcs: 277 # Set a default order of link functions. 278 set_duplicate('hard-soft-copy') 279 fs = source[0].fs 280 # Now link the files with the previously specified order. 281 for func in Link_Funcs: 282 try: 283 func(fs, src, dest) 284 break 285 except (IOError, OSError): 286 # An OSError indicates something happened like a permissions 287 # problem or an attempt to symlink across file-system 288 # boundaries. An IOError indicates something like the file 289 # not existing. In either case, keeping trying additional 290 # functions in the list and only raise an error if the last 291 # one failed. 292 if func == Link_Funcs[-1]: 293 # exception of the last link method (copy) are fatal 294 raise 295 return 0
296 297 Link = SCons.Action.Action(LinkFunc, None)
298 -def LocalString(target, source, env):
299 return 'Local copy of %s from %s' % (target[0], source[0])
300 301 LocalCopy = SCons.Action.Action(LinkFunc, LocalString) 302
303 -def UnlinkFunc(target, source, env):
304 t = target[0] 305 t.fs.unlink(t.abspath) 306 return 0
307 308 Unlink = SCons.Action.Action(UnlinkFunc, None) 309
310 -def MkdirFunc(target, source, env):
311 t = target[0] 312 if not t.exists(): 313 t.fs.mkdir(t.abspath) 314 return 0
315 316 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None) 317 318 MkdirBuilder = None 319
320 -def get_MkdirBuilder():
321 global MkdirBuilder 322 if MkdirBuilder is None: 323 import SCons.Builder 324 import SCons.Defaults 325 # "env" will get filled in by Executor.get_build_env() 326 # calling SCons.Defaults.DefaultEnvironment() when necessary. 327 MkdirBuilder = SCons.Builder.Builder(action = Mkdir, 328 env = None, 329 explain = None, 330 is_explicit = None, 331 target_scanner = SCons.Defaults.DirEntryScanner, 332 name = "MkdirBuilder") 333 return MkdirBuilder
334
335 -class _Null(object):
336 pass
337 338 _null = _Null() 339 340 DefaultSCCSBuilder = None 341 DefaultRCSBuilder = None 342
343 -def get_DefaultSCCSBuilder():
344 global DefaultSCCSBuilder 345 if DefaultSCCSBuilder is None: 346 import SCons.Builder 347 # "env" will get filled in by Executor.get_build_env() 348 # calling SCons.Defaults.DefaultEnvironment() when necessary. 349 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR') 350 DefaultSCCSBuilder = SCons.Builder.Builder(action = act, 351 env = None, 352 name = "DefaultSCCSBuilder") 353 return DefaultSCCSBuilder
354
355 -def get_DefaultRCSBuilder():
356 global DefaultRCSBuilder 357 if DefaultRCSBuilder is None: 358 import SCons.Builder 359 # "env" will get filled in by Executor.get_build_env() 360 # calling SCons.Defaults.DefaultEnvironment() when necessary. 361 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR') 362 DefaultRCSBuilder = SCons.Builder.Builder(action = act, 363 env = None, 364 name = "DefaultRCSBuilder") 365 return DefaultRCSBuilder
366 367 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem. 368 _is_cygwin = sys.platform == "cygwin" 369 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
370 - def _my_normcase(x):
371 return x
372 else:
373 - def _my_normcase(x):
374 return x.upper()
375 376 377
378 -class DiskChecker(object):
379 - def __init__(self, type, do, ignore):
380 self.type = type 381 self.do = do 382 self.ignore = ignore 383 self.func = do
384 - def __call__(self, *args, **kw):
385 return self.func(*args, **kw)
386 - def set(self, list):
387 if self.type in list: 388 self.func = self.do 389 else: 390 self.func = self.ignore
391
392 -def do_diskcheck_match(node, predicate, errorfmt):
393 result = predicate() 394 try: 395 # If calling the predicate() cached a None value from stat(), 396 # remove it so it doesn't interfere with later attempts to 397 # build this Node as we walk the DAG. (This isn't a great way 398 # to do this, we're reaching into an interface that doesn't 399 # really belong to us, but it's all about performance, so 400 # for now we'll just document the dependency...) 401 if node._memo['stat'] is None: 402 del node._memo['stat'] 403 except (AttributeError, KeyError): 404 pass 405 if result: 406 raise TypeError(errorfmt % node.abspath)
407
408 -def ignore_diskcheck_match(node, predicate, errorfmt):
409 pass
410
411 -def do_diskcheck_rcs(node, name):
412 try: 413 rcs_dir = node.rcs_dir 414 except AttributeError: 415 if node.entry_exists_on_disk('RCS'): 416 rcs_dir = node.Dir('RCS') 417 else: 418 rcs_dir = None 419 node.rcs_dir = rcs_dir 420 if rcs_dir: 421 return rcs_dir.entry_exists_on_disk(name+',v') 422 return None
423
424 -def ignore_diskcheck_rcs(node, name):
425 return None
426
427 -def do_diskcheck_sccs(node, name):
428 try: 429 sccs_dir = node.sccs_dir 430 except AttributeError: 431 if node.entry_exists_on_disk('SCCS'): 432 sccs_dir = node.Dir('SCCS') 433 else: 434 sccs_dir = None 435 node.sccs_dir = sccs_dir 436 if sccs_dir: 437 return sccs_dir.entry_exists_on_disk('s.'+name) 438 return None
439
440 -def ignore_diskcheck_sccs(node, name):
441 return None
442 443 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match) 444 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs) 445 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs) 446 447 diskcheckers = [ 448 diskcheck_match, 449 diskcheck_rcs, 450 diskcheck_sccs, 451 ] 452
453 -def set_diskcheck(list):
454 for dc in diskcheckers: 455 dc.set(list)
456
457 -def diskcheck_types():
458 return [dc.type for dc in diskcheckers]
459 460 461
462 -class EntryProxy(SCons.Util.Proxy):
463 464 __str__ = SCons.Util.Delegate('__str__') 465
466 - def __get_abspath(self):
467 entry = self.get() 468 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(), 469 entry.name + "_abspath")
470
471 - def __get_filebase(self):
472 name = self.get().name 473 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0], 474 name + "_filebase")
475
476 - def __get_suffix(self):
477 name = self.get().name 478 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1], 479 name + "_suffix")
480
481 - def __get_file(self):
482 name = self.get().name 483 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
484
485 - def __get_base_path(self):
486 """Return the file's directory and file name, with the 487 suffix stripped.""" 488 entry = self.get() 489 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0], 490 entry.name + "_base")
491
492 - def __get_posix_path(self):
493 """Return the path with / as the path separator, 494 regardless of platform.""" 495 if os_sep_is_slash: 496 return self 497 else: 498 entry = self.get() 499 r = entry.get_path().replace(OS_SEP, '/') 500 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
501
502 - def __get_windows_path(self):
503 """Return the path with \ as the path separator, 504 regardless of platform.""" 505 if OS_SEP == '\\': 506 return self 507 else: 508 entry = self.get() 509 r = entry.get_path().replace(OS_SEP, '\\') 510 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
511
512 - def __get_srcnode(self):
513 return EntryProxy(self.get().srcnode())
514
515 - def __get_srcdir(self):
516 """Returns the directory containing the source node linked to this 517 node via VariantDir(), or the directory of this node if not linked.""" 518 return EntryProxy(self.get().srcnode().dir)
519
520 - def __get_rsrcnode(self):
521 return EntryProxy(self.get().srcnode().rfile())
522
523 - def __get_rsrcdir(self):
524 """Returns the directory containing the source node linked to this 525 node via VariantDir(), or the directory of this node if not linked.""" 526 return EntryProxy(self.get().srcnode().rfile().dir)
527
528 - def __get_dir(self):
529 return EntryProxy(self.get().dir)
530 531 dictSpecialAttrs = { "base" : __get_base_path, 532 "posix" : __get_posix_path, 533 "windows" : __get_windows_path, 534 "win32" : __get_windows_path, 535 "srcpath" : __get_srcnode, 536 "srcdir" : __get_srcdir, 537 "dir" : __get_dir, 538 "abspath" : __get_abspath, 539 "filebase" : __get_filebase, 540 "suffix" : __get_suffix, 541 "file" : __get_file, 542 "rsrcpath" : __get_rsrcnode, 543 "rsrcdir" : __get_rsrcdir, 544 } 545
546 - def __getattr__(self, name):
547 # This is how we implement the "special" attributes 548 # such as base, posix, srcdir, etc. 549 try: 550 attr_function = self.dictSpecialAttrs[name] 551 except KeyError: 552 try: 553 attr = SCons.Util.Proxy.__getattr__(self, name) 554 except AttributeError, e: 555 # Raise our own AttributeError subclass with an 556 # overridden __str__() method that identifies the 557 # name of the entry that caused the exception. 558 raise EntryProxyAttributeError(self, name) 559 return attr 560 else: 561 return attr_function(self)
562
563 -class Base(SCons.Node.Node):
564 """A generic class for file system entries. This class is for 565 when we don't know yet whether the entry being looked up is a file 566 or a directory. Instances of this class can morph into either 567 Dir or File objects by a later, more precise lookup. 568 569 Note: this class does not define __cmp__ and __hash__ for 570 efficiency reasons. SCons does a lot of comparing of 571 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be 572 as fast as possible, which means we want to use Python's built-in 573 object identity comparisons. 574 """ 575 576 memoizer_counters = [] 577
578 - def __init__(self, name, directory, fs):
579 """Initialize a generic Node.FS.Base object. 580 581 Call the superclass initialization, take care of setting up 582 our relative and absolute paths, identify our parent 583 directory, and indicate that this node should use 584 signatures.""" 585 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Base') 586 SCons.Node.Node.__init__(self) 587 588 # Filenames and paths are probably reused and are intern'ed to 589 # save some memory. 590 591 #: Filename with extension as it was specified when the object was 592 #: created; to obtain filesystem path, use Python str() function 593 self.name = SCons.Util.silent_intern(name) 594 #: Cached filename extension 595 self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1]) 596 self.fs = fs #: Reference to parent Node.FS object 597 598 assert directory, "A directory must be provided" 599 600 self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name)) 601 self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name)) 602 if directory.path == '.': 603 self.path = SCons.Util.silent_intern(name) 604 else: 605 self.path = SCons.Util.silent_intern(directory.entry_path(name)) 606 if directory.tpath == '.': 607 self.tpath = SCons.Util.silent_intern(name) 608 else: 609 self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name)) 610 self.path_elements = directory.path_elements + [self] 611 612 self.dir = directory 613 self.cwd = None # will hold the SConscript directory for target nodes 614 self.duplicate = directory.duplicate
615
616 - def str_for_display(self):
617 return '"' + self.__str__() + '"'
618
619 - def must_be_same(self, klass):
620 """ 621 This node, which already existed, is being looked up as the 622 specified klass. Raise an exception if it isn't. 623 """ 624 if isinstance(self, klass) or klass is Entry: 625 return 626 raise TypeError("Tried to lookup %s '%s' as a %s." %\ 627 (self.__class__.__name__, self.path, klass.__name__))
628
629 - def get_dir(self):
630 return self.dir
631
632 - def get_suffix(self):
633 return self.suffix
634
635 - def rfile(self):
636 return self
637
638 - def __str__(self):
639 """A Node.FS.Base object's string representation is its path 640 name.""" 641 global Save_Strings 642 if Save_Strings: 643 return self._save_str() 644 return self._get_str()
645 646 memoizer_counters.append(SCons.Memoize.CountValue('_save_str')) 647
648 - def _save_str(self):
649 try: 650 return self._memo['_save_str'] 651 except KeyError: 652 pass 653 result = sys.intern(self._get_str()) 654 self._memo['_save_str'] = result 655 return result
656
657 - def _get_str(self):
658 global Save_Strings 659 if self.duplicate or self.is_derived(): 660 return self.get_path() 661 srcnode = self.srcnode() 662 if srcnode.stat() is None and self.stat() is not None: 663 result = self.get_path() 664 else: 665 result = srcnode.get_path() 666 if not Save_Strings: 667 # We're not at the point where we're saving the string 668 # representations of FS Nodes (because we haven't finished 669 # reading the SConscript files and need to have str() return 670 # things relative to them). That also means we can't yet 671 # cache values returned (or not returned) by stat(), since 672 # Python code in the SConscript files might still create 673 # or otherwise affect the on-disk file. So get rid of the 674 # values that the underlying stat() method saved. 675 try: del self._memo['stat'] 676 except KeyError: pass 677 if self is not srcnode: 678 try: del srcnode._memo['stat'] 679 except KeyError: pass 680 return result
681 682 rstr = __str__ 683 684 memoizer_counters.append(SCons.Memoize.CountValue('stat')) 685
686 - def stat(self):
687 try: return self._memo['stat'] 688 except KeyError: pass 689 try: result = self.fs.stat(self.abspath) 690 except os.error: result = None 691 self._memo['stat'] = result 692 return result
693
694 - def exists(self):
695 return self.stat() is not None
696
697 - def rexists(self):
698 return self.rfile().exists()
699
700 - def getmtime(self):
701 st = self.stat() 702 if st: return st[stat.ST_MTIME] 703 else: return None
704
705 - def getsize(self):
706 st = self.stat() 707 if st: return st[stat.ST_SIZE] 708 else: return None
709
710 - def isdir(self):
711 st = self.stat() 712 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
713
714 - def isfile(self):
715 st = self.stat() 716 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
717 718 if hasattr(os, 'symlink'): 723 else: 726
727 - def is_under(self, dir):
728 if self is dir: 729 return 1 730 else: 731 return self.dir.is_under(dir)
732
733 - def set_local(self):
734 self._local = 1
735
736 - def srcnode(self):
737 """If this node is in a build path, return the node 738 corresponding to its source file. Otherwise, return 739 ourself. 740 """ 741 srcdir_list = self.dir.srcdir_list() 742 if srcdir_list: 743 srcnode = srcdir_list[0].Entry(self.name) 744 srcnode.must_be_same(self.__class__) 745 return srcnode 746 return self
747
748 - def get_path(self, dir=None):
749 """Return path relative to the current working directory of the 750 Node.FS.Base object that owns us.""" 751 if not dir: 752 dir = self.fs.getcwd() 753 if self == dir: 754 return '.' 755 path_elems = self.path_elements 756 pathname = '' 757 try: i = path_elems.index(dir) 758 except ValueError: 759 for p in path_elems[:-1]: 760 pathname += p.dirname 761 else: 762 for p in path_elems[i+1:-1]: 763 pathname += p.dirname 764 return pathname + path_elems[-1].name
765
766 - def set_src_builder(self, builder):
767 """Set the source code builder for this node.""" 768 self.sbuilder = builder 769 if not self.has_builder(): 770 self.builder_set(builder)
771
772 - def src_builder(self):
773 """Fetch the source code builder for this node. 774 775 If there isn't one, we cache the source code builder specified 776 for the directory (which in turn will cache the value from its 777 parent directory, and so on up to the file system root). 778 """ 779 try: 780 scb = self.sbuilder 781 except AttributeError: 782 scb = self.dir.src_builder() 783 self.sbuilder = scb 784 return scb
785
786 - def get_abspath(self):
787 """Get the absolute path of the file.""" 788 return self.abspath
789
790 - def for_signature(self):
791 # Return just our name. Even an absolute path would not work, 792 # because that can change thanks to symlinks or remapped network 793 # paths. 794 return self.name
795
796 - def get_subst_proxy(self):
797 try: 798 return self._proxy 799 except AttributeError: 800 ret = EntryProxy(self) 801 self._proxy = ret 802 return ret
803
804 - def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
805 """ 806 807 Generates a target entry that corresponds to this entry (usually 808 a source file) with the specified prefix and suffix. 809 810 Note that this method can be overridden dynamically for generated 811 files that need different behavior. See Tool/swig.py for 812 an example. 813 """ 814 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
815
816 - def _Rfindalldirs_key(self, pathlist):
817 return pathlist
818 819 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key)) 820
821 - def Rfindalldirs(self, pathlist):
822 """ 823 Return all of the directories for a given path list, including 824 corresponding "backing" directories in any repositories. 825 826 The Node lookups are relative to this Node (typically a 827 directory), so memoizing result saves cycles from looking 828 up the same path for each target in a given directory. 829 """ 830 try: 831 memo_dict = self._memo['Rfindalldirs'] 832 except KeyError: 833 memo_dict = {} 834 self._memo['Rfindalldirs'] = memo_dict 835 else: 836 try: 837 return memo_dict[pathlist] 838 except KeyError: 839 pass 840 841 create_dir_relative_to_self = self.Dir 842 result = [] 843 for path in pathlist: 844 if isinstance(path, SCons.Node.Node): 845 result.append(path) 846 else: 847 dir = create_dir_relative_to_self(path) 848 result.extend(dir.get_all_rdirs()) 849 850 memo_dict[pathlist] = result 851 852 return result
853
854 - def RDirs(self, pathlist):
855 """Search for a list of directories in the Repository list.""" 856 cwd = self.cwd or self.fs._cwd 857 return cwd.Rfindalldirs(pathlist)
858 859 memoizer_counters.append(SCons.Memoize.CountValue('rentry')) 860
861 - def rentry(self):
862 try: 863 return self._memo['rentry'] 864 except KeyError: 865 pass 866 result = self 867 if not self.exists(): 868 norm_name = _my_normcase(self.name) 869 for dir in self.dir.get_all_rdirs(): 870 try: 871 node = dir.entries[norm_name] 872 except KeyError: 873 if dir.entry_exists_on_disk(self.name): 874 result = dir.Entry(self.name) 875 break 876 self._memo['rentry'] = result 877 return result
878
879 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
880 return []
881
882 -class Entry(Base):
883 """This is the class for generic Node.FS entries--that is, things 884 that could be a File or a Dir, but we're just not sure yet. 885 Consequently, the methods in this class really exist just to 886 transform their associated object into the right class when the 887 time comes, and then call the same-named method in the transformed 888 class.""" 889
890 - def diskcheck_match(self):
891 pass
892
893 - def disambiguate(self, must_exist=None):
894 """ 895 """ 896 if self.isdir(): 897 self.__class__ = Dir 898 self._morph() 899 elif self.isfile(): 900 self.__class__ = File 901 self._morph() 902 self.clear() 903 else: 904 # There was nothing on-disk at this location, so look in 905 # the src directory. 906 # 907 # We can't just use self.srcnode() straight away because 908 # that would create an actual Node for this file in the src 909 # directory, and there might not be one. Instead, use the 910 # dir_on_disk() method to see if there's something on-disk 911 # with that name, in which case we can go ahead and call 912 # self.srcnode() to create the right type of entry. 913 srcdir = self.dir.srcnode() 914 if srcdir != self.dir and \ 915 srcdir.entry_exists_on_disk(self.name) and \ 916 self.srcnode().isdir(): 917 self.__class__ = Dir 918 self._morph() 919 elif must_exist: 920 msg = "No such file or directory: '%s'" % self.abspath 921 raise SCons.Errors.UserError(msg) 922 else: 923 self.__class__ = File 924 self._morph() 925 self.clear() 926 return self
927
928 - def rfile(self):
929 """We're a generic Entry, but the caller is actually looking for 930 a File at this point, so morph into one.""" 931 self.__class__ = File 932 self._morph() 933 self.clear() 934 return File.rfile(self)
935
936 - def scanner_key(self):
937 return self.get_suffix()
938
939 - def get_contents(self):
940 """Fetch the contents of the entry. Returns the exact binary 941 contents of the file.""" 942 try: 943 self = self.disambiguate(must_exist=1) 944 except SCons.Errors.UserError: 945 # There was nothing on disk with which to disambiguate 946 # this entry. Leave it as an Entry, but return a null 947 # string so calls to get_contents() in emitters and the 948 # like (e.g. in qt.py) don't have to disambiguate by hand 949 # or catch the exception. 950 return '' 951 else: 952 return self.get_contents()
953
954 - def get_text_contents(self):
955 """Fetch the decoded text contents of a Unicode encoded Entry. 956 957 Since this should return the text contents from the file 958 system, we check to see into what sort of subclass we should 959 morph this Entry.""" 960 try: 961 self = self.disambiguate(must_exist=1) 962 except SCons.Errors.UserError: 963 # There was nothing on disk with which to disambiguate 964 # this entry. Leave it as an Entry, but return a null 965 # string so calls to get_text_contents() in emitters and 966 # the like (e.g. in qt.py) don't have to disambiguate by 967 # hand or catch the exception. 968 return '' 969 else: 970 return self.get_text_contents()
971
972 - def must_be_same(self, klass):
973 """Called to make sure a Node is a Dir. Since we're an 974 Entry, we can morph into one.""" 975 if self.__class__ is not klass: 976 self.__class__ = klass 977 self._morph() 978 self.clear()
979 980 # The following methods can get called before the Taskmaster has 981 # had a chance to call disambiguate() directly to see if this Entry 982 # should really be a Dir or a File. We therefore use these to call 983 # disambiguate() transparently (from our caller's point of view). 984 # 985 # Right now, this minimal set of methods has been derived by just 986 # looking at some of the methods that will obviously be called early 987 # in any of the various Taskmasters' calling sequences, and then 988 # empirically figuring out which additional methods are necessary 989 # to make various tests pass. 990
991 - def exists(self):
992 """Return if the Entry exists. Check the file system to see 993 what we should turn into first. Assume a file if there's no 994 directory.""" 995 return self.disambiguate().exists()
996
997 - def rel_path(self, other):
998 d = self.disambiguate() 999 if d.__class__ is Entry: 1000 raise Exception("rel_path() could not disambiguate File/Dir") 1001 return d.rel_path(other)
1002
1003 - def new_ninfo(self):
1004 return self.disambiguate().new_ninfo()
1005
1006 - def changed_since_last_build(self, target, prev_ni):
1007 return self.disambiguate().changed_since_last_build(target, prev_ni)
1008
1009 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1010 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
1011
1012 - def get_subst_proxy(self):
1013 return self.disambiguate().get_subst_proxy()
1014 1015 # This is for later so we can differentiate between Entry the class and Entry 1016 # the method of the FS class. 1017 _classEntry = Entry 1018 1019
1020 -class LocalFS(object):
1021 1022 if SCons.Memoize.use_memoizer: 1023 __metaclass__ = SCons.Memoize.Memoized_Metaclass 1024 1025 # This class implements an abstraction layer for operations involving 1026 # a local file system. Essentially, this wraps any function in 1027 # the os, os.path or shutil modules that we use to actually go do 1028 # anything with or to the local file system. 1029 # 1030 # Note that there's a very good chance we'll refactor this part of 1031 # the architecture in some way as we really implement the interface(s) 1032 # for remote file system Nodes. For example, the right architecture 1033 # might be to have this be a subclass instead of a base class. 1034 # Nevertheless, we're using this as a first step in that direction. 1035 # 1036 # We're not using chdir() yet because the calling subclass method 1037 # needs to use os.chdir() directly to avoid recursion. Will we 1038 # really need this one? 1039 #def chdir(self, path): 1040 # return os.chdir(path)
1041 - def chmod(self, path, mode):
1042 return os.chmod(path, mode)
1043 - def copy(self, src, dst):
1044 return shutil.copy(src, dst)
1045 - def copy2(self, src, dst):
1046 return shutil.copy2(src, dst)
1047 - def exists(self, path):
1048 return os.path.exists(path)
1049 - def getmtime(self, path):
1050 return os.path.getmtime(path)
1051 - def getsize(self, path):
1052 return os.path.getsize(path)
1053 - def isdir(self, path):
1054 return os.path.isdir(path)
1055 - def isfile(self, path):
1056 return os.path.isfile(path)
1059 - def lstat(self, path):
1060 return os.lstat(path)
1061 - def listdir(self, path):
1062 return os.listdir(path)
1063 - def makedirs(self, path):
1064 return os.makedirs(path)
1065 - def mkdir(self, path):
1066 return os.mkdir(path)
1067 - def rename(self, old, new):
1068 return os.rename(old, new)
1069 - def stat(self, path):
1070 return os.stat(path)
1073 - def open(self, path):
1074 return open(path)
1077 1078 if hasattr(os, 'symlink'): 1081 else: 1084 1085 if hasattr(os, 'readlink'): 1088 else:
1091 1092 1093 #class RemoteFS: 1094 # # Skeleton for the obvious methods we might need from the 1095 # # abstraction layer for a remote filesystem. 1096 # def upload(self, local_src, remote_dst): 1097 # pass 1098 # def download(self, remote_src, local_dst): 1099 # pass 1100 1101
1102 -class FS(LocalFS):
1103 1104 memoizer_counters = [] 1105
1106 - def __init__(self, path = None):
1107 """Initialize the Node.FS subsystem. 1108 1109 The supplied path is the top of the source tree, where we 1110 expect to find the top-level build file. If no path is 1111 supplied, the current directory is the default. 1112 1113 The path argument must be a valid absolute path. 1114 """ 1115 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS') 1116 1117 self._memo = {} 1118 1119 self.Root = {} 1120 self.SConstruct_dir = None 1121 self.max_drift = default_max_drift 1122 1123 self.Top = None 1124 if path is None: 1125 self.pathTop = os.getcwd() 1126 else: 1127 self.pathTop = path 1128 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0]) 1129 1130 self.Top = self.Dir(self.pathTop) 1131 self.Top.path = '.' 1132 self.Top.tpath = '.' 1133 self._cwd = self.Top 1134 1135 DirNodeInfo.fs = self 1136 FileNodeInfo.fs = self
1137
1138 - def set_SConstruct_dir(self, dir):
1139 self.SConstruct_dir = dir
1140
1141 - def get_max_drift(self):
1142 return self.max_drift
1143
1144 - def set_max_drift(self, max_drift):
1145 self.max_drift = max_drift
1146
1147 - def getcwd(self):
1148 if hasattr(self, "_cwd"): 1149 return self._cwd 1150 else: 1151 return "<no cwd>"
1152
1153 - def chdir(self, dir, change_os_dir=0):
1154 """Change the current working directory for lookups. 1155 If change_os_dir is true, we will also change the "real" cwd 1156 to match. 1157 """ 1158 curr=self._cwd 1159 try: 1160 if dir is not None: 1161 self._cwd = dir 1162 if change_os_dir: 1163 os.chdir(dir.abspath) 1164 except OSError: 1165 self._cwd = curr 1166 raise
1167
1168 - def get_root(self, drive):
1169 """ 1170 Returns the root directory for the specified drive, creating 1171 it if necessary. 1172 """ 1173 drive = _my_normcase(drive) 1174 try: 1175 return self.Root[drive] 1176 except KeyError: 1177 root = RootDir(drive, self) 1178 self.Root[drive] = root 1179 if not drive: 1180 self.Root[self.defaultDrive] = root 1181 elif drive == self.defaultDrive: 1182 self.Root[''] = root 1183 return root
1184
1185 - def _lookup(self, p, directory, fsclass, create=1):
1186 """ 1187 The generic entry point for Node lookup with user-supplied data. 1188 1189 This translates arbitrary input into a canonical Node.FS object 1190 of the specified fsclass. The general approach for strings is 1191 to turn it into a fully normalized absolute path and then call 1192 the root directory's lookup_abs() method for the heavy lifting. 1193 1194 If the path name begins with '#', it is unconditionally 1195 interpreted relative to the top-level directory of this FS. '#' 1196 is treated as a synonym for the top-level SConstruct directory, 1197 much like '~' is treated as a synonym for the user's home 1198 directory in a UNIX shell. So both '#foo' and '#/foo' refer 1199 to the 'foo' subdirectory underneath the top-level SConstruct 1200 directory. 1201 1202 If the path name is relative, then the path is looked up relative 1203 to the specified directory, or the current directory (self._cwd, 1204 typically the SConscript directory) if the specified directory 1205 is None. 1206 """ 1207 if isinstance(p, Base): 1208 # It's already a Node.FS object. Make sure it's the right 1209 # class and return. 1210 p.must_be_same(fsclass) 1211 return p 1212 # str(p) in case it's something like a proxy object 1213 p = str(p) 1214 1215 if not os_sep_is_slash: 1216 p = p.replace(OS_SEP, '/') 1217 1218 if p[0:1] == '#': 1219 # There was an initial '#', so we strip it and override 1220 # whatever directory they may have specified with the 1221 # top-level SConstruct directory. 1222 p = p[1:] 1223 directory = self.Top 1224 1225 # There might be a drive letter following the 1226 # '#'. Although it is not described in the SCons man page, 1227 # the regression test suite explicitly tests for that 1228 # syntax. It seems to mean the following thing: 1229 # 1230 # Assuming the the SCons top dir is in C:/xxx/yyy, 1231 # '#X:/toto' means X:/xxx/yyy/toto. 1232 # 1233 # i.e. it assumes that the X: drive has a directory 1234 # structure similar to the one found on drive C:. 1235 if do_splitdrive: 1236 drive, p = _my_splitdrive(p) 1237 if drive: 1238 root = self.get_root(drive) 1239 else: 1240 root = directory.root 1241 else: 1242 root = directory.root 1243 1244 # We can only strip trailing after splitting the drive 1245 # since the drive might the UNC '//' prefix. 1246 p = p.strip('/') 1247 1248 needs_normpath = needs_normpath_match(p) 1249 1250 # The path is relative to the top-level SCons directory. 1251 if p in ('', '.'): 1252 p = directory.labspath 1253 else: 1254 p = directory.labspath + '/' + p 1255 else: 1256 if do_splitdrive: 1257 drive, p = _my_splitdrive(p) 1258 if drive and not p: 1259 # This causes a naked drive letter to be treated 1260 # as a synonym for the root directory on that 1261 # drive. 1262 p = '/' 1263 else: 1264 drive = '' 1265 1266 # We can only strip trailing '/' since the drive might the 1267 # UNC '//' prefix. 1268 if p != '/': 1269 p = p.rstrip('/') 1270 1271 needs_normpath = needs_normpath_match(p) 1272 1273 if p[0:1] == '/': 1274 # Absolute path 1275 root = self.get_root(drive) 1276 else: 1277 # This is a relative lookup or to the current directory 1278 # (the path name is not absolute). Add the string to the 1279 # appropriate directory lookup path, after which the whole 1280 # thing gets normalized. 1281 if directory: 1282 if not isinstance(directory, Dir): 1283 directory = self.Dir(directory) 1284 else: 1285 directory = self._cwd 1286 1287 if p in ('', '.'): 1288 p = directory.labspath 1289 else: 1290 p = directory.labspath + '/' + p 1291 1292 if drive: 1293 root = self.get_root(drive) 1294 else: 1295 root = directory.root 1296 1297 if needs_normpath is not None: 1298 # Normalize a pathname. Will return the same result for 1299 # equivalent paths. 1300 # 1301 # We take advantage of the fact that we have an absolute 1302 # path here for sure. In addition, we know that the 1303 # components of lookup path are separated by slashes at 1304 # this point. Because of this, this code is about 2X 1305 # faster than calling os.path.normpath() followed by 1306 # replacing os.sep with '/' again. 1307 ins = p.split('/')[1:] 1308 outs = [] 1309 for d in ins: 1310 if d == '..': 1311 try: 1312 outs.pop() 1313 except IndexError: 1314 pass 1315 elif d not in ('', '.'): 1316 outs.append(d) 1317 p = '/' + '/'.join(outs) 1318 1319 return root._lookup_abs(p, fsclass, create)
1320
1321 - def Entry(self, name, directory = None, create = 1):
1322 """Look up or create a generic Entry node with the specified name. 1323 If the name is a relative path (begins with ./, ../, or a file 1324 name), then it is looked up relative to the supplied directory 1325 node, or to the top level directory of the FS (supplied at 1326 construction time) if no directory is supplied. 1327 """ 1328 return self._lookup(name, directory, Entry, create)
1329
1330 - def File(self, name, directory = None, create = 1):
1331 """Look up or create a File node with the specified name. If 1332 the name is a relative path (begins with ./, ../, or a file name), 1333 then it is looked up relative to the supplied directory node, 1334 or to the top level directory of the FS (supplied at construction 1335 time) if no directory is supplied. 1336 1337 This method will raise TypeError if a directory is found at the 1338 specified path. 1339 """ 1340 return self._lookup(name, directory, File, create)
1341
1342 - def Dir(self, name, directory = None, create = True):
1343 """Look up or create a Dir node with the specified name. If 1344 the name is a relative path (begins with ./, ../, or a file name), 1345 then it is looked up relative to the supplied directory node, 1346 or to the top level directory of the FS (supplied at construction 1347 time) if no directory is supplied. 1348 1349 This method will raise TypeError if a normal file is found at the 1350 specified path. 1351 """ 1352 return self._lookup(name, directory, Dir, create)
1353
1354 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1355 """Link the supplied variant directory to the source directory 1356 for purposes of building files.""" 1357 1358 if not isinstance(src_dir, SCons.Node.Node): 1359 src_dir = self.Dir(src_dir) 1360 if not isinstance(variant_dir, SCons.Node.Node): 1361 variant_dir = self.Dir(variant_dir) 1362 if src_dir.is_under(variant_dir): 1363 raise SCons.Errors.UserError("Source directory cannot be under variant directory.") 1364 if variant_dir.srcdir: 1365 if variant_dir.srcdir == src_dir: 1366 return # We already did this. 1367 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)) 1368 variant_dir.link(src_dir, duplicate)
1369
1370 - def Repository(self, *dirs):
1371 """Specify Repository directories to search.""" 1372 for d in dirs: 1373 if not isinstance(d, SCons.Node.Node): 1374 d = self.Dir(d) 1375 self.Top.addRepository(d)
1376
1377 - def variant_dir_target_climb(self, orig, dir, tail):
1378 """Create targets in corresponding variant directories 1379 1380 Climb the directory tree, and look up path names 1381 relative to any linked variant directories we find. 1382 1383 Even though this loops and walks up the tree, we don't memoize 1384 the return value because this is really only used to process 1385 the command-line targets. 1386 """ 1387 targets = [] 1388 message = None 1389 fmt = "building associated VariantDir targets: %s" 1390 start_dir = dir 1391 while dir: 1392 for bd in dir.variant_dirs: 1393 if start_dir.is_under(bd): 1394 # If already in the build-dir location, don't reflect 1395 return [orig], fmt % str(orig) 1396 p = os.path.join(bd.path, *tail) 1397 targets.append(self.Entry(p)) 1398 tail = [dir.name] + tail 1399 dir = dir.up() 1400 if targets: 1401 message = fmt % ' '.join(map(str, targets)) 1402 return targets, message
1403
1404 - def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1405 """ 1406 Globs 1407 1408 This is mainly a shim layer 1409 """ 1410 if cwd is None: 1411 cwd = self.getcwd() 1412 return cwd.glob(pathname, ondisk, source, strings)
1413
1414 -class DirNodeInfo(SCons.Node.NodeInfoBase):
1415 # This should get reset by the FS initialization. 1416 current_version_id = 1 1417 1418 fs = None 1419
1420 - def str_to_node(self, s):
1421 top = self.fs.Top 1422 root = top.root 1423 if do_splitdrive: 1424 drive, s = _my_splitdrive(s) 1425 if drive: 1426 root = self.fs.get_root(drive) 1427 if not os.path.isabs(s): 1428 s = top.labspath + '/' + s 1429 return root._lookup_abs(s, Entry)
1430
1431 -class DirBuildInfo(SCons.Node.BuildInfoBase):
1432 current_version_id = 1
1433 1434 glob_magic_check = re.compile('[*?[]') 1435
1436 -def has_glob_magic(s):
1437 return glob_magic_check.search(s) is not None
1438
1439 -class Dir(Base):
1440 """A class for directories in a file system. 1441 """ 1442 1443 memoizer_counters = [] 1444 1445 NodeInfo = DirNodeInfo 1446 BuildInfo = DirBuildInfo 1447
1448 - def __init__(self, name, directory, fs):
1449 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Dir') 1450 Base.__init__(self, name, directory, fs) 1451 self._morph()
1452
1453 - def _morph(self):
1454 """Turn a file system Node (either a freshly initialized directory 1455 object or a separate Entry object) into a proper directory object. 1456 1457 Set up this directory's entries and hook it into the file 1458 system tree. Specify that directories (this Node) don't use 1459 signatures for calculating whether they're current. 1460 """ 1461 1462 self.repositories = [] 1463 self.srcdir = None 1464 1465 self.entries = {} 1466 self.entries['.'] = self 1467 self.entries['..'] = self.dir 1468 self.cwd = self 1469 self.searched = 0 1470 self._sconsign = None 1471 self.variant_dirs = [] 1472 self.root = self.dir.root 1473 1474 # For directories, we make a difference between the directory 1475 # 'name' and the directory 'dirname'. The 'name' attribute is 1476 # used when we need to print the 'name' of the directory or 1477 # when we it is used as the last part of a path. The 'dirname' 1478 # is used when the directory is not the last element of the 1479 # path. The main reason for making that distinction is that 1480 # for RoorDir's the dirname can not be easily inferred from 1481 # the name. For example, we have to add a '/' after a drive 1482 # letter but not after a UNC path prefix ('//'). 1483 self.dirname = self.name + OS_SEP 1484 1485 # Don't just reset the executor, replace its action list, 1486 # because it might have some pre-or post-actions that need to 1487 # be preserved. 1488 # 1489 # But don't reset the executor if there is a non-null executor 1490 # attached already. The existing executor might have other 1491 # targets, in which case replacing the action list with a 1492 # Mkdir action is a big mistake. 1493 if not hasattr(self, 'executor'): 1494 self.builder = get_MkdirBuilder() 1495 self.get_executor().set_action_list(self.builder.action) 1496 else: 1497 # Prepend MkdirBuilder action to existing action list 1498 l = self.get_executor().action_list 1499 a = get_MkdirBuilder().action 1500 l.insert(0, a) 1501 self.get_executor().set_action_list(l)
1502
1503 - def diskcheck_match(self):
1504 diskcheck_match(self, self.isfile, 1505 "File %s found where directory expected.")
1506
1507 - def __clearRepositoryCache(self, duplicate=None):
1508 """Called when we change the repository(ies) for a directory. 1509 This clears any cached information that is invalidated by changing 1510 the repository.""" 1511 1512 for node in self.entries.values(): 1513 if node != self.dir: 1514 if node != self and isinstance(node, Dir): 1515 node.__clearRepositoryCache(duplicate) 1516 else: 1517 node.clear() 1518 try: 1519 del node._srcreps 1520 except AttributeError: 1521 pass 1522 if duplicate is not None: 1523 node.duplicate=duplicate
1524
1525 - def __resetDuplicate(self, node):
1526 if node != self: 1527 node.duplicate = node.get_dir().duplicate
1528
1529 - def Entry(self, name):
1530 """ 1531 Looks up or creates an entry node named 'name' relative to 1532 this directory. 1533 """ 1534 return self.fs.Entry(name, self)
1535
1536 - def Dir(self, name, create=True):
1537 """ 1538 Looks up or creates a directory node named 'name' relative to 1539 this directory. 1540 """ 1541 return self.fs.Dir(name, self, create)
1542
1543 - def File(self, name):
1544 """ 1545 Looks up or creates a file node named 'name' relative to 1546 this directory. 1547 """ 1548 return self.fs.File(name, self)
1549 1557
1558 - def getRepositories(self):
1559 """Returns a list of repositories for this directory. 1560 """ 1561 if self.srcdir and not self.duplicate: 1562 return self.srcdir.get_all_rdirs() + self.repositories 1563 return self.repositories
1564 1565 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs')) 1566
1567 - def get_all_rdirs(self):
1568 try: 1569 return list(self._memo['get_all_rdirs']) 1570 except KeyError: 1571 pass 1572 1573 result = [self] 1574 fname = '.' 1575 dir = self 1576 while dir: 1577 for rep in dir.getRepositories(): 1578 result.append(rep.Dir(fname)) 1579 if fname == '.': 1580 fname = dir.name 1581 else: 1582 fname = dir.name + OS_SEP + fname 1583 dir = dir.up() 1584 1585 self._memo['get_all_rdirs'] = list(result) 1586 1587 return result
1588
1589 - def addRepository(self, dir):
1590 if dir != self and not dir in self.repositories: 1591 self.repositories.append(dir) 1592 dir.tpath = '.' 1593 self.__clearRepositoryCache()
1594
1595 - def up(self):
1596 return self.dir
1597
1598 - def _rel_path_key(self, other):
1599 return str(other)
1600 1601 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key)) 1602
1603 - def rel_path(self, other):
1604 """Return a path to "other" relative to this directory. 1605 """ 1606 1607 # This complicated and expensive method, which constructs relative 1608 # paths between arbitrary Node.FS objects, is no longer used 1609 # by SCons itself. It was introduced to store dependency paths 1610 # in .sconsign files relative to the target, but that ended up 1611 # being significantly inefficient. 1612 # 1613 # We're continuing to support the method because some SConstruct 1614 # files out there started using it when it was available, and 1615 # we're all about backwards compatibility.. 1616 1617 try: 1618 memo_dict = self._memo['rel_path'] 1619 except KeyError: 1620 memo_dict = {} 1621 self._memo['rel_path'] = memo_dict 1622 else: 1623 try: 1624 return memo_dict[other] 1625 except KeyError: 1626 pass 1627 1628 if self is other: 1629 result = '.' 1630 1631 elif not other in self.path_elements: 1632 try: 1633 other_dir = other.get_dir() 1634 except AttributeError: 1635 result = str(other) 1636 else: 1637 if other_dir is None: 1638 result = other.name 1639 else: 1640 dir_rel_path = self.rel_path(other_dir) 1641 if dir_rel_path == '.': 1642 result = other.name 1643 else: 1644 result = dir_rel_path + OS_SEP + other.name 1645 else: 1646 i = self.path_elements.index(other) + 1 1647 1648 path_elems = ['..'] * (len(self.path_elements) - i) \ 1649 + [n.name for n in other.path_elements[i:]] 1650 1651 result = OS_SEP.join(path_elems) 1652 1653 memo_dict[other] = result 1654 1655 return result
1656
1657 - def get_env_scanner(self, env, kw={}):
1658 import SCons.Defaults 1659 return SCons.Defaults.DirEntryScanner
1660
1661 - def get_target_scanner(self):
1662 import SCons.Defaults 1663 return SCons.Defaults.DirEntryScanner
1664
1665 - def get_found_includes(self, env, scanner, path):
1666 """Return this directory's implicit dependencies. 1667 1668 We don't bother caching the results because the scan typically 1669 shouldn't be requested more than once (as opposed to scanning 1670 .h file contents, which can be requested as many times as the 1671 files is #included by other files). 1672 """ 1673 if not scanner: 1674 return [] 1675 # Clear cached info for this Dir. If we already visited this 1676 # directory on our walk down the tree (because we didn't know at 1677 # that point it was being used as the source for another Node) 1678 # then we may have calculated build signature before realizing 1679 # we had to scan the disk. Now that we have to, though, we need 1680 # to invalidate the old calculated signature so that any node 1681 # dependent on our directory structure gets one that includes 1682 # info about everything on disk. 1683 self.clear() 1684 return scanner(self, env, path)
1685 1686 # 1687 # Taskmaster interface subsystem 1688 # 1689
1690 - def prepare(self):
1691 pass
1692
1693 - def build(self, **kw):
1694 """A null "builder" for directories.""" 1695 global MkdirBuilder 1696 if self.builder is not MkdirBuilder: 1697 SCons.Node.Node.build(self, **kw)
1698 1699 # 1700 # 1701 # 1702
1703 - def _create(self):
1704 """Create this directory, silently and without worrying about 1705 whether the builder is the default or not.""" 1706 listDirs = [] 1707 parent = self 1708 while parent: 1709 if parent.exists(): 1710 break 1711 listDirs.append(parent) 1712 p = parent.up() 1713 if p is None: 1714 # Don't use while: - else: for this condition because 1715 # if so, then parent is None and has no .path attribute. 1716 raise SCons.Errors.StopError(parent.path) 1717 parent = p 1718 listDirs.reverse() 1719 for dirnode in listDirs: 1720 try: 1721 # Don't call dirnode.build(), call the base Node method 1722 # directly because we definitely *must* create this 1723 # directory. The dirnode.build() method will suppress 1724 # the build if it's the default builder. 1725 SCons.Node.Node.build(dirnode) 1726 dirnode.get_executor().nullify() 1727 # The build() action may or may not have actually 1728 # created the directory, depending on whether the -n 1729 # option was used or not. Delete the _exists and 1730 # _rexists attributes so they can be reevaluated. 1731 dirnode.clear() 1732 except OSError: 1733 pass
1734
1736 global MkdirBuilder 1737 return self.builder is not MkdirBuilder and self.has_builder()
1738
1739 - def alter_targets(self):
1740 """Return any corresponding targets in a variant directory. 1741 """ 1742 return self.fs.variant_dir_target_climb(self, self, [])
1743
1744 - def scanner_key(self):
1745 """A directory does not get scanned.""" 1746 return None
1747
1748 - def get_text_contents(self):
1749 """We already emit things in text, so just return the binary 1750 version.""" 1751 return self.get_contents()
1752
1753 - def get_contents(self):
1754 """Return content signatures and names of all our children 1755 separated by new-lines. Ensure that the nodes are sorted.""" 1756 contents = [] 1757 for node in sorted(self.children(), key=lambda t: t.name): 1758 contents.append('%s %s\n' % (node.get_csig(), node.name)) 1759 return ''.join(contents)
1760
1761 - def get_csig(self):
1762 """Compute the content signature for Directory nodes. In 1763 general, this is not needed and the content signature is not 1764 stored in the DirNodeInfo. However, if get_contents on a Dir 1765 node is called which has a child directory, the child 1766 directory should return the hash of its contents.""" 1767 contents = self.get_contents() 1768 return SCons.Util.MD5signature(contents)
1769
1770 - def do_duplicate(self, src):
1771 pass
1772 1773 changed_since_last_build = SCons.Node.Node.state_has_changed 1774
1775 - def is_up_to_date(self):
1776 """If any child is not up-to-date, then this directory isn't, 1777 either.""" 1778 if self.builder is not MkdirBuilder and not self.exists(): 1779 return 0 1780 up_to_date = SCons.Node.up_to_date 1781 for kid in self.children(): 1782 if kid.get_state() > up_to_date: 1783 return 0 1784 return 1
1785
1786 - def rdir(self):
1787 if not self.exists(): 1788 norm_name = _my_normcase(self.name) 1789 for dir in self.dir.get_all_rdirs(): 1790 try: node = dir.entries[norm_name] 1791 except KeyError: node = dir.dir_on_disk(self.name) 1792 if node and node.exists() and \ 1793 (isinstance(dir, Dir) or isinstance(dir, Entry)): 1794 return node 1795 return self
1796
1797 - def sconsign(self):
1798 """Return the .sconsign file info for this directory, 1799 creating it first if necessary.""" 1800 if not self._sconsign: 1801 import SCons.SConsign 1802 self._sconsign = SCons.SConsign.ForDirectory(self) 1803 return self._sconsign
1804
1805 - def srcnode(self):
1806 """Dir has a special need for srcnode()...if we 1807 have a srcdir attribute set, then that *is* our srcnode.""" 1808 if self.srcdir: 1809 return self.srcdir 1810 return Base.srcnode(self)
1811
1812 - def get_timestamp(self):
1813 """Return the latest timestamp from among our children""" 1814 stamp = 0 1815 for kid in self.children(): 1816 if kid.get_timestamp() > stamp: 1817 stamp = kid.get_timestamp() 1818 return stamp
1819
1820 - def entry_abspath(self, name):
1821 return self.abspath + OS_SEP + name
1822
1823 - def entry_labspath(self, name):
1824 return self.labspath + '/' + name
1825
1826 - def entry_path(self, name):
1827 return self.path + OS_SEP + name
1828
1829 - def entry_tpath(self, name):
1830 return self.tpath + OS_SEP + name
1831
1832 - def entry_exists_on_disk(self, name):
1833 try: 1834 d = self.on_disk_entries 1835 except AttributeError: 1836 d = {} 1837 try: 1838 entries = os.listdir(self.abspath) 1839 except OSError: 1840 pass 1841 else: 1842 for entry in map(_my_normcase, entries): 1843 d[entry] = True 1844 self.on_disk_entries = d 1845 if sys.platform == 'win32' or sys.platform == 'cygwin': 1846 name = _my_normcase(name) 1847 result = d.get(name) 1848 if result is None: 1849 # Belt-and-suspenders for Windows: check directly for 1850 # 8.3 file names that don't show up in os.listdir(). 1851 result = os.path.exists(self.abspath + OS_SEP + name) 1852 d[name] = result 1853 return result 1854 else: 1855 return name in d
1856 1857 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list')) 1858
1859 - def srcdir_list(self):
1860 try: 1861 return self._memo['srcdir_list'] 1862 except KeyError: 1863 pass 1864 1865 result = [] 1866 1867 dirname = '.' 1868 dir = self 1869 while dir: 1870 if dir.srcdir: 1871 result.append(dir.srcdir.Dir(dirname)) 1872 dirname = dir.name + OS_SEP + dirname 1873 dir = dir.up() 1874 1875 self._memo['srcdir_list'] = result 1876 1877 return result
1878
1879 - def srcdir_duplicate(self, name):
1880 for dir in self.srcdir_list(): 1881 if self.is_under(dir): 1882 # We shouldn't source from something in the build path; 1883 # variant_dir is probably under src_dir, in which case 1884 # we are reflecting. 1885 break 1886 if dir.entry_exists_on_disk(name): 1887 srcnode = dir.Entry(name).disambiguate() 1888 if self.duplicate: 1889 node = self.Entry(name).disambiguate() 1890 node.do_duplicate(srcnode) 1891 return node 1892 else: 1893 return srcnode 1894 return None
1895
1896 - def _srcdir_find_file_key(self, filename):
1897 return filename
1898 1899 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key)) 1900
1901 - def srcdir_find_file(self, filename):
1902 try: 1903 memo_dict = self._memo['srcdir_find_file'] 1904 except KeyError: 1905 memo_dict = {} 1906 self._memo['srcdir_find_file'] = memo_dict 1907 else: 1908 try: 1909 return memo_dict[filename] 1910 except KeyError: 1911 pass 1912 1913 def func(node): 1914 if (isinstance(node, File) or isinstance(node, Entry)) and \ 1915 (node.is_derived() or node.exists()): 1916 return node 1917 return None
1918 1919 norm_name = _my_normcase(filename) 1920 1921 for rdir in self.get_all_rdirs(): 1922 try: node = rdir.entries[norm_name] 1923 except KeyError: node = rdir.file_on_disk(filename) 1924 else: node = func(node) 1925 if node: 1926 result = (node, self) 1927 memo_dict[filename] = result 1928 return result 1929 1930 for srcdir in self.srcdir_list(): 1931 for rdir in srcdir.get_all_rdirs(): 1932 try: node = rdir.entries[norm_name] 1933 except KeyError: node = rdir.file_on_disk(filename) 1934 else: node = func(node) 1935 if node: 1936 result = (File(filename, self, self.fs), srcdir) 1937 memo_dict[filename] = result 1938 return result 1939 1940 result = (None, None) 1941 memo_dict[filename] = result 1942 return result
1943
1944 - def dir_on_disk(self, name):
1945 if self.entry_exists_on_disk(name): 1946 try: return self.Dir(name) 1947 except TypeError: pass 1948 node = self.srcdir_duplicate(name) 1949 if isinstance(node, File): 1950 return None 1951 return node
1952
1953 - def file_on_disk(self, name):
1954 if self.entry_exists_on_disk(name) or \ 1955 diskcheck_rcs(self, name) or \ 1956 diskcheck_sccs(self, name): 1957 try: return self.File(name) 1958 except TypeError: pass 1959 node = self.srcdir_duplicate(name) 1960 if isinstance(node, Dir): 1961 return None 1962 return node
1963
1964 - def walk(self, func, arg):
1965 """ 1966 Walk this directory tree by calling the specified function 1967 for each directory in the tree. 1968 1969 This behaves like the os.path.walk() function, but for in-memory 1970 Node.FS.Dir objects. The function takes the same arguments as 1971 the functions passed to os.path.walk(): 1972 1973 func(arg, dirname, fnames) 1974 1975 Except that "dirname" will actually be the directory *Node*, 1976 not the string. The '.' and '..' entries are excluded from 1977 fnames. The fnames list may be modified in-place to filter the 1978 subdirectories visited or otherwise impose a specific order. 1979 The "arg" argument is always passed to func() and may be used 1980 in any way (or ignored, passing None is common). 1981 """ 1982 entries = self.entries 1983 names = list(entries.keys()) 1984 names.remove('.') 1985 names.remove('..') 1986 func(arg, self, names) 1987 for dirname in [n for n in names if isinstance(entries[n], Dir)]: 1988 entries[dirname].walk(func, arg)
1989
1990 - def glob(self, pathname, ondisk=True, source=False, strings=False):
1991 """ 1992 Returns a list of Nodes (or strings) matching a specified 1993 pathname pattern. 1994 1995 Pathname patterns follow UNIX shell semantics: * matches 1996 any-length strings of any characters, ? matches any character, 1997 and [] can enclose lists or ranges of characters. Matches do 1998 not span directory separators. 1999 2000 The matches take into account Repositories, returning local 2001 Nodes if a corresponding entry exists in a Repository (either 2002 an in-memory Node or something on disk). 2003 2004 By defafult, the glob() function matches entries that exist 2005 on-disk, in addition to in-memory Nodes. Setting the "ondisk" 2006 argument to False (or some other non-true value) causes the glob() 2007 function to only match in-memory Nodes. The default behavior is 2008 to return both the on-disk and in-memory Nodes. 2009 2010 The "source" argument, when true, specifies that corresponding 2011 source Nodes must be returned if you're globbing in a build 2012 directory (initialized with VariantDir()). The default behavior 2013 is to return Nodes local to the VariantDir(). 2014 2015 The "strings" argument, when true, returns the matches as strings, 2016 not Nodes. The strings are path names relative to this directory. 2017 2018 The underlying algorithm is adapted from the glob.glob() function 2019 in the Python library (but heavily modified), and uses fnmatch() 2020 under the covers. 2021 """ 2022 dirname, basename = os.path.split(pathname) 2023 if not dirname: 2024 return sorted(self._glob1(basename, ondisk, source, strings), 2025 key=lambda t: str(t)) 2026 if has_glob_magic(dirname): 2027 list = self.glob(dirname, ondisk, source, strings=False) 2028 else: 2029 list = [self.Dir(dirname, create=True)] 2030 result = [] 2031 for dir in list: 2032 r = dir._glob1(basename, ondisk, source, strings) 2033 if strings: 2034 r = [os.path.join(str(dir), x) for x in r] 2035 result.extend(r) 2036 return sorted(result, key=lambda a: str(a))
2037
2038 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2039 """ 2040 Globs for and returns a list of entry names matching a single 2041 pattern in this directory. 2042 2043 This searches any repositories and source directories for 2044 corresponding entries and returns a Node (or string) relative 2045 to the current directory if an entry is found anywhere. 2046 2047 TODO: handle pattern with no wildcard 2048 """ 2049 search_dir_list = self.get_all_rdirs() 2050 for srcdir in self.srcdir_list(): 2051 search_dir_list.extend(srcdir.get_all_rdirs()) 2052 2053 selfEntry = self.Entry 2054 names = [] 2055 for dir in search_dir_list: 2056 # We use the .name attribute from the Node because the keys of 2057 # the dir.entries dictionary are normalized (that is, all upper 2058 # case) on case-insensitive systems like Windows. 2059 node_names = [ v.name for k, v in dir.entries.items() 2060 if k not in ('.', '..') ] 2061 names.extend(node_names) 2062 if not strings: 2063 # Make sure the working directory (self) actually has 2064 # entries for all Nodes in repositories or variant dirs. 2065 for name in node_names: selfEntry(name) 2066 if ondisk: 2067 try: 2068 disk_names = os.listdir(dir.abspath) 2069 except os.error: 2070 continue 2071 names.extend(disk_names) 2072 if not strings: 2073 # We're going to return corresponding Nodes in 2074 # the local directory, so we need to make sure 2075 # those Nodes exist. We only want to create 2076 # Nodes for the entries that will match the 2077 # specified pattern, though, which means we 2078 # need to filter the list here, even though 2079 # the overall list will also be filtered later, 2080 # after we exit this loop. 2081 if pattern[0] != '.': 2082 #disk_names = [ d for d in disk_names if d[0] != '.' ] 2083 disk_names = [x for x in disk_names if x[0] != '.'] 2084 disk_names = fnmatch.filter(disk_names, pattern) 2085 dirEntry = dir.Entry 2086 for name in disk_names: 2087 # Add './' before disk filename so that '#' at 2088 # beginning of filename isn't interpreted. 2089 name = './' + name 2090 node = dirEntry(name).disambiguate() 2091 n = selfEntry(name) 2092 if n.__class__ != node.__class__: 2093 n.__class__ = node.__class__ 2094 n._morph() 2095 2096 names = set(names) 2097 if pattern[0] != '.': 2098 #names = [ n for n in names if n[0] != '.' ] 2099 names = [x for x in names if x[0] != '.'] 2100 names = fnmatch.filter(names, pattern) 2101 2102 if strings: 2103 return names 2104 2105 #return [ self.entries[_my_normcase(n)] for n in names ] 2106 return [self.entries[_my_normcase(n)] for n in names]
2107
2108 -class RootDir(Dir):
2109 """A class for the root directory of a file system. 2110 2111 This is the same as a Dir class, except that the path separator 2112 ('/' or '\\') is actually part of the name, so we don't need to 2113 add a separator when creating the path names of entries within 2114 this directory. 2115 """
2116 - def __init__(self, drive, fs):
2117 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir') 2118 # We're going to be our own parent directory (".." entry and .dir 2119 # attribute) so we have to set up some values so Base.__init__() 2120 # won't gag won't it calls some of our methods. 2121 self.abspath = '' 2122 self.labspath = '' 2123 self.path = '' 2124 self.tpath = '' 2125 self.path_elements = [] 2126 self.duplicate = 0 2127 self.root = self 2128 2129 # Handle all the types of drives: 2130 if drive == '': 2131 # No drive, regular UNIX root or Windows default drive. 2132 name = OS_SEP 2133 dirname = OS_SEP 2134 elif drive == '//': 2135 # UNC path 2136 name = UNC_PREFIX 2137 dirname = UNC_PREFIX 2138 else: 2139 # Windows drive letter 2140 name = drive 2141 dirname = drive + OS_SEP 2142 2143 Base.__init__(self, name, self, fs) 2144 2145 # Now set our paths to what we really want them to be. The 2146 # name should already contain any necessary separators, such 2147 # as the initial drive letter (the name) plus the directory 2148 # separator, except for the "lookup abspath," which does not 2149 # have the drive letter. 2150 self.abspath = dirname 2151 self.labspath = '' 2152 self.path = dirname 2153 self.tpath = dirname 2154 self._morph() 2155 2156 # Must be reset after Dir._morph() is invoked... 2157 self.dirname = dirname 2158 2159 self._lookupDict = {} 2160 2161 self._lookupDict[''] = self 2162 self._lookupDict['/'] = self 2163 2164 # The // entry is necessary because os.path.normpath() 2165 # preserves double slashes at the beginning of a path on Posix 2166 # platforms. 2167 if not has_unc: 2168 self._lookupDict['//'] = self
2169
2170 - def must_be_same(self, klass):
2171 if klass is Dir: 2172 return 2173 Base.must_be_same(self, klass)
2174
2175 - def _lookup_abs(self, p, klass, create=1):
2176 """ 2177 Fast (?) lookup of a *normalized* absolute path. 2178 2179 This method is intended for use by internal lookups with 2180 already-normalized path data. For general-purpose lookups, 2181 use the FS.Entry(), FS.Dir() or FS.File() methods. 2182 2183 The caller is responsible for making sure we're passed a 2184 normalized absolute path; we merely let Python's dictionary look 2185 up and return the One True Node.FS object for the path. 2186 2187 If a Node for the specified "p" doesn't already exist, and 2188 "create" is specified, the Node may be created after recursive 2189 invocation to find or create the parent directory or directories. 2190 """ 2191 k = _my_normcase(p) 2192 try: 2193 result = self._lookupDict[k] 2194 except KeyError: 2195 if not create: 2196 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self)) 2197 raise SCons.Errors.UserError(msg) 2198 # There is no Node for this path name, and we're allowed 2199 # to create it. 2200 # (note: would like to use p.rsplit('/',1) here but 2201 # that's not in python 2.3) 2202 # e.g.: dir_name, file_name = p.rsplit('/',1) 2203 last_slash = p.rindex('/') 2204 if (last_slash >= 0): 2205 dir_name = p[:last_slash] 2206 file_name = p[last_slash+1:] 2207 else: 2208 dir_name = p # shouldn't happen, just in case 2209 file_name = '' 2210 2211 dir_node = self._lookup_abs(dir_name, Dir) 2212 result = klass(file_name, dir_node, self.fs) 2213 2214 # Double-check on disk (as configured) that the Node we 2215 # created matches whatever is out there in the real world. 2216 result.diskcheck_match() 2217 2218 self._lookupDict[k] = result 2219 dir_node.entries[_my_normcase(file_name)] = result 2220 dir_node.implicit = None 2221 else: 2222 # There is already a Node for this path name. Allow it to 2223 # complain if we were looking for an inappropriate type. 2224 result.must_be_same(klass) 2225 return result
2226
2227 - def __str__(self):
2228 return self.abspath
2229
2230 - def entry_abspath(self, name):
2231 return self.abspath + name
2232
2233 - def entry_labspath(self, name):
2234 return '/' + name
2235
2236 - def entry_path(self, name):
2237 return self.path + name
2238
2239 - def entry_tpath(self, name):
2240 return self.tpath + name
2241
2242 - def is_under(self, dir):
2243 if self is dir: 2244 return 1 2245 else: 2246 return 0
2247
2248 - def up(self):
2249 return None
2250
2251 - def get_dir(self):
2252 return None
2253
2254 - def src_builder(self):
2255 return _null
2256
2257 -class FileNodeInfo(SCons.Node.NodeInfoBase):
2258 current_version_id = 1 2259 2260 field_list = ['csig', 'timestamp', 'size'] 2261 2262 # This should get reset by the FS initialization. 2263 fs = None 2264
2265 - def str_to_node(self, s):
2266 top = self.fs.Top 2267 root = top.root 2268 if do_splitdrive: 2269 drive, s = _my_splitdrive(s) 2270 if drive: 2271 root = self.fs.get_root(drive) 2272 if not os.path.isabs(s): 2273 s = top.labspath + '/' + s 2274 return root._lookup_abs(s, Entry)
2275
2276 -class FileBuildInfo(SCons.Node.BuildInfoBase):
2277 current_version_id = 1 2278
2279 - def convert_to_sconsign(self):
2280 """ 2281 Converts this FileBuildInfo object for writing to a .sconsign file 2282 2283 This replaces each Node in our various dependency lists with its 2284 usual string representation: relative to the top-level SConstruct 2285 directory, or an absolute path if it's outside. 2286 """ 2287 if os_sep_is_slash: 2288 node_to_str = str 2289 else: 2290 def node_to_str(n): 2291 try: 2292 s = n.path 2293 except AttributeError: 2294 s = str(n) 2295 else: 2296 s = s.replace(OS_SEP, '/') 2297 return s
2298 for attr in ['bsources', 'bdepends', 'bimplicit']: 2299 try: 2300 val = getattr(self, attr) 2301 except AttributeError: 2302 pass 2303 else: 2304 setattr(self, attr, list(map(node_to_str, val)))
2305 - def convert_from_sconsign(self, dir, name):
2306 """ 2307 Converts a newly-read FileBuildInfo object for in-SCons use 2308 2309 For normal up-to-date checking, we don't have any conversion to 2310 perform--but we're leaving this method here to make that clear. 2311 """ 2312 pass
2313 - def prepare_dependencies(self):
2314 """ 2315 Prepares a FileBuildInfo object for explaining what changed 2316 2317 The bsources, bdepends and bimplicit lists have all been 2318 stored on disk as paths relative to the top-level SConstruct 2319 directory. Convert the strings to actual Nodes (for use by the 2320 --debug=explain code and --implicit-cache). 2321 """ 2322 attrs = [ 2323 ('bsources', 'bsourcesigs'), 2324 ('bdepends', 'bdependsigs'), 2325 ('bimplicit', 'bimplicitsigs'), 2326 ] 2327 for (nattr, sattr) in attrs: 2328 try: 2329 strings = getattr(self, nattr) 2330 nodeinfos = getattr(self, sattr) 2331 except AttributeError: 2332 continue 2333 nodes = [] 2334 for s, ni in zip(strings, nodeinfos): 2335 if not isinstance(s, SCons.Node.Node): 2336 s = ni.str_to_node(s) 2337 nodes.append(s) 2338 setattr(self, nattr, nodes)
2339 - def format(self, names=0):
2340 result = [] 2341 bkids = self.bsources + self.bdepends + self.bimplicit 2342 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs 2343 for bkid, bkidsig in zip(bkids, bkidsigs): 2344 result.append(str(bkid) + ': ' + 2345 ' '.join(bkidsig.format(names=names))) 2346 result.append('%s [%s]' % (self.bactsig, self.bact)) 2347 return '\n'.join(result)
2348
2349 -class File(Base):
2350 """A class for files in a file system. 2351 """ 2352 2353 memoizer_counters = [] 2354 2355 NodeInfo = FileNodeInfo 2356 BuildInfo = FileBuildInfo 2357 2358 md5_chunksize = 64 2359
2360 - def diskcheck_match(self):
2361 diskcheck_match(self, self.isdir, 2362 "Directory %s found where file expected.")
2363
2364 - def __init__(self, name, directory, fs):
2365 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.File') 2366 Base.__init__(self, name, directory, fs) 2367 self._morph()
2368
2369 - def Entry(self, name):
2370 """Create an entry node named 'name' relative to 2371 the directory of this file.""" 2372 return self.dir.Entry(name)
2373
2374 - def Dir(self, name, create=True):
2375 """Create a directory node named 'name' relative to 2376 the directory of this file.""" 2377 return self.dir.Dir(name, create=create)
2378
2379 - def Dirs(self, pathlist):
2380 """Create a list of directories relative to the SConscript 2381 directory of this file.""" 2382 return [self.Dir(p) for p in pathlist]
2383
2384 - def File(self, name):
2385 """Create a file node named 'name' relative to 2386 the directory of this file.""" 2387 return self.dir.File(name)
2388 2389 #def generate_build_dict(self): 2390 # """Return an appropriate dictionary of values for building 2391 # this File.""" 2392 # return {'Dir' : self.Dir, 2393 # 'File' : self.File, 2394 # 'RDirs' : self.RDirs} 2395
2396 - def _morph(self):
2397 """Turn a file system node into a File object.""" 2398 self.scanner_paths = {} 2399 if not hasattr(self, '_local'): 2400 self._local = 0 2401 if not hasattr(self, 'released_target_info'): 2402 self.released_target_info = False 2403 2404 # If there was already a Builder set on this entry, then 2405 # we need to make sure we call the target-decider function, 2406 # not the source-decider. Reaching in and doing this by hand 2407 # is a little bogus. We'd prefer to handle this by adding 2408 # an Entry.builder_set() method that disambiguates like the 2409 # other methods, but that starts running into problems with the 2410 # fragile way we initialize Dir Nodes with their Mkdir builders, 2411 # yet still allow them to be overridden by the user. Since it's 2412 # not clear right now how to fix that, stick with what works 2413 # until it becomes clear... 2414 if self.has_builder(): 2415 self.changed_since_last_build = self.decide_target
2416
2417 - def scanner_key(self):
2418 return self.get_suffix()
2419
2420 - def get_contents(self):
2421 if not self.rexists(): 2422 return '' 2423 fname = self.rfile().abspath 2424 try: 2425 contents = open(fname, "rb").read() 2426 except EnvironmentError, e: 2427 if not e.filename: 2428 e.filename = fname 2429 raise 2430 return contents
2431 2432 # This attempts to figure out what the encoding of the text is 2433 # based upon the BOM bytes, and then decodes the contents so that 2434 # it's a valid python string.
2435 - def get_text_contents(self):
2436 contents = self.get_contents() 2437 # The behavior of various decode() methods and functions 2438 # w.r.t. the initial BOM bytes is different for different 2439 # encodings and/or Python versions. ('utf-8' does not strip 2440 # them, but has a 'utf-8-sig' which does; 'utf-16' seems to 2441 # strip them; etc.) Just sidestep all the complication by 2442 # explicitly stripping the BOM before we decode(). 2443 if contents.startswith(codecs.BOM_UTF8): 2444 return contents[len(codecs.BOM_UTF8):].decode('utf-8') 2445 if contents.startswith(codecs.BOM_UTF16_LE): 2446 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le') 2447 if contents.startswith(codecs.BOM_UTF16_BE): 2448 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be') 2449 return contents
2450
2451 - def get_content_hash(self):
2452 """ 2453 Compute and return the MD5 hash for this file. 2454 """ 2455 if not self.rexists(): 2456 return SCons.Util.MD5signature('') 2457 fname = self.rfile().abspath 2458 try: 2459 cs = SCons.Util.MD5filesignature(fname, 2460 chunksize=SCons.Node.FS.File.md5_chunksize*1024) 2461 except EnvironmentError, e: 2462 if not e.filename: 2463 e.filename = fname 2464 raise 2465 return cs
2466 2467 2468 memoizer_counters.append(SCons.Memoize.CountValue('get_size')) 2469
2470 - def get_size(self):
2471 try: 2472 return self._memo['get_size'] 2473 except KeyError: 2474 pass 2475 2476 if self.rexists(): 2477 size = self.rfile().getsize() 2478 else: 2479 size = 0 2480 2481 self._memo['get_size'] = size 2482 2483 return size
2484 2485 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp')) 2486
2487 - def get_timestamp(self):
2488 try: 2489 return self._memo['get_timestamp'] 2490 except KeyError: 2491 pass 2492 2493 if self.rexists(): 2494 timestamp = self.rfile().getmtime() 2495 else: 2496 timestamp = 0 2497 2498 self._memo['get_timestamp'] = timestamp 2499 2500 return timestamp
2501
2502 - def store_info(self):
2503 # Merge our build information into the already-stored entry. 2504 # This accomodates "chained builds" where a file that's a target 2505 # in one build (SConstruct file) is a source in a different build. 2506 # See test/chained-build.py for the use case. 2507 if do_store_info: 2508 self.dir.sconsign().store_info(self.name, self)
2509 2510 convert_copy_attrs = [ 2511 'bsources', 2512 'bimplicit', 2513 'bdepends', 2514 'bact', 2515 'bactsig', 2516 'ninfo', 2517 ] 2518 2519 2520 convert_sig_attrs = [ 2521 'bsourcesigs', 2522 'bimplicitsigs', 2523 'bdependsigs', 2524 ] 2525
2526 - def convert_old_entry(self, old_entry):
2527 # Convert a .sconsign entry from before the Big Signature 2528 # Refactoring, doing what we can to convert its information 2529 # to the new .sconsign entry format. 2530 # 2531 # The old format looked essentially like this: 2532 # 2533 # BuildInfo 2534 # .ninfo (NodeInfo) 2535 # .bsig 2536 # .csig 2537 # .timestamp 2538 # .size 2539 # .bsources 2540 # .bsourcesigs ("signature" list) 2541 # .bdepends 2542 # .bdependsigs ("signature" list) 2543 # .bimplicit 2544 # .bimplicitsigs ("signature" list) 2545 # .bact 2546 # .bactsig 2547 # 2548 # The new format looks like this: 2549 # 2550 # .ninfo (NodeInfo) 2551 # .bsig 2552 # .csig 2553 # .timestamp 2554 # .size 2555 # .binfo (BuildInfo) 2556 # .bsources 2557 # .bsourcesigs (NodeInfo list) 2558 # .bsig 2559 # .csig 2560 # .timestamp 2561 # .size 2562 # .bdepends 2563 # .bdependsigs (NodeInfo list) 2564 # .bsig 2565 # .csig 2566 # .timestamp 2567 # .size 2568 # .bimplicit 2569 # .bimplicitsigs (NodeInfo list) 2570 # .bsig 2571 # .csig 2572 # .timestamp 2573 # .size 2574 # .bact 2575 # .bactsig 2576 # 2577 # The basic idea of the new structure is that a NodeInfo always 2578 # holds all available information about the state of a given Node 2579 # at a certain point in time. The various .b*sigs lists can just 2580 # be a list of pointers to the .ninfo attributes of the different 2581 # dependent nodes, without any copying of information until it's 2582 # time to pickle it for writing out to a .sconsign file. 2583 # 2584 # The complicating issue is that the *old* format only stored one 2585 # "signature" per dependency, based on however the *last* build 2586 # was configured. We don't know from just looking at it whether 2587 # it was a build signature, a content signature, or a timestamp 2588 # "signature". Since we no longer use build signatures, the 2589 # best we can do is look at the length and if it's thirty two, 2590 # assume that it was (or might have been) a content signature. 2591 # If it was actually a build signature, then it will cause a 2592 # rebuild anyway when it doesn't match the new content signature, 2593 # but that's probably the best we can do. 2594 import SCons.SConsign 2595 new_entry = SCons.SConsign.SConsignEntry() 2596 new_entry.binfo = self.new_binfo() 2597 binfo = new_entry.binfo 2598 for attr in self.convert_copy_attrs: 2599 try: 2600 value = getattr(old_entry, attr) 2601 except AttributeError: 2602 continue 2603 setattr(binfo, attr, value) 2604 delattr(old_entry, attr) 2605 for attr in self.convert_sig_attrs: 2606 try: 2607 sig_list = getattr(old_entry, attr) 2608 except AttributeError: 2609 continue 2610 value = [] 2611 for sig in sig_list: 2612 ninfo = self.new_ninfo() 2613 if len(sig) == 32: 2614 ninfo.csig = sig 2615 else: 2616 ninfo.timestamp = sig 2617 value.append(ninfo) 2618 setattr(binfo, attr, value) 2619 delattr(old_entry, attr) 2620 return new_entry
2621 2622 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info')) 2623
2624 - def get_stored_info(self):
2625 try: 2626 return self._memo['get_stored_info'] 2627 except KeyError: 2628 pass 2629 2630 try: 2631 sconsign_entry = self.dir.sconsign().get_entry(self.name) 2632 except (KeyError, EnvironmentError): 2633 import SCons.SConsign 2634 sconsign_entry = SCons.SConsign.SConsignEntry() 2635 sconsign_entry.binfo = self.new_binfo() 2636 sconsign_entry.ninfo = self.new_ninfo() 2637 else: 2638 if isinstance(sconsign_entry, FileBuildInfo): 2639 # This is a .sconsign file from before the Big Signature 2640 # Refactoring; convert it as best we can. 2641 sconsign_entry = self.convert_old_entry(sconsign_entry) 2642 try: 2643 delattr(sconsign_entry.ninfo, 'bsig') 2644 except AttributeError: 2645 pass 2646 2647 self._memo['get_stored_info'] = sconsign_entry 2648 2649 return sconsign_entry
2650
2651 - def get_stored_implicit(self):
2652 binfo = self.get_stored_info().binfo 2653 binfo.prepare_dependencies() 2654 try: return binfo.bimplicit 2655 except AttributeError: return None
2656
2657 - def rel_path(self, other):
2658 return self.dir.rel_path(other)
2659
2660 - def _get_found_includes_key(self, env, scanner, path):
2661 return (id(env), id(scanner), path)
2662 2663 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key)) 2664
2665 - def get_found_includes(self, env, scanner, path):
2666 """Return the included implicit dependencies in this file. 2667 Cache results so we only scan the file once per path 2668 regardless of how many times this information is requested. 2669 """ 2670 memo_key = (id(env), id(scanner), path) 2671 try: 2672 memo_dict = self._memo['get_found_includes'] 2673 except KeyError: 2674 memo_dict = {} 2675 self._memo['get_found_includes'] = memo_dict 2676 else: 2677 try: 2678 return memo_dict[memo_key] 2679 except KeyError: 2680 pass 2681 2682 if scanner: 2683 # result = [n.disambiguate() for n in scanner(self, env, path)] 2684 result = scanner(self, env, path) 2685 result = [N.disambiguate() for N in result] 2686 else: 2687 result = [] 2688 2689 memo_dict[memo_key] = result 2690 2691 return result
2692
2693 - def _createDir(self):
2694 # ensure that the directories for this node are 2695 # created. 2696 self.dir._create()
2697
2698 - def push_to_cache(self):
2699 """Try to push the node into a cache 2700 """ 2701 # This should get called before the Nodes' .built() method is 2702 # called, which would clear the build signature if the file has 2703 # a source scanner. 2704 # 2705 # We have to clear the local memoized values *before* we push 2706 # the node to cache so that the memoization of the self.exists() 2707 # return value doesn't interfere. 2708 if self.nocache: 2709 return 2710 self.clear_memoized_values() 2711 if self.exists(): 2712 self.get_build_env().get_CacheDir().push(self)
2713
2714 - def retrieve_from_cache(self):
2715 """Try to retrieve the node's content from a cache 2716 2717 This method is called from multiple threads in a parallel build, 2718 so only do thread safe stuff here. Do thread unsafe stuff in 2719 built(). 2720 2721 Returns true if the node was successfully retrieved. 2722 """ 2723 if self.nocache: 2724 return None 2725 if not self.is_derived(): 2726 return None 2727 return self.get_build_env().get_CacheDir().retrieve(self)
2728
2729 - def visited(self):
2730 if self.exists() and self.executor is not None: 2731 self.get_build_env().get_CacheDir().push_if_forced(self) 2732 2733 ninfo = self.get_ninfo() 2734 2735 csig = self.get_max_drift_csig() 2736 if csig: 2737 ninfo.csig = csig 2738 2739 ninfo.timestamp = self.get_timestamp() 2740 ninfo.size = self.get_size() 2741 2742 if not self.has_builder(): 2743 # This is a source file, but it might have been a target file 2744 # in another build that included more of the DAG. Copy 2745 # any build information that's stored in the .sconsign file 2746 # into our binfo object so it doesn't get lost. 2747 old = self.get_stored_info() 2748 self.get_binfo().__dict__.update(old.binfo.__dict__) 2749 2750 self.store_info()
2751
2752 - def release_target_info(self):
2753 """Called just after this node has been marked 2754 up-to-date or was built completely. 2755 2756 This is where we try to release as many target node infos 2757 as possible for clean builds and update runs, in order 2758 to minimize the overall memory consumption. 2759 2760 We'd like to remove a lot more attributes like self.sources 2761 and self.sources_set, but they might get used 2762 in a next build step. For example, during configuration 2763 the source files for a built *.o file are used to figure out 2764 which linker to use for the resulting Program (gcc vs. g++)! 2765 That's why we check for the 'keep_targetinfo' attribute, 2766 config Nodes and the Interactive mode just don't allow 2767 an early release of most variables. 2768 2769 In the same manner, we can't simply remove the self.attributes 2770 here. The smart linking relies on the shared flag, and some 2771 parts of the java Tool use it to transport information 2772 about nodes... 2773 2774 @see: built() and Node.release_target_info() 2775 """ 2776 if (self.released_target_info or SCons.Node.interactive): 2777 return 2778 2779 if not hasattr(self.attributes, 'keep_targetinfo'): 2780 # Cache some required values, before releasing 2781 # stuff like env, executor and builder... 2782 self.changed(allowcache=True) 2783 self.get_contents_sig() 2784 self.get_build_env() 2785 # Now purge unneeded stuff to free memory... 2786 self.executor = None 2787 self._memo.pop('rfile', None) 2788 self.prerequisites = None 2789 # Cleanup lists, but only if they're empty 2790 if not len(self.ignore_set): 2791 self.ignore_set = None 2792 if not len(self.implicit_set): 2793 self.implicit_set = None 2794 if not len(self.depends_set): 2795 self.depends_set = None 2796 if not len(self.ignore): 2797 self.ignore = None 2798 if not len(self.depends): 2799 self.depends = None 2800 # Mark this node as done, we only have to release 2801 # the memory once... 2802 self.released_target_info = True
2803
2804 - def find_src_builder(self):
2805 if self.rexists(): 2806 return None 2807 scb = self.dir.src_builder() 2808 if scb is _null: 2809 if diskcheck_sccs(self.dir, self.name): 2810 scb = get_DefaultSCCSBuilder() 2811 elif diskcheck_rcs(self.dir, self.name): 2812 scb = get_DefaultRCSBuilder() 2813 else: 2814 scb = None 2815 if scb is not None: 2816 try: 2817 b = self.builder 2818 except AttributeError: 2819 b = None 2820 if b is None: 2821 self.builder_set(scb) 2822 return scb
2823
2824 - def has_src_builder(self):
2825 """Return whether this Node has a source builder or not. 2826 2827 If this Node doesn't have an explicit source code builder, this 2828 is where we figure out, on the fly, if there's a transparent 2829 source code builder for it. 2830 2831 Note that if we found a source builder, we also set the 2832 self.builder attribute, so that all of the methods that actually 2833 *build* this file don't have to do anything different. 2834 """ 2835 try: 2836 scb = self.sbuilder 2837 except AttributeError: 2838 scb = self.sbuilder = self.find_src_builder() 2839 return scb is not None
2840
2841 - def alter_targets(self):
2842 """Return any corresponding targets in a variant directory. 2843 """ 2844 if self.is_derived(): 2845 return [], None 2846 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2847
2848 - def _rmv_existing(self):
2849 self.clear_memoized_values() 2850 if print_duplicate: 2851 print "dup: removing existing target %s"%self 2852 e = Unlink(self, [], None) 2853 if isinstance(e, SCons.Errors.BuildError): 2854 raise e
2855 2856 # 2857 # Taskmaster interface subsystem 2858 # 2859
2860 - def make_ready(self):
2861 self.has_src_builder() 2862 self.get_binfo()
2863
2864 - def prepare(self):
2865 """Prepare for this file to be created.""" 2866 SCons.Node.Node.prepare(self) 2867 2868 if self.get_state() != SCons.Node.up_to_date: 2869 if self.exists(): 2870 if self.is_derived() and not self.precious: 2871 self._rmv_existing() 2872 else: 2873 try: 2874 self._createDir() 2875 except SCons.Errors.StopError, drive: 2876 desc = "No drive `%s' for target `%s'." % (drive, self) 2877 raise SCons.Errors.StopError(desc)
2878 2879 # 2880 # 2881 # 2882
2883 - def remove(self):
2884 """Remove this file.""" 2885 if self.exists() or self.islink(): 2886 self.fs.unlink(self.path) 2887 return 1 2888 return None
2889
2890 - def do_duplicate(self, src):
2891 self._createDir() 2892 if print_duplicate: 2893 print "dup: relinking variant '%s' from '%s'"%(self, src) 2894 Unlink(self, None, None) 2895 e = Link(self, src, None) 2896 if isinstance(e, SCons.Errors.BuildError): 2897 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr) 2898 raise SCons.Errors.StopError(desc) 2899 self.linked = 1 2900 # The Link() action may or may not have actually 2901 # created the file, depending on whether the -n 2902 # option was used or not. Delete the _exists and 2903 # _rexists attributes so they can be reevaluated. 2904 self.clear()
2905 2906 memoizer_counters.append(SCons.Memoize.CountValue('exists')) 2907
2908 - def exists(self):
2909 try: 2910 return self._memo['exists'] 2911 except KeyError: 2912 pass 2913 # Duplicate from source path if we are set up to do this. 2914 if self.duplicate and not self.is_derived() and not self.linked: 2915 src = self.srcnode() 2916 if src is not self: 2917 # At this point, src is meant to be copied in a variant directory. 2918 src = src.rfile() 2919 if src.abspath != self.abspath: 2920 if src.exists(): 2921 self.do_duplicate(src) 2922 # Can't return 1 here because the duplication might 2923 # not actually occur if the -n option is being used. 2924 else: 2925 # The source file does not exist. Make sure no old 2926 # copy remains in the variant directory. 2927 if print_duplicate: 2928 print "dup: no src for %s, unlinking old variant copy"%self 2929 if Base.exists(self) or self.islink(): 2930 self.fs.unlink(self.path) 2931 # Return None explicitly because the Base.exists() call 2932 # above will have cached its value if the file existed. 2933 self._memo['exists'] = None 2934 return None 2935 result = Base.exists(self) 2936 self._memo['exists'] = result 2937 return result
2938 2939 # 2940 # SIGNATURE SUBSYSTEM 2941 # 2942
2943 - def get_max_drift_csig(self):
2944 """ 2945 Returns the content signature currently stored for this node 2946 if it's been unmodified longer than the max_drift value, or the 2947 max_drift value is 0. Returns None otherwise. 2948 """ 2949 old = self.get_stored_info() 2950 mtime = self.get_timestamp() 2951 2952 max_drift = self.fs.max_drift 2953 if max_drift > 0: 2954 if (time.time() - mtime) > max_drift: 2955 try: 2956 n = old.ninfo 2957 if n.timestamp and n.csig and n.timestamp == mtime: 2958 return n.csig 2959 except AttributeError: 2960 pass 2961 elif max_drift == 0: 2962 try: 2963 return old.ninfo.csig 2964 except AttributeError: 2965 pass 2966 2967 return None
2968
2969 - def get_csig(self):
2970 """ 2971 Generate a node's content signature, the digested signature 2972 of its content. 2973 2974 node - the node 2975 cache - alternate node to use for the signature cache 2976 returns - the content signature 2977 """ 2978 ninfo = self.get_ninfo() 2979 try: 2980 return ninfo.csig 2981 except AttributeError: 2982 pass 2983 2984 csig = self.get_max_drift_csig() 2985 if csig is None: 2986 2987 try: 2988 if self.get_size() < SCons.Node.FS.File.md5_chunksize: 2989 contents = self.get_contents() 2990 else: 2991 csig = self.get_content_hash() 2992 except IOError: 2993 # This can happen if there's actually a directory on-disk, 2994 # which can be the case if they've disabled disk checks, 2995 # or if an action with a File target actually happens to 2996 # create a same-named directory by mistake. 2997 csig = '' 2998 else: 2999 if not csig: 3000 csig = SCons.Util.MD5signature(contents) 3001 3002 ninfo.csig = csig 3003 3004 return csig
3005 3006 # 3007 # DECISION SUBSYSTEM 3008 # 3009
3010 - def builder_set(self, builder):
3011 SCons.Node.Node.builder_set(self, builder) 3012 self.changed_since_last_build = self.decide_target
3013
3014 - def built(self):
3015 """Called just after this File node is successfully built. 3016 3017 Just like for 'release_target_info' we try to release 3018 some more target node attributes in order to minimize the 3019 overall memory consumption. 3020 3021 @see: release_target_info 3022 """ 3023 3024 SCons.Node.Node.built(self) 3025 3026 if (not SCons.Node.interactive and 3027 not hasattr(self.attributes, 'keep_targetinfo')): 3028 # Ensure that the build infos get computed and cached... 3029 self.store_info() 3030 # ... then release some more variables. 3031 self._specific_sources = False 3032 self.labspath = None 3033 self._save_str() 3034 self.cwd = None 3035 3036 self.scanner_paths = None
3037
3038 - def changed(self, node=None, allowcache=False):
3039 """ 3040 Returns if the node is up-to-date with respect to the BuildInfo 3041 stored last time it was built. 3042 3043 For File nodes this is basically a wrapper around Node.changed(), 3044 but we allow the return value to get cached after the reference 3045 to the Executor got released in release_target_info(). 3046 3047 @see: Node.changed() 3048 """ 3049 if node is None: 3050 try: 3051 return self._memo['changed'] 3052 except KeyError: 3053 pass 3054 3055 has_changed = SCons.Node.Node.changed(self, node) 3056 if allowcache: 3057 self._memo['changed'] = has_changed 3058 return has_changed
3059
3060 - def changed_content(self, target, prev_ni):
3061 cur_csig = self.get_csig() 3062 try: 3063 return cur_csig != prev_ni.csig 3064 except AttributeError: 3065 return 1
3066
3067 - def changed_state(self, target, prev_ni):
3068 return self.state != SCons.Node.up_to_date
3069
3070 - def changed_timestamp_then_content(self, target, prev_ni):
3071 if not self.changed_timestamp_match(target, prev_ni): 3072 try: 3073 self.get_ninfo().csig = prev_ni.csig 3074 except AttributeError: 3075 pass 3076 return False 3077 return self.changed_content(target, prev_ni)
3078
3079 - def changed_timestamp_newer(self, target, prev_ni):
3080 try: 3081 return self.get_timestamp() > target.get_timestamp() 3082 except AttributeError: 3083 return 1
3084
3085 - def changed_timestamp_match(self, target, prev_ni):
3086 try: 3087 return self.get_timestamp() != prev_ni.timestamp 3088 except AttributeError: 3089 return 1
3090
3091 - def decide_source(self, target, prev_ni):
3092 return target.get_build_env().decide_source(self, target, prev_ni)
3093
3094 - def decide_target(self, target, prev_ni):
3095 return target.get_build_env().decide_target(self, target, prev_ni)
3096 3097 # Initialize this Node's decider function to decide_source() because 3098 # every file is a source file until it has a Builder attached... 3099 changed_since_last_build = decide_source 3100
3101 - def is_up_to_date(self):
3102 T = 0 3103 if T: Trace('is_up_to_date(%s):' % self) 3104 if not self.exists(): 3105 if T: Trace(' not self.exists():') 3106 # The file doesn't exist locally... 3107 r = self.rfile() 3108 if r != self: 3109 # ...but there is one in a Repository... 3110 if not self.changed(r): 3111 if T: Trace(' changed(%s):' % r) 3112 # ...and it's even up-to-date... 3113 if self._local: 3114 # ...and they'd like a local copy. 3115 e = LocalCopy(self, r, None) 3116 if isinstance(e, SCons.Errors.BuildError): 3117 raise 3118 self.store_info() 3119 if T: Trace(' 1\n') 3120 return 1 3121 self.changed() 3122 if T: Trace(' None\n') 3123 return None 3124 else: 3125 r = self.changed() 3126 if T: Trace(' self.exists(): %s\n' % r) 3127 return not r
3128 3129 memoizer_counters.append(SCons.Memoize.CountValue('rfile')) 3130
3131 - def rfile(self):
3132 try: 3133 return self._memo['rfile'] 3134 except KeyError: 3135 pass 3136 result = self 3137 if not self.exists(): 3138 norm_name = _my_normcase(self.name) 3139 for dir in self.dir.get_all_rdirs(): 3140 try: node = dir.entries[norm_name] 3141 except KeyError: node = dir.file_on_disk(self.name) 3142 if node and node.exists() and \ 3143 (isinstance(node, File) or isinstance(node, Entry) \ 3144 or not node.is_derived()): 3145 result = node 3146 # Copy over our local attributes to the repository 3147 # Node so we identify shared object files in the 3148 # repository and don't assume they're static. 3149 # 3150 # This isn't perfect; the attribute would ideally 3151 # be attached to the object in the repository in 3152 # case it was built statically in the repository 3153 # and we changed it to shared locally, but that's 3154 # rarely the case and would only occur if you 3155 # intentionally used the same suffix for both 3156 # shared and static objects anyway. So this 3157 # should work well in practice. 3158 result.attributes = self.attributes 3159 break 3160 self._memo['rfile'] = result 3161 return result
3162
3163 - def rstr(self):
3164 return str(self.rfile())
3165
3166 - def get_cachedir_csig(self):
3167 """ 3168 Fetch a Node's content signature for purposes of computing 3169 another Node's cachesig. 3170 3171 This is a wrapper around the normal get_csig() method that handles 3172 the somewhat obscure case of using CacheDir with the -n option. 3173 Any files that don't exist would normally be "built" by fetching 3174 them from the cache, but the normal get_csig() method will try 3175 to open up the local file, which doesn't exist because the -n 3176 option meant we didn't actually pull the file from cachedir. 3177 But since the file *does* actually exist in the cachedir, we 3178 can use its contents for the csig. 3179 """ 3180 try: 3181 return self.cachedir_csig 3182 except AttributeError: 3183 pass 3184 3185 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self) 3186 if not self.exists() and cachefile and os.path.exists(cachefile): 3187 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \ 3188 SCons.Node.FS.File.md5_chunksize * 1024) 3189 else: 3190 self.cachedir_csig = self.get_csig() 3191 return self.cachedir_csig
3192
3193 - def get_contents_sig(self):
3194 """ 3195 A helper method for get_cachedir_bsig. 3196 3197 It computes and returns the signature for this 3198 node's contents. 3199 """ 3200 3201 try: 3202 return self.contentsig 3203 except AttributeError: 3204 pass 3205 3206 executor = self.get_executor() 3207 3208 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents()) 3209 return result
3210
3211 - def get_cachedir_bsig(self):
3212 """ 3213 Return the signature for a cached file, including 3214 its children. 3215 3216 It adds the path of the cached file to the cache signature, 3217 because multiple targets built by the same action will all 3218 have the same build signature, and we have to differentiate 3219 them somehow. 3220 """ 3221 try: 3222 return self.cachesig 3223 except AttributeError: 3224 pass 3225 3226 # Collect signatures for all children 3227 children = self.children() 3228 sigs = [n.get_cachedir_csig() for n in children] 3229 # Append this node's signature... 3230 sigs.append(self.get_contents_sig()) 3231 # ...and it's path 3232 sigs.append(self.path) 3233 # Merge this all into a single signature 3234 result = self.cachesig = SCons.Util.MD5collect(sigs) 3235 return result
3236 3237 default_fs = None 3238
3239 -def get_default_fs():
3240 global default_fs 3241 if not default_fs: 3242 default_fs = FS() 3243 return default_fs
3244
3245 -class FileFinder(object):
3246 """ 3247 """ 3248 if SCons.Memoize.use_memoizer: 3249 __metaclass__ = SCons.Memoize.Memoized_Metaclass 3250 3251 memoizer_counters = [] 3252
3253 - def __init__(self):
3254 self._memo = {}
3255
3256 - def filedir_lookup(self, p, fd=None):
3257 """ 3258 A helper method for find_file() that looks up a directory for 3259 a file we're trying to find. This only creates the Dir Node if 3260 it exists on-disk, since if the directory doesn't exist we know 3261 we won't find any files in it... :-) 3262 3263 It would be more compact to just use this as a nested function 3264 with a default keyword argument (see the commented-out version 3265 below), but that doesn't work unless you have nested scopes, 3266 so we define it here just so this work under Python 1.5.2. 3267 """ 3268 if fd is None: 3269 fd = self.default_filedir 3270 dir, name = os.path.split(fd) 3271 drive, d = _my_splitdrive(dir) 3272 if not name and d[:1] in ('/', OS_SEP): 3273 #return p.fs.get_root(drive).dir_on_disk(name) 3274 return p.fs.get_root(drive) 3275 if dir: 3276 p = self.filedir_lookup(p, dir) 3277 if not p: 3278 return None 3279 norm_name = _my_normcase(name) 3280 try: 3281 node = p.entries[norm_name] 3282 except KeyError: 3283 return p.dir_on_disk(name) 3284 if isinstance(node, Dir): 3285 return node 3286 if isinstance(node, Entry): 3287 node.must_be_same(Dir) 3288 return node 3289 return None
3290
3291 - def _find_file_key(self, filename, paths, verbose=None):
3292 return (filename, paths)
3293 3294 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key)) 3295
3296 - def find_file(self, filename, paths, verbose=None):
3297 """ 3298 find_file(str, [Dir()]) -> [nodes] 3299 3300 filename - a filename to find 3301 paths - a list of directory path *nodes* to search in. Can be 3302 represented as a list, a tuple, or a callable that is 3303 called with no arguments and returns the list or tuple. 3304 3305 returns - the node created from the found file. 3306 3307 Find a node corresponding to either a derived file or a file 3308 that exists already. 3309 3310 Only the first file found is returned, and none is returned 3311 if no file is found. 3312 """ 3313 memo_key = self._find_file_key(filename, paths) 3314 try: 3315 memo_dict = self._memo['find_file'] 3316 except KeyError: 3317 memo_dict = {} 3318 self._memo['find_file'] = memo_dict 3319 else: 3320 try: 3321 return memo_dict[memo_key] 3322 except KeyError: 3323 pass 3324 3325 if verbose and not callable(verbose): 3326 if not SCons.Util.is_String(verbose): 3327 verbose = "find_file" 3328 _verbose = u' %s: ' % verbose 3329 verbose = lambda s: sys.stdout.write(_verbose + s) 3330 3331 filedir, filename = os.path.split(filename) 3332 if filedir: 3333 # More compact code that we can't use until we drop 3334 # support for Python 1.5.2: 3335 # 3336 #def filedir_lookup(p, fd=filedir): 3337 # """ 3338 # A helper function that looks up a directory for a file 3339 # we're trying to find. This only creates the Dir Node 3340 # if it exists on-disk, since if the directory doesn't 3341 # exist we know we won't find any files in it... :-) 3342 # """ 3343 # dir, name = os.path.split(fd) 3344 # if dir: 3345 # p = filedir_lookup(p, dir) 3346 # if not p: 3347 # return None 3348 # norm_name = _my_normcase(name) 3349 # try: 3350 # node = p.entries[norm_name] 3351 # except KeyError: 3352 # return p.dir_on_disk(name) 3353 # if isinstance(node, Dir): 3354 # return node 3355 # if isinstance(node, Entry): 3356 # node.must_be_same(Dir) 3357 # return node 3358 # if isinstance(node, Dir) or isinstance(node, Entry): 3359 # return node 3360 # return None 3361 #paths = [_f for _f in map(filedir_lookup, paths) if _f] 3362 3363 self.default_filedir = filedir 3364 paths = [_f for _f in map(self.filedir_lookup, paths) if _f] 3365 3366 result = None 3367 for dir in paths: 3368 if verbose: 3369 verbose("looking for '%s' in '%s' ...\n" % (filename, dir)) 3370 node, d = dir.srcdir_find_file(filename) 3371 if node: 3372 if verbose: 3373 verbose("... FOUND '%s' in '%s'\n" % (filename, d)) 3374 result = node 3375 break 3376 3377 memo_dict[memo_key] = result 3378 3379 return result
3380 3381 find_file = FileFinder().find_file 3382 3383
3384 -def invalidate_node_memos(targets):
3385 """ 3386 Invalidate the memoized values of all Nodes (files or directories) 3387 that are associated with the given entries. Has been added to 3388 clear the cache of nodes affected by a direct execution of an 3389 action (e.g. Delete/Copy/Chmod). Existing Node caches become 3390 inconsistent if the action is run through Execute(). The argument 3391 `targets` can be a single Node object or filename, or a sequence 3392 of Nodes/filenames. 3393 """ 3394 from traceback import extract_stack 3395 3396 # First check if the cache really needs to be flushed. Only 3397 # actions run in the SConscript with Execute() seem to be 3398 # affected. XXX The way to check if Execute() is in the stacktrace 3399 # is a very dirty hack and should be replaced by a more sensible 3400 # solution. 3401 for f in extract_stack(): 3402 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py': 3403 break 3404 else: 3405 # Dont have to invalidate, so return 3406 return 3407 3408 if not SCons.Util.is_List(targets): 3409 targets = [targets] 3410 3411 for entry in targets: 3412 # If the target is a Node object, clear the cache. If it is a 3413 # filename, look up potentially existing Node object first. 3414 try: 3415 entry.clear_memoized_values() 3416 except AttributeError: 3417 # Not a Node object, try to look up Node by filename. XXX 3418 # This creates Node objects even for those filenames which 3419 # do not correspond to an existing Node object. 3420 node = get_default_fs().Entry(entry) 3421 if node: 3422 node.clear_memoized_values()
3423 3424 # Local Variables: 3425 # tab-width:4 3426 # indent-tabs-mode:nil 3427 # End: 3428 # vim: set expandtab tabstop=4 shiftwidth=4: 3429