slot collision suggestions: Avoid showing redundant suggestions.
authorSebastian Luther <SebastianLuther@gmx.de>
Mon, 31 Jan 2011 12:45:43 +0000 (13:45 +0100)
committerZac Medico <zmedico@gentoo.org>
Mon, 31 Jan 2011 21:03:14 +0000 (13:03 -0800)
http://forums.gentoo.org/viewtopic-t-862257.html?sid=9ba8646a15044fe024a41154df351c6a

pym/_emerge/resolver/slot_collision.py
pym/portage/tests/resolver/ResolverPlayground.py
pym/portage/tests/resolver/test_slot_collisions.py

index 0f4c6a53fa7cbd867d3e5e1990904b48f554f2e3..b3d816b881c0a0af459d38ffc84f21b27a6ca9d4 100644 (file)
@@ -128,9 +128,12 @@ class slot_conflict_handler(object):
 
                self._prepare_conflict_msg_and_check_for_specificity()
 
-               #a list of dicts that hold the needed USE changes to solve all conflicts
+               #a list of dicts that hold the needed USE values to solve all conflicts
                self.solutions = []
-               
+
+               #a list of dicts that hold the needed USE changes to solve all conflicts
+               self.changes = []
+
                #configuration = a list of packages with exactly one package from every
                #single slot conflict
                config_gen = _configuration_generator(conflict_pkgs)
@@ -152,6 +155,7 @@ class slot_conflict_handler(object):
 
                        if new_solutions:
                                self.solutions.extend(new_solutions)
+
                                if first_config:
                                        #If the "all ebuild"-config gives a solution, use it.
                                        #Otherwise enumerate all other solutions.
@@ -168,10 +172,63 @@ class slot_conflict_handler(object):
                                        writemsg("\nAborting search due to excessive number of configurations.\n", noiselevel=-1)
                                break
 
+               for solution in self.solutions:
+                       self._add_change(self._get_change(solution))
+
 
        def get_conflict(self):
                return "".join(self.conflict_msg)
