Skip to content

Commit

Permalink
Detect and handle unnecessary package reinstall
Browse files Browse the repository at this point in the history
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.

Bug: https://bugs.gentoo.org/439688
Signed-off-by: Zac Medico <zmedico@gentoo.org>
  • Loading branch information
zmedico committed Oct 12, 2023
1 parent 01245d7 commit fde0b4f
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 32 deletions.
207 changes: 177 additions & 30 deletions lib/_emerge/depgraph.py
Expand Up @@ -53,6 +53,7 @@
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
Expand Down Expand Up @@ -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
Expand All @@ -459,7 +462,7 @@ def __init__(self, depgraph, myparams, allow_backtracking, backtrack_parameters)
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 = {}
Expand Down Expand Up @@ -570,6 +573,12 @@ def graph_tree():

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
Expand Down Expand Up @@ -3588,7 +3597,149 @@ def _add_pkg_soname_deps(self, pkg, allow_unsatisfied=False):
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 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.
"""
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 = False
for parent, atom in self._dynamic_config._parent_atoms[pkg]:
if not atom.match(installed_instance):
unsatisfied_parent = True
break
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

changed_deps = False
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:
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"]
else:
pkg_metadata = pkg._raw_metadata
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.
Expand Down Expand Up @@ -3643,12 +3794,13 @@ def _remove_pkg(self, pkg):
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()
Expand Down Expand Up @@ -5173,25 +5325,6 @@ def _resolve(self, myfavorites):
):
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
Expand Down Expand Up @@ -5243,15 +5376,29 @@ def _resolve(self, myfavorites):
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
Expand Down
19 changes: 17 additions & 2 deletions lib/portage/tests/resolver/soname/test_skip_update.py
@@ -1,7 +1,8 @@
# Copyright 2015 Gentoo Foundation
# Copyright 2015-2023 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

import sys
from unittest.mock import patch

from portage.const import SUPPORTED_GENTOO_BINPKG_FORMATS
from portage.tests import TestCase
Expand All @@ -13,7 +14,19 @@


class SonameSkipUpdateTestCase(TestCase):
def testSonameSkipUpdate(self):
def testSonameSkipUpdateNoPruneRebuilds(self):
"""
Make sure that there are fewer backtracking runs required if we
disable prune_rebuilds backtracking, which shows that
_eliminate_rebuilds works for the purposes of bug 915494.
"""
with patch(
"_emerge.depgraph._dynamic_depgraph_config._ENABLE_PRUNE_REBUILDS",
new=False,
):
self.testSonameSkipUpdate(backtrack=2)

def testSonameSkipUpdate(self, backtrack=3):
binpkgs = {
"app-misc/A-1": {
"RDEPEND": "dev-libs/B",
Expand Down Expand Up @@ -52,6 +65,7 @@ def testSonameSkipUpdate(self):
"--ignore-soname-deps": "y",
"--update": True,
"--usepkgonly": True,
"--backtrack": backtrack,
},
success=True,
mergelist=[
Expand All @@ -68,6 +82,7 @@ def testSonameSkipUpdate(self):
"--ignore-soname-deps": "n",
"--update": True,
"--usepkgonly": True,
"--backtrack": backtrack,
},
success=True,
mergelist=[],
Expand Down

0 comments on commit fde0b4f

Please sign in to comment.