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