From 729c626da75b25af0640a77efdbbfc57e567e1f9 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 18 May 2021 22:47:31 +0800 Subject: [PATCH] Exclude a known incompatible installed candidate The resolver collects previously known incompatibilites and sends them to the provider. But previously the provider does not correctly exclude the currently-installed candidate if it is present in that incompatibility list, causing the resolver to enter a loop trying that same candidate. This patch correctly applies incompat_ids when producing an AlreadyInstalledCandidate and exclude it if its id() is in the set. --- news/9841.bugfix.rst | 3 ++ .../resolution/resolvelib/factory.py | 37 ++++++++++++------- 2 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 news/9841.bugfix.rst diff --git a/news/9841.bugfix.rst b/news/9841.bugfix.rst new file mode 100644 index 000000000..46e00dc22 --- /dev/null +++ b/news/9841.bugfix.rst @@ -0,0 +1,3 @@ +New resolver: Correctly exclude an already installed package if its version is +known to be incompatible to stop the dependency resolution process with a clear +error message. diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 6e3f19518..5816a0ede 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -240,18 +240,29 @@ class Factory: hashes &= ireq.hashes(trust_internet=False) extras |= frozenset(ireq.extras) - # Get the installed version, if it matches, unless the user - # specified `--force-reinstall`, when we want the version from - # the index instead. - installed_candidate = None - if not self._force_reinstall and name in self._installed_dists: - installed_dist = self._installed_dists[name] - if specifier.contains(installed_dist.version, prereleases=True): - installed_candidate = self._make_candidate_from_dist( - dist=installed_dist, - extras=extras, - template=template, - ) + def _get_installed_candidate() -> Optional[Candidate]: + """Get the candidate for the currently-installed version.""" + # If --force-reinstall is set, we want the version from the index + # instead, so we "pretend" there is nothing installed. + if self._force_reinstall: + return None + try: + installed_dist = self._installed_dists[name] + except KeyError: + return None + # Don't use the installed distribution if its version does not fit + # the current dependency graph. + if not specifier.contains(installed_dist.version, prereleases=True): + return None + candidate = self._make_candidate_from_dist( + dist=installed_dist, + extras=extras, + template=template, + ) + # The candidate is a known incompatiblity. Don't use it. + if id(candidate) in incompatible_ids: + return None + return candidate def iter_index_candidate_infos(): # type: () -> Iterator[IndexCandidateInfo] @@ -283,7 +294,7 @@ class Factory: return FoundCandidates( iter_index_candidate_infos, - installed_candidate, + _get_installed_candidate(), prefers_installed, incompatible_ids, )