From 96a9fa9101e9ed50149d284ff32c361084223fa4 Mon Sep 17 00:00:00 2001 From: Adam Kocoloski Date: Mon, 23 Aug 2010 13:51:50 -0400 Subject: consolidate all couchjs stuff in one subdir --- scons/scons-local-2.0.1/SCons/Node/FS.py | 3142 ------------------------------ 1 file changed, 3142 deletions(-) delete mode 100644 scons/scons-local-2.0.1/SCons/Node/FS.py (limited to 'scons/scons-local-2.0.1/SCons/Node/FS.py') diff --git a/scons/scons-local-2.0.1/SCons/Node/FS.py b/scons/scons-local-2.0.1/SCons/Node/FS.py deleted file mode 100644 index 0997fc07..00000000 --- a/scons/scons-local-2.0.1/SCons/Node/FS.py +++ /dev/null @@ -1,3142 +0,0 @@ -"""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. - """ - if self.srcdir and not self.duplicate: - return self.srcdir.get_all_rdirs() + self.repositories - return self.repositories - - memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs')) - - def get_all_rdirs(self): - try: - return list(self._memo['get_all_rdirs']) - except KeyError: - pass - - result = [self] - fname = '.' - dir = self - while dir: - for rep in dir.getRepositories(): - result.append(rep.Dir(fname)) - if fname == '.': - fname = dir.name - else: - fname = dir.name + os.sep + fname - dir = dir.up() - - self._memo['get_all_rdirs'] = list(result) - - return result - - def addRepository(self, dir): - if dir != self and not dir in self.repositories: - self.repositories.append(dir) - dir.tpath = '.' - self.__clearRepositoryCache() - - def up(self): - return self.entries['..'] - - def _rel_path_key(self, other): - return str(other) - - memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key)) - - def rel_path(self, other): - """Return a path to "other" relative to this directory. - """ - - # This complicated and expensive method, which constructs relative - # paths between arbitrary Node.FS objects, is no longer used - # by SCons itself. It was introduced to store dependency paths - # in .sconsign files relative to the target, but that ended up - # being significantly inefficient. - # - # We're continuing to support the method because some SConstruct - # files out there started using it when it was available, and - # we're all about backwards compatibility.. - - try: - memo_dict = self._memo['rel_path'] - except KeyError: - memo_dict = {} - self._memo['rel_path'] = memo_dict - else: - try: - return memo_dict[other] - except KeyError: - pass - - if self is other: - result = '.' - - elif not other in self.path_elements: - try: - other_dir = other.get_dir() - except AttributeError: - result = str(other) - else: - if other_dir is None: - result = other.name - else: - dir_rel_path = self.rel_path(other_dir) - if dir_rel_path == '.': - result = other.name - else: - result = dir_rel_path + os.sep + other.name - else: - i = self.path_elements.index(other) + 1 - - path_elems = ['..'] * (len(self.path_elements) - i) \ - + [n.name for n in other.path_elements[i:]] - - result = os.sep.join(path_elems) - - memo_dict[other] = result - - return result - - def get_env_scanner(self, env, kw={}): - import SCons.Defaults - return SCons.Defaults.DirEntryScanner - - def get_target_scanner(self): - import SCons.Defaults - return SCons.Defaults.DirEntryScanner - - def get_found_includes(self, env, scanner, path): - """Return this directory's implicit dependencies. - - We don't bother caching the results because the scan typically - shouldn't be requested more than once (as opposed to scanning - .h file contents, which can be requested as many times as the - files is #included by other files). - """ - if not scanner: - return [] - # Clear cached info for this Dir. If we already visited this - # directory on our walk down the tree (because we didn't know at - # that point it was being used as the source for another Node) - # then we may have calculated build signature before realizing - # we had to scan the disk. Now that we have to, though, we need - # to invalidate the old calculated signature so that any node - # dependent on our directory structure gets one that includes - # info about everything on disk. - self.clear() - return scanner(self, env, path) - - # - # Taskmaster interface subsystem - # - - def prepare(self): - pass - - def build(self, **kw): - """A null "builder" for directories.""" - global MkdirBuilder - if self.builder is not MkdirBuilder: - SCons.Node.Node.build(self, **kw) - - # - # - # - - def _create(self): - """Create this directory, silently and without worrying about - whether the builder is the default or not.""" - listDirs = [] - parent = self - while parent: - if parent.exists(): - break - listDirs.append(parent) - p = parent.up() - if p is None: - # Don't use while: - else: for this condition because - # if so, then parent is None and has no .path attribute. - raise SCons.Errors.StopError(parent.path) - parent = p - listDirs.reverse() - for dirnode in listDirs: - try: - # Don't call dirnode.build(), call the base Node method - # directly because we definitely *must* create this - # directory. The dirnode.build() method will suppress - # the build if it's the default builder. - SCons.Node.Node.build(dirnode) - dirnode.get_executor().nullify() - # The build() action may or may not have actually - # created the directory, depending on whether the -n - # option was used or not. Delete the _exists and - # _rexists attributes so they can be reevaluated. - dirnode.clear() - except OSError: - pass - - def multiple_side_effect_has_builder(self): - global MkdirBuilder - return self.builder is not MkdirBuilder and self.has_builder() - - def alter_targets(self): - """Return any corresponding targets in a variant directory. - """ - return self.fs.variant_dir_target_climb(self, self, []) - - def scanner_key(self): - """A directory does not get scanned.""" - return None - - def get_text_contents(self): - """We already emit things in text, so just return the binary - version.""" - return self.get_contents() - - def get_contents(self): - """Return content signatures and names of all our children - separated by new-lines. Ensure that the nodes are sorted.""" - contents = [] - for node in sorted(self.children(), key=lambda t: t.name): - contents.append('%s %s\n' % (node.get_csig(), node.name)) - return ''.join(contents) - - def get_csig(self): - """Compute the content signature for Directory nodes. In - general, this is not needed and the content signature is not - stored in the DirNodeInfo. However, if get_contents on a Dir - node is called which has a child directory, the child - directory should return the hash of its contents.""" - contents = self.get_contents() - return SCons.Util.MD5signature(contents) - - def do_duplicate(self, src): - pass - - changed_since_last_build = SCons.Node.Node.state_has_changed - - def is_up_to_date(self): - """If any child is not up-to-date, then this directory isn't, - either.""" - if self.builder is not MkdirBuilder and not self.exists(): - return 0 - up_to_date = SCons.Node.up_to_date - for kid in self.children(): - if kid.get_state() > up_to_date: - return 0 - return 1 - - def rdir(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: node = dir.dir_on_disk(self.name) - if node and node.exists() and \ - (isinstance(dir, Dir) or isinstance(dir, Entry)): - return node - return self - - def sconsign(self): - """Return the .sconsign file info for this directory, - creating it first if necessary.""" - if not self._sconsign: - import SCons.SConsign - self._sconsign = SCons.SConsign.ForDirectory(self) - return self._sconsign - - def srcnode(self): - """Dir has a special need for srcnode()...if we - have a srcdir attribute set, then that *is* our srcnode.""" - if self.srcdir: - return self.srcdir - return Base.srcnode(self) - - def get_timestamp(self): - """Return the latest timestamp from among our children""" - stamp = 0 - for kid in self.children(): - if kid.get_timestamp() > stamp: - stamp = kid.get_timestamp() - return stamp - - def entry_abspath(self, name): - return self.abspath + os.sep + name - - def entry_labspath(self, name): - return self.labspath + '/' + name - - def entry_path(self, name): - return self.path + os.sep + name - - def entry_tpath(self, name): - return self.tpath + os.sep + name - - def entry_exists_on_disk(self, name): - try: - d = self.on_disk_entries - except AttributeError: - d = {} - try: - entries = os.listdir(self.abspath) - except OSError: - pass - else: - for entry in map(_my_normcase, entries): - d[entry] = True - self.on_disk_entries = d - if sys.platform == 'win32': - name = _my_normcase(name) - result = d.get(name) - if result is None: - # Belt-and-suspenders for Windows: check directly for - # 8.3 file names that don't show up in os.listdir(). - result = os.path.exists(self.abspath + os.sep + name) - d[name] = result - return result - else: - return name in d - - memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list')) - - def srcdir_list(self): - try: - return self._memo['srcdir_list'] - except KeyError: - pass - - result = [] - - dirname = '.' - dir = self - while dir: - if dir.srcdir: - result.append(dir.srcdir.Dir(dirname)) - dirname = dir.name + os.sep + dirname - dir = dir.up() - - self._memo['srcdir_list'] = result - - return result - - def srcdir_duplicate(self, name): - for dir in self.srcdir_list(): - if self.is_under(dir): - # We shouldn't source from something in the build path; - # variant_dir is probably under src_dir, in which case - # we are reflecting. - break - if dir.entry_exists_on_disk(name): - srcnode = dir.Entry(name).disambiguate() - if self.duplicate: - node = self.Entry(name).disambiguate() - node.do_duplicate(srcnode) - return node - else: - return srcnode - return None - - def _srcdir_find_file_key(self, filename): - return filename - - memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key)) - - def srcdir_find_file(self, filename): - try: - memo_dict = self._memo['srcdir_find_file'] - except KeyError: - memo_dict = {} - self._memo['srcdir_find_file'] = memo_dict - else: - try: - return memo_dict[filename] - except KeyError: - pass - - def func(node): - if (isinstance(node, File) or isinstance(node, Entry)) and \ - (node.is_derived() or node.exists()): - return node - return None - - norm_name = _my_normcase(filename) - - for rdir in self.get_all_rdirs(): - try: node = rdir.entries[norm_name] - except KeyError: node = rdir.file_on_disk(filename) - else: node = func(node) - if node: - result = (node, self) - memo_dict[filename] = result - return result - - for srcdir in self.srcdir_list(): - for rdir in srcdir.get_all_rdirs(): - try: node = rdir.entries[norm_name] - except KeyError: node = rdir.file_on_disk(filename) - else: node = func(node) - if node: - result = (File(filename, self, self.fs), srcdir) - memo_dict[filename] = result - return result - - result = (None, None) - memo_dict[filename] = result - return result - - def dir_on_disk(self, name): - if self.entry_exists_on_disk(name): - try: return self.Dir(name) - except TypeError: pass - node = self.srcdir_duplicate(name) - if isinstance(node, File): - return None - return node - - def file_on_disk(self, name): - if self.entry_exists_on_disk(name) or \ - diskcheck_rcs(self, name) or \ - diskcheck_sccs(self, name): - try: return self.File(name) - except TypeError: pass - node = self.srcdir_duplicate(name) - if isinstance(node, Dir): - return None - return node - - def walk(self, func, arg): - """ - Walk this directory tree by calling the specified function - for each directory in the tree. - - This behaves like the os.path.walk() function, but for in-memory - Node.FS.Dir objects. The function takes the same arguments as - the functions passed to os.path.walk(): - - func(arg, dirname, fnames) - - Except that "dirname" will actually be the directory *Node*, - not the string. The '.' and '..' entries are excluded from - fnames. The fnames list may be modified in-place to filter the - subdirectories visited or otherwise impose a specific order. - The "arg" argument is always passed to func() and may be used - in any way (or ignored, passing None is common). - """ - entries = self.entries - names = list(entries.keys()) - names.remove('.') - names.remove('..') - func(arg, self, names) - for dirname in [n for n in names if isinstance(entries[n], Dir)]: - entries[dirname].walk(func, arg) - - def glob(self, pathname, ondisk=True, source=False, strings=False): - """ - Returns a list of Nodes (or strings) matching a specified - pathname pattern. - - Pathname patterns follow UNIX shell semantics: * matches - any-length strings of any characters, ? matches any character, - and [] can enclose lists or ranges of characters. Matches do - not span directory separators. - - The matches take into account Repositories, returning local - Nodes if a corresponding entry exists in a Repository (either - an in-memory Node or something on disk). - - By defafult, the glob() function matches entries that exist - on-disk, in addition to in-memory Nodes. Setting the "ondisk" - argument to False (or some other non-true value) causes the glob() - function to only match in-memory Nodes. The default behavior is - to return both the on-disk and in-memory Nodes. - - The "source" argument, when true, specifies that corresponding - source Nodes must be returned if you're globbing in a build - directory (initialized with VariantDir()). The default behavior - is to return Nodes local to the VariantDir(). - - The "strings" argument, when true, returns the matches as strings, - not Nodes. The strings are path names relative to this directory. - - The underlying algorithm is adapted from the glob.glob() function - in the Python library (but heavily modified), and uses fnmatch() - under the covers. - """ - dirname, basename = os.path.split(pathname) - if not dirname: - return sorted(self._glob1(basename, ondisk, source, strings), - key=lambda t: str(t)) - if has_glob_magic(dirname): - list = self.glob(dirname, ondisk, source, strings=False) - else: - list = [self.Dir(dirname, create=True)] - result = [] - for dir in list: - r = dir._glob1(basename, ondisk, source, strings) - if strings: - r = [os.path.join(str(dir), x) for x in r] - result.extend(r) - return sorted(result, key=lambda a: str(a)) - - def _glob1(self, pattern, ondisk=True, source=False, strings=False): - """ - Globs for and returns a list of entry names matching a single - pattern in this directory. - - This searches any repositories and source directories for - corresponding entries and returns a Node (or string) relative - to the current directory if an entry is found anywhere. - - TODO: handle pattern with no wildcard - """ - search_dir_list = self.get_all_rdirs() - for srcdir in self.srcdir_list(): - search_dir_list.extend(srcdir.get_all_rdirs()) - - selfEntry = self.Entry - names = [] - for dir in search_dir_list: - # We use the .name attribute from the Node because the keys of - # the dir.entries dictionary are normalized (that is, all upper - # case) on case-insensitive systems like Windows. - node_names = [ v.name for k, v in dir.entries.items() - if k not in ('.', '..') ] - names.extend(node_names) - if not strings: - # Make sure the working directory (self) actually has - # entries for all Nodes in repositories or variant dirs. - for name in node_names: selfEntry(name) - if ondisk: - try: - disk_names = os.listdir(dir.abspath) - except os.error: - continue - names.extend(disk_names) - if not strings: - # We're going to return corresponding Nodes in - # the local directory, so we need to make sure - # those Nodes exist. We only want to create - # Nodes for the entries that will match the - # specified pattern, though, which means we - # need to filter the list here, even though - # the overall list will also be filtered later, - # after we exit this loop. - if pattern[0] != '.': - #disk_names = [ d for d in disk_names if d[0] != '.' ] - disk_names = [x for x in disk_names if x[0] != '.'] - disk_names = fnmatch.filter(disk_names, pattern) - dirEntry = dir.Entry - for name in disk_names: - # Add './' before disk filename so that '#' at - # beginning of filename isn't interpreted. - name = './' + name - node = dirEntry(name).disambiguate() - n = selfEntry(name) - if n.__class__ != node.__class__: - n.__class__ = node.__class__ - n._morph() - - names = set(names) - if pattern[0] != '.': - #names = [ n for n in names if n[0] != '.' ] - names = [x for x in names if x[0] != '.'] - names = fnmatch.filter(names, pattern) - - if strings: - return names - - #return [ self.entries[_my_normcase(n)] for n in names ] - return [self.entries[_my_normcase(n)] for n in names] - -class RootDir(Dir): - """A class for the root directory of a file system. - - This is the same as a Dir class, except that the path separator - ('/' or '\\') is actually part of the name, so we don't need to - add a separator when creating the path names of entries within - this directory. - """ - def __init__(self, name, fs): - if __debug__: logInstanceCreation(self, 'Node.FS.RootDir') - # We're going to be our own parent directory (".." entry and .dir - # attribute) so we have to set up some values so Base.__init__() - # won't gag won't it calls some of our methods. - self.abspath = '' - self.labspath = '' - self.path = '' - self.tpath = '' - self.path_elements = [] - self.duplicate = 0 - self.root = self - Base.__init__(self, name, self, fs) - - # Now set our paths to what we really want them to be: the - # initial drive letter (the name) plus the directory separator, - # except for the "lookup abspath," which does not have the - # drive letter. - self.abspath = name + os.sep - self.labspath = '' - self.path = name + os.sep - self.tpath = name + os.sep - self._morph() - - self._lookupDict = {} - - # The // and os.sep + os.sep entries are necessary because - # os.path.normpath() seems to preserve double slashes at the - # beginning of a path (presumably for UNC path names), but - # collapses triple slashes to a single slash. - self._lookupDict[''] = self - self._lookupDict['/'] = self - self._lookupDict['//'] = self - self._lookupDict[os.sep] = self - self._lookupDict[os.sep + os.sep] = self - - def must_be_same(self, klass): - if klass is Dir: - return - Base.must_be_same(self, klass) - - def _lookup_abs(self, p, klass, create=1): - """ - Fast (?) lookup of a *normalized* absolute path. - - This method is intended for use by internal lookups with - already-normalized path data. For general-purpose lookups, - use the FS.Entry(), FS.Dir() or FS.File() methods. - - The caller is responsible for making sure we're passed a - normalized absolute path; we merely let Python's dictionary look - up and return the One True Node.FS object for the path. - - If no Node for the specified "p" doesn't already exist, and - "create" is specified, the Node may be created after recursive - invocation to find or create the parent directory or directories. - """ - k = _my_normcase(p) - try: - result = self._lookupDict[k] - except KeyError: - if not create: - msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self)) - raise SCons.Errors.UserError(msg) - # There is no Node for this path name, and we're allowed - # to create it. - dir_name, file_name = os.path.split(p) - dir_node = self._lookup_abs(dir_name, Dir) - result = klass(file_name, dir_node, self.fs) - - # Double-check on disk (as configured) that the Node we - # created matches whatever is out there in the real world. - result.diskcheck_match() - - self._lookupDict[k] = result - dir_node.entries[_my_normcase(file_name)] = result - dir_node.implicit = None - else: - # There is already a Node for this path name. Allow it to - # complain if we were looking for an inappropriate type. - result.must_be_same(klass) - return result - - def __str__(self): - return self.abspath - - def entry_abspath(self, name): - return self.abspath + name - - def entry_labspath(self, name): - return '/' + name - - def entry_path(self, name): - return self.path + name - - def entry_tpath(self, name): - return self.tpath + name - - def is_under(self, dir): - if self is dir: - return 1 - else: - return 0 - - def up(self): - return None - - def get_dir(self): - return None - - def src_builder(self): - return _null - -class FileNodeInfo(SCons.Node.NodeInfoBase): - current_version_id = 1 - - field_list = ['csig', 'timestamp', 'size'] - - # This should get reset by the FS initialization. - 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 FileBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 - - def convert_to_sconsign(self): - """ - Converts this FileBuildInfo object for writing to a .sconsign file - - This replaces each Node in our various dependency lists with its - usual string representation: relative to the top-level SConstruct - directory, or an absolute path if it's outside. - """ - if os.sep == '/': - node_to_str = str - else: - def node_to_str(n): - try: - s = n.path - except AttributeError: - s = str(n) - else: - s = s.replace(os.sep, '/') - return s - for attr in ['bsources', 'bdepends', 'bimplicit']: - try: - val = getattr(self, attr) - except AttributeError: - pass - else: - setattr(self, attr, list(map(node_to_str, val))) - def convert_from_sconsign(self, dir, name): - """ - Converts a newly-read FileBuildInfo object for in-SCons use - - For normal up-to-date checking, we don't have any conversion to - perform--but we're leaving this method here to make that clear. - """ - pass - def prepare_dependencies(self): - """ - Prepares a FileBuildInfo object for explaining what changed - - The bsources, bdepends and bimplicit lists have all been - stored on disk as paths relative to the top-level SConstruct - directory. Convert the strings to actual Nodes (for use by the - --debug=explain code and --implicit-cache). - """ - attrs = [ - ('bsources', 'bsourcesigs'), - ('bdepends', 'bdependsigs'), - ('bimplicit', 'bimplicitsigs'), - ] - for (nattr, sattr) in attrs: - try: - strings = getattr(self, nattr) - nodeinfos = getattr(self, sattr) - except AttributeError: - continue - nodes = [] - for s, ni in zip(strings, nodeinfos): - if not isinstance(s, SCons.Node.Node): - s = ni.str_to_node(s) - nodes.append(s) - setattr(self, nattr, nodes) - def format(self, names=0): - result = [] - bkids = self.bsources + self.bdepends + self.bimplicit - bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs - for bkid, bkidsig in zip(bkids, bkidsigs): - result.append(str(bkid) + ': ' + - ' '.join(bkidsig.format(names=names))) - result.append('%s [%s]' % (self.bactsig, self.bact)) - return '\n'.join(result) - -class File(Base): - """A class for files in a file system. - """ - - memoizer_counters = [] - - NodeInfo = FileNodeInfo - BuildInfo = FileBuildInfo - - md5_chunksize = 64 - - def diskcheck_match(self): - diskcheck_match(self, self.isdir, - "Directory %s found where file expected.") - - def __init__(self, name, directory, fs): - if __debug__: logInstanceCreation(self, 'Node.FS.File') - Base.__init__(self, name, directory, fs) - self._morph() - - def Entry(self, name): - """Create an entry node named 'name' relative to - the directory of this file.""" - return self.dir.Entry(name) - - def Dir(self, name, create=True): - """Create a directory node named 'name' relative to - the directory of this file.""" - return self.dir.Dir(name, create=create) - - def Dirs(self, pathlist): - """Create a list of directories relative to the SConscript - directory of this file.""" - return [self.Dir(p) for p in pathlist] - - def File(self, name): - """Create a file node named 'name' relative to - the directory of this file.""" - return self.dir.File(name) - - #def generate_build_dict(self): - # """Return an appropriate dictionary of values for building - # this File.""" - # return {'Dir' : self.Dir, - # 'File' : self.File, - # 'RDirs' : self.RDirs} - - def _morph(self): - """Turn a file system node into a File object.""" - self.scanner_paths = {} - if not hasattr(self, '_local'): - self._local = 0 - - # If there was already a Builder set on this entry, then - # we need to make sure we call the target-decider function, - # not the source-decider. Reaching in and doing this by hand - # is a little bogus. We'd prefer to handle this by adding - # an Entry.builder_set() method that disambiguates like the - # other methods, but that starts running into problems with the - # fragile way we initialize Dir Nodes with their Mkdir builders, - # yet still allow them to be overridden by the user. Since it's - # not clear right now how to fix that, stick with what works - # until it becomes clear... - if self.has_builder(): - self.changed_since_last_build = self.decide_target - - def scanner_key(self): - return self.get_suffix() - - def get_contents(self): - if not self.rexists(): - return '' - fname = self.rfile().abspath - try: - contents = open(fname, "rb").read() - except EnvironmentError, e: - if not e.filename: - e.filename = fname - raise - return contents - - # This attempts to figure out what the encoding of the text is - # based upon the BOM bytes, and then decodes the contents so that - # it's a valid python string. - def get_text_contents(self): - contents = self.get_contents() - # The behavior of various decode() methods and functions - # w.r.t. the initial BOM bytes is different for different - # encodings and/or Python versions. ('utf-8' does not strip - # them, but has a 'utf-8-sig' which does; 'utf-16' seems to - # strip them; etc.) Just sidestep all the complication by - # explicitly stripping the BOM before we decode(). - if contents.startswith(codecs.BOM_UTF8): - return contents[len(codecs.BOM_UTF8):].decode('utf-8') - if contents.startswith(codecs.BOM_UTF16_LE): - return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le') - if contents.startswith(codecs.BOM_UTF16_BE): - return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be') - return contents - - def get_content_hash(self): - """ - Compute and return the MD5 hash for this file. - """ - if not self.rexists(): - return SCons.Util.MD5signature('') - fname = self.rfile().abspath - try: - cs = SCons.Util.MD5filesignature(fname, - chunksize=SCons.Node.FS.File.md5_chunksize*1024) - except EnvironmentError, e: - if not e.filename: - e.filename = fname - raise - return cs - - - memoizer_counters.append(SCons.Memoize.CountValue('get_size')) - - def get_size(self): - try: - return self._memo['get_size'] - except KeyError: - pass - - if self.rexists(): - size = self.rfile().getsize() - else: - size = 0 - - self._memo['get_size'] = size - - return size - - memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp')) - - def get_timestamp(self): - try: - return self._memo['get_timestamp'] - except KeyError: - pass - - if self.rexists(): - timestamp = self.rfile().getmtime() - else: - timestamp = 0 - - self._memo['get_timestamp'] = timestamp - - return timestamp - - def store_info(self): - # Merge our build information into the already-stored entry. - # This accomodates "chained builds" where a file that's a target - # in one build (SConstruct file) is a source in a different build. - # See test/chained-build.py for the use case. - if do_store_info: - self.dir.sconsign().store_info(self.name, self) - - convert_copy_attrs = [ - 'bsources', - 'bimplicit', - 'bdepends', - 'bact', - 'bactsig', - 'ninfo', - ] - - - convert_sig_attrs = [ - 'bsourcesigs', - 'bimplicitsigs', - 'bdependsigs', - ] - - def convert_old_entry(self, old_entry): - # Convert a .sconsign entry from before the Big Signature - # Refactoring, doing what we can to convert its information - # to the new .sconsign entry format. - # - # The old format looked essentially like this: - # - # BuildInfo - # .ninfo (NodeInfo) - # .bsig - # .csig - # .timestamp - # .size - # .bsources - # .bsourcesigs ("signature" list) - # .bdepends - # .bdependsigs ("signature" list) - # .bimplicit - # .bimplicitsigs ("signature" list) - # .bact - # .bactsig - # - # The new format looks like this: - # - # .ninfo (NodeInfo) - # .bsig - # .csig - # .timestamp - # .size - # .binfo (BuildInfo) - # .bsources - # .bsourcesigs (NodeInfo list) - # .bsig - # .csig - # .timestamp - # .size - # .bdepends - # .bdependsigs (NodeInfo list) - # .bsig - # .csig - # .timestamp - # .size - # .bimplicit - # .bimplicitsigs (NodeInfo list) - # .bsig - # .csig - # .timestamp - # .size - # .bact - # .bactsig - # - # The basic idea of the new structure is that a NodeInfo always - # holds all available information about the state of a given Node - # at a certain point in time. The various .b*sigs lists can just - # be a list of pointers to the .ninfo attributes of the different - # dependent nodes, without any copying of information until it's - # time to pickle it for writing out to a .sconsign file. - # - # The complicating issue is that the *old* format only stored one - # "signature" per dependency, based on however the *last* build - # was configured. We don't know from just looking at it whether - # it was a build signature, a content signature, or a timestamp - # "signature". Since we no longer use build signatures, the - # best we can do is look at the length and if it's thirty two, - # assume that it was (or might have been) a content signature. - # If it was actually a build signature, then it will cause a - # rebuild anyway when it doesn't match the new content signature, - # but that's probably the best we can do. - import SCons.SConsign - new_entry = SCons.SConsign.SConsignEntry() - new_entry.binfo = self.new_binfo() - binfo = new_entry.binfo - for attr in self.convert_copy_attrs: - try: - value = getattr(old_entry, attr) - except AttributeError: - continue - setattr(binfo, attr, value) - delattr(old_entry, attr) - for attr in self.convert_sig_attrs: - try: - sig_list = getattr(old_entry, attr) - except AttributeError: - continue - value = [] - for sig in sig_list: - ninfo = self.new_ninfo() - if len(sig) == 32: - ninfo.csig = sig - else: - ninfo.timestamp = sig - value.append(ninfo) - setattr(binfo, attr, value) - delattr(old_entry, attr) - return new_entry - - memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info')) - - def get_stored_info(self): - try: - return self._memo['get_stored_info'] - except KeyError: - pass - - try: - sconsign_entry = self.dir.sconsign().get_entry(self.name) - except (KeyError, EnvironmentError): - import SCons.SConsign - sconsign_entry = SCons.SConsign.SConsignEntry() - sconsign_entry.binfo = self.new_binfo() - sconsign_entry.ninfo = self.new_ninfo() - else: - if isinstance(sconsign_entry, FileBuildInfo): - # This is a .sconsign file from before the Big Signature - # Refactoring; convert it as best we can. - sconsign_entry = self.convert_old_entry(sconsign_entry) - try: - delattr(sconsign_entry.ninfo, 'bsig') - except AttributeError: - pass - - self._memo['get_stored_info'] = sconsign_entry - - return sconsign_entry - - def get_stored_implicit(self): - binfo = self.get_stored_info().binfo - binfo.prepare_dependencies() - try: return binfo.bimplicit - except AttributeError: return None - - def rel_path(self, other): - return self.dir.rel_path(other) - - def _get_found_includes_key(self, env, scanner, path): - return (id(env), id(scanner), path) - - memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key)) - - def get_found_includes(self, env, scanner, path): - """Return the included implicit dependencies in this file. - Cache results so we only scan the file once per path - regardless of how many times this information is requested. - """ - memo_key = (id(env), id(scanner), path) - try: - memo_dict = self._memo['get_found_includes'] - except KeyError: - memo_dict = {} - self._memo['get_found_includes'] = memo_dict - else: - try: - return memo_dict[memo_key] - except KeyError: - pass - - if scanner: - # result = [n.disambiguate() for n in scanner(self, env, path)] - result = scanner(self, env, path) - result = [N.disambiguate() for N in result] - else: - result = [] - - memo_dict[memo_key] = result - - return result - - def _createDir(self): - # ensure that the directories for this node are - # created. - self.dir._create() - - def push_to_cache(self): - """Try to push the node into a cache - """ - # This should get called before the Nodes' .built() method is - # called, which would clear the build signature if the file has - # a source scanner. - # - # We have to clear the local memoized values *before* we push - # the node to cache so that the memoization of the self.exists() - # return value doesn't interfere. - if self.nocache: - return - self.clear_memoized_values() - if self.exists(): - self.get_build_env().get_CacheDir().push(self) - - def retrieve_from_cache(self): - """Try to retrieve the node's content from a cache - - This method is called from multiple threads in a parallel build, - so only do thread safe stuff here. Do thread unsafe stuff in - built(). - - Returns true iff the node was successfully retrieved. - """ - if self.nocache: - return None - if not self.is_derived(): - return None - return self.get_build_env().get_CacheDir().retrieve(self) - - def visited(self): - if self.exists(): - self.get_build_env().get_CacheDir().push_if_forced(self) - - ninfo = self.get_ninfo() - - csig = self.get_max_drift_csig() - if csig: - ninfo.csig = csig - - ninfo.timestamp = self.get_timestamp() - ninfo.size = self.get_size() - - if not self.has_builder(): - # This is a source file, but it might have been a target file - # in another build that included more of the DAG. Copy - # any build information that's stored in the .sconsign file - # into our binfo object so it doesn't get lost. - old = self.get_stored_info() - self.get_binfo().__dict__.update(old.binfo.__dict__) - - self.store_info() - - def find_src_builder(self): - if self.rexists(): - return None - scb = self.dir.src_builder() - if scb is _null: - if diskcheck_sccs(self.dir, self.name): - scb = get_DefaultSCCSBuilder() - elif diskcheck_rcs(self.dir, self.name): - scb = get_DefaultRCSBuilder() - else: - scb = None - if scb is not None: - try: - b = self.builder - except AttributeError: - b = None - if b is None: - self.builder_set(scb) - return scb - - def has_src_builder(self): - """Return whether this Node has a source builder or not. - - If this Node doesn't have an explicit source code builder, this - is where we figure out, on the fly, if there's a transparent - source code builder for it. - - Note that if we found a source builder, we also set the - self.builder attribute, so that all of the methods that actually - *build* this file don't have to do anything different. - """ - try: - scb = self.sbuilder - except AttributeError: - scb = self.sbuilder = self.find_src_builder() - return scb is not None - - def alter_targets(self): - """Return any corresponding targets in a variant directory. - """ - if self.is_derived(): - return [], None - return self.fs.variant_dir_target_climb(self, self.dir, [self.name]) - - def _rmv_existing(self): - self.clear_memoized_values() - e = Unlink(self, [], None) - if isinstance(e, SCons.Errors.BuildError): - raise e - - # - # Taskmaster interface subsystem - # - - def make_ready(self): - self.has_src_builder() - self.get_binfo() - - def prepare(self): - """Prepare for this file to be created.""" - SCons.Node.Node.prepare(self) - - if self.get_state() != SCons.Node.up_to_date: - if self.exists(): - if self.is_derived() and not self.precious: - self._rmv_existing() - else: - try: - self._createDir() - except SCons.Errors.StopError, drive: - desc = "No drive `%s' for target `%s'." % (drive, self) - raise SCons.Errors.StopError(desc) - - # - # - # - - def remove(self): - """Remove this file.""" - if self.exists() or self.islink(): - self.fs.unlink(self.path) - return 1 - return None - - def do_duplicate(self, src): - self._createDir() - Unlink(self, None, None) - e = Link(self, src, None) - if isinstance(e, SCons.Errors.BuildError): - desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr) - raise SCons.Errors.StopError(desc) - self.linked = 1 - # The Link() action may or may not have actually - # created the file, depending on whether the -n - # option was used or not. Delete the _exists and - # _rexists attributes so they can be reevaluated. - self.clear() - - memoizer_counters.append(SCons.Memoize.CountValue('exists')) - - def exists(self): - try: - return self._memo['exists'] - except KeyError: - pass - # Duplicate from source path if we are set up to do this. - if self.duplicate and not self.is_derived() and not self.linked: - src = self.srcnode() - if src is not self: - # At this point, src is meant to be copied in a variant directory. - src = src.rfile() - if src.abspath != self.abspath: - if src.exists(): - self.do_duplicate(src) - # Can't return 1 here because the duplication might - # not actually occur if the -n option is being used. - else: - # The source file does not exist. Make sure no old - # copy remains in the variant directory. - if Base.exists(self) or self.islink(): - self.fs.unlink(self.path) - # Return None explicitly because the Base.exists() call - # above will have cached its value if the file existed. - self._memo['exists'] = None - return None - result = Base.exists(self) - self._memo['exists'] = result - return result - - # - # SIGNATURE SUBSYSTEM - # - - def get_max_drift_csig(self): - """ - Returns the content signature currently stored for this node - if it's been unmodified longer than the max_drift value, or the - max_drift value is 0. Returns None otherwise. - """ - old = self.get_stored_info() - mtime = self.get_timestamp() - - max_drift = self.fs.max_drift - if max_drift > 0: - if (time.time() - mtime) > max_drift: - try: - n = old.ninfo - if n.timestamp and n.csig and n.timestamp == mtime: - return n.csig - except AttributeError: - pass - elif max_drift == 0: - try: - return old.ninfo.csig - except AttributeError: - pass - - return None - - def get_csig(self): - """ - Generate a node's content signature, the digested signature - of its content. - - node - the node - cache - alternate node to use for the signature cache - returns - the content signature - """ - ninfo = self.get_ninfo() - try: - return ninfo.csig - except AttributeError: - pass - - csig = self.get_max_drift_csig() - if csig is None: - - try: - if self.get_size() < SCons.Node.FS.File.md5_chunksize: - contents = self.get_contents() - else: - csig = self.get_content_hash() - except IOError: - # This can happen if there's actually a directory on-disk, - # which can be the case if they've disabled disk checks, - # or if an action with a File target actually happens to - # create a same-named directory by mistake. - csig = '' - else: - if not csig: - csig = SCons.Util.MD5signature(contents) - - ninfo.csig = csig - - return csig - - # - # DECISION SUBSYSTEM - # - - def builder_set(self, builder): - SCons.Node.Node.builder_set(self, builder) - self.changed_since_last_build = self.decide_target - - def changed_content(self, target, prev_ni): - cur_csig = self.get_csig() - try: - return cur_csig != prev_ni.csig - except AttributeError: - return 1 - - def changed_state(self, target, prev_ni): - return self.state != SCons.Node.up_to_date - - def changed_timestamp_then_content(self, target, prev_ni): - if not self.changed_timestamp_match(target, prev_ni): - try: - self.get_ninfo().csig = prev_ni.csig - except AttributeError: - pass - return False - return self.changed_content(target, prev_ni) - - def changed_timestamp_newer(self, target, prev_ni): - try: - return self.get_timestamp() > target.get_timestamp() - except AttributeError: - return 1 - - def changed_timestamp_match(self, target, prev_ni): - try: - return self.get_timestamp() != prev_ni.timestamp - except AttributeError: - return 1 - - def decide_source(self, target, prev_ni): - return target.get_build_env().decide_source(self, target, prev_ni) - - def decide_target(self, target, prev_ni): - return target.get_build_env().decide_target(self, target, prev_ni) - - # Initialize this Node's decider function to decide_source() because - # every file is a source file until it has a Builder attached... - changed_since_last_build = decide_source - - def is_up_to_date(self): - T = 0 - if T: Trace('is_up_to_date(%s):' % self) - if not self.exists(): - if T: Trace(' not self.exists():') - # The file doesn't exist locally... - r = self.rfile() - if r != self: - # ...but there is one in a Repository... - if not self.changed(r): - if T: Trace(' changed(%s):' % r) - # ...and it's even up-to-date... - if self._local: - # ...and they'd like a local copy. - e = LocalCopy(self, r, None) - if isinstance(e, SCons.Errors.BuildError): - raise - self.store_info() - if T: Trace(' 1\n') - return 1 - self.changed() - if T: Trace(' None\n') - return None - else: - r = self.changed() - if T: Trace(' self.exists(): %s\n' % r) - return not r - - memoizer_counters.append(SCons.Memoize.CountValue('rfile')) - - def rfile(self): - try: - return self._memo['rfile'] - 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: node = dir.file_on_disk(self.name) - if node and node.exists() and \ - (isinstance(node, File) or isinstance(node, Entry) \ - or not node.is_derived()): - result = node - # Copy over our local attributes to the repository - # Node so we identify shared object files in the - # repository and don't assume they're static. - # - # This isn't perfect; the attribute would ideally - # be attached to the object in the repository in - # case it was built statically in the repository - # and we changed it to shared locally, but that's - # rarely the case and would only occur if you - # intentionally used the same suffix for both - # shared and static objects anyway. So this - # should work well in practice. - result.attributes = self.attributes - break - self._memo['rfile'] = result - return result - - def rstr(self): - return str(self.rfile()) - - def get_cachedir_csig(self): - """ - Fetch a Node's content signature for purposes of computing - another Node's cachesig. - - This is a wrapper around the normal get_csig() method that handles - the somewhat obscure case of using CacheDir with the -n option. - Any files that don't exist would normally be "built" by fetching - them from the cache, but the normal get_csig() method will try - to open up the local file, which doesn't exist because the -n - option meant we didn't actually pull the file from cachedir. - But since the file *does* actually exist in the cachedir, we - can use its contents for the csig. - """ - try: - return self.cachedir_csig - except AttributeError: - pass - - cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self) - if not self.exists() and cachefile and os.path.exists(cachefile): - self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \ - SCons.Node.FS.File.md5_chunksize * 1024) - else: - self.cachedir_csig = self.get_csig() - return self.cachedir_csig - - def get_cachedir_bsig(self): - try: - return self.cachesig - except AttributeError: - pass - - # Add the path to the cache signature, because multiple - # targets built by the same action will all have the same - # build signature, and we have to differentiate them somehow. - children = self.children() - executor = self.get_executor() - # sigs = [n.get_cachedir_csig() for n in children] - sigs = [n.get_cachedir_csig() for n in children] - sigs.append(SCons.Util.MD5signature(executor.get_contents())) - sigs.append(self.path) - result = self.cachesig = SCons.Util.MD5collect(sigs) - return result - - -default_fs = None - -def get_default_fs(): - global default_fs - if not default_fs: - default_fs = FS() - return default_fs - -class FileFinder(object): - """ - """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] - - def __init__(self): - self._memo = {} - - def filedir_lookup(self, p, fd=None): - """ - A helper method for find_file() that looks up a directory for - a file we're trying to find. This only creates the Dir Node if - it exists on-disk, since if the directory doesn't exist we know - we won't find any files in it... :-) - - It would be more compact to just use this as a nested function - with a default keyword argument (see the commented-out version - below), but that doesn't work unless you have nested scopes, - so we define it here just so this work under Python 1.5.2. - """ - if fd is None: - fd = self.default_filedir - dir, name = os.path.split(fd) - drive, d = os.path.splitdrive(dir) - if not name and d[:1] in ('/', os.sep): - #return p.fs.get_root(drive).dir_on_disk(name) - return p.fs.get_root(drive) - if dir: - p = self.filedir_lookup(p, dir) - if not p: - return None - norm_name = _my_normcase(name) - try: - node = p.entries[norm_name] - except KeyError: - return p.dir_on_disk(name) - if isinstance(node, Dir): - return node - if isinstance(node, Entry): - node.must_be_same(Dir) - return node - return None - - def _find_file_key(self, filename, paths, verbose=None): - return (filename, paths) - - memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key)) - - def find_file(self, filename, paths, verbose=None): - """ - find_file(str, [Dir()]) -> [nodes] - - filename - a filename to find - paths - a list of directory path *nodes* to search in. Can be - represented as a list, a tuple, or a callable that is - called with no arguments and returns the list or tuple. - - returns - the node created from the found file. - - Find a node corresponding to either a derived file or a file - that exists already. - - Only the first file found is returned, and none is returned - if no file is found. - """ - memo_key = self._find_file_key(filename, paths) - try: - memo_dict = self._memo['find_file'] - except KeyError: - memo_dict = {} - self._memo['find_file'] = memo_dict - else: - try: - return memo_dict[memo_key] - except KeyError: - pass - - if verbose and not callable(verbose): - if not SCons.Util.is_String(verbose): - verbose = "find_file" - _verbose = u' %s: ' % verbose - verbose = lambda s: sys.stdout.write(_verbose + s) - - filedir, filename = os.path.split(filename) - if filedir: - # More compact code that we can't use until we drop - # support for Python 1.5.2: - # - #def filedir_lookup(p, fd=filedir): - # """ - # A helper function that looks up a directory for a file - # we're trying to find. This only creates the Dir Node - # if it exists on-disk, since if the directory doesn't - # exist we know we won't find any files in it... :-) - # """ - # dir, name = os.path.split(fd) - # if dir: - # p = filedir_lookup(p, dir) - # if not p: - # return None - # norm_name = _my_normcase(name) - # try: - # node = p.entries[norm_name] - # except KeyError: - # return p.dir_on_disk(name) - # if isinstance(node, Dir): - # return node - # if isinstance(node, Entry): - # node.must_be_same(Dir) - # return node - # if isinstance(node, Dir) or isinstance(node, Entry): - # return node - # return None - #paths = [_f for _f in map(filedir_lookup, paths) if _f] - - self.default_filedir = filedir - paths = [_f for _f in map(self.filedir_lookup, paths) if _f] - - result = None - for dir in paths: - if verbose: - verbose("looking for '%s' in '%s' ...\n" % (filename, dir)) - node, d = dir.srcdir_find_file(filename) - if node: - if verbose: - verbose("... FOUND '%s' in '%s'\n" % (filename, d)) - result = node - break - - memo_dict[memo_key] = result - - return result - -find_file = FileFinder().find_file - - -def invalidate_node_memos(targets): - """ - Invalidate the memoized values of all Nodes (files or directories) - that are associated with the given entries. Has been added to - clear the cache of nodes affected by a direct execution of an - action (e.g. Delete/Copy/Chmod). Existing Node caches become - inconsistent if the action is run through Execute(). The argument - `targets` can be a single Node object or filename, or a sequence - of Nodes/filenames. - """ - from traceback import extract_stack - - # First check if the cache really needs to be flushed. Only - # actions run in the SConscript with Execute() seem to be - # affected. XXX The way to check if Execute() is in the stacktrace - # is a very dirty hack and should be replaced by a more sensible - # solution. - for f in extract_stack(): - if f[2] == 'Execute' and f[0][-14:] == 'Environment.py': - break - else: - # Dont have to invalidate, so return - return - - if not SCons.Util.is_List(targets): - targets = [targets] - - for entry in targets: - # If the target is a Node object, clear the cache. If it is a - # filename, look up potentially existing Node object first. - try: - entry.clear_memoized_values() - except AttributeError: - # Not a Node object, try to look up Node by filename. XXX - # This creates Node objects even for those filenames which - # do not correspond to an existing Node object. - node = get_default_fs().Entry(entry) - if node: - node.clear_memoized_values() - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: -- cgit v1.2.3