commit:     a5078e5774932103d4ad367de4c6bf130a6da34f
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Mon Jan 15 23:10:03 2024 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Tue Jan 16 16:01:53 2024 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=a5078e57

emerge: backtrack consistently regardless of --fetchonly

Make the _accept_blocker_conflicts method always return
True when backtracking is enabled, so that backtracking
results will be identical regardless of options that
_accept_blocker_conflicts treats specially. This way,
conflicts will only be accepted when backtracking is
disabled or all backtracking tries have been exhausted.
Make --nodeps imply --backtrack=0, since backtracking
is only useful with dependencies.

Make _eliminate_rebuilds return early if there are
slot conflicts, since the merge list is invalid anyway,
and its possible that state inconsistencies could
trigger unexpected exceptions as in bug 922038. Make
the KeyError from bug 922038 a warning, and include
relevant information to help trace the inconsistency
back to the _eliminate_rebuilds method or some other
source like  _solve_non_slot_operator_slot_conflicts.

Bug: https://bugs.gentoo.org/161422
Bug: https://bugs.gentoo.org/607252
Bug: https://bugs.gentoo.org/675748
Bug: https://bugs.gentoo.org/922038
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/depgraph.py | 63 +++++++++++++++++++++++++++++++++++++++++++++----
 man/emerge.1            |  5 ++--
 2 files changed, 61 insertions(+), 7 deletions(-)

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index b859e68224..7491d970c3 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2023 Gentoo Authors
+# Copyright 1999-2024 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import errno
@@ -3674,9 +3674,19 @@ class depgraph:
         careful to obey the user's wishes if they have explicitly requested
         for a package to be rebuilt or reinstalled for some reason.
         """
-        if "empty" in self._dynamic_config.myparams:
+        # Skip for slot conflicts since the merge list is not valid
+        # anyway, and possible state inconsistencies can trigger
+        # unexpected exceptions as in bug 922038.
+        if "empty" in self._dynamic_config.myparams or any(
+            self._dynamic_config._package_tracker.slot_conflicts()
+        ):
             return False
 
+        # Track packages that we remove from the graph during
+        # this method call, in order to help trace any detected
+        # inconsistency back to this method or some other source
+        # such as _solve_non_slot_operator_slot_conflicts.
+        removed = []
         modified = False
         selective = "selective" in self._dynamic_config.myparams
         for root, atom in 
self._dynamic_config._slot_operator_replace_installed:
@@ -3777,11 +3787,45 @@ class depgraph:
                 modified = True
                 parent_atoms = []
                 for parent, parent_atom in 
self._dynamic_config._parent_atoms[pkg]:
-                    priorities = 
self._dynamic_config.digraph.nodes[pkg][1][parent][:]
+                    try:
+                        priorities = 
self._dynamic_config.digraph.nodes[pkg][1][parent][
+                            :
+                        ]
+                    except KeyError:
+                        optional_msg = " ({} previously removed from graph)"
+                        warnings.warn(
+                            f"_eliminate_rebuilds inconsistency: parent 
priorities missing for {parent} -> {pkg} edge"(
+                                optional_msg.format("parent and child")
+                                if parent in removed and pkg in removed
+                                else optional_msg.format("parent")
+                                if parent in removed
+                                else optional_msg.format("child")
+                                if pkg in removed
+                                else ""
+                            )
+                        )
+                        priorities = []
                     parent_atoms.append((parent, parent_atom, priorities))
                 child_parents = {}
                 for child in self._dynamic_config.digraph.child_nodes(pkg):
-                    priorities = 
self._dynamic_config.digraph.nodes[child][1][pkg][:]
+                    try:
+                        priorities = 
self._dynamic_config.digraph.nodes[child][1][pkg][
+                            :
+                        ]
+                    except KeyError:
+                        optional_msg = " ({} previously removed from graph)"
+                        warnings.warn(
+                            f"_eliminate_rebuilds inconsistency: parent 
priorities missing for {pkg} -> {child} edge"(
+                                optional_msg.format("parent and child")
+                                if pkg in removed and child in removed
+                                else optional_msg.format("parent")
+                                if pkg in removed
+                                else optional_msg.format("child")
+                                if child in removed
+                                else ""
+                            )
+                        )
+                        priorities = []
                     child_parents[child] = (
                         [
                             atom
@@ -3793,6 +3837,7 @@ class depgraph:
                         priorities,
                     )
                 self._remove_pkg(pkg, remove_orphans=False)
+                removed.append(pkg)
                 for parent, atom, priorities in parent_atoms:
                     self._add_parent_atom(installed_instance, (parent, atom))
                     for priority in priorities:
@@ -8881,6 +8926,13 @@ class depgraph:
         return True
 
     def _accept_blocker_conflicts(self):
+        """
+        Always returns False when backtracking is enabled, for
+        consistent results. When backtracking is disabled, returns
+        True for options that tolerate conflicts.
+        """
+        if self._dynamic_config._allow_backtracking:
+            return False
         acceptable = False
         for x in ("--buildpkgonly", "--fetchonly", "--fetch-all-uri", 
"--nodeps"):
             if x in self._frozen_config.myopts:
@@ -11752,7 +11804,8 @@ def _backtrack_depgraph(
 ) -> tuple[Any, depgraph, list[str], int, int]:
     debug = "--debug" in myopts
     mydepgraph = None
-    max_retries = myopts.get("--backtrack", 20)
+    nodeps = "--nodeps" in myopts
+    max_retries = 0 if nodeps else myopts.get("--backtrack", 20)
     max_depth = max(1, (max_retries + 1) // 2)
     allow_backtracking = max_retries > 0
     backtracker = Backtracker(max_depth)

diff --git a/man/emerge.1 b/man/emerge.1
index e18bc9e257..43dc3f26b1 100644
--- a/man/emerge.1
+++ b/man/emerge.1
@@ -1,4 +1,4 @@
-.TH "EMERGE" "1" "Mar 2023" "Portage @VERSION@" "Portage"
+.TH "EMERGE" "1" "Jan 2024" "Portage @VERSION@" "Portage"
 .SH "NAME"
 emerge \- Command\-line interface to the Portage system
 .SH "SYNOPSIS"
@@ -747,7 +747,8 @@ file to always be merged.
 .TP
 .BR \-\-nodeps ", " \-O
 Merges specified packages without merging any dependencies.  Note that
-the build may fail if the dependencies aren't satisfied.
+the build may fail if the dependencies aren't satisfied. This option
+implies \fB--backtrack=0\fR.
 .TP
 .BR \-\-noreplace ", " \-n
 Skips the packages specified on the command\-line that have already

Reply via email to