# Copyright 2007-2012 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import time from portage import os from portage.versions import best, catsplit, vercmp from portage.dep import Atom from portage.localization import _ from portage._sets.base import PackageSet from portage._sets import SetConfigError, get_boolean import portage __all__ = ["CategorySet", "DowngradeSet", "EverythingSet", "OwnerSet", "VariableSet"] class EverythingSet(PackageSet): _operations = ["merge"] description = "Package set which contains SLOT " + \ "atoms to match all installed packages" _filter = None def __init__(self, vdbapi, **kwargs): super(EverythingSet, self).__init__() self._db = vdbapi def load(self): myatoms = [] pkg_str = self._db._pkg_str cp_list = self._db.cp_list for cp in self._db.cp_all(): for cpv in cp_list(cp): # NOTE: Create SLOT atoms even when there is only one # SLOT installed, in order to avoid the possibility # of unwanted upgrades as reported in bug #338959. pkg = pkg_str(cpv, None) atom = Atom("%s:%s" % (pkg.cp, pkg.slot)) if self._filter: if self._filter(atom): myatoms.append(atom) else: myatoms.append(atom) self._setAtoms(myatoms) def singleBuilder(self, options, settings, trees): return EverythingSet(trees["vartree"].dbapi) singleBuilder = classmethod(singleBuilder) class OwnerSet(PackageSet): _operations = ["merge", "unmerge"] description = "Package set which contains all packages " + \ "that own one or more files." def __init__(self, vardb=None, exclude_files=None, files=None): super(OwnerSet, self).__init__() self._db = vardb self._exclude_files = exclude_files self._files = files def mapPathsToAtoms(self, paths, exclude_paths=None): """ All paths must have $EROOT stripped from the left side. """ rValue = set() vardb = self._db pkg_str = vardb._pkg_str if exclude_paths is None: for link, p in vardb._owners.iter_owners(paths): pkg = pkg_str(link.mycpv, None) rValue.add("%s:%s" % (pkg.cp, pkg.slot)) else: all_paths = set() all_paths.update(paths) all_paths.update(exclude_paths) exclude_atoms = set() for link, p in vardb._owners.iter_owners(all_paths): pkg = pkg_str(link.mycpv, None) atom = "%s:%s" % (pkg.cp, pkg.slot) rValue.add(atom) if p in exclude_paths: exclude_atoms.add(atom) rValue.difference_update(exclude_atoms) return rValue def load(self): self._setAtoms(self.mapPathsToAtoms(self._files, exclude_paths=self._exclude_files)) def singleBuilder(cls, options, settings, trees): if not "files" in options: raise SetConfigError(_("no files given")) exclude_files = options.get("exclude-files") if exclude_files is not None: exclude_files = frozenset(portage.util.shlex_split(exclude_files)) return cls(vardb=trees["vartree"].dbapi, exclude_files=exclude_files, files=frozenset(portage.util.shlex_split(options["files"]))) singleBuilder = classmethod(singleBuilder) class VariableSet(EverythingSet): _operations = ["merge", "unmerge"] description = "Package set which contains all packages " + \ "that match specified values of a specified variable." def __init__(self, vardb, metadatadb=None, variable=None, includes=None, excludes=None): super(VariableSet, self).__init__(vardb) self._metadatadb = metadatadb self._variable = variable self._includes = includes self._excludes = excludes def _filter(self, atom): ebuild = best(self._metadatadb.match(atom)) if not ebuild: return False values, = self._metadatadb.aux_get(ebuild, [self._variable]) values = values.split() if self._includes and not self._includes.intersection(values): return False if self._excludes and self._excludes.intersection(values): return False return True def singleBuilder(cls, options, settings, trees): variable = options.get("variable") if variable is None: raise SetConfigError(_("missing required attribute: 'variable'")) includes = options.get("includes", "") excludes = options.get("excludes", "") if not (includes or excludes): raise SetConfigError(_("no includes or excludes given")) metadatadb = options.get("metadata-source", "vartree") if not metadatadb in trees: raise SetConfigError(_("invalid value '%s' for option metadata-source") % metadatadb) return cls(trees["vartree"].dbapi, metadatadb=trees[metadatadb].dbapi, excludes=frozenset(excludes.split()), includes=frozenset(includes.split()), variable=variable) singleBuilder = classmethod(singleBuilder) class DowngradeSet(PackageSet): _operations = ["merge", "unmerge"] description = "Package set which contains all packages " + \ "for which the highest visible ebuild version is lower than " + \ "the currently installed version." def __init__(self, portdb=None, vardb=None): super(DowngradeSet, self).__init__() self._portdb = portdb self._vardb = vardb def load(self): atoms = [] xmatch = self._portdb.xmatch xmatch_level = "bestmatch-visible" cp_list = self._vardb.cp_list pkg_str = self._vardb._pkg_str for cp in self._vardb.cp_all(): for cpv in cp_list(cp): pkg = pkg_str(cpv, None) slot_atom = "%s:%s" % (pkg.cp, pkg.slot) ebuild = xmatch(xmatch_level, slot_atom) if not ebuild: continue if vercmp(cpv.version, ebuild.version) > 0: atoms.append(slot_atom) self._setAtoms(atoms) def singleBuilder(cls, options, settings, trees): return cls(portdb=trees["porttree"].dbapi, vardb=trees["vartree"].dbapi) singleBuilder = classmethod(singleBuilder) class UnavailableSet(EverythingSet): _operations = ["unmerge"] description = "Package set which contains all installed " + \ "packages for which there are no visible ebuilds " + \ "corresponding to the same $CATEGORY/$PN:$SLOT." def __init__(self, vardb, metadatadb=None): super(UnavailableSet, self).__init__(vardb) self._metadatadb = metadatadb def _filter(self, atom): return not self._metadatadb.match(atom) def singleBuilder(cls, options, settings, trees): metadatadb = options.get("metadata-source", "porttree") if not metadatadb in trees: raise SetConfigError(_("invalid value '%s' for option " "metadata-source") % (metadatadb,)) return cls(trees["vartree"].dbapi, metadatadb=trees[metadatadb].dbapi) singleBuilder = classmethod(singleBuilder) class UnavailableBinaries(EverythingSet): _operations = ('merge', 'unmerge',) description = "Package set which contains all installed " + \ "packages for which corresponding binary packages " + \ "are not available." def __init__(self, vardb, metadatadb=None): super(UnavailableBinaries, self).__init__(vardb) self._metadatadb = metadatadb def _filter(self, atom): inst_pkg = self._db.match(atom) if not inst_pkg: return False inst_cpv = inst_pkg[0] return not self._metadatadb.cpv_exists(inst_cpv) def singleBuilder(cls, options, settings, trees): metadatadb = options.get("metadata-source", "bintree") if not metadatadb in trees: raise SetConfigError(_("invalid value '%s' for option " "metadata-source") % (metadatadb,)) return cls(trees["vartree"].dbapi, metadatadb=trees[metadatadb].dbapi) singleBuilder = classmethod(singleBuilder) class CategorySet(PackageSet): _operations = ["merge", "unmerge"] def __init__(self, category, dbapi, only_visible=True): super(CategorySet, self).__init__() self._db = dbapi self._category = category self._check = only_visible if only_visible: s="visible" else: s="all" self.description = "Package set containing %s packages of category %s" % (s, self._category) def load(self): myatoms = [] for cp in self._db.cp_all(): if catsplit(cp)[0] == self._category: if (not self._check) or len(self._db.match(cp)) > 0: myatoms.append(cp) self._setAtoms(myatoms) def _builderGetRepository(cls, options, repositories): repository = options.get("repository", "porttree") if not repository in repositories: raise SetConfigError(_("invalid repository class '%s'") % repository) return repository _builderGetRepository = classmethod(_builderGetRepository) def _builderGetVisible(cls, options): return get_boolean(options, "only_visible", True) _builderGetVisible = classmethod(_builderGetVisible) def singleBuilder(cls, options, settings, trees): if not "category" in options: raise SetConfigError(_("no category given")) category = options["category"] if not category in settings.categories: raise SetConfigError(_("invalid category name '%s'") % category) repository = cls._builderGetRepository(options, trees.keys()) visible = cls._builderGetVisible(options) return CategorySet(category, dbapi=trees[repository].dbapi, only_visible=visible) singleBuilder = classmethod(singleBuilder) def multiBuilder(cls, options, settings, trees): rValue = {} if "categories" in options: categories = options["categories"].split() invalid = set(categories).difference(settings.categories) if invalid: raise SetConfigError(_("invalid categories: %s") % ", ".join(list(invalid))) else: categories = settings.categories repository = cls._builderGetRepository(options, trees.keys()) visible = cls._builderGetVisible(options) name_pattern = options.get("name_pattern", "$category/*") if not "$category" in name_pattern and not "${category}" in name_pattern: raise SetConfigError(_("name_pattern doesn't include $category placeholder")) for cat in categories: myset = CategorySet(cat, trees[repository].dbapi, only_visible=visible) myname = name_pattern.replace("$category", cat) myname = myname.replace("${category}", cat) rValue[myname] = myset return rValue multiBuilder = classmethod(multiBuilder) class AgeSet(EverythingSet): _operations = ["merge", "unmerge"] _aux_keys = ('BUILD_TIME',) def __init__(self, vardb, mode="older", age=7): super(AgeSet, self).__init__(vardb) self._mode = mode self._age = age def _filter(self, atom): cpv = self._db.match(atom)[0] try: date, = self._db.aux_get(cpv, self._aux_keys) date = int(date) except (KeyError, ValueError): return bool(self._mode == "older") age = (time.time() - date) / (3600 * 24) if ((self._mode == "older" and age <= self._age) \ or (self._mode == "newer" and age >= self._age)): return False else: return True def singleBuilder(cls, options, settings, trees): mode = options.get("mode", "older") if str(mode).lower() not in ["newer", "older"]: raise SetConfigError(_("invalid 'mode' value %s (use either 'newer' or 'older')") % mode) try: age = int(options.get("age", "7")) except ValueError as e: raise SetConfigError(_("value of option 'age' is not an integer")) return AgeSet(vardb=trees["vartree"].dbapi, mode=mode, age=age) singleBuilder = classmethod(singleBuilder) class DateSet(EverythingSet): _operations = ["merge", "unmerge"] _aux_keys = ('BUILD_TIME',) def __init__(self, vardb, date, mode="older"): super(DateSet, self).__init__(vardb) self._mode = mode self._date = date def _filter(self, atom): cpv = self._db.match(atom)[0] try: date, = self._db.aux_get(cpv, self._aux_keys) date = int(date) except (KeyError, ValueError): return bool(self._mode == "older") # Make sure inequality is _strict_ to exclude tested package if ((self._mode == "older" and date < self._date) \ or (self._mode == "newer" and date > self._date)): return True else: return False def singleBuilder(cls, options, settings, trees): vardbapi = trees["vartree"].dbapi mode = options.get("mode", "older") if str(mode).lower() not in ["newer", "older"]: raise SetConfigError(_("invalid 'mode' value %s (use either 'newer' or 'older')") % mode) formats = [] if options.get("package") is not None: formats.append("package") if options.get("filestamp") is not None: formats.append("filestamp") if options.get("seconds") is not None: formats.append("seconds") if options.get("date") is not None: formats.append("date") if not formats: raise SetConfigError(_("none of these options specified: 'package', 'filestamp', 'seconds', 'date'")) elif len(formats) > 1: raise SetConfigError(_("no more than one of these options is allowed: 'package', 'filestamp', 'seconds', 'date'")) format = formats[0] if (format == "package"): package = options.get("package") try: cpv = vardbapi.match(package)[0] date, = vardbapi.aux_get(cpv, ('BUILD_TIME',)) date = int(date) except (KeyError, ValueError): raise SetConfigError(_("cannot determine installation date of package %s") % package) elif (format == "filestamp"): filestamp = options.get("filestamp") try: date = int(os.stat(filestamp).st_mtime) except (OSError, ValueError): raise SetConfigError(_("cannot determine 'filestamp' of '%s'") % filestamp) elif (format == "seconds"): try: date = int(options.get("seconds")) except ValueError: raise SetConfigError(_("option 'seconds' must be an integer")) else: dateopt = options.get("date") try: dateformat = options.get("dateformat", "%x %X") date = int(time.mktime(time.strptime(dateopt, dateformat))) except ValueError: raise SetConfigError(_("'date=%s' does not match 'dateformat=%s'") % (dateopt, dateformat)) return DateSet(vardb=vardbapi, date=date, mode=mode) singleBuilder = classmethod(singleBuilder) class RebuiltBinaries(EverythingSet): _operations = ('merge',) _aux_keys = ('BUILD_TIME',) def __init__(self, vardb, bindb=None): super(RebuiltBinaries, self).__init__(vardb, bindb=bindb) self._bindb = bindb def _filter(self, atom): cpv = self._db.match(atom)[0] inst_build_time, = self._db.aux_get(cpv, self._aux_keys) try: bin_build_time, = self._bindb.aux_get(cpv, self._aux_keys) except KeyError: return False return bool(bin_build_time and (inst_build_time != bin_build_time)) def singleBuilder(cls, options, settings, trees): return RebuiltBinaries(trees["vartree"].dbapi, bindb=trees["bintree"].dbapi) singleBuilder = classmethod(singleBuilder)