Skip to content

Commit

Permalink
Detect and handle unnecessary package reinstall
Browse files Browse the repository at this point in the history
Bug: https://bugs.gentoo.org/439688
Signed-off-by: Zac Medico <zmedico@gentoo.org>
  • Loading branch information
zmedico committed Oct 11, 2023
1 parent 18db7f8 commit 1dec035
Showing 1 changed file with 152 additions and 29 deletions.
181 changes: 152 additions & 29 deletions lib/_emerge/depgraph.py
Expand Up @@ -3588,7 +3588,135 @@ def _add_pkg_soname_deps(self, pkg, allow_unsatisfied=False):
return False
return True

def _remove_pkg(self, pkg):
def _eliminate_rebuilds(self):
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 = []
for k in depvars:
dep_struct = portage.dep.use_reduce(
pkg._raw_metadata[k],
uselist=self._pkg_use_enabled(pkg),
eapi=pkg.eapi,
token_class=Atom,
)
# TODO: Evaluate slot-operator deps for unbuilt
# ebuilds, based on dependencies selected to
# satisfy them.
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 +3771,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 +5302,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 +5353,28 @@ 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._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

0 comments on commit 1dec035

Please sign in to comment.