/couchjs/scons/scons-local-2.0.1/SCons/Node/FS.py
Python | 3142 lines | 3135 code | 0 blank | 7 comment | 3 complexity | d19f553cac14f1ab777ac4f52b971912 MD5 | raw file
Possible License(s): Apache-2.0
Large files files are truncated, but you can click here to view the full file
- """scons.Node.FS
- File system nodes.
- These Nodes represent the canonical external objects that people think
- of when they think of building software: files and directories.
- This holds a "default_fs" variable that should be initialized with an FS
- that can be used by scripts or modules looking for the canonical default.
- """
- #
- # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation
- #
- # Permission is hereby granted, free of charge, to any person obtaining
- # a copy of this software and associated documentation files (the
- # "Software"), to deal in the Software without restriction, including
- # without limitation the rights to use, copy, modify, merge, publish,
- # distribute, sublicense, and/or sell copies of the Software, and to
- # permit persons to whom the Software is furnished to do so, subject to
- # the following conditions:
- #
- # The above copyright notice and this permission notice shall be included
- # in all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
- # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- __revision__ = "src/engine/SCons/Node/FS.py 5134 2010/08/16 23:02:40 bdeegan"
- import fnmatch
- import os
- import re
- import shutil
- import stat
- import sys
- import time
- import codecs
- import SCons.Action
- from SCons.Debug import logInstanceCreation
- import SCons.Errors
- import SCons.Memoize
- import SCons.Node
- import SCons.Node.Alias
- import SCons.Subst
- import SCons.Util
- import SCons.Warnings
- from SCons.Debug import Trace
- do_store_info = True
- class EntryProxyAttributeError(AttributeError):
- """
- An AttributeError subclass for recording and displaying the name
- of the underlying Entry involved in an AttributeError exception.
- """
- def __init__(self, entry_proxy, attribute):
- AttributeError.__init__(self)
- self.entry_proxy = entry_proxy
- self.attribute = attribute
- def __str__(self):
- entry = self.entry_proxy.get()
- fmt = "%s instance %s has no attribute %s"
- return fmt % (entry.__class__.__name__,
- repr(entry.name),
- repr(self.attribute))
- # The max_drift value: by default, use a cached signature value for
- # any file that's been untouched for more than two days.
- default_max_drift = 2*24*60*60
- #
- # We stringify these file system Nodes a lot. Turning a file system Node
- # into a string is non-trivial, because the final string representation
- # can depend on a lot of factors: whether it's a derived target or not,
- # whether it's linked to a repository or source directory, and whether
- # there's duplication going on. The normal technique for optimizing
- # calculations like this is to memoize (cache) the string value, so you
- # only have to do the calculation once.
- #
- # A number of the above factors, however, can be set after we've already
- # been asked to return a string for a Node, because a Repository() or
- # VariantDir() call or the like may not occur until later in SConscript
- # files. So this variable controls whether we bother trying to save
- # string values for Nodes. The wrapper interface can set this whenever
- # they're done mucking with Repository and VariantDir and the other stuff,
- # to let this module know it can start returning saved string values
- # for Nodes.
- #
- Save_Strings = None
- def save_strings(val):
- global Save_Strings
- Save_Strings = val
- #
- # Avoid unnecessary function calls by recording a Boolean value that
- # tells us whether or not os.path.splitdrive() actually does anything
- # on this system, and therefore whether we need to bother calling it
- # when looking up path names in various methods below.
- #
- do_splitdrive = None
- def initialize_do_splitdrive():
- global do_splitdrive
- drive, path = os.path.splitdrive('X:/foo')
- do_splitdrive = not not drive
- initialize_do_splitdrive()
- #
- needs_normpath_check = None
- def initialize_normpath_check():
- """
- Initialize the normpath_check regular expression.
- This function is used by the unit tests to re-initialize the pattern
- when testing for behavior with different values of os.sep.
- """
- global needs_normpath_check
- if os.sep == '/':
- pattern = r'.*/|\.$|\.\.$'
- else:
- pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
- needs_normpath_check = re.compile(pattern)
- initialize_normpath_check()
- #
- # SCons.Action objects for interacting with the outside world.
- #
- # The Node.FS methods in this module should use these actions to
- # create and/or remove files and directories; they should *not* use
- # os.{link,symlink,unlink,mkdir}(), etc., directly.
- #
- # Using these SCons.Action objects ensures that descriptions of these
- # external activities are properly displayed, that the displays are
- # suppressed when the -s (silent) option is used, and (most importantly)
- # the actions are disabled when the the -n option is used, in which case
- # there should be *no* changes to the external file system(s)...
- #
- if hasattr(os, 'link'):
- def _hardlink_func(fs, src, dst):
- # If the source is a symlink, we can't just hard-link to it
- # because a relative symlink may point somewhere completely
- # different. We must disambiguate the symlink and then
- # hard-link the final destination file.
- while fs.islink(src):
- link = fs.readlink(src)
- if not os.path.isabs(link):
- src = link
- else:
- src = os.path.join(os.path.dirname(src), link)
- fs.link(src, dst)
- else:
- _hardlink_func = None
- if hasattr(os, 'symlink'):
- def _softlink_func(fs, src, dst):
- fs.symlink(src, dst)
- else:
- _softlink_func = None
- def _copy_func(fs, src, dest):
- shutil.copy2(src, dest)
- st = fs.stat(src)
- fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
- Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
- 'hard-copy', 'soft-copy', 'copy']
- Link_Funcs = [] # contains the callables of the specified duplication style
- def set_duplicate(duplicate):
- # Fill in the Link_Funcs list according to the argument
- # (discarding those not available on the platform).
- # Set up the dictionary that maps the argument names to the
- # underlying implementations. We do this inside this function,
- # not in the top-level module code, so that we can remap os.link
- # and os.symlink for testing purposes.
- link_dict = {
- 'hard' : _hardlink_func,
- 'soft' : _softlink_func,
- 'copy' : _copy_func
- }
- if not duplicate in Valid_Duplicates:
- raise SCons.Errors.InternalError("The argument of set_duplicate "
- "should be in Valid_Duplicates")
- global Link_Funcs
- Link_Funcs = []
- for func in duplicate.split('-'):
- if link_dict[func]:
- Link_Funcs.append(link_dict[func])
- def LinkFunc(target, source, env):
- # Relative paths cause problems with symbolic links, so
- # we use absolute paths, which may be a problem for people
- # who want to move their soft-linked src-trees around. Those
- # people should use the 'hard-copy' mode, softlinks cannot be
- # used for that; at least I have no idea how ...
- src = source[0].abspath
- dest = target[0].abspath
- dir, file = os.path.split(dest)
- if dir and not target[0].fs.isdir(dir):
- os.makedirs(dir)
- if not Link_Funcs:
- # Set a default order of link functions.
- set_duplicate('hard-soft-copy')
- fs = source[0].fs
- # Now link the files with the previously specified order.
- for func in Link_Funcs:
- try:
- func(fs, src, dest)
- break
- except (IOError, OSError):
- # An OSError indicates something happened like a permissions
- # problem or an attempt to symlink across file-system
- # boundaries. An IOError indicates something like the file
- # not existing. In either case, keeping trying additional
- # functions in the list and only raise an error if the last
- # one failed.
- if func == Link_Funcs[-1]:
- # exception of the last link method (copy) are fatal
- raise
- return 0
- Link = SCons.Action.Action(LinkFunc, None)
- def LocalString(target, source, env):
- return 'Local copy of %s from %s' % (target[0], source[0])
- LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
- def UnlinkFunc(target, source, env):
- t = target[0]
- t.fs.unlink(t.abspath)
- return 0
- Unlink = SCons.Action.Action(UnlinkFunc, None)
- def MkdirFunc(target, source, env):
- t = target[0]
- if not t.exists():
- t.fs.mkdir(t.abspath)
- return 0
- Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
- MkdirBuilder = None
- def get_MkdirBuilder():
- global MkdirBuilder
- if MkdirBuilder is None:
- import SCons.Builder
- import SCons.Defaults
- # "env" will get filled in by Executor.get_build_env()
- # calling SCons.Defaults.DefaultEnvironment() when necessary.
- MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
- env = None,
- explain = None,
- is_explicit = None,
- target_scanner = SCons.Defaults.DirEntryScanner,
- name = "MkdirBuilder")
- return MkdirBuilder
- class _Null(object):
- pass
- _null = _Null()
- DefaultSCCSBuilder = None
- DefaultRCSBuilder = None
- def get_DefaultSCCSBuilder():
- global DefaultSCCSBuilder
- if DefaultSCCSBuilder is None:
- import SCons.Builder
- # "env" will get filled in by Executor.get_build_env()
- # calling SCons.Defaults.DefaultEnvironment() when necessary.
- act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
- DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
- env = None,
- name = "DefaultSCCSBuilder")
- return DefaultSCCSBuilder
- def get_DefaultRCSBuilder():
- global DefaultRCSBuilder
- if DefaultRCSBuilder is None:
- import SCons.Builder
- # "env" will get filled in by Executor.get_build_env()
- # calling SCons.Defaults.DefaultEnvironment() when necessary.
- act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
- DefaultRCSBuilder = SCons.Builder.Builder(action = act,
- env = None,
- name = "DefaultRCSBuilder")
- return DefaultRCSBuilder
- # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
- _is_cygwin = sys.platform == "cygwin"
- if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
- def _my_normcase(x):
- return x
- else:
- def _my_normcase(x):
- return x.upper()
- class DiskChecker(object):
- def __init__(self, type, do, ignore):
- self.type = type
- self.do = do
- self.ignore = ignore
- self.func = do
- def __call__(self, *args, **kw):
- return self.func(*args, **kw)
- def set(self, list):
- if self.type in list:
- self.func = self.do
- else:
- self.func = self.ignore
- def do_diskcheck_match(node, predicate, errorfmt):
- result = predicate()
- try:
- # If calling the predicate() cached a None value from stat(),
- # remove it so it doesn't interfere with later attempts to
- # build this Node as we walk the DAG. (This isn't a great way
- # to do this, we're reaching into an interface that doesn't
- # really belong to us, but it's all about performance, so
- # for now we'll just document the dependency...)
- if node._memo['stat'] is None:
- del node._memo['stat']
- except (AttributeError, KeyError):
- pass
- if result:
- raise TypeError(errorfmt % node.abspath)
- def ignore_diskcheck_match(node, predicate, errorfmt):
- pass
- def do_diskcheck_rcs(node, name):
- try:
- rcs_dir = node.rcs_dir
- except AttributeError:
- if node.entry_exists_on_disk('RCS'):
- rcs_dir = node.Dir('RCS')
- else:
- rcs_dir = None
- node.rcs_dir = rcs_dir
- if rcs_dir:
- return rcs_dir.entry_exists_on_disk(name+',v')
- return None
- def ignore_diskcheck_rcs(node, name):
- return None
- def do_diskcheck_sccs(node, name):
- try:
- sccs_dir = node.sccs_dir
- except AttributeError:
- if node.entry_exists_on_disk('SCCS'):
- sccs_dir = node.Dir('SCCS')
- else:
- sccs_dir = None
- node.sccs_dir = sccs_dir
- if sccs_dir:
- return sccs_dir.entry_exists_on_disk('s.'+name)
- return None
- def ignore_diskcheck_sccs(node, name):
- return None
- diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
- diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
- diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
- diskcheckers = [
- diskcheck_match,
- diskcheck_rcs,
- diskcheck_sccs,
- ]
- def set_diskcheck(list):
- for dc in diskcheckers:
- dc.set(list)
- def diskcheck_types():
- return [dc.type for dc in diskcheckers]
- class EntryProxy(SCons.Util.Proxy):
- __str__ = SCons.Util.Delegate('__str__')
- def __get_abspath(self):
- entry = self.get()
- return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
- entry.name + "_abspath")
- def __get_filebase(self):
- name = self.get().name
- return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
- name + "_filebase")
- def __get_suffix(self):
- name = self.get().name
- return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
- name + "_suffix")
- def __get_file(self):
- name = self.get().name
- return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
- def __get_base_path(self):
- """Return the file's directory and file name, with the
- suffix stripped."""
- entry = self.get()
- return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
- entry.name + "_base")
- def __get_posix_path(self):
- """Return the path with / as the path separator,
- regardless of platform."""
- if os.sep == '/':
- return self
- else:
- entry = self.get()
- r = entry.get_path().replace(os.sep, '/')
- return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
- def __get_windows_path(self):
- """Return the path with \ as the path separator,
- regardless of platform."""
- if os.sep == '\\':
- return self
- else:
- entry = self.get()
- r = entry.get_path().replace(os.sep, '\\')
- return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
- def __get_srcnode(self):
- return EntryProxy(self.get().srcnode())
- def __get_srcdir(self):
- """Returns the directory containing the source node linked to this
- node via VariantDir(), or the directory of this node if not linked."""
- return EntryProxy(self.get().srcnode().dir)
- def __get_rsrcnode(self):
- return EntryProxy(self.get().srcnode().rfile())
- def __get_rsrcdir(self):
- """Returns the directory containing the source node linked to this
- node via VariantDir(), or the directory of this node if not linked."""
- return EntryProxy(self.get().srcnode().rfile().dir)
- def __get_dir(self):
- return EntryProxy(self.get().dir)
- dictSpecialAttrs = { "base" : __get_base_path,
- "posix" : __get_posix_path,
- "windows" : __get_windows_path,
- "win32" : __get_windows_path,
- "srcpath" : __get_srcnode,
- "srcdir" : __get_srcdir,
- "dir" : __get_dir,
- "abspath" : __get_abspath,
- "filebase" : __get_filebase,
- "suffix" : __get_suffix,
- "file" : __get_file,
- "rsrcpath" : __get_rsrcnode,
- "rsrcdir" : __get_rsrcdir,
- }
- def __getattr__(self, name):
- # This is how we implement the "special" attributes
- # such as base, posix, srcdir, etc.
- try:
- attr_function = self.dictSpecialAttrs[name]
- except KeyError:
- try:
- attr = SCons.Util.Proxy.__getattr__(self, name)
- except AttributeError, e:
- # Raise our own AttributeError subclass with an
- # overridden __str__() method that identifies the
- # name of the entry that caused the exception.
- raise EntryProxyAttributeError(self, name)
- return attr
- else:
- return attr_function(self)
- class Base(SCons.Node.Node):
- """A generic class for file system entries. This class is for
- when we don't know yet whether the entry being looked up is a file
- or a directory. Instances of this class can morph into either
- Dir or File objects by a later, more precise lookup.
- Note: this class does not define __cmp__ and __hash__ for
- efficiency reasons. SCons does a lot of comparing of
- Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
- as fast as possible, which means we want to use Python's built-in
- object identity comparisons.
- """
- memoizer_counters = []
- def __init__(self, name, directory, fs):
- """Initialize a generic Node.FS.Base object.
- Call the superclass initialization, take care of setting up
- our relative and absolute paths, identify our parent
- directory, and indicate that this node should use
- signatures."""
- if __debug__: logInstanceCreation(self, 'Node.FS.Base')
- SCons.Node.Node.__init__(self)
- # Filenames and paths are probably reused and are intern'ed to
- # save some memory.
- self.name = SCons.Util.silent_intern(name)
- self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1])
- self.fs = fs
- assert directory, "A directory must be provided"
- self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name))
- self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name))
- if directory.path == '.':
- self.path = SCons.Util.silent_intern(name)
- else:
- self.path = SCons.Util.silent_intern(directory.entry_path(name))
- if directory.tpath == '.':
- self.tpath = SCons.Util.silent_intern(name)
- else:
- self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name))
- self.path_elements = directory.path_elements + [self]
- self.dir = directory
- self.cwd = None # will hold the SConscript directory for target nodes
- self.duplicate = directory.duplicate
- def str_for_display(self):
- return '"' + self.__str__() + '"'
- def must_be_same(self, klass):
- """
- This node, which already existed, is being looked up as the
- specified klass. Raise an exception if it isn't.
- """
- if isinstance(self, klass) or klass is Entry:
- return
- raise TypeError("Tried to lookup %s '%s' as a %s." %\
- (self.__class__.__name__, self.path, klass.__name__))
- def get_dir(self):
- return self.dir
- def get_suffix(self):
- return self.suffix
- def rfile(self):
- return self
- def __str__(self):
- """A Node.FS.Base object's string representation is its path
- name."""
- global Save_Strings
- if Save_Strings:
- return self._save_str()
- return self._get_str()
- memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
- def _save_str(self):
- try:
- return self._memo['_save_str']
- except KeyError:
- pass
- result = sys.intern(self._get_str())
- self._memo['_save_str'] = result
- return result
- def _get_str(self):
- global Save_Strings
- if self.duplicate or self.is_derived():
- return self.get_path()
- srcnode = self.srcnode()
- if srcnode.stat() is None and self.stat() is not None:
- result = self.get_path()
- else:
- result = srcnode.get_path()
- if not Save_Strings:
- # We're not at the point where we're saving the string string
- # representations of FS Nodes (because we haven't finished
- # reading the SConscript files and need to have str() return
- # things relative to them). That also means we can't yet
- # cache values returned (or not returned) by stat(), since
- # Python code in the SConscript files might still create
- # or otherwise affect the on-disk file. So get rid of the
- # values that the underlying stat() method saved.
- try: del self._memo['stat']
- except KeyError: pass
- if self is not srcnode:
- try: del srcnode._memo['stat']
- except KeyError: pass
- return result
- rstr = __str__
- memoizer_counters.append(SCons.Memoize.CountValue('stat'))
- def stat(self):
- try: return self._memo['stat']
- except KeyError: pass
- try: result = self.fs.stat(self.abspath)
- except os.error: result = None
- self._memo['stat'] = result
- return result
- def exists(self):
- return self.stat() is not None
- def rexists(self):
- return self.rfile().exists()
- def getmtime(self):
- st = self.stat()
- if st: return st[stat.ST_MTIME]
- else: return None
- def getsize(self):
- st = self.stat()
- if st: return st[stat.ST_SIZE]
- else: return None
- def isdir(self):
- st = self.stat()
- return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
- def isfile(self):
- st = self.stat()
- return st is not None and stat.S_ISREG(st[stat.ST_MODE])
- if hasattr(os, 'symlink'):
- def islink(self):
- try: st = self.fs.lstat(self.abspath)
- except os.error: return 0
- return stat.S_ISLNK(st[stat.ST_MODE])
- else:
- def islink(self):
- return 0 # no symlinks
- def is_under(self, dir):
- if self is dir:
- return 1
- else:
- return self.dir.is_under(dir)
- def set_local(self):
- self._local = 1
- def srcnode(self):
- """If this node is in a build path, return the node
- corresponding to its source file. Otherwise, return
- ourself.
- """
- srcdir_list = self.dir.srcdir_list()
- if srcdir_list:
- srcnode = srcdir_list[0].Entry(self.name)
- srcnode.must_be_same(self.__class__)
- return srcnode
- return self
- def get_path(self, dir=None):
- """Return path relative to the current working directory of the
- Node.FS.Base object that owns us."""
- if not dir:
- dir = self.fs.getcwd()
- if self == dir:
- return '.'
- path_elems = self.path_elements
- try: i = path_elems.index(dir)
- except ValueError: pass
- else: path_elems = path_elems[i+1:]
- path_elems = [n.name for n in path_elems]
- return os.sep.join(path_elems)
- def set_src_builder(self, builder):
- """Set the source code builder for this node."""
- self.sbuilder = builder
- if not self.has_builder():
- self.builder_set(builder)
- def src_builder(self):
- """Fetch the source code builder for this node.
- If there isn't one, we cache the source code builder specified
- for the directory (which in turn will cache the value from its
- parent directory, and so on up to the file system root).
- """
- try:
- scb = self.sbuilder
- except AttributeError:
- scb = self.dir.src_builder()
- self.sbuilder = scb
- return scb
- def get_abspath(self):
- """Get the absolute path of the file."""
- return self.abspath
- def for_signature(self):
- # Return just our name. Even an absolute path would not work,
- # because that can change thanks to symlinks or remapped network
- # paths.
- return self.name
- def get_subst_proxy(self):
- try:
- return self._proxy
- except AttributeError:
- ret = EntryProxy(self)
- self._proxy = ret
- return ret
- def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
- """
- Generates a target entry that corresponds to this entry (usually
- a source file) with the specified prefix and suffix.
- Note that this method can be overridden dynamically for generated
- files that need different behavior. See Tool/swig.py for
- an example.
- """
- return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
- def _Rfindalldirs_key(self, pathlist):
- return pathlist
- memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
- def Rfindalldirs(self, pathlist):
- """
- Return all of the directories for a given path list, including
- corresponding "backing" directories in any repositories.
- The Node lookups are relative to this Node (typically a
- directory), so memoizing result saves cycles from looking
- up the same path for each target in a given directory.
- """
- try:
- memo_dict = self._memo['Rfindalldirs']
- except KeyError:
- memo_dict = {}
- self._memo['Rfindalldirs'] = memo_dict
- else:
- try:
- return memo_dict[pathlist]
- except KeyError:
- pass
- create_dir_relative_to_self = self.Dir
- result = []
- for path in pathlist:
- if isinstance(path, SCons.Node.Node):
- result.append(path)
- else:
- dir = create_dir_relative_to_self(path)
- result.extend(dir.get_all_rdirs())
- memo_dict[pathlist] = result
- return result
- def RDirs(self, pathlist):
- """Search for a list of directories in the Repository list."""
- cwd = self.cwd or self.fs._cwd
- return cwd.Rfindalldirs(pathlist)
- memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
- def rentry(self):
- try:
- return self._memo['rentry']
- except KeyError:
- pass
- result = self
- if not self.exists():
- norm_name = _my_normcase(self.name)
- for dir in self.dir.get_all_rdirs():
- try:
- node = dir.entries[norm_name]
- except KeyError:
- if dir.entry_exists_on_disk(self.name):
- result = dir.Entry(self.name)
- break
- self._memo['rentry'] = result
- return result
- def _glob1(self, pattern, ondisk=True, source=False, strings=False):
- return []
- class Entry(Base):
- """This is the class for generic Node.FS entries--that is, things
- that could be a File or a Dir, but we're just not sure yet.
- Consequently, the methods in this class really exist just to
- transform their associated object into the right class when the
- time comes, and then call the same-named method in the transformed
- class."""
- def diskcheck_match(self):
- pass
- def disambiguate(self, must_exist=None):
- """
- """
- if self.isdir():
- self.__class__ = Dir
- self._morph()
- elif self.isfile():
- self.__class__ = File
- self._morph()
- self.clear()
- else:
- # There was nothing on-disk at this location, so look in
- # the src directory.
- #
- # We can't just use self.srcnode() straight away because
- # that would create an actual Node for this file in the src
- # directory, and there might not be one. Instead, use the
- # dir_on_disk() method to see if there's something on-disk
- # with that name, in which case we can go ahead and call
- # self.srcnode() to create the right type of entry.
- srcdir = self.dir.srcnode()
- if srcdir != self.dir and \
- srcdir.entry_exists_on_disk(self.name) and \
- self.srcnode().isdir():
- self.__class__ = Dir
- self._morph()
- elif must_exist:
- msg = "No such file or directory: '%s'" % self.abspath
- raise SCons.Errors.UserError(msg)
- else:
- self.__class__ = File
- self._morph()
- self.clear()
- return self
- def rfile(self):
- """We're a generic Entry, but the caller is actually looking for
- a File at this point, so morph into one."""
- self.__class__ = File
- self._morph()
- self.clear()
- return File.rfile(self)
- def scanner_key(self):
- return self.get_suffix()
- def get_contents(self):
- """Fetch the contents of the entry. Returns the exact binary
- contents of the file."""
- try:
- self = self.disambiguate(must_exist=1)
- except SCons.Errors.UserError:
- # There was nothing on disk with which to disambiguate
- # this entry. Leave it as an Entry, but return a null
- # string so calls to get_contents() in emitters and the
- # like (e.g. in qt.py) don't have to disambiguate by hand
- # or catch the exception.
- return ''
- else:
- return self.get_contents()
- def get_text_contents(self):
- """Fetch the decoded text contents of a Unicode encoded Entry.
- Since this should return the text contents from the file
- system, we check to see into what sort of subclass we should
- morph this Entry."""
- try:
- self = self.disambiguate(must_exist=1)
- except SCons.Errors.UserError:
- # There was nothing on disk with which to disambiguate
- # this entry. Leave it as an Entry, but return a null
- # string so calls to get_text_contents() in emitters and
- # the like (e.g. in qt.py) don't have to disambiguate by
- # hand or catch the exception.
- return ''
- else:
- return self.get_text_contents()
- def must_be_same(self, klass):
- """Called to make sure a Node is a Dir. Since we're an
- Entry, we can morph into one."""
- if self.__class__ is not klass:
- self.__class__ = klass
- self._morph()
- self.clear()
- # The following methods can get called before the Taskmaster has
- # had a chance to call disambiguate() directly to see if this Entry
- # should really be a Dir or a File. We therefore use these to call
- # disambiguate() transparently (from our caller's point of view).
- #
- # Right now, this minimal set of methods has been derived by just
- # looking at some of the methods that will obviously be called early
- # in any of the various Taskmasters' calling sequences, and then
- # empirically figuring out which additional methods are necessary
- # to make various tests pass.
- def exists(self):
- """Return if the Entry exists. Check the file system to see
- what we should turn into first. Assume a file if there's no
- directory."""
- return self.disambiguate().exists()
- def rel_path(self, other):
- d = self.disambiguate()
- if d.__class__ is Entry:
- raise Exception("rel_path() could not disambiguate File/Dir")
- return d.rel_path(other)
- def new_ninfo(self):
- return self.disambiguate().new_ninfo()
- def changed_since_last_build(self, target, prev_ni):
- return self.disambiguate().changed_since_last_build(target, prev_ni)
- def _glob1(self, pattern, ondisk=True, source=False, strings=False):
- return self.disambiguate()._glob1(pattern, ondisk, source, strings)
- def get_subst_proxy(self):
- return self.disambiguate().get_subst_proxy()
- # This is for later so we can differentiate between Entry the class and Entry
- # the method of the FS class.
- _classEntry = Entry
- class LocalFS(object):
- if SCons.Memoize.use_memoizer:
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
- # This class implements an abstraction layer for operations involving
- # a local file system. Essentially, this wraps any function in
- # the os, os.path or shutil modules that we use to actually go do
- # anything with or to the local file system.
- #
- # Note that there's a very good chance we'll refactor this part of
- # the architecture in some way as we really implement the interface(s)
- # for remote file system Nodes. For example, the right architecture
- # might be to have this be a subclass instead of a base class.
- # Nevertheless, we're using this as a first step in that direction.
- #
- # We're not using chdir() yet because the calling subclass method
- # needs to use os.chdir() directly to avoid recursion. Will we
- # really need this one?
- #def chdir(self, path):
- # return os.chdir(path)
- def chmod(self, path, mode):
- return os.chmod(path, mode)
- def copy(self, src, dst):
- return shutil.copy(src, dst)
- def copy2(self, src, dst):
- return shutil.copy2(src, dst)
- def exists(self, path):
- return os.path.exists(path)
- def getmtime(self, path):
- return os.path.getmtime(path)
- def getsize(self, path):
- return os.path.getsize(path)
- def isdir(self, path):
- return os.path.isdir(path)
- def isfile(self, path):
- return os.path.isfile(path)
- def link(self, src, dst):
- return os.link(src, dst)
- def lstat(self, path):
- return os.lstat(path)
- def listdir(self, path):
- return os.listdir(path)
- def makedirs(self, path):
- return os.makedirs(path)
- def mkdir(self, path):
- return os.mkdir(path)
- def rename(self, old, new):
- return os.rename(old, new)
- def stat(self, path):
- return os.stat(path)
- def symlink(self, src, dst):
- return os.symlink(src, dst)
- def open(self, path):
- return open(path)
- def unlink(self, path):
- return os.unlink(path)
- if hasattr(os, 'symlink'):
- def islink(self, path):
- return os.path.islink(path)
- else:
- def islink(self, path):
- return 0 # no symlinks
- if hasattr(os, 'readlink'):
- def readlink(self, file):
- return os.readlink(file)
- else:
- def readlink(self, file):
- return ''
- #class RemoteFS:
- # # Skeleton for the obvious methods we might need from the
- # # abstraction layer for a remote filesystem.
- # def upload(self, local_src, remote_dst):
- # pass
- # def download(self, remote_src, local_dst):
- # pass
- class FS(LocalFS):
- memoizer_counters = []
- def __init__(self, path = None):
- """Initialize the Node.FS subsystem.
- The supplied path is the top of the source tree, where we
- expect to find the top-level build file. If no path is
- supplied, the current directory is the default.
- The path argument must be a valid absolute path.
- """
- if __debug__: logInstanceCreation(self, 'Node.FS')
- self._memo = {}
- self.Root = {}
- self.SConstruct_dir = None
- self.max_drift = default_max_drift
- self.Top = None
- if path is None:
- self.pathTop = os.getcwd()
- else:
- self.pathTop = path
- self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
- self.Top = self.Dir(self.pathTop)
- self.Top.path = '.'
- self.Top.tpath = '.'
- self._cwd = self.Top
- DirNodeInfo.fs = self
- FileNodeInfo.fs = self
-
- def set_SConstruct_dir(self, dir):
- self.SConstruct_dir = dir
- def get_max_drift(self):
- return self.max_drift
- def set_max_drift(self, max_drift):
- self.max_drift = max_drift
- def getcwd(self):
- return self._cwd
- def chdir(self, dir, change_os_dir=0):
- """Change the current working directory for lookups.
- If change_os_dir is true, we will also change the "real" cwd
- to match.
- """
- curr=self._cwd
- try:
- if dir is not None:
- self._cwd = dir
- if change_os_dir:
- os.chdir(dir.abspath)
- except OSError:
- self._cwd = curr
- raise
- def get_root(self, drive):
- """
- Returns the root directory for the specified drive, creating
- it if necessary.
- """
- drive = _my_normcase(drive)
- try:
- return self.Root[drive]
- except KeyError:
- root = RootDir(drive, self)
- self.Root[drive] = root
- if not drive:
- self.Root[self.defaultDrive] = root
- elif drive == self.defaultDrive:
- self.Root[''] = root
- return root
- def _lookup(self, p, directory, fsclass, create=1):
- """
- The generic entry point for Node lookup with user-supplied data.
- This translates arbitrary input into a canonical Node.FS object
- of the specified fsclass. The general approach for strings is
- to turn it into a fully normalized absolute path and then call
- the root directory's lookup_abs() method for the heavy lifting.
- If the path name begins with '#', it is unconditionally
- interpreted relative to the top-level directory of this FS. '#'
- is treated as a synonym for the top-level SConstruct directory,
- much like '~' is treated as a synonym for the user's home
- directory in a UNIX shell. So both '#foo' and '#/foo' refer
- to the 'foo' subdirectory underneath the top-level SConstruct
- directory.
- If the path name is relative, then the path is looked up relative
- to the specified directory, or the current directory (self._cwd,
- typically the SConscript directory) if the specified directory
- is None.
- """
- if isinstance(p, Base):
- # It's already a Node.FS object. Make sure it's the right
- # class and return.
- p.must_be_same(fsclass)
- return p
- # str(p) in case it's something like a proxy object
- p = str(p)
- initial_hash = (p[0:1] == '#')
- if initial_hash:
- # There was an initial '#', so we strip it and override
- # whatever directory they may have specified with the
- # top-level SConstruct directory.
- p = p[1:]
- directory = self.Top
- if directory and not isinstance(directory, Dir):
- directory = self.Dir(directory)
- if do_splitdrive:
- drive, p = os.path.splitdrive(p)
- else:
- drive = ''
- if drive and not p:
- # This causes a naked drive letter to be treated as a synonym
- # for the root directory on that drive.
- p = os.sep
- absolute = os.path.isabs(p)
- needs_normpath = needs_normpath_check.match(p)
- if initial_hash or not absolute:
- # This is a relative lookup, either to the top-level
- # SConstruct directory (because of the initial '#') or to
- # the current directory (the path name is not absolute).
- # Add the string to the appropriate directory lookup path,
- # after which the whole thing gets normalized.
- if not directory:
- directory = self._cwd
- if p:
- p = directory.labspath + '/' + p
- else:
- p = directory.labspath
- if needs_normpath:
- p = os.path.normpath(p)
- if drive or absolute:
- root = self.get_root(drive)
- else:
- if not directory:
- directory = self._cwd
- root = directory.root
- if os.sep != '/':
- p = p.replace(os.sep, '/')
- return root._lookup_abs(p, fsclass, create)
- def Entry(self, name, directory = None, create = 1):
- """Look up or create a generic Entry node with the specified name.
- If the name is a relative path (begins with ./, ../, or a file
- name), then it is looked up relative to the supplied directory
- node, or to the top level directory of the FS (supplied at
- construction time) if no directory is supplied.
- """
- return self._lookup(name, directory, Entry, create)
- def File(self, name, directory = None, create = 1):
- """Look up or create a File node with the specified name. If
- the name is a relative path (begins with ./, ../, or a file name),
- then it is looked up relative to the supplied directory node,
- or to the top level directory of the FS (supplied at construction
- time) if no directory is supplied.
- This method will raise TypeError if a directory is found at the
- specified path.
- """
- return self._lookup(name, directory, File, create)
- def Dir(self, name, directory = None, create = True):
- """Look up or create a Dir node with the specified name. If
- the name is a relative path (begins with ./, ../, or a file name),
- then it is looked up relative to the supplied directory node,
- or to the top level directory of the FS (supplied at construction
- time) if no directory is supplied.
- This method will raise TypeError if a normal file is found at the
- specified path.
- """
- return self._lookup(name, directory, Dir, create)
- def VariantDir(self, variant_dir, src_dir, duplicate=1):
- """Link the supplied variant directory to the source directory
- for purposes of building files."""
- if not isinstance(src_dir, SCons.Node.Node):
- src_dir = self.Dir(src_dir)
- if not isinstance(variant_dir, SCons.Node.Node):
- variant_dir = self.Dir(variant_dir)
- if src_dir.is_under(variant_dir):
- raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
- if variant_dir.srcdir:
- if variant_dir.srcdir == src_dir:
- return # We already did this.
- raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
- variant_dir.link(src_dir, duplicate)
- def Repository(self, *dirs):
- """Specify Repository directories to search."""
- for d in dirs:
- if not isinstance(d, SCons.Node.Node):
- d = self.Dir(d)
- self.Top.addRepository(d)
- def variant_dir_target_climb(self, orig, dir, tail):
- """Create targets in corresponding variant directories
- Climb the directory tree, and look up path names
- relative to any linked variant directories we find.
- Even though this loops and walks up the tree, we don't memoize
- the return value because this is really only used to process
- the command-line targets.
- """
- targets = []
- message = None
- fmt = "building associated VariantDir targets: %s"
- start_dir = dir
- while dir:
- for bd in dir.variant_dirs:
- if start_dir.is_under(bd):
- # If already in the build-dir location, don't reflect
- return [orig], fmt % str(orig)
- p = os.path.join(bd.path, *tail)
- targets.append(self.Entry(p))
- tail = [dir.name] + tail
- dir = dir.up()
- if targets:
- message = fmt % ' '.join(map(str, targets))
- return targets, message
- def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
- """
- Globs
- This is mainly a shim layer
- """
- if cwd is None:
- cwd = self.getcwd()
- return cwd.glob(pathname, ondisk, source, strings)
- class DirNodeInfo(SCons.Node.NodeInfoBase):
- # This should get reset by the FS initialization.
- current_version_id = 1
- fs = None
- def str_to_node(self, s):
- top = self.fs.Top
- root = top.root
- if do_splitdrive:
- drive, s = os.path.splitdrive(s)
- if drive:
- root = self.fs.get_root(drive)
- if not os.path.isabs(s):
- s = top.labspath + '/' + s
- return root._lookup_abs(s, Entry)
- class DirBuildInfo(SCons.Node.BuildInfoBase):
- current_version_id = 1
- glob_magic_check = re.compile('[*?[]')
- def has_glob_magic(s):
- return glob_magic_check.search(s) is not None
- class Dir(Base):
- """A class for directories in a file system.
- """
- memoizer_counters = []
- NodeInfo = DirNodeInfo
- BuildInfo = DirBuildInfo
- def __init__(self, name, directory, fs):
- if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
- Base.__init__(self, name, directory, fs)
- self._morph()
- def _morph(self):
- """Turn a file system Node (either a freshly initialized directory
- object or a separate Entry object) into a proper directory object.
- Set up this directory's entries and hook it into the file
- system tree. Specify that directories (this Node) don't use
- signatures for calculating whether they're current.
- """
- self.repositories = []
- self.srcdir = None
- self.entries = {}
- self.entries['.'] = self
- self.entries['..'] = self.dir
- self.cwd = self
- self.searched = 0
- self._sconsign = None
- self.variant_dirs = []
- self.root = self.dir.root
- # Don't just reset the executor, replace its action list,
- # because it might have some pre-or post-actions that need to
- # be preserved.
- self.builder = get_MkdirBuilder()
- self.get_executor().set_action_list(self.builder.action)
- def diskcheck_match(self):
- diskcheck_match(self, self.isfile,
- "File %s found where directory expected.")
- def __clearRepositoryCache(self, duplicate=None):
- """Called when we change the repository(ies) for a directory.
- This clears any cached information that is invalidated by changing
- the repository."""
- for node in self.entries.values():
- if node != self.dir:
- if node != self and isinstance(node, Dir):
- node.__clearRepositoryCache(duplicate)
- else:
- node.clear()
- try:
- del node._srcreps
- except AttributeError:
- pass
- if duplicate is not None:
- node.duplicate=duplicate
- def __resetDuplicate(self, node):
- if node != self:
- node.duplicate = node.get_dir().duplicate
- def Entry(self, name):
- """
- Looks up or creates an entry node named 'name' relative to
- this directory.
- """
- return self.fs.Entry(name, self)
- def Dir(self, name, create=True):
- """
- Looks up or creates a directory node named 'name' relative to
- this directory.
- """
- return self.fs.Dir(name, self, create)
- def File(self, name):
- """
- Looks up or creates a file node named 'name' relative to
- this directory.
- """
- return self.fs.File(name, self)
- def _lookup_rel(self, name, klass, create=1):
- """
- Looks up a *normalized* relative path name, relative to this
- directory.
- This method is intended for use by internal lookups with
- already-normalized path data. For general-purpose lookups,
- use the Entry(), Dir() and File() methods above.
- This method does *no* input checking and will die or give
- incorrect results if it's passed a non-normalized path name (e.g.,
- a path containing '..'), an absolute path name, a top-relative
- ('#foo') path name, or any kind of object.
- """
- name = self.entry_labspath(name)
- return self.root._lookup_abs(name, klass, create)
- def link(self, srcdir, duplicate):
- """Set this directory as the variant directory for the
- supplied source directory."""
- self.srcdir = srcdir
- self.duplicate = duplicate
- self.__clearRepositoryCache(duplicate)
- srcdir.variant_dirs.append(self)
- def getRepositories(self):
- """Returns a list of repositories for this directory.
- """
- …
Large files files are truncated, but you can click here to view the full file