diff options
author | Zac Medico <zmedico@gentoo.org> | 2023-10-12 11:05:29 -0700 |
---|---|---|
committer | Zac Medico <zmedico@gentoo.org> | 2023-10-12 11:37:26 -0700 |
commit | 4978b913df39abbb3369739e370856cf8292a203 (patch) | |
tree | 42404570c6417b0c61ec83e6fd661980ce2d8749 /lib/_emerge/depgraph.py | |
parent | emerge: Increase default number of maximum backtrack attempts from 10 to 20 (diff) | |
download | gentoo-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.py | 206 |
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 |