| # Copyright 2005-2014 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| # Author(s): Nicholas Carpaski (carpaski@gentoo.org), Brian Harring (ferringb@gentoo.org) |
| |
| from __future__ import unicode_literals |
| |
| __all__ = ["cache"] |
| |
| import stat |
| import sys |
| import operator |
| import warnings |
| from portage.util import normalize_path |
| import errno |
| from portage.exception import FileNotFound, PermissionDenied |
| from portage import os |
| from portage import checksum |
| from portage import _shell_quote |
| |
| if sys.hexversion >= 0x3000000: |
| # pylint: disable=W0622 |
| long = int |
| |
| |
| class hashed_path(object): |
| |
| def __init__(self, location): |
| self.location = location |
| |
| def __getattr__(self, attr): |
| if attr == 'mtime': |
| # use stat.ST_MTIME; accessing .st_mtime gets you a float |
| # depending on the python version, and long(float) introduces |
| # some rounding issues that aren't present for people using |
| # the straight c api. |
| # thus use the defacto python compatibility work around; |
| # access via index, which guarantees you get the raw long. |
| try: |
| self.mtime = obj = os.stat(self.location)[stat.ST_MTIME] |
| except OSError as e: |
| if e.errno in (errno.ENOENT, errno.ESTALE): |
| raise FileNotFound(self.location) |
| elif e.errno == PermissionDenied.errno: |
| raise PermissionDenied(self.location) |
| raise |
| return obj |
| if not attr.islower(): |
| # we don't care to allow .mD5 as an alias for .md5 |
| raise AttributeError(attr) |
| hashname = attr.upper() |
| if hashname not in checksum.hashfunc_map: |
| raise AttributeError(attr) |
| val = checksum.perform_checksum(self.location, hashname)[0] |
| setattr(self, attr, val) |
| return val |
| |
| def __repr__(self): |
| return "<portage.eclass_cache.hashed_path('%s')>" % (self.location,) |
| |
| class cache(object): |
| """ |
| Maintains the cache information about eclasses used in ebuild. |
| """ |
| def __init__(self, porttree_root, overlays=None): |
| if overlays is not None: |
| warnings.warn("overlays parameter of portage.eclass_cache.cache constructor is deprecated and no longer used", |
| DeprecationWarning, stacklevel=2) |
| |
| self.eclasses = {} # {"Name": hashed_path} |
| self._eclass_locations = {} |
| self._eclass_locations_str = None |
| |
| # screw with the porttree ordering, w/out having bash inherit match it, and I'll hurt you. |
| # ~harring |
| if porttree_root: |
| self.porttree_root = porttree_root |
| self.porttrees = (normalize_path(self.porttree_root),) |
| self._master_eclass_root = os.path.join(self.porttrees[0], "eclass") |
| self.update_eclasses() |
| else: |
| self.porttree_root = None |
| self.porttrees = () |
| self._master_eclass_root = None |
| |
| def copy(self): |
| return self.__copy__() |
| |
| def __copy__(self): |
| result = self.__class__(None) |
| result.eclasses = self.eclasses.copy() |
| result._eclass_locations = self._eclass_locations.copy() |
| result.porttree_root = self.porttree_root |
| result.porttrees = self.porttrees |
| result._master_eclass_root = self._master_eclass_root |
| return result |
| |
| def append(self, other): |
| """ |
| Append another instance to this instance. This will cause eclasses |
| from the other instance to override any eclasses from this instance |
| that have the same name. |
| """ |
| if not isinstance(other, self.__class__): |
| raise TypeError( |
| "expected type %s, got %s" % (self.__class__, type(other))) |
| self.porttrees = self.porttrees + other.porttrees |
| self.eclasses.update(other.eclasses) |
| self._eclass_locations.update(other._eclass_locations) |
| self._eclass_locations_str = None |
| |
| def update_eclasses(self): |
| self.eclasses = {} |
| self._eclass_locations = {} |
| master_eclasses = {} |
| eclass_len = len(".eclass") |
| ignored_listdir_errnos = (errno.ENOENT, errno.ENOTDIR) |
| for x in [normalize_path(os.path.join(y,"eclass")) for y in self.porttrees]: |
| try: |
| eclass_filenames = os.listdir(x) |
| except OSError as e: |
| if e.errno in ignored_listdir_errnos: |
| del e |
| continue |
| elif e.errno == PermissionDenied.errno: |
| raise PermissionDenied(x) |
| raise |
| for y in eclass_filenames: |
| if not y.endswith(".eclass"): |
| continue |
| obj = hashed_path(os.path.join(x, y)) |
| obj.eclass_dir = x |
| try: |
| mtime = obj.mtime |
| except FileNotFound: |
| continue |
| ys = y[:-eclass_len] |
| if x == self._master_eclass_root: |
| master_eclasses[ys] = mtime |
| self.eclasses[ys] = obj |
| self._eclass_locations[ys] = x |
| continue |
| |
| master_mtime = master_eclasses.get(ys) |
| if master_mtime is not None: |
| if master_mtime == mtime: |
| # It appears to be identical to the master, |
| # so prefer the master entry. |
| continue |
| |
| self.eclasses[ys] = obj |
| self._eclass_locations[ys] = x |
| |
| def validate_and_rewrite_cache(self, ec_dict, chf_type, stores_paths): |
| """ |
| This will return an empty dict if the ec_dict parameter happens |
| to be empty, therefore callers must take care to distinguish |
| between empty dict and None return values. |
| """ |
| if not isinstance(ec_dict, dict): |
| return None |
| our_getter = operator.attrgetter(chf_type) |
| cache_getter = lambda x:x |
| if stores_paths: |
| cache_getter = operator.itemgetter(1) |
| d = {} |
| for eclass, ec_data in ec_dict.items(): |
| cached_data = self.eclasses.get(eclass) |
| if cached_data is None: |
| return None |
| if cache_getter(ec_data) != our_getter(cached_data): |
| return None |
| d[eclass] = cached_data |
| return d |
| |
| def get_eclass_data(self, inherits): |
| ec_dict = {} |
| for x in inherits: |
| ec_dict[x] = self.eclasses[x] |
| |
| return ec_dict |
| |
| @property |
| def eclass_locations_string(self): |
| if self._eclass_locations_str is None: |
| self._eclass_locations_str = " ".join(_shell_quote(x) |
| for x in reversed(self.porttrees)) |
| return self._eclass_locations_str |