FS.py.svn-base
上传用户:market2
上传日期:2018-11-18
资源大小:18786k
文件大小:101k
源码类别:

外挂编程

开发平台:

Windows_Unix

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