From f7d83d75c6b05a16ef07473917082dbd0cd9955c Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Sat, 25 Jan 2020 17:44:14 -0800 Subject: dep_zapdeps: adjust || preference for slot upgrades (bug 706278) Prefer choices that include a slot upgrade when appropriate, like for the || ( llvm:10 ... llvm:7 ) case reported in bug 706278. In order to avoid pulling in inappropriate slot upgrades, like those which should only be pulled in with --update and --deep, add a want_update flag to each choice which is True for choices that pull in a new slot for which an update is desirable. Mark the test case for bug 480736 as todo, since the "undesirable" slot upgrade which triggers a blocker conflict in this test case is practically indistinguishable from a desirable slot upgrade. This particular blocker conflict is no longer relevant, since current versions of media-libs/libpostproc are no longer compatible with any available media-video/ffmpeg slot. In order to solve this test case, some fancy backtracking (like for bug 382421) will be required. Bug: https://bugs.gentoo.org/706278 Bug: https://bugs.gentoo.org/480736 Signed-off-by: Zac Medico --- lib/_emerge/depgraph.py | 16 ++++- lib/portage/dep/dep_check.py | 79 ++++++++++++---------- lib/portage/tests/resolver/test_or_choices.py | 9 +++ .../tests/resolver/test_or_upgrade_installed.py | 3 +- 4 files changed, 67 insertions(+), 40 deletions(-) diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py index bf8882774..cae1c4470 100644 --- a/lib/_emerge/depgraph.py +++ b/lib/_emerge/depgraph.py @@ -95,6 +95,14 @@ if sys.hexversion >= 0x3000000: else: _unicode = unicode +# Exposes a depgraph interface to dep_check. +_dep_check_graph_interface = collections.namedtuple('_dep_check_graph_interface',( + # Indicates a removal action, like depclean or prune. + 'removal_action', + # Checks if update is desirable for a given package. + 'want_update_pkg', +)) + class _scheduler_graph_config(object): def __init__(self, trees, pkg_cache, graph, mergelist): self.trees = trees @@ -510,6 +518,10 @@ class _dynamic_depgraph_config(object): soname_deps=depgraph._frozen_config.soname_deps_enabled) # Track missed updates caused by solved conflicts. self._conflict_missed_update = collections.defaultdict(dict) + dep_check_iface = _dep_check_graph_interface( + removal_action="remove" in myparams, + want_update_pkg=depgraph._want_update_pkg, + ) for myroot in depgraph._frozen_config.trees: self.sets[myroot] = _depgraph_sets() @@ -530,7 +542,7 @@ class _dynamic_depgraph_config(object): self._graph_trees[myroot]["vartree"] = graph_tree self._graph_trees[myroot]["graph_db"] = graph_tree.dbapi self._graph_trees[myroot]["graph"] = self.digraph - self._graph_trees[myroot]["want_update_pkg"] = depgraph._want_update_pkg + self._graph_trees[myroot]["graph_interface"] = dep_check_iface self._graph_trees[myroot]["downgrade_probe"] = depgraph._downgrade_probe def filtered_tree(): pass @@ -558,7 +570,7 @@ class _dynamic_depgraph_config(object): self._filtered_trees[myroot]["graph"] = self.digraph self._filtered_trees[myroot]["vartree"] = \ depgraph._frozen_config.trees[myroot]["vartree"] - self._filtered_trees[myroot]["want_update_pkg"] = depgraph._want_update_pkg + self._filtered_trees[myroot]["graph_interface"] = dep_check_iface self._filtered_trees[myroot]["downgrade_probe"] = depgraph._downgrade_probe dbs = [] diff --git a/lib/portage/dep/dep_check.py b/lib/portage/dep/dep_check.py index 321d961dd..a7ae2cfa4 100644 --- a/lib/portage/dep/dep_check.py +++ b/lib/portage/dep/dep_check.py @@ -296,7 +296,7 @@ def dep_eval(deplist): class _dep_choice(SlotObject): __slots__ = ('atoms', 'slot_map', 'cp_map', 'all_available', - 'all_installed_slots', 'new_slot_count') + 'all_installed_slots', 'new_slot_count', 'want_update', 'all_in_graph') def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, minimize_slots=False): @@ -331,9 +331,9 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, # c) contains masked installed packages # d) is the first item - preferred_installed = [] preferred_in_graph = [] - preferred_any_slot = [] + preferred_installed = preferred_in_graph + preferred_any_slot = preferred_in_graph preferred_non_installed = [] unsat_use_in_graph = [] unsat_use_installed = [] @@ -347,8 +347,6 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, # for correct ordering in cases like || ( foo[a] foo[b] ). choice_bins = ( preferred_in_graph, - preferred_installed, - preferred_any_slot, preferred_non_installed, unsat_use_in_graph, unsat_use_installed, @@ -365,7 +363,7 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, graph_db = trees[myroot].get("graph_db") graph = trees[myroot].get("graph") pkg_use_enabled = trees[myroot].get("pkg_use_enabled") - want_update_pkg = trees[myroot].get("want_update_pkg") + graph_interface = trees[myroot].get("graph_interface") downgrade_probe = trees[myroot].get("downgrade_probe") circular_dependency = trees[myroot].get("circular_dependency") vardb = None @@ -506,14 +504,24 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, if current_higher or (all_match_current and not all_match_previous): cp_map[avail_pkg.cp] = avail_pkg - new_slot_count = (len(slot_map) if graph_db is None else - sum(not graph_db.match_pkgs(slot_atom) for slot_atom in slot_map - if not slot_atom.cp.startswith("virtual/"))) + want_update = False + if graph_interface is None or graph_interface.removal_action: + new_slot_count = len(slot_map) + else: + new_slot_count = 0 + for slot_atom, avail_pkg in slot_map.items(): + if graph_interface.want_update_pkg(parent, avail_pkg): + want_update = True + if (not slot_atom.cp.startswith("virtual/") + and not graph_db.match_pkgs(slot_atom)): + new_slot_count += 1 this_choice = _dep_choice(atoms=atoms, slot_map=slot_map, cp_map=cp_map, all_available=all_available, all_installed_slots=False, - new_slot_count=new_slot_count) + new_slot_count=new_slot_count, + all_in_graph=False, + want_update=want_update) if all_available: # The "all installed" criterion is not version or slot specific. # If any version of a package is already in the graph then we @@ -567,6 +575,8 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, graph_db.match_pkgs(atom)): all_in_graph = False break + this_choice.all_in_graph = all_in_graph + circular_atom = None if not (parent is None or priority is None) and \ (parent.onlydeps or @@ -607,27 +617,8 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, elif all_installed: if all_installed_slots: preferred_installed.append(this_choice) - elif parent is None or want_update_pkg is None: - preferred_any_slot.append(this_choice) else: - # When appropriate, prefer a slot that is not - # installed yet for bug #478188. - want_update = True - for slot_atom, avail_pkg in slot_map.items(): - if avail_pkg in graph: - continue - # New-style virtuals have zero cost to install. - if slot_atom.startswith("virtual/") or \ - vardb.match(slot_atom): - continue - if not want_update_pkg(parent, avail_pkg): - want_update = False - break - - if want_update: - preferred_installed.append(this_choice) - else: - preferred_any_slot.append(this_choice) + preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: @@ -676,10 +667,6 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, if len(choices) < 2: continue - sort_keys = [] - # Prefer choices with all_installed_slots for bug #480736. - sort_keys.append(lambda x: not x.all_installed_slots) - if minimize_slots: # Prefer choices having fewer new slots. When used with DNF form, # this can eliminate unecessary packages that depclean would @@ -694,15 +681,35 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, # contribute to outcomes that appear to be random. Meanwhile, # the order specified in the ebuild is without variance, so it # does not have this problem. - sort_keys.append(lambda x: x.new_slot_count) + choices.sort(key=operator.attrgetter('new_slot_count')) - choices.sort(key=lambda x: tuple(f(x) for f in sort_keys)) for choice_1 in choices[1:]: cps = set(choice_1.cp_map) for choice_2 in choices: if choice_1 is choice_2: # choice_1 will not be promoted, so move on break + if ( + # For removal actions, prefer choices where all packages + # have been pulled into the graph. + (graph_interface and graph_interface.removal_action and + choice_1.all_in_graph and not choice_2.all_in_graph) + + # Prefer choices where all_installed_slots is True, except + # in cases where we want to upgrade to a new slot as in + # bug 706278. Don't compare new_slot_count here since that + # would aggressively override the preference order defined + # in the ebuild, breaking the test case for bug 645002. + or (choice_1.all_installed_slots and + not choice_2.all_installed_slots and + not choice_2.want_update) + ): + # promote choice_1 in front of choice_2 + choices.remove(choice_1) + index_2 = choices.index(choice_2) + choices.insert(index_2, choice_1) + break + intersecting_cps = cps.intersection(choice_2.cp_map) if not intersecting_cps: continue diff --git a/lib/portage/tests/resolver/test_or_choices.py b/lib/portage/tests/resolver/test_or_choices.py index c0316bfb3..a50ad0151 100644 --- a/lib/portage/tests/resolver/test_or_choices.py +++ b/lib/portage/tests/resolver/test_or_choices.py @@ -288,6 +288,15 @@ class OrChoicesTestCase(TestCase): class OrChoicesLibpostprocTestCase(TestCase): def testOrChoicesLibpostproc(self): + # This test case is expected to fail after the fix for bug 706278, + # since the "undesirable" slot upgrade which triggers a blocker conflict + # in this test case is practically indistinguishable from a desirable + # slot upgrade. This particular blocker conflict is no longer relevant, + # since current versions of media-libs/libpostproc are no longer + # compatible with any available media-video/ffmpeg slot. In order to + # solve this test case, some fancy backtracking (like for bug 382421) + # will be required. + self.todo = True ebuilds = { "media-video/ffmpeg-0.10" : { diff --git a/lib/portage/tests/resolver/test_or_upgrade_installed.py b/lib/portage/tests/resolver/test_or_upgrade_installed.py index c3efebf55..3889d53dc 100644 --- a/lib/portage/tests/resolver/test_or_upgrade_installed.py +++ b/lib/portage/tests/resolver/test_or_upgrade_installed.py @@ -213,8 +213,7 @@ class OrUpgradeInstalledTestCase(TestCase): ['@world'], options={'--update': True, '--deep': True}, success=True, - mergelist=[], - #mergelist=['sys-devel/llvm-9'], + mergelist=['sys-devel/llvm-9', 'media-libs/mesa-19.2.8'], ), ) -- cgit v1.2.3-18-g5258