| """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: |