| # Copyright 2007 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| # $Id$ |
| |
| from portage import cpv_getkey, flatten |
| from portage.dep import Atom, isvalidatom, match_from_list, \ |
| best_match_to_list, dep_getkey, use_reduce, paren_reduce |
| from portage.exception import InvalidAtom |
| |
| 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): |
| self._atoms = set() |
| self._atommap = {} |
| self._loaded = False |
| self._loading = False |
| self.errors = [] |
| self._nonatoms = set() |
| self.world_candidate = True |
| |
| 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 __nonzero__(self): |
| self._load() |
| return bool(self._atoms or self._nonatoms) |
| |
| 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) |
| except InvalidAtom: |
| self._nonatoms.add(a) |
| continue |
| 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: |
| cp = dep_getkey(a) |
| self._atommap.setdefault(cp, set()) |
| self._atommap[cp].add(a) |
| |
| # Not sure if this one should really be in PackageSet |
| def findAtomForPackage(self, pkg): |
| """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.""" |
| |
| # 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))] = atom |
| best_match = best_match_to_list(pkg, rev_transform.iterkeys()) |
| 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 |
| if not pkg.metadata["PROVIDE"]: |
| return |
| provides = flatten(use_reduce(paren_reduce(pkg.metadata["PROVIDE"]), |
| uselist=pkg.metadata["USE"].split())) |
| for provide in provides: |
| provided_cp = dep_getkey(provide) |
| 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 update(self, atoms): |
| self._load() |
| modified = False |
| normal_atoms = [] |
| for a in atoms: |
| if not isinstance(a, Atom): |
| try: |
| a = Atom(a) |
| except InvalidAtom: |
| modified = True |
| self._nonatoms.add(a) |
| continue |
| 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 dep_getkey(a) == 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): |
| super(InternalPackageSet, self).__init__() |
| if initial_atoms != None: |
| self.update(initial_atoms) |
| |
| def clear(self): |
| self._atoms.clear() |
| self._updateAtomMap() |
| |
| def load(self): |
| pass |
| |
| def write(self): |
| pass |
| |