commit:     3f0799054b4e5ef88feb59d20d262668ca79df33
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Fri Sep 12 07:07:13 2014 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Wed Sep 17 16:29:07 2014 +0000
URL:        
http://sources.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=3f079905

_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(-)

diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
index d85494a..6332733 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 b1974d7..646987d 100644
--- a/pym/portage/tests/resolver/ResolverPlayground.py
+++ b/pym/portage/tests/resolver/ResolverPlayground.py
@@ -677,6 +677,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))
@@ -690,9 +693,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"
                )
 
@@ -709,6 +713,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 = []
@@ -772,6 +777,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()

Reply via email to