-               
+
+       def _is_subset(self, change1, change2):
+               """
+               Checks if a set of changes 'change1' is a subset of the changes 'change2'.
+               """
+               #All pkgs of change1 have to be in change2.
+               #For every package in change1, the changes have to be a subset of
+               #the corresponding changes in change2.
+               for pkg in change1:
+                       if pkg not in change2:
+                               return False
+
+                       for flag in change1[pkg]:
+                               if flag not in change2[pkg]:
+                                       return False
+                               if change1[pkg][flag] != change2[pkg][flag]:
+                                       return False
+               return True
+
+       def _add_change(self, new_change):
+               """
+               Make sure to keep only minimal changes. If "+foo", does the job, discard "+foo -bar".
+               """
+               changes = self.changes
+               #Make sure there is no other solution that is a subset of the new solution.
+               ignore = False
+               to_be_removed = []
+               for change in changes:
+                       if self._is_subset(change, new_change):
+                               ignore = True
+                               break
+                       elif self._is_subset(new_change, change):
+                               to_be_removed.append(change)
+
+               if not ignore:
+                       #Discard all existing change that are a superset of the new change.
+                       for obsolete_change in to_be_removed:
+                               changes.remove(obsolete_change)
+                       changes.append(new_change)
+
+       def _get_change(self, solution):
+               _pkg_use_enabled = self.depgraph._pkg_use_enabled
+               new_change = {}
+               for pkg in solution:
+                       for flag, state in solution[pkg].items():
+                               if state == "enabled" and flag not in _pkg_use_enabled(pkg):
+                                       new_change.setdefault(pkg, {})[flag] = True
+                               elif state == "disabled" and flag in _pkg_use_enabled(pkg):
+                                       new_change.setdefault(pkg, {})[flag] = False
+               return new_change
+
        def _prepare_conflict_msg_and_check_for_specificity(self):
                """
                Print all slot conflicts in a human readable way.
@@ -420,26 +477,26 @@ class slot_conflict_handler(object):
                                msg += "It might be possible to solve these slot collisions\n"
                        msg += "by applying one of the following solutions:\n"
 
-               def print_solution(solution, indent=""):
+               def print_change(change, indent=""):
                        mymsg = ""
-                       for pkg in solution:
+                       for pkg in change:
                                changes = []
-                               for flag, state in solution[pkg].items():
-                                       if state == "enabled" and flag not in _pkg_use_enabled(pkg):
+                               for flag, state in change[pkg].items():
+                                       if state:
                                                changes.append(colorize("red", "+" + flag))
-                                       elif state == "disabled" and flag in _pkg_use_enabled(pkg):
+                                       else:
                                                changes.append(colorize("blue", "-" + flag))
-                               if changes:
-                                       mymsg += indent + "- " + pkg.cpv + " (Change USE: %s" % " ".join(changes) + ")\n"
+                               mymsg += indent + "- " + pkg.cpv + " (Change USE: %s" % " ".join(changes) + ")\n"
                        mymsg += "\n"
                        return mymsg
 
-               if len(solutions) == 1:
-                       msg += print_solution(solutions[0], "   ")
+
+               if len(self.changes) == 1:
+                       msg += print_change(self.changes[0], "   ")
                else:
-                       for solution in solutions:
+                       for change in self.changes:
                                msg += "  Solution: Apply all of:\n"
-                               msg += print_solution(solution, "     ")
+                               msg += print_change(change, "     ")
 
                return msg
 
index a2b715128d120285de4aa335f99b4498a2dceca7..eb591bba5002ca13f40d29a4ed30f3f2a2bcf805 100644 (file)
@@ -581,17 +581,11 @@ class ResolverPlaygroundResult(object):
                        self.slot_collision_solutions  = []
                        handler = self.depgraph._dynamic_config._slot_conflict_handler
 
-                       for solution in handler.solutions:
-                               s = {}
-                               for pkg in solution:
-                                       changes = {}
-                                       for flag, state in solution[pkg].items():
-                                               if state == "enabled":
-                                                       changes[flag] = True
-                                               else:
-                                                       changes[flag] = False
-                                       s[pkg.cpv] = changes
-                               self.slot_collision_solutions.append(s)
+                       for change in handler.changes:
+                               new_change = {}
+                               for pkg in change:
+                                       new_change[pkg.cpv] = change[pkg]
+                               self.slot_collision_solutions.append(new_change)
 
                if self.depgraph._dynamic_config._circular_dependency_handler is not None:
                        handler = self.depgraph._dynamic_config._circular_dependency_handler
index 6f61c6c31fdb71c02f3f853849d31486418c2b84..4c6a272d259fdefab777f9b6a64c6cf12af44328 100644 (file)
@@ -27,6 +27,10 @@ class SlotCollisionTestCase(TestCase):
                        "sci-libs/L-1": { "DEPEND": "sci-libs/K[-foo]", "EAPI": 2 },
                        "sci-libs/M-1": { "DEPEND": "sci-libs/K[foo=]", "IUSE": "+foo", "EAPI": 2 },
 
+                       "sci-libs/Q-1": { "SLOT": "1", "IUSE": "+bar foo", "EAPI": 1 },
+                       "sci-libs/Q-2": { "SLOT": "2", "IUSE": "+bar +foo", "EAPI": 2, "PDEPEND": "sci-libs/Q:1[bar?,foo?]" },
+                       "sci-libs/P-1": { "DEPEND": "sci-libs/Q:1[foo=]", "IUSE": "foo", "EAPI": 2 },
+
                        "app-misc/A-1": { "IUSE": "foo +bar", "REQUIRED_USE": "^^ ( foo bar )", "EAPI": "4" },
                        "app-misc/B-1": { "DEPEND": "=app-misc/A-1[foo=]", "IUSE": "foo", "EAPI": 2 },
                        "app-misc/C-1": { "DEPEND": "=app-misc/A-1[foo]", "EAPI": 2 },
@@ -42,6 +46,9 @@ class SlotCollisionTestCase(TestCase):
                        "sci-libs/K-1": { "IUSE": "foo", "USE": "" },
                        "sci-libs/L-1": { "DEPEND": "sci-libs/K[-foo]" },
 
+                       "sci-libs/Q-1": { "SLOT": "1", "IUSE": "+bar +foo", "USE": "bar foo", "EAPI": 1 },
+                       "sci-libs/Q-2": { "SLOT": "2", "IUSE": "+bar +foo", "USE": "bar foo", "EAPI": 2, "PDEPEND": "sci-libs/Q:1[bar?,foo?]" },
+
                        "app-misc/A-1": { "IUSE": "+foo bar", "USE": "foo", "REQUIRED_USE": "^^ ( foo bar )", "EAPI": "4" },
                        }
 
@@ -87,6 +94,17 @@ class SlotCollisionTestCase(TestCase):
                                slot_collision_solutions = [{"sci-libs/K-1": {"foo": False}, "sci-libs/M-1": {"foo": False}}]
                                ),
 
+                       #Avoid dupliacates.
+                       ResolverPlaygroundTestCase(
+                               ["sci-libs/P", "sci-libs/Q:2"],
+                               success = False,
+                               options = { "--update": True, "--complete-graph": True },
+                               mergelist = ["sci-libs/P-1", "sci-libs/Q-1"],
+                               ignore_mergelist_order = True,
+                               all_permutations=True,
+                               slot_collision_solutions = [{"sci-libs/Q-1": {"foo": True}, "sci-libs/P-1": {"foo": True}}]
+                               ),
+
                        #Conflict with REQUIRED_USE
                        ResolverPlaygroundTestCase(
                                ["=app-misc/C-1", "=app-misc/B-1"],