---
 pym/_emerge/resolver/package_tracker.py | 310 ++++++++++++++++++++++++++++++++
 1 file changed, 310 insertions(+)
 create mode 100644 pym/_emerge/resolver/package_tracker.py

diff --git a/pym/_emerge/resolver/package_tracker.py 
b/pym/_emerge/resolver/package_tracker.py
new file mode 100644
index 0000000..4aee4ea
--- /dev/null
+++ b/pym/_emerge/resolver/package_tracker.py
@@ -0,0 +1,310 @@
+# Copyright 2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+from __future__ import print_function
+
+import collections
+
+import portage
+portage.proxy.lazyimport.lazyimport(globals(),
+       'portage:OrderedDict',
+       'portage.dep:Atom,match_from_list',
+       'portage.util:cmp_sort_key',
+       'portage.versions:vercmp',
+)
+
+_PackageConflict = collections.namedtuple("_PackageConflict", ["root", "pkgs", 
"atom", "description"])
+
+class PackageConflict(_PackageConflict):
+       """
+       Class to track the reason for a conflict and the conflicting packages.
+       """
+       def __iter__(self):
+               return iter(self.pkgs)
+
+       def __contains__(self, pkg):
+               return pkg in self.pkgs
+
+       def __len__(self):
+               return len(self.pkgs)
+
+
+class PackageTracker(object):
+       """
+       This class tracks packages which are currently
+       installed and packages which have been pulled into
+       the dependency graph.
+
+       It automatically tracks conflicts between packages.
+
+       Possible conflicts:
+               1) Packages that share the same SLOT.
+               2) Packages with the same cpv.
+               Not yet implemented:
+               3) Packages that block each other.
+       """
+
+       def __init__(self):
+               # Mapping from package keys to set of packages.
+               self._cp_pkg_map = collections.defaultdict(list)
+               self._cp_vdb_pkg_map = collections.defaultdict(list)
+               # List of package keys that may contain conflicts.
+               # The insetation order must be preserved.
+               self._multi_pkgs = []
+
+               # Cache for result of conflicts().
+               self._conflicts_cache = None
+
+               # Records for each pulled package which installed package
+               # are replaced.
+               self._replacing = collections.defaultdict(list)
+               # Records which pulled packages replace this package.
+               self._replaced_by = collections.defaultdict(list)
+
+               self._match_cache = collections.defaultdict(dict)
+
+       def add_pkg(self, pkg):
+               """
+               Add a new package to the tracker. Records conflicts as 
necessary.
+               """
+               cp_key = pkg.root, pkg.cp
+
+               try:
+                       if any(other is pkg for other in 
self._cp_pkg_map[cp_key]):
+                               return
+               except KeyError:
+                       self._cp_pkg_map[cp_key] = [pkg]
+               else:
+                       self._cp_pkg_map[cp_key].append(pkg)
+                       if len(self._cp_pkg_map[cp_key]) == 2:
+                               self._multi_pkgs.append(cp_key)
+                       self._conflicts_cache = None
+
+               self._replacing[pkg] = []
+               for installed in self._cp_vdb_pkg_map[cp_key]:
+                       if installed.slot_atom == pkg.slot_atom or \
+                               installed.cpv == pkg.cpv:
+                               self._replacing[pkg].append(installed)
+                               self._replaced_by[installed].append(pkg)
+
+               self._match_cache.pop((pkg.root, pkg.cp))
+
+       def add_installed_pkg(self, installed):
+               """
+               Add an installed package during vdb load. These packages
+               are not returned by matched_pull as long as add_pkg hasn't
+               been called with them. They are only returned by match_final.
+               """
+               cp_key = installed.root, installed.cp
+               try:
+                       if any(other is installed for other in 
self._cp_vdb_pkg_map[cp_key]):
+                               return
+               except KeyError:
+                       self._cp_vdb_pkg_map[cp_key] = [installed]
+               else:
+                       self._cp_vdb_pkg_map[cp_key].append(installed)
+
+               for pkg in self._cp_pkg_map[cp_key]:
+                       if installed.slot_atom == pkg.slot_atom or \
+                               installed.cpv == pkg.cpv:
+                               self._replacing[pkg].append(installed)
+                               self._replaced_by[installed].append(pkg)
+
+       def remove_pkg(self, pkg):
+               """
+               Removes the package from the tracker.
+               Raises KeyError if it isn't present.
+               """
+               cp_key = pkg.root, pkg.cp
+               try:
+                       self._cp_pkg_map[cp_key].remove(pkg)
+               except ValueError:
+                       raise KeyError
+
+               if self._cp_pkg_map[cp_key]:
+                       self._conflicts_cache = None
+
+               if not self._cp_pkg_map[cp_key]:
+                       del self._cp_pkg_map[cp_key]
+               elif len(self._cp_pkg_map[cp_key]) == 1:
+                       self._multi_pkgs = [other_cp_key for other_cp_key in 
self._multi_pkgs \
+                       if other_cp_key != cp_key]
+
+               for installed in self._replacing[pkg]:
+                       self._replaced_by[installed].remove(pkg)
+                       if not self._replaced_by[installed]:
+                               del self._replaced_by[installed]
+               del self._replacing[pkg]
+
+               self._match_cache.pop((pkg.root, pkg.cp))
+
+       def discard_pkg(self, pkg):
+               """
+               Removes the package from the tracker.
+               Does not raises KeyError if it is not present.
+               """
+               try:
+                       self.remove_pkg(pkg)
+               except KeyError:
+                       pass
+
+       def match(self, root, atom, installed=True):
+               """
+               Iterates over the packages matching 'atom'.
+               If 'installed' is True, installed non-replaced
+               packages may also be returned.
+               """
+               cache_key = (root, atom, installed)
+               try:
+                       return iter(self._match_cache[(root, 
atom.cp)][cache_key])
+               except KeyError:
+                       pass
+
+               cp_key = root, atom.cp
+               try:
+                       candidates = self._cp_pkg_map[cp_key][:]
+               except KeyError:
+                       candidates = []
+
+               if installed:
+                       try:
+                               for installed in self._cp_vdb_pkg_map[cp_key]:
+                                       if installed not in self._replaced_by:
+                                               candidates.append(installed)
+                       except KeyError:
+                               pass
+
+               ret = match_from_list(atom, candidates)
+               ret.sort(key=cmp_sort_key(lambda x, y: vercmp(x.version, 
y.version)))
+               self._match_cache[(root, atom.cp)][cache_key] = ret
+
+               return iter(ret)
+
+       def conflicts(self):
+               """
+               Iterates over the curently existing conflicts.
+               """
+               if self._conflicts_cache is None:
+                       self._conflicts_cache = []
+
+                       for cp_key in self._multi_pkgs:
+
+                               # Categorize packages according to cpv and slot.
+                               slot_map = collections.defaultdict(set)
+                               cpv_map = collections.defaultdict(set)
+                               for pkg in self._cp_pkg_map[cp_key]:
+                                       slot_key = pkg.root, pkg.slot_atom
+                                       cpv_key = pkg.root, pkg.cpv
+                                       slot_map[slot_key].add(pkg)
+                                       cpv_map[cpv_key].add(pkg)
+
+                               # Slot conflicts.
+                               for slot_key in slot_map:
+                                       slot_pkgs = slot_map[slot_key]
+                                       if len(slot_pkgs) > 1:
+                                               
self._conflicts_cache.append(PackageConflict(
+                                                       description = "slot 
conflict",
+                                                       root = slot_key[0],
+                                                       atom = slot_key[1],
+                                                       pkgs = tuple(slot_pkgs),
+                                                       ))
+
+                               # CPV conflicts.
+                               for cpv_key in cpv_map:
+                                       cpv_pkgs = cpv_map[cpv_key]
+                                       if len(cpv_pkgs) > 1:
+                                               # Make sure this cpv conflict 
is not a slot conflict at the same time.
+                                               # Ignore it if it is.
+                                               slots = set(pkg.slot for pkg in 
cpv_pkgs)
+                                               if len(slots) > 1:
+                                                       
self._conflicts_cache.append(PackageConflict(
+                                                               description = 
"cpv conflict",
+                                                               root = 
cpv_pkgs[0],
+                                                               atom = 
cpv_pkgs[1],
+                                                               pkgs = 
tuple(cpv_pkgs),
+                                                               ))
+
+               return iter(self._conflicts_cache)
+
+       def slot_conflicts(self):
+               """
+               Iterates over present slot conflicts.
+               This is only intended for consumers that haven't been
+               updated to deal with other kinds of conflicts.
+               This funcion should be removed once all consumers are updated.
+               """
+               return (conflict for conflict in self.conflicts() \
+                       if conflict.description == "slot conflict")
+
+       def all_pkgs(self, root):
+               """
+               Iterates over all packages for the given root
+               present in the tracker, including the installed
+               packages.
+               """
+               for cp_key in self._cp_pkg_map:
+                       if cp_key[0] == root:
+                               for pkg in self._cp_pkg_map[cp_key]:
+                                       yield pkg
+
+               for cp_key in self._cp_vdb_pkg_map:
+                       if cp_key[0] == root:
+                               for installed in self._cp_vdb_pkg_map[cp_key]:
+                                       if installed not in self._replaced_by:
+                                               yield installed
+
+       def contains(self, pkg, installed=True):
+               """
+               Checks if the package is in the tracker.
+               If 'installed' is True, returns True for
+               non-replaced installed packages.
+               """
+               cp_key = pkg.root, pkg.cp
+               for other in self._cp_pkg_map[cp_key]:
+                       if other is pkg:
+                               return True
+
+               if installed:
+                       for installed in self._cp_vdb_pkg_map[cp_key]:
+                               if installed is pkg and \
+                                       installed not in self._replaced_by:
+                                       return True
+
+               return False
+
+       def __contains__(self, pkg):
+               """
+               Checks if the package is in the tracker.
+               Returns True for non-replaced installed packages.
+               """
+               return self.contains(pkg, installed=True)
+
+
+class PackageTrackerDbapiWrapper(object):
+       """
+       A wrpper class that provides parts of the legacy
+       dbapi interface. Remove it once all consumers have
+       died.
+       """
+       def __init__(self, root, package_tracker):
+               self._root = root
+               self._package_tracker = package_tracker
+
+       def cpv_inject(self, pkg):
+               self._package_tracker.add_pkg(pkg)
+
+       def match_pkgs(self, atom):
+               if not isinstance(atom, Atom):
+                       atom = Atom(atom)
+               ret = sorted(self._package_tracker.match(self._root, atom),
+                       key=cmp_sort_key(lambda x, y: vercmp(x.version, 
y.version)))
+               return ret
+
+       def __iter__(self):
+               return self._package_tracker.all_pkgs(self._root)
+
+       def match(self, atom, use_cache=None):
+               return self.match_pkgs(atom)
+
+       def cp_list(self, cp):
+               return self.match_pkgs(cp)
-- 
1.8.3.2


Reply via email to