factory.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. import logging
  2. from pip._vendor.packaging.utils import canonicalize_name
  3. from pip._internal.exceptions import (
  4. DistributionNotFound,
  5. InstallationError,
  6. InstallationSubprocessError,
  7. MetadataInconsistent,
  8. UnsupportedPythonVersion,
  9. UnsupportedWheel,
  10. )
  11. from pip._internal.models.wheel import Wheel
  12. from pip._internal.req.req_install import InstallRequirement
  13. from pip._internal.utils.compatibility_tags import get_supported
  14. from pip._internal.utils.hashes import Hashes
  15. from pip._internal.utils.misc import (
  16. dist_in_site_packages,
  17. dist_in_usersite,
  18. get_installed_distributions,
  19. )
  20. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  21. from pip._internal.utils.virtualenv import running_under_virtualenv
  22. from .base import Constraint
  23. from .candidates import (
  24. AlreadyInstalledCandidate,
  25. EditableCandidate,
  26. ExtrasCandidate,
  27. LinkCandidate,
  28. RequiresPythonCandidate,
  29. )
  30. from .found_candidates import FoundCandidates
  31. from .requirements import (
  32. ExplicitRequirement,
  33. RequiresPythonRequirement,
  34. SpecifierRequirement,
  35. UnsatisfiableRequirement,
  36. )
  37. if MYPY_CHECK_RUNNING:
  38. from typing import (
  39. Dict,
  40. FrozenSet,
  41. Iterable,
  42. Iterator,
  43. List,
  44. Optional,
  45. Sequence,
  46. Set,
  47. Tuple,
  48. TypeVar,
  49. )
  50. from pip._vendor.packaging.specifiers import SpecifierSet
  51. from pip._vendor.packaging.version import _BaseVersion
  52. from pip._vendor.pkg_resources import Distribution
  53. from pip._vendor.resolvelib import ResolutionImpossible
  54. from pip._internal.cache import CacheEntry, WheelCache
  55. from pip._internal.index.package_finder import PackageFinder
  56. from pip._internal.models.link import Link
  57. from pip._internal.operations.prepare import RequirementPreparer
  58. from pip._internal.resolution.base import InstallRequirementProvider
  59. from .base import Candidate, Requirement
  60. from .candidates import BaseCandidate
  61. C = TypeVar("C")
  62. Cache = Dict[Link, C]
  63. VersionCandidates = Dict[_BaseVersion, Candidate]
  64. logger = logging.getLogger(__name__)
  65. class Factory(object):
  66. def __init__(
  67. self,
  68. finder, # type: PackageFinder
  69. preparer, # type: RequirementPreparer
  70. make_install_req, # type: InstallRequirementProvider
  71. wheel_cache, # type: Optional[WheelCache]
  72. use_user_site, # type: bool
  73. force_reinstall, # type: bool
  74. ignore_installed, # type: bool
  75. ignore_requires_python, # type: bool
  76. py_version_info=None, # type: Optional[Tuple[int, ...]]
  77. ):
  78. # type: (...) -> None
  79. self._finder = finder
  80. self.preparer = preparer
  81. self._wheel_cache = wheel_cache
  82. self._python_candidate = RequiresPythonCandidate(py_version_info)
  83. self._make_install_req_from_spec = make_install_req
  84. self._use_user_site = use_user_site
  85. self._force_reinstall = force_reinstall
  86. self._ignore_requires_python = ignore_requires_python
  87. self._build_failures = {} # type: Cache[InstallationError]
  88. self._link_candidate_cache = {} # type: Cache[LinkCandidate]
  89. self._editable_candidate_cache = {} # type: Cache[EditableCandidate]
  90. self._installed_candidate_cache = {
  91. } # type: Dict[str, AlreadyInstalledCandidate]
  92. if not ignore_installed:
  93. self._installed_dists = {
  94. canonicalize_name(dist.project_name): dist
  95. for dist in get_installed_distributions(local_only=False)
  96. }
  97. else:
  98. self._installed_dists = {}
  99. @property
  100. def force_reinstall(self):
  101. # type: () -> bool
  102. return self._force_reinstall
  103. def _make_candidate_from_dist(
  104. self,
  105. dist, # type: Distribution
  106. extras, # type: FrozenSet[str]
  107. template, # type: InstallRequirement
  108. ):
  109. # type: (...) -> Candidate
  110. try:
  111. base = self._installed_candidate_cache[dist.key]
  112. except KeyError:
  113. base = AlreadyInstalledCandidate(dist, template, factory=self)
  114. self._installed_candidate_cache[dist.key] = base
  115. if extras:
  116. return ExtrasCandidate(base, extras)
  117. return base
  118. def _make_candidate_from_link(
  119. self,
  120. link, # type: Link
  121. extras, # type: FrozenSet[str]
  122. template, # type: InstallRequirement
  123. name, # type: Optional[str]
  124. version, # type: Optional[_BaseVersion]
  125. ):
  126. # type: (...) -> Optional[Candidate]
  127. # TODO: Check already installed candidate, and use it if the link and
  128. # editable flag match.
  129. if link in self._build_failures:
  130. # We already tried this candidate before, and it does not build.
  131. # Don't bother trying again.
  132. return None
  133. if template.editable:
  134. if link not in self._editable_candidate_cache:
  135. try:
  136. self._editable_candidate_cache[link] = EditableCandidate(
  137. link, template, factory=self,
  138. name=name, version=version,
  139. )
  140. except (InstallationSubprocessError, MetadataInconsistent) as e:
  141. logger.warning("Discarding %s. %s", link, e)
  142. self._build_failures[link] = e
  143. return None
  144. base = self._editable_candidate_cache[link] # type: BaseCandidate
  145. else:
  146. if link not in self._link_candidate_cache:
  147. try:
  148. self._link_candidate_cache[link] = LinkCandidate(
  149. link, template, factory=self,
  150. name=name, version=version,
  151. )
  152. except (InstallationSubprocessError, MetadataInconsistent) as e:
  153. logger.warning("Discarding %s. %s", link, e)
  154. self._build_failures[link] = e
  155. return None
  156. base = self._link_candidate_cache[link]
  157. if extras:
  158. return ExtrasCandidate(base, extras)
  159. return base
  160. def _iter_found_candidates(
  161. self,
  162. ireqs, # type: Sequence[InstallRequirement]
  163. specifier, # type: SpecifierSet
  164. hashes, # type: Hashes
  165. prefers_installed, # type: bool
  166. ):
  167. # type: (...) -> Iterable[Candidate]
  168. if not ireqs:
  169. return ()
  170. # The InstallRequirement implementation requires us to give it a
  171. # "template". Here we just choose the first requirement to represent
  172. # all of them.
  173. # Hopefully the Project model can correct this mismatch in the future.
  174. template = ireqs[0]
  175. name = canonicalize_name(template.req.name)
  176. extras = frozenset() # type: FrozenSet[str]
  177. for ireq in ireqs:
  178. specifier &= ireq.req.specifier
  179. hashes &= ireq.hashes(trust_internet=False)
  180. extras |= frozenset(ireq.extras)
  181. # Get the installed version, if it matches, unless the user
  182. # specified `--force-reinstall`, when we want the version from
  183. # the index instead.
  184. installed_candidate = None
  185. if not self._force_reinstall and name in self._installed_dists:
  186. installed_dist = self._installed_dists[name]
  187. if specifier.contains(installed_dist.version, prereleases=True):
  188. installed_candidate = self._make_candidate_from_dist(
  189. dist=installed_dist,
  190. extras=extras,
  191. template=template,
  192. )
  193. def iter_index_candidates():
  194. # type: () -> Iterator[Candidate]
  195. result = self._finder.find_best_candidate(
  196. project_name=name,
  197. specifier=specifier,
  198. hashes=hashes,
  199. )
  200. icans = list(result.iter_applicable())
  201. # PEP 592: Yanked releases must be ignored unless only yanked
  202. # releases can satisfy the version range. So if this is false,
  203. # all yanked icans need to be skipped.
  204. all_yanked = all(ican.link.is_yanked for ican in icans)
  205. # PackageFinder returns earlier versions first, so we reverse.
  206. versions_found = set() # type: Set[_BaseVersion]
  207. for ican in reversed(icans):
  208. if not all_yanked and ican.link.is_yanked:
  209. continue
  210. if ican.version in versions_found:
  211. continue
  212. candidate = self._make_candidate_from_link(
  213. link=ican.link,
  214. extras=extras,
  215. template=template,
  216. name=name,
  217. version=ican.version,
  218. )
  219. if candidate is None:
  220. continue
  221. yield candidate
  222. versions_found.add(ican.version)
  223. return FoundCandidates(
  224. iter_index_candidates,
  225. installed_candidate,
  226. prefers_installed,
  227. )
  228. def find_candidates(
  229. self,
  230. requirements, # type: Sequence[Requirement]
  231. constraint, # type: Constraint
  232. prefers_installed, # type: bool
  233. ):
  234. # type: (...) -> Iterable[Candidate]
  235. explicit_candidates = set() # type: Set[Candidate]
  236. ireqs = [] # type: List[InstallRequirement]
  237. for req in requirements:
  238. cand, ireq = req.get_candidate_lookup()
  239. if cand is not None:
  240. explicit_candidates.add(cand)
  241. if ireq is not None:
  242. ireqs.append(ireq)
  243. # If none of the requirements want an explicit candidate, we can ask
  244. # the finder for candidates.
  245. if not explicit_candidates:
  246. return self._iter_found_candidates(
  247. ireqs,
  248. constraint.specifier,
  249. constraint.hashes,
  250. prefers_installed,
  251. )
  252. return (
  253. c for c in explicit_candidates
  254. if constraint.is_satisfied_by(c)
  255. and all(req.is_satisfied_by(c) for req in requirements)
  256. )
  257. def make_requirement_from_install_req(self, ireq, requested_extras):
  258. # type: (InstallRequirement, Iterable[str]) -> Optional[Requirement]
  259. if not ireq.match_markers(requested_extras):
  260. logger.info(
  261. "Ignoring %s: markers '%s' don't match your environment",
  262. ireq.name, ireq.markers,
  263. )
  264. return None
  265. if not ireq.link:
  266. return SpecifierRequirement(ireq)
  267. if ireq.link.is_wheel:
  268. wheel = Wheel(ireq.link.filename)
  269. if not wheel.supported(self._finder.target_python.get_tags()):
  270. msg = "{} is not a supported wheel on this platform.".format(
  271. wheel.filename,
  272. )
  273. raise UnsupportedWheel(msg)
  274. cand = self._make_candidate_from_link(
  275. ireq.link,
  276. extras=frozenset(ireq.extras),
  277. template=ireq,
  278. name=canonicalize_name(ireq.name) if ireq.name else None,
  279. version=None,
  280. )
  281. if cand is None:
  282. # There's no way we can satisfy a URL requirement if the underlying
  283. # candidate fails to build. An unnamed URL must be user-supplied, so
  284. # we fail eagerly. If the URL is named, an unsatisfiable requirement
  285. # can make the resolver do the right thing, either backtrack (and
  286. # maybe find some other requirement that's buildable) or raise a
  287. # ResolutionImpossible eventually.
  288. if not ireq.name:
  289. raise self._build_failures[ireq.link]
  290. return UnsatisfiableRequirement(canonicalize_name(ireq.name))
  291. return self.make_requirement_from_candidate(cand)
  292. def make_requirement_from_candidate(self, candidate):
  293. # type: (Candidate) -> ExplicitRequirement
  294. return ExplicitRequirement(candidate)
  295. def make_requirement_from_spec(
  296. self,
  297. specifier, # type: str
  298. comes_from, # type: InstallRequirement
  299. requested_extras=(), # type: Iterable[str]
  300. ):
  301. # type: (...) -> Optional[Requirement]
  302. ireq = self._make_install_req_from_spec(specifier, comes_from)
  303. return self.make_requirement_from_install_req(ireq, requested_extras)
  304. def make_requires_python_requirement(self, specifier):
  305. # type: (Optional[SpecifierSet]) -> Optional[Requirement]
  306. if self._ignore_requires_python or specifier is None:
  307. return None
  308. return RequiresPythonRequirement(specifier, self._python_candidate)
  309. def get_wheel_cache_entry(self, link, name):
  310. # type: (Link, Optional[str]) -> Optional[CacheEntry]
  311. """Look up the link in the wheel cache.
  312. If ``preparer.require_hashes`` is True, don't use the wheel cache,
  313. because cached wheels, always built locally, have different hashes
  314. than the files downloaded from the index server and thus throw false
  315. hash mismatches. Furthermore, cached wheels at present have
  316. nondeterministic contents due to file modification times.
  317. """
  318. if self._wheel_cache is None or self.preparer.require_hashes:
  319. return None
  320. return self._wheel_cache.get_cache_entry(
  321. link=link,
  322. package_name=name,
  323. supported_tags=get_supported(),
  324. )
  325. def get_dist_to_uninstall(self, candidate):
  326. # type: (Candidate) -> Optional[Distribution]
  327. # TODO: Are there more cases this needs to return True? Editable?
  328. dist = self._installed_dists.get(candidate.name)
  329. if dist is None: # Not installed, no uninstallation required.
  330. return None
  331. # We're installing into global site. The current installation must
  332. # be uninstalled, no matter it's in global or user site, because the
  333. # user site installation has precedence over global.
  334. if not self._use_user_site:
  335. return dist
  336. # We're installing into user site. Remove the user site installation.
  337. if dist_in_usersite(dist):
  338. return dist
  339. # We're installing into user site, but the installed incompatible
  340. # package is in global site. We can't uninstall that, and would let
  341. # the new user installation to "shadow" it. But shadowing won't work
  342. # in virtual environments, so we error out.
  343. if running_under_virtualenv() and dist_in_site_packages(dist):
  344. raise InstallationError(
  345. "Will not install to the user site because it will "
  346. "lack sys.path precedence to {} in {}".format(
  347. dist.project_name, dist.location,
  348. )
  349. )
  350. return None
  351. def _report_requires_python_error(
  352. self,
  353. requirement, # type: RequiresPythonRequirement
  354. template, # type: Candidate
  355. ):
  356. # type: (...) -> UnsupportedPythonVersion
  357. message_format = (
  358. "Package {package!r} requires a different Python: "
  359. "{version} not in {specifier!r}"
  360. )
  361. message = message_format.format(
  362. package=template.name,
  363. version=self._python_candidate.version,
  364. specifier=str(requirement.specifier),
  365. )
  366. return UnsupportedPythonVersion(message)
  367. def get_installation_error(self, e):
  368. # type: (ResolutionImpossible) -> InstallationError
  369. assert e.causes, "Installation error reported with no cause"
  370. # If one of the things we can't solve is "we need Python X.Y",
  371. # that is what we report.
  372. for cause in e.causes:
  373. if isinstance(cause.requirement, RequiresPythonRequirement):
  374. return self._report_requires_python_error(
  375. cause.requirement,
  376. cause.parent,
  377. )
  378. # Otherwise, we have a set of causes which can't all be satisfied
  379. # at once.
  380. # The simplest case is when we have *one* cause that can't be
  381. # satisfied. We just report that case.
  382. if len(e.causes) == 1:
  383. req, parent = e.causes[0]
  384. if parent is None:
  385. req_disp = str(req)
  386. else:
  387. req_disp = '{} (from {})'.format(req, parent.name)
  388. logger.critical(
  389. "Could not find a version that satisfies the requirement %s",
  390. req_disp,
  391. )
  392. return DistributionNotFound(
  393. 'No matching distribution found for {}'.format(req)
  394. )
  395. # OK, we now have a list of requirements that can't all be
  396. # satisfied at once.
  397. # A couple of formatting helpers
  398. def text_join(parts):
  399. # type: (List[str]) -> str
  400. if len(parts) == 1:
  401. return parts[0]
  402. return ", ".join(parts[:-1]) + " and " + parts[-1]
  403. def describe_trigger(parent):
  404. # type: (Candidate) -> str
  405. ireq = parent.get_install_requirement()
  406. if not ireq or not ireq.comes_from:
  407. return "{}=={}".format(parent.name, parent.version)
  408. if isinstance(ireq.comes_from, InstallRequirement):
  409. return str(ireq.comes_from.name)
  410. return str(ireq.comes_from)
  411. triggers = set()
  412. for req, parent in e.causes:
  413. if parent is None:
  414. # This is a root requirement, so we can report it directly
  415. trigger = req.format_for_error()
  416. else:
  417. trigger = describe_trigger(parent)
  418. triggers.add(trigger)
  419. if triggers:
  420. info = text_join(sorted(triggers))
  421. else:
  422. info = "the requested packages"
  423. msg = "Cannot install {} because these package versions " \
  424. "have conflicting dependencies.".format(info)
  425. logger.critical(msg)
  426. msg = "\nThe conflict is caused by:"
  427. for req, parent in e.causes:
  428. msg = msg + "\n "
  429. if parent:
  430. msg = msg + "{} {} depends on ".format(
  431. parent.name,
  432. parent.version
  433. )
  434. else:
  435. msg = msg + "The user requested "
  436. msg = msg + req.format_for_error()
  437. msg = msg + "\n\n" + \
  438. "To fix this you could try to:\n" + \
  439. "1. loosen the range of package versions you've specified\n" + \
  440. "2. remove package versions to allow pip attempt to solve " + \
  441. "the dependency conflict\n"
  442. logger.info(msg)
  443. return DistributionNotFound(
  444. "ResolutionImpossible: for help visit "
  445. "https://pip.pypa.io/en/latest/user_guide/"
  446. "#fixing-conflicting-dependencies"
  447. )