factory.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. import contextlib
  2. import functools
  3. import logging
  4. from typing import (
  5. TYPE_CHECKING,
  6. Dict,
  7. FrozenSet,
  8. Iterable,
  9. Iterator,
  10. List,
  11. Mapping,
  12. Optional,
  13. Sequence,
  14. Set,
  15. Tuple,
  16. TypeVar,
  17. cast,
  18. )
  19. from pip._vendor.packaging.requirements import InvalidRequirement
  20. from pip._vendor.packaging.requirements import Requirement as PackagingRequirement
  21. from pip._vendor.packaging.specifiers import SpecifierSet
  22. from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
  23. from pip._vendor.pkg_resources import Distribution
  24. from pip._vendor.resolvelib import ResolutionImpossible
  25. from pip._internal.cache import CacheEntry, WheelCache
  26. from pip._internal.exceptions import (
  27. DistributionNotFound,
  28. InstallationError,
  29. InstallationSubprocessError,
  30. MetadataInconsistent,
  31. UnsupportedPythonVersion,
  32. UnsupportedWheel,
  33. )
  34. from pip._internal.index.package_finder import PackageFinder
  35. from pip._internal.models.link import Link
  36. from pip._internal.models.wheel import Wheel
  37. from pip._internal.operations.prepare import RequirementPreparer
  38. from pip._internal.req.constructors import install_req_from_link_and_ireq
  39. from pip._internal.req.req_install import InstallRequirement
  40. from pip._internal.resolution.base import InstallRequirementProvider
  41. from pip._internal.utils.compatibility_tags import get_supported
  42. from pip._internal.utils.hashes import Hashes
  43. from pip._internal.utils.misc import (
  44. dist_in_site_packages,
  45. dist_in_usersite,
  46. get_installed_distributions,
  47. )
  48. from pip._internal.utils.virtualenv import running_under_virtualenv
  49. from .base import Candidate, CandidateVersion, Constraint, Requirement
  50. from .candidates import (
  51. AlreadyInstalledCandidate,
  52. BaseCandidate,
  53. EditableCandidate,
  54. ExtrasCandidate,
  55. LinkCandidate,
  56. RequiresPythonCandidate,
  57. as_base_candidate,
  58. )
  59. from .found_candidates import FoundCandidates, IndexCandidateInfo
  60. from .requirements import (
  61. ExplicitRequirement,
  62. RequiresPythonRequirement,
  63. SpecifierRequirement,
  64. UnsatisfiableRequirement,
  65. )
  66. if TYPE_CHECKING:
  67. from typing import Protocol
  68. class ConflictCause(Protocol):
  69. requirement: RequiresPythonRequirement
  70. parent: Candidate
  71. logger = logging.getLogger(__name__)
  72. C = TypeVar("C")
  73. Cache = Dict[Link, C]
  74. class Factory:
  75. def __init__(
  76. self,
  77. finder, # type: PackageFinder
  78. preparer, # type: RequirementPreparer
  79. make_install_req, # type: InstallRequirementProvider
  80. wheel_cache, # type: Optional[WheelCache]
  81. use_user_site, # type: bool
  82. force_reinstall, # type: bool
  83. ignore_installed, # type: bool
  84. ignore_requires_python, # type: bool
  85. py_version_info=None, # type: Optional[Tuple[int, ...]]
  86. ):
  87. # type: (...) -> None
  88. self._finder = finder
  89. self.preparer = preparer
  90. self._wheel_cache = wheel_cache
  91. self._python_candidate = RequiresPythonCandidate(py_version_info)
  92. self._make_install_req_from_spec = make_install_req
  93. self._use_user_site = use_user_site
  94. self._force_reinstall = force_reinstall
  95. self._ignore_requires_python = ignore_requires_python
  96. self._build_failures = {} # type: Cache[InstallationError]
  97. self._link_candidate_cache = {} # type: Cache[LinkCandidate]
  98. self._editable_candidate_cache = {} # type: Cache[EditableCandidate]
  99. self._installed_candidate_cache = (
  100. {}
  101. ) # type: Dict[str, AlreadyInstalledCandidate]
  102. self._extras_candidate_cache = (
  103. {}
  104. ) # type: Dict[Tuple[int, FrozenSet[str]], ExtrasCandidate]
  105. if not ignore_installed:
  106. self._installed_dists = {
  107. canonicalize_name(dist.project_name): dist
  108. for dist in get_installed_distributions(local_only=False)
  109. }
  110. else:
  111. self._installed_dists = {}
  112. @property
  113. def force_reinstall(self):
  114. # type: () -> bool
  115. return self._force_reinstall
  116. def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None:
  117. if not link.is_wheel:
  118. return
  119. wheel = Wheel(link.filename)
  120. if wheel.supported(self._finder.target_python.get_tags()):
  121. return
  122. msg = f"{link.filename} is not a supported wheel on this platform."
  123. raise UnsupportedWheel(msg)
  124. def _make_extras_candidate(self, base, extras):
  125. # type: (BaseCandidate, FrozenSet[str]) -> ExtrasCandidate
  126. cache_key = (id(base), extras)
  127. try:
  128. candidate = self._extras_candidate_cache[cache_key]
  129. except KeyError:
  130. candidate = ExtrasCandidate(base, extras)
  131. self._extras_candidate_cache[cache_key] = candidate
  132. return candidate
  133. def _make_candidate_from_dist(
  134. self,
  135. dist, # type: Distribution
  136. extras, # type: FrozenSet[str]
  137. template, # type: InstallRequirement
  138. ):
  139. # type: (...) -> Candidate
  140. try:
  141. base = self._installed_candidate_cache[dist.key]
  142. except KeyError:
  143. base = AlreadyInstalledCandidate(dist, template, factory=self)
  144. self._installed_candidate_cache[dist.key] = base
  145. if not extras:
  146. return base
  147. return self._make_extras_candidate(base, extras)
  148. def _make_candidate_from_link(
  149. self,
  150. link, # type: Link
  151. extras, # type: FrozenSet[str]
  152. template, # type: InstallRequirement
  153. name, # type: Optional[NormalizedName]
  154. version, # type: Optional[CandidateVersion]
  155. ):
  156. # type: (...) -> Optional[Candidate]
  157. # TODO: Check already installed candidate, and use it if the link and
  158. # editable flag match.
  159. if link in self._build_failures:
  160. # We already tried this candidate before, and it does not build.
  161. # Don't bother trying again.
  162. return None
  163. if template.editable:
  164. if link not in self._editable_candidate_cache:
  165. try:
  166. self._editable_candidate_cache[link] = EditableCandidate(
  167. link,
  168. template,
  169. factory=self,
  170. name=name,
  171. version=version,
  172. )
  173. except (InstallationSubprocessError, MetadataInconsistent) as e:
  174. logger.warning("Discarding %s. %s", link, e)
  175. self._build_failures[link] = e
  176. return None
  177. base = self._editable_candidate_cache[link] # type: BaseCandidate
  178. else:
  179. if link not in self._link_candidate_cache:
  180. try:
  181. self._link_candidate_cache[link] = LinkCandidate(
  182. link,
  183. template,
  184. factory=self,
  185. name=name,
  186. version=version,
  187. )
  188. except (InstallationSubprocessError, MetadataInconsistent) as e:
  189. logger.warning("Discarding %s. %s", link, e)
  190. self._build_failures[link] = e
  191. return None
  192. base = self._link_candidate_cache[link]
  193. if not extras:
  194. return base
  195. return self._make_extras_candidate(base, extras)
  196. def _iter_found_candidates(
  197. self,
  198. ireqs: Sequence[InstallRequirement],
  199. specifier: SpecifierSet,
  200. hashes: Hashes,
  201. prefers_installed: bool,
  202. incompatible_ids: Set[int],
  203. ) -> Iterable[Candidate]:
  204. if not ireqs:
  205. return ()
  206. # The InstallRequirement implementation requires us to give it a
  207. # "template". Here we just choose the first requirement to represent
  208. # all of them.
  209. # Hopefully the Project model can correct this mismatch in the future.
  210. template = ireqs[0]
  211. assert template.req, "Candidates found on index must be PEP 508"
  212. name = canonicalize_name(template.req.name)
  213. extras = frozenset() # type: FrozenSet[str]
  214. for ireq in ireqs:
  215. assert ireq.req, "Candidates found on index must be PEP 508"
  216. specifier &= ireq.req.specifier
  217. hashes &= ireq.hashes(trust_internet=False)
  218. extras |= frozenset(ireq.extras)
  219. # Get the installed version, if it matches, unless the user
  220. # specified `--force-reinstall`, when we want the version from
  221. # the index instead.
  222. installed_candidate = None
  223. if not self._force_reinstall and name in self._installed_dists:
  224. installed_dist = self._installed_dists[name]
  225. if specifier.contains(installed_dist.version, prereleases=True):
  226. installed_candidate = self._make_candidate_from_dist(
  227. dist=installed_dist,
  228. extras=extras,
  229. template=template,
  230. )
  231. def iter_index_candidate_infos():
  232. # type: () -> Iterator[IndexCandidateInfo]
  233. result = self._finder.find_best_candidate(
  234. project_name=name,
  235. specifier=specifier,
  236. hashes=hashes,
  237. )
  238. icans = list(result.iter_applicable())
  239. # PEP 592: Yanked releases must be ignored unless only yanked
  240. # releases can satisfy the version range. So if this is false,
  241. # all yanked icans need to be skipped.
  242. all_yanked = all(ican.link.is_yanked for ican in icans)
  243. # PackageFinder returns earlier versions first, so we reverse.
  244. for ican in reversed(icans):
  245. if not all_yanked and ican.link.is_yanked:
  246. continue
  247. func = functools.partial(
  248. self._make_candidate_from_link,
  249. link=ican.link,
  250. extras=extras,
  251. template=template,
  252. name=name,
  253. version=ican.version,
  254. )
  255. yield ican.version, func
  256. return FoundCandidates(
  257. iter_index_candidate_infos,
  258. installed_candidate,
  259. prefers_installed,
  260. incompatible_ids,
  261. )
  262. def _iter_explicit_candidates_from_base(
  263. self,
  264. base_requirements: Iterable[Requirement],
  265. extras: FrozenSet[str],
  266. ) -> Iterator[Candidate]:
  267. """Produce explicit candidates from the base given an extra-ed package.
  268. :param base_requirements: Requirements known to the resolver. The
  269. requirements are guaranteed to not have extras.
  270. :param extras: The extras to inject into the explicit requirements'
  271. candidates.
  272. """
  273. for req in base_requirements:
  274. lookup_cand, _ = req.get_candidate_lookup()
  275. if lookup_cand is None: # Not explicit.
  276. continue
  277. # We've stripped extras from the identifier, and should always
  278. # get a BaseCandidate here, unless there's a bug elsewhere.
  279. base_cand = as_base_candidate(lookup_cand)
  280. assert base_cand is not None, "no extras here"
  281. yield self._make_extras_candidate(base_cand, extras)
  282. def _iter_candidates_from_constraints(
  283. self,
  284. identifier: str,
  285. constraint: Constraint,
  286. template: InstallRequirement,
  287. ) -> Iterator[Candidate]:
  288. """Produce explicit candidates from constraints.
  289. This creates "fake" InstallRequirement objects that are basically clones
  290. of what "should" be the template, but with original_link set to link.
  291. """
  292. for link in constraint.links:
  293. self._fail_if_link_is_unsupported_wheel(link)
  294. candidate = self._make_candidate_from_link(
  295. link,
  296. extras=frozenset(),
  297. template=install_req_from_link_and_ireq(link, template),
  298. name=canonicalize_name(identifier),
  299. version=None,
  300. )
  301. if candidate:
  302. yield candidate
  303. def find_candidates(
  304. self,
  305. identifier: str,
  306. requirements: Mapping[str, Iterator[Requirement]],
  307. incompatibilities: Mapping[str, Iterator[Candidate]],
  308. constraint: Constraint,
  309. prefers_installed: bool,
  310. ) -> Iterable[Candidate]:
  311. # Collect basic lookup information from the requirements.
  312. explicit_candidates = set() # type: Set[Candidate]
  313. ireqs = [] # type: List[InstallRequirement]
  314. for req in requirements[identifier]:
  315. cand, ireq = req.get_candidate_lookup()
  316. if cand is not None:
  317. explicit_candidates.add(cand)
  318. if ireq is not None:
  319. ireqs.append(ireq)
  320. # If the current identifier contains extras, add explicit candidates
  321. # from entries from extra-less identifier.
  322. with contextlib.suppress(InvalidRequirement):
  323. parsed_requirement = PackagingRequirement(identifier)
  324. explicit_candidates.update(
  325. self._iter_explicit_candidates_from_base(
  326. requirements.get(parsed_requirement.name, ()),
  327. frozenset(parsed_requirement.extras),
  328. ),
  329. )
  330. # Add explicit candidates from constraints. We only do this if there are
  331. # kown ireqs, which represent requirements not already explicit. If
  332. # there are no ireqs, we're constraining already-explicit requirements,
  333. # which is handled later when we return the explicit candidates.
  334. if ireqs:
  335. try:
  336. explicit_candidates.update(
  337. self._iter_candidates_from_constraints(
  338. identifier,
  339. constraint,
  340. template=ireqs[0],
  341. ),
  342. )
  343. except UnsupportedWheel:
  344. # If we're constrained to install a wheel incompatible with the
  345. # target architecture, no candidates will ever be valid.
  346. return ()
  347. # Since we cache all the candidates, incompatibility identification
  348. # can be made quicker by comparing only the id() values.
  349. incompat_ids = {id(c) for c in incompatibilities.get(identifier, ())}
  350. # If none of the requirements want an explicit candidate, we can ask
  351. # the finder for candidates.
  352. if not explicit_candidates:
  353. return self._iter_found_candidates(
  354. ireqs,
  355. constraint.specifier,
  356. constraint.hashes,
  357. prefers_installed,
  358. incompat_ids,
  359. )
  360. return (
  361. c
  362. for c in explicit_candidates
  363. if id(c) not in incompat_ids
  364. and constraint.is_satisfied_by(c)
  365. and all(req.is_satisfied_by(c) for req in requirements[identifier])
  366. )
  367. def make_requirement_from_install_req(self, ireq, requested_extras):
  368. # type: (InstallRequirement, Iterable[str]) -> Optional[Requirement]
  369. if not ireq.match_markers(requested_extras):
  370. logger.info(
  371. "Ignoring %s: markers '%s' don't match your environment",
  372. ireq.name,
  373. ireq.markers,
  374. )
  375. return None
  376. if not ireq.link:
  377. return SpecifierRequirement(ireq)
  378. self._fail_if_link_is_unsupported_wheel(ireq.link)
  379. cand = self._make_candidate_from_link(
  380. ireq.link,
  381. extras=frozenset(ireq.extras),
  382. template=ireq,
  383. name=canonicalize_name(ireq.name) if ireq.name else None,
  384. version=None,
  385. )
  386. if cand is None:
  387. # There's no way we can satisfy a URL requirement if the underlying
  388. # candidate fails to build. An unnamed URL must be user-supplied, so
  389. # we fail eagerly. If the URL is named, an unsatisfiable requirement
  390. # can make the resolver do the right thing, either backtrack (and
  391. # maybe find some other requirement that's buildable) or raise a
  392. # ResolutionImpossible eventually.
  393. if not ireq.name:
  394. raise self._build_failures[ireq.link]
  395. return UnsatisfiableRequirement(canonicalize_name(ireq.name))
  396. return self.make_requirement_from_candidate(cand)
  397. def make_requirement_from_candidate(self, candidate):
  398. # type: (Candidate) -> ExplicitRequirement
  399. return ExplicitRequirement(candidate)
  400. def make_requirement_from_spec(
  401. self,
  402. specifier, # type: str
  403. comes_from, # type: InstallRequirement
  404. requested_extras=(), # type: Iterable[str]
  405. ):
  406. # type: (...) -> Optional[Requirement]
  407. ireq = self._make_install_req_from_spec(specifier, comes_from)
  408. return self.make_requirement_from_install_req(ireq, requested_extras)
  409. def make_requires_python_requirement(self, specifier):
  410. # type: (Optional[SpecifierSet]) -> Optional[Requirement]
  411. if self._ignore_requires_python or specifier is None:
  412. return None
  413. return RequiresPythonRequirement(specifier, self._python_candidate)
  414. def get_wheel_cache_entry(self, link, name):
  415. # type: (Link, Optional[str]) -> Optional[CacheEntry]
  416. """Look up the link in the wheel cache.
  417. If ``preparer.require_hashes`` is True, don't use the wheel cache,
  418. because cached wheels, always built locally, have different hashes
  419. than the files downloaded from the index server and thus throw false
  420. hash mismatches. Furthermore, cached wheels at present have
  421. nondeterministic contents due to file modification times.
  422. """
  423. if self._wheel_cache is None or self.preparer.require_hashes:
  424. return None
  425. return self._wheel_cache.get_cache_entry(
  426. link=link,
  427. package_name=name,
  428. supported_tags=get_supported(),
  429. )
  430. def get_dist_to_uninstall(self, candidate):
  431. # type: (Candidate) -> Optional[Distribution]
  432. # TODO: Are there more cases this needs to return True? Editable?
  433. dist = self._installed_dists.get(candidate.project_name)
  434. if dist is None: # Not installed, no uninstallation required.
  435. return None
  436. # We're installing into global site. The current installation must
  437. # be uninstalled, no matter it's in global or user site, because the
  438. # user site installation has precedence over global.
  439. if not self._use_user_site:
  440. return dist
  441. # We're installing into user site. Remove the user site installation.
  442. if dist_in_usersite(dist):
  443. return dist
  444. # We're installing into user site, but the installed incompatible
  445. # package is in global site. We can't uninstall that, and would let
  446. # the new user installation to "shadow" it. But shadowing won't work
  447. # in virtual environments, so we error out.
  448. if running_under_virtualenv() and dist_in_site_packages(dist):
  449. raise InstallationError(
  450. "Will not install to the user site because it will "
  451. "lack sys.path precedence to {} in {}".format(
  452. dist.project_name,
  453. dist.location,
  454. )
  455. )
  456. return None
  457. def _report_requires_python_error(self, causes):
  458. # type: (Sequence[ConflictCause]) -> UnsupportedPythonVersion
  459. assert causes, "Requires-Python error reported with no cause"
  460. version = self._python_candidate.version
  461. if len(causes) == 1:
  462. specifier = str(causes[0].requirement.specifier)
  463. message = (
  464. f"Package {causes[0].parent.name!r} requires a different "
  465. f"Python: {version} not in {specifier!r}"
  466. )
  467. return UnsupportedPythonVersion(message)
  468. message = f"Packages require a different Python. {version} not in:"
  469. for cause in causes:
  470. package = cause.parent.format_for_error()
  471. specifier = str(cause.requirement.specifier)
  472. message += f"\n{specifier!r} (required by {package})"
  473. return UnsupportedPythonVersion(message)
  474. def _report_single_requirement_conflict(self, req, parent):
  475. # type: (Requirement, Optional[Candidate]) -> DistributionNotFound
  476. if parent is None:
  477. req_disp = str(req)
  478. else:
  479. req_disp = f"{req} (from {parent.name})"
  480. cands = self._finder.find_all_candidates(req.project_name)
  481. versions = [str(v) for v in sorted({c.version for c in cands})]
  482. logger.critical(
  483. "Could not find a version that satisfies the requirement %s "
  484. "(from versions: %s)",
  485. req_disp,
  486. ", ".join(versions) or "none",
  487. )
  488. return DistributionNotFound(f"No matching distribution found for {req}")
  489. def get_installation_error(
  490. self,
  491. e, # type: ResolutionImpossible[Requirement, Candidate]
  492. constraints, # type: Dict[str, Constraint]
  493. ):
  494. # type: (...) -> InstallationError
  495. assert e.causes, "Installation error reported with no cause"
  496. # If one of the things we can't solve is "we need Python X.Y",
  497. # that is what we report.
  498. requires_python_causes = [
  499. cause
  500. for cause in e.causes
  501. if isinstance(cause.requirement, RequiresPythonRequirement)
  502. and not cause.requirement.is_satisfied_by(self._python_candidate)
  503. ]
  504. if requires_python_causes:
  505. # The comprehension above makes sure all Requirement instances are
  506. # RequiresPythonRequirement, so let's cast for convinience.
  507. return self._report_requires_python_error(
  508. cast("Sequence[ConflictCause]", requires_python_causes),
  509. )
  510. # Otherwise, we have a set of causes which can't all be satisfied
  511. # at once.
  512. # The simplest case is when we have *one* cause that can't be
  513. # satisfied. We just report that case.
  514. if len(e.causes) == 1:
  515. req, parent = e.causes[0]
  516. if req.name not in constraints:
  517. return self._report_single_requirement_conflict(req, parent)
  518. # OK, we now have a list of requirements that can't all be
  519. # satisfied at once.
  520. # A couple of formatting helpers
  521. def text_join(parts):
  522. # type: (List[str]) -> str
  523. if len(parts) == 1:
  524. return parts[0]
  525. return ", ".join(parts[:-1]) + " and " + parts[-1]
  526. def describe_trigger(parent):
  527. # type: (Candidate) -> str
  528. ireq = parent.get_install_requirement()
  529. if not ireq or not ireq.comes_from:
  530. return f"{parent.name}=={parent.version}"
  531. if isinstance(ireq.comes_from, InstallRequirement):
  532. return str(ireq.comes_from.name)
  533. return str(ireq.comes_from)
  534. triggers = set()
  535. for req, parent in e.causes:
  536. if parent is None:
  537. # This is a root requirement, so we can report it directly
  538. trigger = req.format_for_error()
  539. else:
  540. trigger = describe_trigger(parent)
  541. triggers.add(trigger)
  542. if triggers:
  543. info = text_join(sorted(triggers))
  544. else:
  545. info = "the requested packages"
  546. msg = (
  547. "Cannot install {} because these package versions "
  548. "have conflicting dependencies.".format(info)
  549. )
  550. logger.critical(msg)
  551. msg = "\nThe conflict is caused by:"
  552. relevant_constraints = set()
  553. for req, parent in e.causes:
  554. if req.name in constraints:
  555. relevant_constraints.add(req.name)
  556. msg = msg + "\n "
  557. if parent:
  558. msg = msg + f"{parent.name} {parent.version} depends on "
  559. else:
  560. msg = msg + "The user requested "
  561. msg = msg + req.format_for_error()
  562. for key in relevant_constraints:
  563. spec = constraints[key].specifier
  564. msg += f"\n The user requested (constraint) {key}{spec}"
  565. msg = (
  566. msg
  567. + "\n\n"
  568. + "To fix this you could try to:\n"
  569. + "1. loosen the range of package versions you've specified\n"
  570. + "2. remove package versions to allow pip attempt to solve "
  571. + "the dependency conflict\n"
  572. )
  573. logger.info(msg)
  574. return DistributionNotFound(
  575. "ResolutionImpossible: for help visit "
  576. "https://pip.pypa.io/en/latest/user_guide/"
  577. "#fixing-conflicting-dependencies"
  578. )