sdist.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import logging
  2. from typing import Iterable, Set, Tuple
  3. from pip._internal.build_env import BuildEnvironment
  4. from pip._internal.distributions.base import AbstractDistribution
  5. from pip._internal.exceptions import InstallationError
  6. from pip._internal.index.package_finder import PackageFinder
  7. from pip._internal.metadata import BaseDistribution
  8. from pip._internal.utils.subprocess import runner_with_spinner_message
  9. logger = logging.getLogger(__name__)
  10. class SourceDistribution(AbstractDistribution):
  11. """Represents a source distribution.
  12. The preparation step for these needs metadata for the packages to be
  13. generated, either using PEP 517 or using the legacy `setup.py egg_info`.
  14. """
  15. def get_metadata_distribution(self) -> BaseDistribution:
  16. return self.req.get_dist()
  17. def prepare_distribution_metadata(
  18. self, finder: PackageFinder, build_isolation: bool
  19. ) -> None:
  20. # Load pyproject.toml, to determine whether PEP 517 is to be used
  21. self.req.load_pyproject_toml()
  22. # Set up the build isolation, if this requirement should be isolated
  23. should_isolate = self.req.use_pep517 and build_isolation
  24. if should_isolate:
  25. # Setup an isolated environment and install the build backend static
  26. # requirements in it.
  27. self._prepare_build_backend(finder)
  28. # Check that if the requirement is editable, it either supports PEP 660 or
  29. # has a setup.py or a setup.cfg. This cannot be done earlier because we need
  30. # to setup the build backend to verify it supports build_editable, nor can
  31. # it be done later, because we want to avoid installing build requirements
  32. # needlessly. Doing it here also works around setuptools generating
  33. # UNKNOWN.egg-info when running get_requires_for_build_wheel on a directory
  34. # without setup.py nor setup.cfg.
  35. self.req.isolated_editable_sanity_check()
  36. # Install the dynamic build requirements.
  37. self._install_build_reqs(finder)
  38. self.req.prepare_metadata()
  39. def _prepare_build_backend(self, finder: PackageFinder) -> None:
  40. # Isolate in a BuildEnvironment and install the build-time
  41. # requirements.
  42. pyproject_requires = self.req.pyproject_requires
  43. assert pyproject_requires is not None
  44. self.req.build_env = BuildEnvironment()
  45. self.req.build_env.install_requirements(
  46. finder, pyproject_requires, "overlay", kind="build dependencies"
  47. )
  48. conflicting, missing = self.req.build_env.check_requirements(
  49. self.req.requirements_to_check
  50. )
  51. if conflicting:
  52. self._raise_conflicts("PEP 517/518 supported requirements", conflicting)
  53. if missing:
  54. logger.warning(
  55. "Missing build requirements in pyproject.toml for %s.",
  56. self.req,
  57. )
  58. logger.warning(
  59. "The project does not specify a build backend, and "
  60. "pip cannot fall back to setuptools without %s.",
  61. " and ".join(map(repr, sorted(missing))),
  62. )
  63. def _get_build_requires_wheel(self) -> Iterable[str]:
  64. with self.req.build_env:
  65. runner = runner_with_spinner_message("Getting requirements to build wheel")
  66. backend = self.req.pep517_backend
  67. assert backend is not None
  68. with backend.subprocess_runner(runner):
  69. return backend.get_requires_for_build_wheel()
  70. def _get_build_requires_editable(self) -> Iterable[str]:
  71. with self.req.build_env:
  72. runner = runner_with_spinner_message(
  73. "Getting requirements to build editable"
  74. )
  75. backend = self.req.pep517_backend
  76. assert backend is not None
  77. with backend.subprocess_runner(runner):
  78. return backend.get_requires_for_build_editable()
  79. def _install_build_reqs(self, finder: PackageFinder) -> None:
  80. # Install any extra build dependencies that the backend requests.
  81. # This must be done in a second pass, as the pyproject.toml
  82. # dependencies must be installed before we can call the backend.
  83. if (
  84. self.req.editable
  85. and self.req.permit_editable_wheels
  86. and self.req.supports_pyproject_editable()
  87. ):
  88. build_reqs = self._get_build_requires_editable()
  89. else:
  90. build_reqs = self._get_build_requires_wheel()
  91. conflicting, missing = self.req.build_env.check_requirements(build_reqs)
  92. if conflicting:
  93. self._raise_conflicts("the backend dependencies", conflicting)
  94. self.req.build_env.install_requirements(
  95. finder, missing, "normal", kind="backend dependencies"
  96. )
  97. def _raise_conflicts(
  98. self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]]
  99. ) -> None:
  100. format_string = (
  101. "Some build dependencies for {requirement} "
  102. "conflict with {conflicting_with}: {description}."
  103. )
  104. error_message = format_string.format(
  105. requirement=self.req,
  106. conflicting_with=conflicting_with,
  107. description=", ".join(
  108. f"{installed} is incompatible with {wanted}"
  109. for installed, wanted in sorted(conflicting_reqs)
  110. ),
  111. )
  112. raise InstallationError(error_message)