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.
This commit is contained in:
Tzu-ping Chung 2021-05-18 22:47:31 +08:00 committed by Stéphane Bidoul
parent 1c31d3314c
commit 729c626da7
No known key found for this signature in database
GPG Key ID: BCAB2555446B5B92
2 changed files with 27 additions and 13 deletions

3
news/9841.bugfix.rst Normal file
View File

@ -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.

View File

@ -240,18 +240,29 @@ class Factory:
hashes &= ireq.hashes(trust_internet=False) hashes &= ireq.hashes(trust_internet=False)
extras |= frozenset(ireq.extras) extras |= frozenset(ireq.extras)
# Get the installed version, if it matches, unless the user def _get_installed_candidate() -> Optional[Candidate]:
# specified `--force-reinstall`, when we want the version from """Get the candidate for the currently-installed version."""
# the index instead. # If --force-reinstall is set, we want the version from the index
installed_candidate = None # instead, so we "pretend" there is nothing installed.
if not self._force_reinstall and name in self._installed_dists: if self._force_reinstall:
installed_dist = self._installed_dists[name] return None
if specifier.contains(installed_dist.version, prereleases=True): try:
installed_candidate = self._make_candidate_from_dist( installed_dist = self._installed_dists[name]
dist=installed_dist, except KeyError:
extras=extras, return None
template=template, # 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(): def iter_index_candidate_infos():
# type: () -> Iterator[IndexCandidateInfo] # type: () -> Iterator[IndexCandidateInfo]
@ -283,7 +294,7 @@ class Factory:
return FoundCandidates( return FoundCandidates(
iter_index_candidate_infos, iter_index_candidate_infos,
installed_candidate, _get_installed_candidate(),
prefers_installed, prefers_installed,
incompatible_ids, incompatible_ids,
) )