SCons.Node.FS

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'):
     140 -    def _hardlink_func(fs, src, dst):
     141          # If the source is a symlink, we can't just hard-link to it
     142          # because a relative symlink may point somewhere completely
     143          # different.  We must disambiguate the symlink and then
     144          # hard-link the final destination file.
     145          while fs.islink(src):
     146              link = fs.readlink(src)
     147              if not os.path.isabs(link):
     148                  src = link
     149              else:
     150                  src = os.path.join(os.path.dirname(src), link)
     151          fs.link(src, dst)
     152  else:
     153      _hardlink_func = None
     154
     155  if hasattr(os, 'symlink'):
     156 -    def _softlink_func(fs, src, dst):
     157          fs.symlink(src, dst)
     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'):
     646 -        def islink(self):
     647              try: st = self.fs.lstat(self.abspath)
     648              except os.error: return 0
     649              return stat.S_ISLNK(st[stat.ST_MODE])
     650      else:
     651 -        def islink(self):
     652              return 0                    # no symlinks
     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)
     962 -    def link(self, src, dst):
     963          return os.link(src, dst)
     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)
     976 -    def symlink(self, src, dst):
     977          return os.symlink(src, dst)
     978 -    def open(self, path):
     979          return open(path)
     980 -    def unlink(self, path):
     981          return os.unlink(path)
     982
     983      if hasattr(os, 'symlink'):
     984 -        def islink(self, path):
     985              return os.path.islink(path)
     986      else:
     987 -        def islink(self, path):
     988              return 0                    # no symlinks
     989
     990      if hasattr(os, 'readlink'):
     991 -        def readlink(self, file):
     992              return os.readlink(file)
     993      else:
     994 -        def readlink(self, file):
     995              return ''
     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
    1391 -    def link(self, srcdir, duplicate):
    1392          """Set this directory as the variant directory for the
    1393          supplied source directory."""
    1394          self.srcdir = srcdir
    1395          self.duplicate = duplicate
    1396          self.__clearRepositoryCache(duplicate)
    1397          srcdir.variant_dirs.append(self)
    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
    1578 -    def multiple_side_effect_has_builder(self):
    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