resolver.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import functools
  2. import logging
  3. from pip._vendor import six
  4. from pip._vendor.packaging.utils import canonicalize_name
  5. from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
  6. from pip._vendor.resolvelib import Resolver as RLResolver
  7. from pip._internal.exceptions import InstallationError
  8. from pip._internal.req.req_set import RequirementSet
  9. from pip._internal.resolution.base import BaseResolver
  10. from pip._internal.resolution.resolvelib.provider import PipProvider
  11. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  12. from .factory import Factory
  13. if MYPY_CHECK_RUNNING:
  14. from typing import Dict, List, Optional, Tuple
  15. from pip._vendor.resolvelib.resolvers import Result
  16. from pip._internal.cache import WheelCache
  17. from pip._internal.index.package_finder import PackageFinder
  18. from pip._internal.operations.prepare import RequirementPreparer
  19. from pip._internal.req.req_install import InstallRequirement
  20. from pip._internal.resolution.base import InstallRequirementProvider
  21. logger = logging.getLogger(__name__)
  22. class Resolver(BaseResolver):
  23. def __init__(
  24. self,
  25. preparer, # type: RequirementPreparer
  26. finder, # type: PackageFinder
  27. wheel_cache, # type: Optional[WheelCache]
  28. make_install_req, # type: InstallRequirementProvider
  29. use_user_site, # type: bool
  30. ignore_dependencies, # type: bool
  31. ignore_installed, # type: bool
  32. ignore_requires_python, # type: bool
  33. force_reinstall, # type: bool
  34. upgrade_strategy, # type: str
  35. py_version_info=None, # type: Optional[Tuple[int, ...]]
  36. ):
  37. super(Resolver, self).__init__()
  38. self.factory = Factory(
  39. finder=finder,
  40. preparer=preparer,
  41. make_install_req=make_install_req,
  42. force_reinstall=force_reinstall,
  43. ignore_installed=ignore_installed,
  44. ignore_requires_python=ignore_requires_python,
  45. py_version_info=py_version_info,
  46. )
  47. self.ignore_dependencies = ignore_dependencies
  48. self._result = None # type: Optional[Result]
  49. def resolve(self, root_reqs, check_supported_wheels):
  50. # type: (List[InstallRequirement], bool) -> RequirementSet
  51. # FIXME: Implement constraints.
  52. if any(r.constraint for r in root_reqs):
  53. raise InstallationError("Constraints are not yet supported.")
  54. provider = PipProvider(
  55. factory=self.factory,
  56. ignore_dependencies=self.ignore_dependencies,
  57. )
  58. reporter = BaseReporter()
  59. resolver = RLResolver(provider, reporter)
  60. requirements = [
  61. self.factory.make_requirement_from_install_req(r)
  62. for r in root_reqs
  63. ]
  64. try:
  65. self._result = resolver.resolve(requirements)
  66. except ResolutionImpossible as e:
  67. error = self.factory.get_installation_error(e)
  68. if not error:
  69. # TODO: This needs fixing, we need to look at the
  70. # factory.get_installation_error infrastructure, as that
  71. # doesn't really allow for the logger.critical calls I'm
  72. # using here.
  73. for req, parent in e.causes:
  74. logger.critical(
  75. "Could not find a version that satisfies " +
  76. "the requirement " +
  77. str(req) +
  78. ("" if parent is None else " (from {})".format(
  79. parent.name
  80. ))
  81. )
  82. raise InstallationError(
  83. "No matching distribution found for " +
  84. ", ".join([r.name for r, _ in e.causes])
  85. )
  86. raise
  87. six.raise_from(error, e)
  88. req_set = RequirementSet(check_supported_wheels=check_supported_wheels)
  89. for candidate in self._result.mapping.values():
  90. ireq = provider.get_install_requirement(candidate)
  91. if ireq is None:
  92. continue
  93. ireq.should_reinstall = self.factory.should_reinstall(candidate)
  94. req_set.add_named_requirement(ireq)
  95. return req_set
  96. def get_installation_order(self, req_set):
  97. # type: (RequirementSet) -> List[InstallRequirement]
  98. """Create a list that orders given requirements for installation.
  99. The returned list should contain all requirements in ``req_set``,
  100. so the caller can loop through it and have a requirement installed
  101. before the requiring thing.
  102. The current implementation walks the resolved dependency graph, and
  103. make sure every node has a greater "weight" than all its parents.
  104. """
  105. assert self._result is not None, "must call resolve() first"
  106. weights = {} # type: Dict[Optional[str], int]
  107. graph = self._result.graph
  108. key_count = len(self._result.mapping) + 1 # Packages plus sentinal.
  109. while len(weights) < key_count:
  110. progressed = False
  111. for key in graph:
  112. if key in weights:
  113. continue
  114. parents = list(graph.iter_parents(key))
  115. if not all(p in weights for p in parents):
  116. continue
  117. if parents:
  118. weight = max(weights[p] for p in parents) + 1
  119. else:
  120. weight = 0
  121. weights[key] = weight
  122. progressed = True
  123. # FIXME: This check will fail if there are unbreakable cycles.
  124. # Implement something to forcifully break them up to continue.
  125. if not progressed:
  126. raise InstallationError(
  127. "Could not determine installation order due to cicular "
  128. "dependency."
  129. )
  130. sorted_items = sorted(
  131. req_set.requirements.items(),
  132. key=functools.partial(_req_set_item_sorter, weights=weights),
  133. reverse=True,
  134. )
  135. return [ireq for _, ireq in sorted_items]
  136. def _req_set_item_sorter(
  137. item, # type: Tuple[str, InstallRequirement]
  138. weights, # type: Dict[Optional[str], int]
  139. ):
  140. # type: (...) -> Tuple[int, str]
  141. """Key function used to sort install requirements for installation.
  142. Based on the "weight" mapping calculated in ``get_installation_order()``.
  143. The canonical package name is returned as the second member as a tie-
  144. breaker to ensure the result is predictable, which is useful in tests.
  145. """
  146. name = canonicalize_name(item[0])
  147. return weights[name], name