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