aboutsummaryrefslogtreecommitdiffstats
path: root/lib/_emerge/depgraph.py
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2023-10-12 11:05:29 -0700
committerZac Medico <zmedico@gentoo.org>2023-10-12 11:37:26 -0700
commit4978b913df39abbb3369739e370856cf8292a203 (patch)
tree42404570c6417b0c61ec83e6fd661980ce2d8749 /lib/_emerge/depgraph.py
parentemerge: Increase default number of maximum backtrack attempts from 10 to 20 (diff)
downloadgentoo-portage-4978b913df39abbb3369739e370856cf8292a203.tar.xz
gentoo-portage-4978b913df39abbb3369739e370856cf8292a203.zip
Detect and handle unnecessary package reinstall
Compare package rebuilds/reinstalls to installed packages of the same exact version, and eliminate unecessary rebuilds/reinstalls triggered solely by the @__auto_slot_operator_replace_installed__ set. This is careful to obey the user's wishes if they have explicitly requested for a package to be rebuilt or reinstalled for some reason. The SonameSkipUpdateTestCase::testSonameSkipUpdateNoPruneRebuilds test case shows that the new depgraph._eliminate_rebuilds method eliminates a backtracking run that is needed for the testSonameSkipUpdate test case to succeed via prune_rebuilds backtracking which was added for bug 439688. Bug: https://bugs.gentoo.org/915494 Signed-off-by: Zac Medico <zmedico@gentoo.org>
Diffstat (limited to 'lib/_emerge/depgraph.py')
-rw-r--r--lib/_emerge/depgraph.py206
1 files changed, 176 insertions, 30 deletions
diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index ad835ac06a05..4e4452dad160 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -53,6 +53,7 @@ from portage.package.ebuild.config import _get_feature_flags
from portage.package.ebuild.getmaskingstatus import _getmaskingstatus, _MaskReason
from portage._sets import SETPREFIX
from portage._sets.base import InternalPackageSet
+from portage.dep._slot_operator import evaluate_slot_operator_equal_deps
from portage.util import ConfigProtect, shlex_split, new_protect_filename
from portage.util import cmp_sort_key, writemsg, writemsg_stdout
from portage.util import ensure_dirs, normalize_path
@@ -448,6 +449,8 @@ class _dynamic_depgraph_config:
"""
+ _ENABLE_PRUNE_REBUILDS = True
+
def __init__(self, depgraph, myparams, allow_backtracking, backtrack_parameters):
self.myparams = myparams.copy()
self._vdb_loaded = False
@@ -459,7 +462,7 @@ class _dynamic_depgraph_config:
self._filtered_trees = {}
# Contains installed packages and new packages that have been added
# to the graph.
- self._graph_trees = {}
+ self._graph_trees = portage._trees_dict()
# Caches visible packages returned from _select_package, for use in
# depgraph._iter_atoms_for_pkg() SLOT logic.
self._visible_pkgs = {}
@@ -570,6 +573,12 @@ class _dynamic_depgraph_config:
graph_tree.dbapi = fakedb
self._graph_trees[myroot] = {}
+ self._graph_trees._running_eroot = (
+ depgraph._frozen_config._trees_orig._running_eroot
+ )
+ self._graph_trees._target_eroot = (
+ depgraph._frozen_config._trees_orig._target_eroot
+ )
self._filtered_trees[myroot] = {}
# Substitute the graph tree for the vartree in dep_check() since we
# want atom selections to be consistent with package selections
@@ -3588,7 +3597,148 @@ class depgraph:
return False
return True
- def _remove_pkg(self, pkg):
+ def _eliminate_rebuilds(self):
+ """
+ Compare package rebuilds/reinstalls to installed packages of the same
+ exact version, and eliminate unnecessary rebuilds/reinstalls triggered
+ solely by the @__auto_slot_operator_replace_installed__ set. This is
+ careful to obey the user's wishes if they have explicitly requested
+ for a package to be rebuilt or reinstalled for some reason.
+ """
+ modified = False
+ selective = "selective" in self._dynamic_config.myparams
+ for root, atom in self._dynamic_config._slot_operator_replace_installed:
+ for pkg in self._dynamic_config._package_tracker.match(
+ root, atom, installed=False
+ ):
+ installed_instance = self._frozen_config.trees[root][
+ "vartree"
+ ].dbapi.match_pkgs(pkg.slot_atom)
+ if not installed_instance:
+ continue
+ installed_instance = installed_instance[0]
+ if installed_instance.cpv != pkg.cpv:
+ continue
+ if pkg in self._dynamic_config._reinstall_nodes:
+ # --newuse, --changed-use
+ continue
+
+ if self._dynamic_config.myparams.get("changed_slot") and (
+ self._changed_slot(pkg) or self._changed_slot(installed_instance)
+ ):
+ continue
+
+ unsatisfied_parent = any(
+ not atom.match(installed_instance)
+ for parent, atom in self._dynamic_config._parent_atoms[pkg]
+ )
+ if unsatisfied_parent:
+ continue
+
+ have_arg = False
+ if not selective:
+ for parent, atom in self._dynamic_config._parent_atoms[pkg]:
+ if isinstance(parent, AtomArg):
+ have_arg = True
+ break
+ elif (
+ isinstance(parent, SetArg)
+ and parent.name
+ != "__auto_slot_operator_replace_installed__"
+ ):
+ have_arg = True
+ break
+ if have_arg:
+ continue
+ if (installed_instance.slot, installed_instance.sub_slot) != (
+ pkg.slot,
+ pkg.sub_slot,
+ ):
+ continue
+ if pkg.built:
+ if pkg.provides != installed_instance.provides:
+ continue
+ if pkg.requires != installed_instance.requires:
+ continue
+
+ depvars = Package._dep_keys
+ try:
+ installed_deps = []
+ for k in depvars:
+ dep_struct = portage.dep.use_reduce(
+ installed_instance._raw_metadata[k],
+ uselist=pkg.use.enabled,
+ eapi=pkg.eapi,
+ token_class=Atom,
+ )
+ installed_deps.append(dep_struct)
+ except InvalidDependString:
+ continue
+ else:
+ new_deps = []
+ new_use = self._pkg_use_enabled(pkg)
+ if pkg.built:
+ pkg_metadata = pkg._raw_metadata
+ else:
+ pkgsettings = self._frozen_config.pkgsettings[root]
+ pkgsettings.setcpv(pkg)
+ evaluate_slot_operator_equal_deps(
+ pkgsettings, new_use, self._dynamic_config._graph_trees
+ )
+ pkg_metadata = pkgsettings.configdict["pkg"]
+
+ for k in depvars:
+ dep_struct = portage.dep.use_reduce(
+ pkg_metadata[k],
+ uselist=new_use,
+ eapi=pkg.eapi,
+ token_class=Atom,
+ )
+ new_deps.append(dep_struct)
+
+ if new_deps != installed_deps:
+ continue
+
+ modified = True
+ parent_atoms = []
+ for parent, parent_atom in self._dynamic_config._parent_atoms[pkg]:
+ priorities = self._dynamic_config.digraph.nodes[pkg][1][parent][:]
+ 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][:]
+ child_parents[child] = (
+ [
+ atom
+ for parent, atom in self._dynamic_config._parent_atoms[
+ child
+ ]
+ if parent is pkg
+ ],
+ priorities,
+ )
+ self._remove_pkg(pkg, remove_orphans=False)
+ for parent, atom, priorities in parent_atoms:
+ self._add_parent_atom(installed_instance, (parent, atom))
+ for priority in priorities:
+ self._dynamic_config.digraph.add(
+ installed_instance,
+ parent,
+ priority=priority,
+ )
+ for child, (atoms, priorities) in child_parents.items():
+ for child_atom in atoms:
+ self._add_parent_atom(child, (installed_instance, child_atom))
+ for priority in priorities:
+ self._dynamic_config.digraph.add(
+ child,
+ installed_instance,
+ priority=priority,
+ )
+
+ return modified
+
+ def _remove_pkg(self, pkg, remove_orphans=True):
"""
Remove a package and all its then parentless digraph
children from all depgraph datastructures.
@@ -3643,12 +3793,13 @@ class depgraph:
self._dynamic_config._blocked_pkgs.discard(pkg)
self._dynamic_config._blocked_world_pkgs.pop(pkg, None)
- for child in children:
- if (
- child in self._dynamic_config.digraph
- and not self._dynamic_config.digraph.parent_nodes(child)
- ):
- self._remove_pkg(child)
+ if remove_orphans:
+ for child in children:
+ if (
+ child in self._dynamic_config.digraph
+ and not self._dynamic_config.digraph.parent_nodes(child)
+ ):
+ self._remove_pkg(child)
# Clear caches.
self._dynamic_config._filtered_trees[pkg.root]["porttree"].dbapi._clear_cache()
@@ -5173,25 +5324,6 @@ class depgraph:
):
return False, myfavorites
- if (
- not self._dynamic_config._prune_rebuilds
- and self._dynamic_config._slot_operator_replace_installed
- and self._get_missed_updates()
- ):
- # When there are missed updates, we might have triggered
- # some unnecessary rebuilds (see bug #439688). So, prune
- # all the rebuilds and backtrack with the problematic
- # updates masked. The next backtrack run should pull in
- # any rebuilds that are really needed, and this
- # prune_rebuilds path should never be entered more than
- # once in a series of backtracking nodes (in order to
- # avoid a backtracking loop).
- backtrack_infos = self._dynamic_config._backtrack_infos
- config = backtrack_infos.setdefault("config", {})
- config["prune_rebuilds"] = True
- self._dynamic_config._need_restart = True
- return False, myfavorites
-
if self.need_restart():
# want_restart_for_use_change triggers this
return False, myfavorites
@@ -5243,15 +5375,29 @@ class depgraph:
self._dynamic_config._skip_restart = True
return False, myfavorites
- if (
- not self._dynamic_config._prune_rebuilds
- and self._ignored_binaries_autounmask_backtrack()
+ if not self._dynamic_config._prune_rebuilds and (
+ self._ignored_binaries_autounmask_backtrack()
+ or (
+ self._dynamic_config._ENABLE_PRUNE_REBUILDS
+ and self._dynamic_config._slot_operator_replace_installed
+ and self._get_missed_updates()
+ )
):
config = self._dynamic_config._backtrack_infos.setdefault("config", {})
config["prune_rebuilds"] = True
self._dynamic_config._need_restart = True
return False, myfavorites
+ if (
+ self._dynamic_config._slot_operator_replace_installed
+ and self._eliminate_rebuilds()
+ ):
+ self._dynamic_config._serialized_tasks_cache = None
+ try:
+ self.altlist()
+ except self._unknown_internal_error:
+ return False, myfavorites
+
# Any failures except those due to autounmask *alone* should return
# before this point, since the success_without_autounmask flag that's
# set below is reserved for cases where there are *zero* other