From 5a55cdc88eda277e47359341c8e5885affd40cf2 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Fri, 12 Sep 2014 00:07:13 -0700 Subject: [PATCH] _compute_abi_rebuild_info: fix bug #521990 Since self._dynamic_config._slot_operator_deps only contains deps for packages added to the graph, it doesn't contain potentially relevant deps of installed packages that have not been added to the graph. Therefore, generate pseudo-deps for such installed packages, and use those to generate the rebuild info. X-Gentoo-Bug: 521990 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=521990 --- pym/_emerge/depgraph.py | 100 +++++++++++++++++---- pym/portage/tests/resolver/ResolverPlayground.py | 15 +++- .../resolver/test_slot_conflict_force_rebuild.py | 84 +++++++++++++++++ 3 files changed, 183 insertions(+), 16 deletions(-) create mode 100644 pym/portage/tests/resolver/test_slot_conflict_force_rebuild.py diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py index cc87d9f..795f4fe 100644 --- a/pym/_emerge/depgraph.py +++ b/pym/_emerge/depgraph.py @@ -647,26 +647,96 @@ class depgraph(object): # Go through all slot operator deps and check if one of these deps # has a parent that is matched by one of the atoms from above. forced_rebuilds = {} - for (root, slot_atom), deps in self._dynamic_config._slot_operator_deps.items(): - rebuild_atoms = atoms.get(root, set()) - for dep in deps: - if not isinstance(dep.parent, Package): - continue + for root, rebuild_atoms in atoms.items(): - if dep.parent.installed or dep.child.installed or \ - dep.parent.slot_atom not in rebuild_atoms: - continue + for slot_atom in rebuild_atoms: + + inst_pkg, reinst_pkg = \ + self._select_pkg_from_installed(root, slot_atom) - # Make sure the child's slot/subslot has changed. If it hasn't, - # then another child has forced this rebuild. - installed_pkg = self._select_pkg_from_installed(root, dep.child.slot_atom)[0] - if installed_pkg and installed_pkg.slot == dep.child.slot and \ - installed_pkg.sub_slot == dep.child.sub_slot: + if inst_pkg is reinst_pkg or reinst_pkg is None: continue - # The child has forced a rebuild of the parent - forced_rebuilds.setdefault(root, {}).setdefault(dep.child, set()).add(dep.parent) + # Generate pseudo-deps for any slot-operator deps of + # inst_pkg. Its deps aren't in _slot_operator_deps + # because it hasn't been added to the graph, but we + # are interested in any rebuilds that it triggered. + built_slot_op_atoms = [] + if inst_pkg is not None: + selected_atoms = self._select_atoms_probe( + inst_pkg.root, inst_pkg) + for atom in selected_atoms: + if atom.slot_operator_built: + built_slot_op_atoms.append(atom) + + if not built_slot_op_atoms: + continue + + # Use a cloned list, since we may append to it below. + deps = self._dynamic_config._slot_operator_deps.get( + (root, slot_atom), [])[:] + + if built_slot_op_atoms and reinst_pkg is not None: + for child in self._dynamic_config.digraph.child_nodes( + reinst_pkg): + + if child.installed: + continue + + for atom in built_slot_op_atoms: + # NOTE: Since atom comes from inst_pkg, and + # reinst_pkg is the replacement parent, there's + # no guarantee that atom will completely match + # child. So, simply use atom.cp and atom.slot + # for matching. + if atom.cp != child.cp: + continue + if atom.slot and atom.slot != child.slot: + continue + deps.append(Dependency(atom=atom, child=child, + root=child.root, parent=reinst_pkg)) + + for dep in deps: + if dep.child.installed: + # Find the replacement child. + child = next((pkg for pkg in + self._dynamic_config._package_tracker.match( + dep.root, dep.child.slot_atom) + if not pkg.installed), None) + + if child is None: + continue + + inst_child = dep.child.installed + + else: + child = dep.child + inst_child = self._select_pkg_from_installed( + child.root, child.slot_atom)[0] + + # Make sure the child's slot/subslot has changed. If it + # hasn't, then another child has forced this rebuild. + if inst_child and inst_child.slot == child.slot and \ + inst_child.sub_slot == child.sub_slot: + continue + + if dep.parent.installed: + # Find the replacement parent. + parent = next((pkg for pkg in + self._dynamic_config._package_tracker.match( + dep.parent.root, dep.parent.slot_atom) + if not pkg.installed), None) + + if parent is None: + continue + + else: + parent = dep.parent + + # The child has forced a rebuild of the parent + forced_rebuilds.setdefault(root, {} + ).setdefault(child, set()).add(parent) if debug: writemsg_level("slot operator dependencies:\n", diff --git a/pym/portage/tests/resolver/ResolverPlayground.py b/pym/portage/tests/resolver/ResolverPlayground.py index 77a5b5c..af54c56 100644 --- a/pym/portage/tests/resolver/ResolverPlayground.py +++ b/pym/portage/tests/resolver/ResolverPlayground.py @@ -668,6 +668,9 @@ class ResolverPlaygroundTestCase(object): "unsatisfied_deps") and expected is not None: expected = set(expected) + elif key == "forced_rebuilds" and expected is not None: + expected = dict((k, set(v)) for k, v in expected.items()) + if got != expected: fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \ key + ", expected: " + str(expected) + ", got: " + str(got)) @@ -681,9 +684,10 @@ class ResolverPlaygroundResult(object): checks = ( "success", "mergelist", "use_changes", "license_changes", "unstable_keywords", "slot_collision_solutions", - "circular_dependency_solutions", "needed_p_mask_changes", "unsatisfied_deps", + "circular_dependency_solutions", "needed_p_mask_changes", "unsatisfied_deps", "forced_rebuilds" ) optional_checks = ( + "forced_rebuilds", "unsatisfied_deps" ) @@ -700,6 +704,7 @@ class ResolverPlaygroundResult(object): self.slot_collision_solutions = None self.circular_dependency_solutions = None self.unsatisfied_deps = frozenset() + self.forced_rebuilds = None if self.depgraph._dynamic_config._serialized_tasks_cache is not None: self.mergelist = [] @@ -763,6 +768,14 @@ class ResolverPlaygroundResult(object): self.unsatisfied_deps = set(dep_info[0][1] for dep_info in self.depgraph._dynamic_config._unsatisfied_deps_for_display) + if self.depgraph._forced_rebuilds: + self.forced_rebuilds = dict(self._iter_forced_rebuilds()) + + def _iter_forced_rebuilds(self): + for child_dict in self.depgraph._forced_rebuilds.values(): + for child, parents in child_dict.items(): + yield child.cpv, set(parent.cpv for parent in parents) + class ResolverPlaygroundDepcleanResult(object): checks = ( diff --git a/pym/portage/tests/resolver/test_slot_conflict_force_rebuild.py b/pym/portage/tests/resolver/test_slot_conflict_force_rebuild.py new file mode 100644 index 0000000..4170bfd --- /dev/null +++ b/pym/portage/tests/resolver/test_slot_conflict_force_rebuild.py @@ -0,0 +1,84 @@ +# Copyright 2014 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 SlotConflictForceRebuildTestCase(TestCase): + + def testSlotConflictForceRebuild(self): + + ebuilds = { + + "app-misc/A-1" : { + "EAPI": "5", + "SLOT": "0/1" + }, + + "app-misc/A-2" : { + "EAPI": "5", + "SLOT": "0/2" + }, + + "app-misc/B-0" : { + "EAPI": "5", + "RDEPEND": "app-misc/A:=" + }, + + "app-misc/C-0" : { + "EAPI": "5", + "RDEPEND": "app-misc/A" + }, + + } + + installed = { + + "app-misc/A-1" : { + "EAPI": "5", + "SLOT": "0/1" + }, + + "app-misc/B-0" : { + "EAPI": "5", + "RDEPEND": "app-misc/A:0/1=" + }, + + "app-misc/C-0" : { + "EAPI": "5", + "RDEPEND": "app-misc/A:0/1=" + }, + + } + + world = ["app-misc/B", "app-misc/C"] + + test_cases = ( + + # Test bug #521990, where forced_rebuilds omits ebuilds that + # had have had their slot operator atoms removed from the + # ebuilds, even though the corresponding installed + # instances had really forced rebuilds due to being built + # with slot-operators in their deps. + ResolverPlaygroundTestCase( + ["app-misc/A"], + options = {}, + success = True, + ambiguous_merge_order = True, + mergelist = ['app-misc/A-2', ('app-misc/B-0', 'app-misc/C-0')], + forced_rebuilds = { + 'app-misc/A-2': ['app-misc/B-0', 'app-misc/C-0'] + } + ), + + ) + + playground = ResolverPlayground(ebuilds=ebuilds, + installed=installed, world=world, debug=False) + 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() -- 1.8.5.5