| # Copyright 2007-2011 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import sys |
| from portage.dep import Atom, ExtendedAtomDict, best_match_to_list, match_from_list |
| from portage.exception import InvalidAtom |
| from portage.versions import cpv_getkey |
| |
| if sys.hexversion >= 0x3000000: |
| basestring = str |
| |
| OPERATIONS = ["merge", "unmerge"] |
| |
| class PackageSet(object): |
| # Set this to operations that are supported by your subclass. While |
| # technically there is no difference between "merge" and "unmerge" regarding |
| # package sets, the latter doesn't make sense for some sets like "system" |
| # or "security" and therefore isn't supported by them. |
| _operations = ["merge"] |
| description = "generic package set" |
| |
| def __init__(self, allow_wildcard=False, allow_repo=False): |
| self._atoms = set() |
| self._atommap = ExtendedAtomDict(set) |
| self._loaded = False |
| self._loading = False |
| self.errors = [] |
| self._nonatoms = set() |
| self.world_candidate = False |
| self._allow_wildcard = allow_wildcard |
| self._allow_repo = allow_repo |
| |
| def __contains__(self, atom): |
| self._load() |
| return atom in self._atoms or atom in self._nonatoms |
| |
| def __iter__(self): |
| self._load() |
| for x in self._atoms: |
| yield x |
| for x in self._nonatoms: |
| yield x |
| |
| def __bool__(self): |
| self._load() |
| return bool(self._atoms or self._nonatoms) |
| |
| if sys.hexversion < 0x3000000: |
| __nonzero__ = __bool__ |
| |
| def supportsOperation(self, op): |
| if not op in OPERATIONS: |
| raise ValueError(op) |
| return op in self._operations |
| |
| def _load(self): |
| if not (self._loaded or self._loading): |
| self._loading = True |
| self.load() |
| self._loaded = True |
| self._loading = False |
| |
| def getAtoms(self): |
| self._load() |
| return self._atoms.copy() |
| |
| def getNonAtoms(self): |
| self._load() |
| return self._nonatoms.copy() |
| |
| def _setAtoms(self, atoms): |
| self._atoms.clear() |
| self._nonatoms.clear() |
| for a in atoms: |
| if not isinstance(a, Atom): |
| if isinstance(a, basestring): |
| a = a.strip() |
| if not a: |
| continue |
| try: |
| a = Atom(a, allow_wildcard=True, allow_repo=True) |
| except InvalidAtom: |
| self._nonatoms.add(a) |
| continue |
| if not self._allow_wildcard and a.extended_syntax: |
| raise InvalidAtom("extended atom syntax not allowed here") |
| if not self._allow_repo and a.repo: |
| raise InvalidAtom("repository specification not allowed here") |
| self._atoms.add(a) |
| |
| self._updateAtomMap() |
| |
| def load(self): |
| # This method must be overwritten by subclasses |
| # Editable sets should use the value of self._mtime to determine if they |
| # need to reload themselves |
| raise NotImplementedError() |
| |
| def containsCPV(self, cpv): |
| self._load() |
| for a in self._atoms: |
| if match_from_list(a, [cpv]): |
| return True |
| return False |
| |
| def getMetadata(self, key): |
| if hasattr(self, key.lower()): |
| return getattr(self, key.lower()) |
| else: |
| return "" |
| |
| def _updateAtomMap(self, atoms=None): |
| """Update self._atommap for specific atoms or all atoms.""" |
| if not atoms: |
| self._atommap.clear() |
| atoms = self._atoms |
| for a in atoms: |
| self._atommap.setdefault(a.cp, set()).add(a) |
| |
| # Not sure if this one should really be in PackageSet |
| def findAtomForPackage(self, pkg, modified_use=None): |
| """Return the best match for a given package from the arguments, or |
| None if there are no matches. This matches virtual arguments against |
| the PROVIDE metadata. This can raise an InvalidDependString exception |
| if an error occurs while parsing PROVIDE.""" |
| |
| if modified_use is not None and modified_use is not pkg.use.enabled: |
| pkg = pkg.copy() |
| pkg._metadata["USE"] = " ".join(modified_use) |
| |
| # Atoms matched via PROVIDE must be temporarily transformed since |
| # match_from_list() only works correctly when atom.cp == pkg.cp. |
| rev_transform = {} |
| for atom in self.iterAtomsForPackage(pkg): |
| if atom.cp == pkg.cp: |
| rev_transform[atom] = atom |
| else: |
| rev_transform[Atom(atom.replace(atom.cp, pkg.cp, 1), allow_wildcard=True, allow_repo=True)] = atom |
| best_match = best_match_to_list(pkg, iter(rev_transform)) |
| if best_match: |
| return rev_transform[best_match] |
| return None |
| |
| def iterAtomsForPackage(self, pkg): |
| """ |
| Find all matching atoms for a given package. This matches virtual |
| arguments against the PROVIDE metadata. This will raise an |
| InvalidDependString exception if PROVIDE is invalid. |
| """ |
| cpv_slot_list = [pkg] |
| cp = cpv_getkey(pkg.cpv) |
| self._load() # make sure the atoms are loaded |
| |
| atoms = self._atommap.get(cp) |
| if atoms: |
| for atom in atoms: |
| if match_from_list(atom, cpv_slot_list): |
| yield atom |
| provides = pkg._metadata['PROVIDE'] |
| if not provides: |
| return |
| provides = provides.split() |
| for provide in provides: |
| try: |
| provided_cp = Atom(provide).cp |
| except InvalidAtom: |
| continue |
| atoms = self._atommap.get(provided_cp) |
| if atoms: |
| for atom in atoms: |
| if match_from_list(atom.replace(provided_cp, cp), |
| cpv_slot_list): |
| yield atom |
| |
| class EditablePackageSet(PackageSet): |
| |
| def __init__(self, allow_wildcard=False, allow_repo=False): |
| super(EditablePackageSet, self).__init__(allow_wildcard=allow_wildcard, allow_repo=allow_repo) |
| |
| def update(self, atoms): |
| self._load() |
| modified = False |
| normal_atoms = [] |
| for a in atoms: |
| if not isinstance(a, Atom): |
| try: |
| a = Atom(a, allow_wildcard=True, allow_repo=True) |
| except InvalidAtom: |
| modified = True |
| self._nonatoms.add(a) |
| continue |
| if not self._allow_wildcard and a.extended_syntax: |
| raise InvalidAtom("extended atom syntax not allowed here") |
| if not self._allow_repo and a.repo: |
| raise InvalidAtom("repository specification not allowed here") |
| normal_atoms.append(a) |
| |
| if normal_atoms: |
| modified = True |
| self._atoms.update(normal_atoms) |
| self._updateAtomMap(atoms=normal_atoms) |
| if modified: |
| self.write() |
| |
| def add(self, atom): |
| self.update([atom]) |
| |
| def replace(self, atoms): |
| self._setAtoms(atoms) |
| self.write() |
| |
| def remove(self, atom): |
| self._load() |
| self._atoms.discard(atom) |
| self._nonatoms.discard(atom) |
| self._updateAtomMap() |
| self.write() |
| |
| def removePackageAtoms(self, cp): |
| self._load() |
| for a in list(self._atoms): |
| if a.cp == cp: |
| self.remove(a) |
| self.write() |
| |
| def write(self): |
| # This method must be overwritten in subclasses that should be editable |
| raise NotImplementedError() |
| |
| class InternalPackageSet(EditablePackageSet): |
| def __init__(self, initial_atoms=None, allow_wildcard=False, allow_repo=True): |
| """ |
| Repo atoms are allowed more often than not, so it makes sense for this |
| class to allow them by default. The Atom constructor and isvalidatom() |
| functions default to allow_repo=False, which is sufficient to ensure |
| that repo atoms are prohibited when necessary. |
| """ |
| super(InternalPackageSet, self).__init__(allow_wildcard=allow_wildcard, allow_repo=allow_repo) |
| if initial_atoms != None: |
| self.update(initial_atoms) |
| |
| def clear(self): |
| self._atoms.clear() |
| self._updateAtomMap() |
| |
| def load(self): |
| pass |
| |
| def write(self): |
| pass |
| |
| class DummyPackageSet(PackageSet): |
| def __init__(self, atoms=None): |
| super(DummyPackageSet, self).__init__() |
| if atoms: |
| self._setAtoms(atoms) |
| |
| def load(self): |
| pass |
| |
| def singleBuilder(cls, options, settings, trees): |
| atoms = options.get("packages", "").split() |
| return DummyPackageSet(atoms=atoms) |
| singleBuilder = classmethod(singleBuilder) |