commit: 15e67f5516e0779d2cba37704c15b42193808197 Author: Zac Medico <zmedico <AT> gentoo <DOT> org> AuthorDate: Wed Mar 15 21:34:37 2017 +0000 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> CommitDate: Thu Mar 16 04:47:41 2017 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=15e67f55
depgraph: fix slot operator rebuild for llvm:0 to llvm:4 upgrade (bug 612772) Fix check_reverse_dependencies to ignore dependencies of parent packages that could be uninstalled in order to solve a blocker conflict. This case is similar to the one from bug 584626, except that the relevant parent package is in an older slot which is blocked by a newer slot. In this case, the _upgrade_available method returns False, because the package in the older slot is the highest version version available for its slot. Therefore, a new _in_blocker_conflict method is needed to detect parent packages that could be uninstalled. The included unit test fails without this fix. Since the _in_blocker_conflict method requires information that is collected by the _validate_blockers method, the _validate_blockers method now has to be called before the _process_slot_conflict and _slot_operator_trigger_reinstalls methods. X-Gentoo-bug: 612772 X-Gentoo-bug-url: https://bugs.gentoo.org/show_bug.cgi?id=612772 Acked-by: Brian Dolbec <dolsen <AT> gentoo.org> pym/_emerge/depgraph.py | 59 ++++++++--- .../resolver/test_slot_operator_exclusive_slots.py | 109 +++++++++++++++++++++ 2 files changed, 155 insertions(+), 13 deletions(-) diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py index 1379b0563..ad94fb70f 100644 --- a/pym/_emerge/depgraph.py +++ b/pym/_emerge/depgraph.py @@ -387,7 +387,10 @@ class _dynamic_depgraph_config(object): # Contains only unsolvable Package -> Blocker edges self._unsolvable_blockers = digraph() # Contains all Blocker -> Blocked Package edges - self._blocked_pkgs = digraph() + # Do not initialize this until the depgraph _validate_blockers + # method is called, so that the _in_blocker_conflict method can + # assert that _validate_blockers has been called first. + self._blocked_pkgs = None # Contains world packages that have been protected from # uninstallation but may not have been added to the graph # if the graph is not complete yet. @@ -1466,9 +1469,22 @@ class depgraph(object): self._solve_non_slot_operator_slot_conflicts() + if not self._validate_blockers(): + # Blockers don't trigger the _skip_restart flag, since + # backtracking may solve blockers when it solves slot + # conflicts (or by blind luck). + raise self._unknown_internal_error() + + # Both _process_slot_conflict and _slot_operator_trigger_reinstalls + # can call _slot_operator_update_probe, which requires that + # self._dynamic_config._blocked_pkgs has been initialized by a + # call to the _validate_blockers method. for conflict in self._dynamic_config._package_tracker.slot_conflicts(): self._process_slot_conflict(conflict) + if self._dynamic_config._allow_backtracking: + self._slot_operator_trigger_reinstalls() + def _process_slot_conflict(self, conflict): """ Process slot conflict data to identify specific atoms which @@ -1829,9 +1845,12 @@ class depgraph(object): not self._frozen_config.excluded_pkgs. findAtomForPackage(parent, modified_use=self._pkg_use_enabled(parent)) and - self._upgrade_available(parent)): + (self._upgrade_available(parent) or + (parent.installed and self._in_blocker_conflict(parent)))): # This parent may be irrelevant, since an - # update is available (see bug 584626). + # update is available (see bug 584626), or + # it could be uninstalled in order to solve + # a blocker conflict (bug 612772). continue atom_set = InternalPackageSet(initial_atoms=(atom,), @@ -2125,6 +2144,24 @@ class depgraph(object): self._dynamic_config._need_restart = True + def _in_blocker_conflict(self, pkg): + """ + Check if pkg is involved in a blocker conflict. This method + only works after the _validate_blockers method has been called. + """ + + if self._dynamic_config._blocked_pkgs is None: + raise AssertionError( + '_in_blocker_conflict called before _validate_blockers') + + if pkg in self._dynamic_config._blocked_pkgs: + return True + + if pkg in self._dynamic_config._blocker_parents: + return True + + return False + def _upgrade_available(self, pkg): """ Detect cases where an upgrade of the given package is available @@ -2925,7 +2962,8 @@ class depgraph(object): self._dynamic_config._blocker_parents.discard(pkg) self._dynamic_config._irrelevant_blockers.discard(pkg) self._dynamic_config._unsolvable_blockers.discard(pkg) - self._dynamic_config._blocked_pkgs.discard(pkg) + if self._dynamic_config._blocked_pkgs is not None: + self._dynamic_config._blocked_pkgs.discard(pkg) self._dynamic_config._blocked_world_pkgs.pop(pkg, None) for child in children: @@ -6619,6 +6657,10 @@ class depgraph(object): installed simultaneously. Also add runtime blockers from all installed packages if any of them haven't been added already (bug 128809).""" + # The _in_blocker_conflict method needs to assert that this method + # has been called before it, by checking that it is not None. + self._dynamic_config._blocked_pkgs = digraph() + if "--buildpkgonly" in self._frozen_config.myopts or \ "--nodeps" in self._frozen_config.myopts: return True @@ -7106,15 +7148,6 @@ class depgraph(object): self._process_slot_conflicts() - if self._dynamic_config._allow_backtracking: - self._slot_operator_trigger_reinstalls() - - if not self._validate_blockers(): - # Blockers don't trigger the _skip_restart flag, since - # backtracking may solve blockers when it solves slot - # conflicts (or by blind luck). - raise self._unknown_internal_error() - def _serialize_tasks(self): debug = "--debug" in self._frozen_config.myopts diff --git a/pym/portage/tests/resolver/test_slot_operator_exclusive_slots.py b/pym/portage/tests/resolver/test_slot_operator_exclusive_slots.py new file mode 100644 index 000000000..2ab379cce --- /dev/null +++ b/pym/portage/tests/resolver/test_slot_operator_exclusive_slots.py @@ -0,0 +1,109 @@ +# Copyright 2017 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +from portage.tests import TestCase +from portage.tests.resolver.ResolverPlayground import ( + ResolverPlayground, + ResolverPlaygroundTestCase, +) + +class SlotOperatorExclusiveSlotsTestCase(TestCase): + + def testSlotOperatorExclusiveSlots(self): + + ebuilds = { + + "media-libs/mesa-17.0.1" : { + "EAPI": "6", + "SLOT": "0", + "RDEPEND": "<sys-devel/llvm-5:=" + }, + + "sys-devel/clang-4.0.0" : { + "EAPI": "6", + "SLOT": "4", + "RDEPEND": ("~sys-devel/llvm-4.0.0:4= " + "!sys-devel/llvm:0 !sys-devel/clang:0"), + }, + + "sys-devel/clang-3.9.1-r100" : { + "EAPI": "6", + "SLOT": "0/3.9.1", + "RDEPEND": "~sys-devel/llvm-3.9.1", + }, + + "sys-devel/llvm-4.0.0" : { + "EAPI": "6", + "SLOT": "4", + "RDEPEND": "!sys-devel/llvm:0", + }, + + "sys-devel/llvm-3.9.1" : { + "EAPI": "6", + "SLOT": "0/3.91", + "RDEPEND": "!sys-devel/llvm:0", + "PDEPEND": "=sys-devel/clang-3.9.1-r100", + }, + + } + + installed = { + + "media-libs/mesa-17.0.1" : { + "EAPI": "6", + "SLOT": "0", + "RDEPEND": "<sys-devel/llvm-5:0/3.9.1=" + }, + + "sys-devel/clang-3.9.1-r100" : { + "EAPI": "6", + "SLOT": "0/3.9.1", + "RDEPEND": "~sys-devel/llvm-3.9.1", + }, + + "sys-devel/llvm-3.9.1" : { + "EAPI": "6", + "SLOT": "0/3.9.1", + "RDEPEND": "!sys-devel/llvm:0", + "PDEPEND": "=sys-devel/clang-3.9.1-r100", + }, + + } + + world = ["sys-devel/clang", "media-libs/mesa"] + + test_cases = ( + + # Test bug #612772, where slot operator rebuilds are not + # properly triggered (for things like mesa) during a + # llvm:0 to llvm:4 upgrade with clang, resulting in + # unsolved blockers. + ResolverPlaygroundTestCase( + ["@world"], + options = {"--update": True, "--deep": True}, + success = True, + ambiguous_merge_order = True, + mergelist = [ + 'sys-devel/llvm-4.0.0', + 'media-libs/mesa-17.0.1', + ( + 'sys-devel/clang-4.0.0', + '[uninstall]sys-devel/llvm-3.9.1', + '!sys-devel/llvm:0', + '[uninstall]sys-devel/clang-3.9.1-r100', + '!sys-devel/clang:0', + ) + ], + ), + + ) + + playground = ResolverPlayground(ebuilds=ebuilds, + installed=installed, world=world) + try: + for test_case in test_cases: + playground.run_TestCase(test_case) + self.assertEqual(test_case.test_success, True, + test_case.fail_msg) + finally: + playground.cleanup()
