diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py index 039dbdc..b05e38a 100644 --- a/pym/_emerge/depgraph.py +++ b/pym/_emerge/depgraph.py @@ -2516,6 +2516,16 @@ class depgraph(object): for root, atom in self._dynamic_config._slot_abi_replace_installed: atom_list.append((root, '__auto_slot_abi_replace_installed__', atom)) + # Add slot atoms for packages where updating might solve a + # conflict, but the package wouldn't be pulled in otherwise. + # Note that this can't result in strange errors being + # displayed to the user, because if the backtracker gives + # up, the resolution is done without _runtime_pkg_mask and + # the original error gets displayed. + for pkg in self._dynamic_config._runtime_pkg_mask: + if "force update" in self._dynamic_config._runtime_pkg_mask[pkg]: + atom_list.append((pkg.root, '__auto_blocker_force_update__', pkg.slot_atom)) + set_dict = {} for root, set_name, atom in atom_list: set_dict.setdefault((root, set_name), []).append(atom) @@ -5077,6 +5087,48 @@ class depgraph(object): if unresolved_blocks: self._dynamic_config._unsolvable_blockers.add(blocker, parent) + #Check if all parents of the package(s) that is/are blocked by this blocker + #are installed. If this is true, the blocked package(s) might not be + #pulled into the tree if we force updates for the parent packages. + pkgs_forced_to_update = set() + all_parents_installed = True + for blocked_pkg in blocked_initial: + for ppkg, patom in self._dynamic_config._parent_atoms.get(blocked_pkg): + if not ppkg.installed: + all_parents_installed = False + pkgs_forced_to_update.add(ppkg) + + if all_parents_installed: + if "--debug" in self._frozen_config.myopts: + msg = [] + msg.append("") + msg.append("") + msg.append("backtracking due to conflicting packages:") + msg.append(" blocker parent: %s" % blocker) + msg.append(" root: %s" % blocker.root) + msg.append(" atom: %s" % blocker.atom) + for blocked_pkg in blocked_initial: + msg.append(" blocked package: %s" % blocked_pkg) + for ppkg, patom in self._dynamic_config._parent_atoms.get(blocked_pkg): + msg.append(" masked parent: %s" % ppkg) + msg.append("") + writemsg_level("".join("%s\n" % l for l in msg), + noiselevel=-1, level=logging.DEBUG) + + for ppkg in pkgs_forced_to_update: + if ppkg in self._dynamic_config._runtime_pkg_mask: + if "--debug" in self._frozen_config.myopts: + writemsg( + "!!! backtracking loop detected: %s %s\n" % \ + (ppkg, + self._dynamic_config._runtime_pkg_mask[ + ppkg]), noiselevel=-1) + self._dynamic_config._runtime_pkg_mask.setdefault( + ppkg, {})["force update"] = \ + set([(ppkg, ppkg.root, blocker.atom)]) + + self._dynamic_config._need_restart = True + return True def _accept_blocker_conflicts(self): diff --git a/pym/portage/tests/resolver/test_backtracking.py b/pym/portage/tests/resolver/test_backtracking.py index 600f682..5565e97 100644 --- a/pym/portage/tests/resolver/test_backtracking.py +++ b/pym/portage/tests/resolver/test_backtracking.py @@ -210,3 +210,47 @@ class BacktrackingTestCase(TestCase): self.assertEqual(test_case.test_success, True, test_case.fail_msg) finally: playground.cleanup() + + def testBacktrackBlockerForceUpdate(self): + """ + Force updates of old packages that trigger blockers, for + bug #290818. This test currently fails because the + dev-libs/B-2 update doesn't get pulled in automatically, + and the old version can't simply be uninstalled because + dev-libs/B is a deep dependency of @world via dev-libs/D. + """ + + ebuilds = { + "dev-libs/A-1": {"RDEPEND": "dev-libs/B"}, + "dev-libs/A-2": {"RDEPEND": "dev-libs/C"}, + "dev-libs/B-1": {}, + "dev-libs/B-2": {}, + "dev-libs/C-1": {"RDEPEND": "!