commit:     e1322d74de41e989f709e0129ac41e5469b9cc4c
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Sat Feb 21 03:05:12 2026 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Sat Feb 21 11:22:25 2026 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=e1322d74

Revert "depgraph: earlier slot operator backtracking"

This reverts commit 8ddd35e04bb4c50a85d7cc61edb85d91dd10ce4b.

Bug: https://bugs.gentoo.org/969654
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                            | 152 +++++++++++----------
 .../test_binpackage_downgrades_slot_dep.py         |   3 +
 lib/portage/tests/resolver/test_missed_update.py   |   3 +
 3 files changed, 86 insertions(+), 72 deletions(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 7ac07ad5e4..5d98882296 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -1937,9 +1937,16 @@ 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
@@ -2895,50 +2902,50 @@ class depgraph:
 
         return None
 
-    def _slot_operator_trigger_backtracking(self, dep: Dependency) -> bool:
+    def _slot_operator_trigger_reinstalls(self):
         """
-        Trigger backtracking for slot operator issues if needed.
-        Return True if this triggers backtracking, and False otherwise.
+        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.
         """
-        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"
         )
 
-        # 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
+        for slot_key, slot_info in 
self._dynamic_config._slot_operator_deps.items():
+            for dep in slot_info:
+                atom = dep.atom
 
-        if want_update_probe:
-            if self._slot_operator_update_probe(dep):
-                self._slot_operator_update_backtrack(dep)
-                return True
+                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
 
-        return False
+                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
+                        )
+
+                if want_update_probe:
+                    if self._slot_operator_update_probe(dep):
+                        self._slot_operator_update_backtrack(dep)
 
     def _reinstall_for_flags(
         self, pkg, forced_flags, orig_use, orig_iuse, cur_use, cur_iuse
@@ -3429,6 +3436,44 @@ 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
@@ -3587,43 +3632,6 @@ 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 b47f73dcb5..699cd1c468 100644
--- a/lib/portage/tests/resolver/test_binpackage_downgrades_slot_dep.py
+++ b/lib/portage/tests/resolver/test_binpackage_downgrades_slot_dep.py
@@ -1,6 +1,8 @@
 # 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,
@@ -9,6 +11,7 @@ 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(-)?"

diff --git a/lib/portage/tests/resolver/test_missed_update.py 
b/lib/portage/tests/resolver/test_missed_update.py
index 6813a91727..2c15a16c80 100644
--- a/lib/portage/tests/resolver/test_missed_update.py
+++ b/lib/portage/tests/resolver/test_missed_update.py
@@ -1,6 +1,8 @@
 # Copyright 2026 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,
@@ -9,6 +11,7 @@ from portage.tests.resolver.ResolverPlayground import (
 
 
 class MissedQtUpdateTestCase(TestCase):
+    @pytest.mark.xfail()
     def testMissedQtUpdate(self):
         """
         Testcase where Portage was unable to upgrade from

Reply via email to