When selecting packages to determine which choices have upgrades or downgrades relative to other choices, make the package selections internally consistent by choosing a package that satisfies all atoms in the choice which match a package in the same slot.
Also, fix the Atom.match() method to handle _pkg_str instances, since dep_zapdeps can pass in _pkg_str instances instead of Package instances. X-Gentoo-Bug: 600346 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=600346 --- [PATCH v2] fixes incorrect logic in dep_zapdeps pym/portage/dep/__init__.py | 16 ++++++++++++---- pym/portage/dep/dep_check.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/pym/portage/dep/__init__.py b/pym/portage/dep/__init__.py index 5dd1638..968ff5b 100644 --- a/pym/portage/dep/__init__.py +++ b/pym/portage/dep/__init__.py @@ -1603,10 +1603,18 @@ class Atom(_unicode): if pkg.cp == self.cp: return bool(match_from_list(self, [pkg])) else: - for provided_cp in pkg.provided_cps: - if provided_cp == self.cp: - return bool(match_from_list( - self.replace(self.cp, provided_cp, 1), [pkg])) + try: + provided_cps = pkg.provided_cps + except AttributeError: + # Since _pkg_str instances lack PROVIDE metadata, + # just ignore this case (PROVIDE has been deprecated + # for years). + pass + else: + for provided_cp in provided_cps: + if provided_cp == self.cp: + return bool(match_from_list( + self.replace(self.cp, provided_cp, 1), [pkg])) return False _extended_cp_re_cache = {} diff --git a/pym/portage/dep/dep_check.py b/pym/portage/dep/dep_check.py index 9d2ca4b..737d2b1 100644 --- a/pym/portage/dep/dep_check.py +++ b/pym/portage/dep/dep_check.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals __all__ = ['dep_check', 'dep_eval', 'dep_wordreduce', 'dep_zapdeps'] +import collections import logging import operator @@ -354,6 +355,7 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): all_use_satisfied = True all_use_unmasked = True conflict_downgrade = False + slot_atoms = collections.defaultdict(list) slot_map = {} cp_map = {} for atom in atoms: @@ -418,9 +420,31 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) slot_map[avail_slot] = avail_pkg + slot_atoms[avail_slot].append(atom) highest_cpv = cp_map.get(avail_pkg.cp) - if highest_cpv is None or \ - vercmp(avail_pkg.version, highest_cpv.version) > 0: + all_match_current = None + all_match_previous = None + if (highest_cpv is not None and + highest_cpv.slot == avail_pkg.slot): + # If possible, make the package selection internally + # consistent by choosing a package that satisfies all + # atoms which match a package in the same slot. Later on, + # the package version chosen here is used in the + # has_upgrade/has_downgrade logic to prefer choices with + # upgrades, and a package choice that is not internally + # consistent will lead the has_upgrade/has_downgrade logic + # to produce invalid results (see bug 600346). + all_match_current = all(a.match(avail_pkg) + for a in slot_atoms[avail_slot]) + all_match_previous = all(a.match(highest_cpv) + for a in slot_atoms[avail_slot]) + if all_match_previous and not all_match_current: + continue + + current_higher = (highest_cpv is None or + vercmp(avail_pkg.version, highest_cpv.version) > 0) + + if current_higher or (all_match_current and not all_match_previous): cp_map[avail_pkg.cp] = avail_pkg this_choice = _dep_choice(atoms=atoms, slot_map=slot_map, -- 2.7.4