# Copyright 2010-2012 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 __all__ = ( 'KeywordsManager', ) from _emerge.Package import Package from portage import os from portage.dep import ExtendedAtomDict, _repo_separator, _slot_separator from portage.localization import _ from portage.package.ebuild._config.helper import ordered_by_atom_specificity from portage.util import grabdict_package, stack_lists, writemsg from portage.versions import _pkg_str class KeywordsManager(object): """Manager class to handle keywords processing and validation""" def __init__(self, profiles, abs_user_config, user_config=True, global_accept_keywords=""): self._pkeywords_list = [] rawpkeywords = [grabdict_package( os.path.join(x.location, "package.keywords"), recursive=x.portage1_directories, verify_eapi=True) \ for x in profiles] for pkeyworddict in rawpkeywords: if not pkeyworddict: # Omit non-existent files from the stack. continue cpdict = {} for k, v in pkeyworddict.items(): cpdict.setdefault(k.cp, {})[k] = v self._pkeywords_list.append(cpdict) self._pkeywords_list = tuple(self._pkeywords_list) self._p_accept_keywords = [] raw_p_accept_keywords = [grabdict_package( os.path.join(x.location, "package.accept_keywords"), recursive=x.portage1_directories, verify_eapi=True) \ for x in profiles] for d in raw_p_accept_keywords: if not d: # Omit non-existent files from the stack. continue cpdict = {} for k, v in d.items(): cpdict.setdefault(k.cp, {})[k] = tuple(v) self._p_accept_keywords.append(cpdict) self._p_accept_keywords = tuple(self._p_accept_keywords) self.pkeywordsdict = ExtendedAtomDict(dict) if user_config: pkgdict = grabdict_package( os.path.join(abs_user_config, "package.keywords"), recursive=1, allow_wildcard=True, allow_repo=True, verify_eapi=False) for k, v in grabdict_package( os.path.join(abs_user_config, "package.accept_keywords"), recursive=1, allow_wildcard=True, allow_repo=True, verify_eapi=False).items(): pkgdict.setdefault(k, []).extend(v) accept_keywords_defaults = global_accept_keywords.split() accept_keywords_defaults = tuple('~' + keyword for keyword in \ accept_keywords_defaults if keyword[:1] not in "~-") for k, v in pkgdict.items(): # default to ~arch if no specific keyword is given if not v: v = accept_keywords_defaults else: v = tuple(v) self.pkeywordsdict.setdefault(k.cp, {})[k] = v def getKeywords(self, cpv, slot, keywords, repo): try: cpv.slot except AttributeError: pkg = _pkg_str(cpv, slot=slot, repo=repo) else: pkg = cpv cp = pkg.cp keywords = [[x for x in keywords.split() if x != "-*"]] for pkeywords_dict in self._pkeywords_list: cpdict = pkeywords_dict.get(cp) if cpdict: pkg_keywords = ordered_by_atom_specificity(cpdict, pkg) if pkg_keywords: keywords.extend(pkg_keywords) return stack_lists(keywords, incremental=True) def isStable(self, pkg, global_accept_keywords, backuped_accept_keywords): mygroups = self.getKeywords(pkg, None, pkg._metadata["KEYWORDS"], None) pgroups = global_accept_keywords.split() unmaskgroups = self.getPKeywords(pkg, None, None, global_accept_keywords) pgroups.extend(unmaskgroups) egroups = backuped_accept_keywords.split() if unmaskgroups or egroups: pgroups = self._getEgroups(egroups, pgroups) else: pgroups = set(pgroups) if self._getMissingKeywords(pkg, pgroups, mygroups): return False if pkg.cpv._settings.local_config: # If replacing all keywords with unstable variants would mask the # package, then it's considered stable. unstable = [] for kw in mygroups: if kw[:1] != "~": kw = "~" + kw unstable.append(kw) return bool(self._getMissingKeywords(pkg, pgroups, set(unstable))) else: # For repoman, if the package has an effective stable keyword that # intersects with the effective ACCEPT_KEYWORDS for the current # profile, then consider it stable. for kw in pgroups: if kw[:1] != "~": if kw in mygroups or '*' in mygroups: return True if kw == '*': for x in mygroups: if x[:1] != "~": return True return False def getMissingKeywords(self, cpv, slot, keywords, repo, global_accept_keywords, backuped_accept_keywords): """ Take a package and return a list of any KEYWORDS that the user may need to accept for the given package. If the KEYWORDS are empty and the the ** keyword has not been accepted, the returned list will contain ** alone (in order to distinguish from the case of "none missing"). @param cpv: The package name (for package.keywords support) @type cpv: String @param slot: The 'SLOT' key from the raw package metadata @type slot: String @param keywords: The 'KEYWORDS' key from the raw package metadata @type keywords: String @param global_accept_keywords: The current value of ACCEPT_KEYWORDS @type global_accept_keywords: String @param backuped_accept_keywords: ACCEPT_KEYWORDS from the backup env @type backuped_accept_keywords: String @rtype: List @return: A list of KEYWORDS that have not been accepted. """ mygroups = self.getKeywords(cpv, slot, keywords, repo) # Repoman may modify this attribute as necessary. pgroups = global_accept_keywords.split() unmaskgroups = self.getPKeywords(cpv, slot, repo, global_accept_keywords) pgroups.extend(unmaskgroups) # Hack: Need to check the env directly here as otherwise stacking # doesn't work properly as negative values are lost in the config # object (bug #139600) egroups = backuped_accept_keywords.split() if unmaskgroups or egroups: pgroups = self._getEgroups(egroups, pgroups) else: pgroups = set(pgroups) return self._getMissingKeywords(cpv, pgroups, mygroups) def getRawMissingKeywords(self, cpv, slot, keywords, repo, global_accept_keywords): """ Take a package and return a list of any KEYWORDS that the user may need to accept for the given package. If the KEYWORDS are empty, the returned list will contain ** alone (in order to distinguish from the case of "none missing"). This DOES NOT apply any user config package.accept_keywords acceptance. @param cpv: The package name (for package.keywords support) @type cpv: String @param slot: The 'SLOT' key from the raw package metadata @type slot: String @param keywords: The 'KEYWORDS' key from the raw package metadata @type keywords: String @param global_accept_keywords: The current value of ACCEPT_KEYWORDS @type global_accept_keywords: String @rtype: List @return: lists of KEYWORDS that have not been accepted and the keywords it looked for. """ mygroups = self.getKeywords(cpv, slot, keywords, repo) pgroups = global_accept_keywords.split() pgroups = set(pgroups) return self._getMissingKeywords(cpv, pgroups, mygroups) @staticmethod def _getEgroups(egroups, mygroups): """gets any keywords defined in the environment @param backuped_accept_keywords: ACCEPT_KEYWORDS from the backup env @type backuped_accept_keywords: String @rtype: List @return: list of KEYWORDS that have been accepted """ mygroups = list(mygroups) mygroups.extend(egroups) inc_pgroups = set() for x in mygroups: if x[:1] == "-": if x == "-*": inc_pgroups.clear() else: inc_pgroups.discard(x[1:]) else: inc_pgroups.add(x) return inc_pgroups @staticmethod def _getMissingKeywords(cpv, pgroups, mygroups): """Determines the missing keywords @param pgroups: The pkg keywords accepted @type pgroups: list @param mygroups: The ebuild keywords @type mygroups: list """ match = False hasstable = False hastesting = False for gp in mygroups: if gp == "*": match = True break elif gp == "~*": hastesting = True for x in pgroups: if x[:1] == "~": match = True break if match: break elif gp in pgroups: match = True break elif gp.startswith("~"): hastesting = True elif not gp.startswith("-"): hasstable = True if not match and \ ((hastesting and "~*" in pgroups) or \ (hasstable and "*" in pgroups) or "**" in pgroups): match = True if match: missing = [] else: if not mygroups: # If KEYWORDS is empty then we still have to return something # in order to distinguish from the case of "none missing". mygroups = ["**"] missing = mygroups return missing def getPKeywords(self, cpv, slot, repo, global_accept_keywords): """Gets any package.keywords settings for cp for the given cpv, slot and repo @param cpv: The package name (for package.keywords support) @type cpv: String @param slot: The 'SLOT' key from the raw package metadata @type slot: String @param keywords: The 'KEYWORDS' key from the raw package metadata @type keywords: String @param global_accept_keywords: The current value of ACCEPT_KEYWORDS @type global_accept_keywords: String @param backuped_accept_keywords: ACCEPT_KEYWORDS from the backup env @type backuped_accept_keywords: String @rtype: List @return: list of KEYWORDS that have been accepted """ pgroups = global_accept_keywords.split() try: cpv.slot except AttributeError: cpv = _pkg_str(cpv, slot=slot, repo=repo) cp = cpv.cp unmaskgroups = [] if self._p_accept_keywords: accept_keywords_defaults = tuple('~' + keyword for keyword in \ pgroups if keyword[:1] not in "~-") for d in self._p_accept_keywords: cpdict = d.get(cp) if cpdict: pkg_accept_keywords = \ ordered_by_atom_specificity(cpdict, cpv) if pkg_accept_keywords: for x in pkg_accept_keywords: if not x: x = accept_keywords_defaults unmaskgroups.extend(x) pkgdict = self.pkeywordsdict.get(cp) if pkgdict: pkg_accept_keywords = \ ordered_by_atom_specificity(pkgdict, cpv) if pkg_accept_keywords: for x in pkg_accept_keywords: unmaskgroups.extend(x) return unmaskgroups