commit: 8ddd35e04bb4c50a85d7cc61edb85d91dd10ce4b Author: Zac Medico <zmedico <AT> gentoo <DOT> org> AuthorDate: Fri Oct 24 02:21:18 2025 +0000 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> CommitDate: Sun Jan 4 02:21:43 2026 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=8ddd35e0
depgraph: earlier slot operator backtracking Trigger backtracking for slot operator issues in the _add_pkg method, allowing irrelevant dependencies to be dropped before REQUIRED_USE checks are enforced. It would not be acceptable to abort depgraph creation here, since that would not scale well for large numbers of slot operator rebuilds. Bug: https://bugs.gentoo.org/964705 Bug: https://bugs.gentoo.org/968228 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org> lib/_emerge/depgraph.py | 154 ++++++++++----------- .../test_binpackage_downgrades_slot_dep.py | 5 +- 2 files changed, 74 insertions(+), 85 deletions(-) diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py index 5ec5d802f5..b6220ba38a 100644 --- a/lib/_emerge/depgraph.py +++ b/lib/_emerge/depgraph.py @@ -1,4 +1,4 @@ -# Copyright 1999-2025 Gentoo Authors +# Copyright 1999-2026 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import errno @@ -1938,16 +1938,9 @@ class depgraph: # conflicts (or by blind luck). raise self._unknown_internal_error() - # Both _process_slot_conflict and _slot_operator_trigger_reinstalls - # can call _slot_operator_update_probe, which requires that - # self._dynamic_config._blocked_pkgs has been initialized by a - # call to the _validate_blockers method. for conflict in self._dynamic_config._package_tracker.slot_conflicts(): self._process_slot_conflict(conflict) - if self._dynamic_config._allow_backtracking: - self._slot_operator_trigger_reinstalls() - def _process_slot_conflict(self, conflict): """ Process slot conflict data to identify specific atoms which @@ -2903,50 +2896,50 @@ class depgraph: return None - def _slot_operator_trigger_reinstalls(self): + def _slot_operator_trigger_backtracking(self, dep: Dependency) -> bool: """ - Search for packages with slot-operator deps on older slots, and schedule - rebuilds if they can link to a newer slot that's in the graph. + Trigger backtracking for slot operator issues if needed. + Return True if this triggers backtracking, and False otherwise. """ + if not self._dynamic_config._allow_backtracking: + return False + + atom = dep.atom + + if not (atom.soname or atom.slot_operator_built): + new_child_slot = self._slot_change_probe(dep) + if new_child_slot is not None: + self._slot_change_backtrack(dep, new_child_slot) + return True + + if not (dep.parent and isinstance(dep.parent, Package) and dep.parent.built): + return False rebuild_if_new_slot = ( self._dynamic_config.myparams.get("rebuild_if_new_slot", "y") == "y" ) - for slot_key, slot_info in self._dynamic_config._slot_operator_deps.items(): - for dep in slot_info: - atom = dep.atom - - if not (atom.soname or atom.slot_operator_built): - new_child_slot = self._slot_change_probe(dep) - if new_child_slot is not None: - self._slot_change_backtrack(dep, new_child_slot) - continue - - if not ( - dep.parent and isinstance(dep.parent, Package) and dep.parent.built - ): - continue + # If the parent is not installed, check if it needs to be + # rebuilt against an installed instance, since otherwise + # it could trigger downgrade of an installed instance as + # in bug #652938. + want_update_probe = dep.want_update or not dep.parent.installed + + # Check for slot update first, since we don't want to + # trigger reinstall of the child package when a newer + # slot will be used instead. + if rebuild_if_new_slot and want_update_probe: + new_dep = self._slot_operator_update_probe(dep, new_child_slot=True) + if new_dep is not None: + self._slot_operator_update_backtrack(dep, new_child_slot=new_dep.child) + return True - # If the parent is not installed, check if it needs to be - # rebuilt against an installed instance, since otherwise - # it could trigger downgrade of an installed instance as - # in bug #652938. - want_update_probe = dep.want_update or not dep.parent.installed - - # Check for slot update first, since we don't want to - # trigger reinstall of the child package when a newer - # slot will be used instead. - if rebuild_if_new_slot and want_update_probe: - new_dep = self._slot_operator_update_probe(dep, new_child_slot=True) - if new_dep is not None: - self._slot_operator_update_backtrack( - dep, new_child_slot=new_dep.child - ) + if want_update_probe: + if self._slot_operator_update_probe(dep): + self._slot_operator_update_backtrack(dep) + return True - if want_update_probe: - if self._slot_operator_update_probe(dep): - self._slot_operator_update_backtrack(dep) + return False def _reinstall_for_flags( self, pkg, forced_flags, orig_use, orig_iuse, cur_use, cur_iuse @@ -3437,44 +3430,6 @@ class depgraph: raise del e - # NOTE: REQUIRED_USE checks are delayed until after - # package selection, since we want to prompt the user - # for USE adjustment rather than have REQUIRED_USE - # affect package selection and || dep choices. - if ( - not pkg.built - and pkg._metadata.get("REQUIRED_USE") - and eapi_has_required_use(pkg.eapi) - ): - required_use_is_sat = check_required_use( - pkg._metadata["REQUIRED_USE"], - self._pkg_use_enabled(pkg), - pkg.iuse.is_valid_flag, - eapi=pkg.eapi, - ) - if not required_use_is_sat: - if dep.atom is not None and dep.parent is not None: - self._add_parent_atom(pkg, (dep.parent, dep.atom)) - - if arg_atoms: - for parent_atom in arg_atoms: - parent, atom = parent_atom - self._add_parent_atom(pkg, parent_atom) - - atom = dep.atom - if atom is None: - atom = Atom("=" + pkg.cpv) - self._dynamic_config._unsatisfied_deps_for_display.append( - ((pkg.root, atom), {"myparent": dep.parent, "show_req_use": pkg}) - ) - self._dynamic_config._required_use_unsatisfied = True - self._dynamic_config._skip_restart = True - # Add pkg to digraph in order to enable autounmask messages - # for this package, which is useful when autounmask USE - # changes have violated REQUIRED_USE. - self._dynamic_config.digraph.add(pkg, dep.parent, priority=priority) - return 0 - if not pkg.onlydeps: existing_node, existing_node_matches = self._check_slot_conflict( pkg, dep.atom @@ -3633,6 +3588,43 @@ class depgraph: and (dep.atom.soname or dep.atom.slot_operator == "=") ): self._add_slot_operator_dep(dep) + if self._slot_operator_trigger_backtracking(dep): + # Drop slot operator deps that trigger backtracking, since + # they may be irrelevant and therefore we don't want to + # enforce the REQUIRED_USE check that comes below (bug 964705). + # Since backtracking has been triggered, the _need_restart flag + # is set and this depgraph is only useful for collecting + # backtracking parameters at this point, so it is acceptable to + # drop dependencies as needed. It would not be acceptable to + # abort depgraph creation here, since that would not scale well + # for large numbers of slot operator rebuilds. + return 1 + + # NOTE: REQUIRED_USE checks are delayed until after + # package selection, since we want to prompt the user + # for USE adjustment rather than have REQUIRED_USE + # affect package selection and || dep choices. + if ( + not pkg.built + and pkg._metadata.get("REQUIRED_USE") + and eapi_has_required_use(pkg.eapi) + ): + required_use_is_sat = check_required_use( + pkg._metadata["REQUIRED_USE"], + self._pkg_use_enabled(pkg), + pkg.iuse.is_valid_flag, + eapi=pkg.eapi, + ) + if not required_use_is_sat: + atom = dep.atom + if atom is None: + atom = Atom("=" + pkg.cpv) + self._dynamic_config._unsatisfied_deps_for_display.append( + ((pkg.root, atom), {"myparent": dep.parent, "show_req_use": pkg}) + ) + self._dynamic_config._required_use_unsatisfied = True + self._dynamic_config._skip_restart = True + return 0 recurse = deep is True or not self._too_deep(self._depth_increment(depth, n=1)) dep_stack = self._dynamic_config._dep_stack diff --git a/lib/portage/tests/resolver/test_binpackage_downgrades_slot_dep.py b/lib/portage/tests/resolver/test_binpackage_downgrades_slot_dep.py index ef434168dd..b47f73dcb5 100644 --- a/lib/portage/tests/resolver/test_binpackage_downgrades_slot_dep.py +++ b/lib/portage/tests/resolver/test_binpackage_downgrades_slot_dep.py @@ -1,8 +1,6 @@ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 -import pytest - from portage.tests import TestCase from portage.tests.resolver.ResolverPlayground import ( ResolverPlayground, @@ -11,7 +9,6 @@ from portage.tests.resolver.ResolverPlayground import ( class BinpackageDowngradesSlotDepTestCase(TestCase): - @pytest.mark.xfail() def testBinpackageDowngradesSlotDep(self): python_use = "python_targets_python3_12 +python_targets_python3_13" python_usedep = "python_targets_python3_12(-)?,python_targets_python3_13(-)?" @@ -92,7 +89,7 @@ class BinpackageDowngradesSlotDepTestCase(TestCase): binpkgs=binpkgs, world=world, user_config=user_config, - debug=True, + debug=False, ) settings = playground.settings
