From e9d570e6260b33706842447d60632a86dc4de0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 24 Sep 2021 22:46:21 +0200 Subject: [PATCH] fixup! ae0bda5e6ed492c4b24c90b42317dd0adf338800 --- src/pip/_internal/distributions/sdist.py | 72 +++++++++++-------- .../_internal/operations/build/metadata.py | 17 ++--- .../operations/build/metadata_editable.py | 32 +++++++++ src/pip/_internal/operations/build/wheel.py | 35 ++------- .../operations/build/wheel_editable.py | 46 ++++++++++++ src/pip/_internal/req/req_install.py | 17 ++--- src/pip/_internal/wheel_builder.py | 22 ++++-- 7 files changed, 157 insertions(+), 84 deletions(-) create mode 100644 src/pip/_internal/operations/build/metadata_editable.py create mode 100644 src/pip/_internal/operations/build/wheel_editable.py diff --git a/src/pip/_internal/distributions/sdist.py b/src/pip/_internal/distributions/sdist.py index 1deb525d7c2..27a09ea2303 100644 --- a/src/pip/_internal/distributions/sdist.py +++ b/src/pip/_internal/distributions/sdist.py @@ -1,5 +1,5 @@ import logging -from typing import Set, Tuple +from typing import Iterable, Set, Tuple from pip._internal.build_env import BuildEnvironment from pip._internal.distributions.base import AbstractDistribution @@ -37,23 +37,17 @@ def prepare_distribution_metadata( self.req.prepare_metadata() def _setup_isolation(self, finder: PackageFinder) -> None: - def _raise_conflicts( - conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]] - ) -> None: - format_string = ( - "Some build dependencies for {requirement} " - "conflict with {conflicting_with}: {description}." - ) - error_message = format_string.format( - requirement=self.req, - conflicting_with=conflicting_with, - description=", ".join( - f"{installed} is incompatible with {wanted}" - for installed, wanted in sorted(conflicting) - ), - ) - raise InstallationError(error_message) + self._prepare_build_backend(finder) + # Install any extra build dependencies that the backend requests. + # This must be done in a second pass, as the pyproject.toml + # dependencies must be installed before we can call the backend. + if self.req.editable and not self.req.no_build_editable: + build_reqs = self._get_build_requires_editable() + else: + build_reqs = self._get_build_requires_wheel() + self._install_build_reqs(finder, build_reqs) + def _prepare_build_backend(self, finder: PackageFinder) -> None: # Isolate in a BuildEnvironment and install the build-time # requirements. pyproject_requires = self.req.pyproject_requires @@ -67,7 +61,7 @@ def _raise_conflicts( self.req.requirements_to_check ) if conflicting: - _raise_conflicts("PEP 517/518 supported requirements", conflicting) + self._raise_conflicts("PEP 517/518 supported requirements", conflicting) if missing: logger.warning( "Missing build requirements in pyproject.toml for %s.", @@ -78,26 +72,46 @@ def _raise_conflicts( "pip cannot fall back to setuptools without %s.", " and ".join(map(repr, sorted(missing))), ) - # Install any extra build dependencies that the backend requests. - # This must be done in a second pass, as the pyproject.toml - # dependencies must be installed before we can call the backend. + + def _get_build_requires_wheel(self) -> Iterable[str]: + with self.req.build_env: + runner = runner_with_spinner_message("Getting requirements to build wheel") + backend = self.req.pep517_backend + assert backend is not None + with backend.subprocess_runner(runner): + return backend.get_requires_for_build_wheel() + + def _get_build_requires_editable(self) -> Iterable[str]: with self.req.build_env: runner = runner_with_spinner_message( - "Getting requirements to build {}".format( - "editable" if self.req.editable else "wheel" - ) + "Getting requirements to build editable" ) backend = self.req.pep517_backend assert backend is not None with backend.subprocess_runner(runner): - if self.req.editable and not self.req.no_build_editable: - reqs = backend.get_requires_for_build_editable() - else: - reqs = backend.get_requires_for_build_wheel() + return backend.get_requires_for_build_editable() + def _install_build_reqs(self, finder: PackageFinder, reqs: Iterable[str]) -> None: conflicting, missing = self.req.build_env.check_requirements(reqs) if conflicting: - _raise_conflicts("the backend dependencies", conflicting) + self._raise_conflicts("the backend dependencies", conflicting) self.req.build_env.install_requirements( finder, missing, "normal", "Installing backend dependencies" ) + + def _raise_conflicts( + self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]] + ) -> None: + format_string = ( + "Some build dependencies for {requirement} " + "conflict with {conflicting_with}: {description}." + ) + error_message = format_string.format( + requirement=self.req, + conflicting_with=conflicting_with, + description=", ".join( + f"{installed} is incompatible with {wanted}" + for installed, wanted in sorted(conflicting_reqs) + ), + ) + raise InstallationError(error_message) diff --git a/src/pip/_internal/operations/build/metadata.py b/src/pip/_internal/operations/build/metadata.py index 49013d56288..e7672352715 100644 --- a/src/pip/_internal/operations/build/metadata.py +++ b/src/pip/_internal/operations/build/metadata.py @@ -10,10 +10,8 @@ from pip._internal.utils.temp_dir import TempDirectory -def generate_metadata( - build_env: BuildEnvironment, backend: Pep517HookCaller, editable: bool -) -> str: - """Generate metadata using mechanisms described in PEP 517 and PEP 660. +def generate_metadata(build_env: BuildEnvironment, backend: Pep517HookCaller) -> str: + """Generate metadata using mechanisms described in PEP 517. Returns the generated metadata directory. """ @@ -23,15 +21,10 @@ def generate_metadata( with build_env: # Note that Pep517HookCaller implements a fallback for - # prepare_metadata_for_build_wheel/editable, so we don't have to + # prepare_metadata_for_build_wheel, so we don't have to # consider the possibility that this hook doesn't exist. - runner = runner_with_spinner_message( - "Preparing {} metadata".format("editable" if editable else "wheel") - ) + runner = runner_with_spinner_message("Preparing wheel metadata") with backend.subprocess_runner(runner): - if editable: - distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir) - else: - distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir) + distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir) return os.path.join(metadata_dir, distinfo_dir) diff --git a/src/pip/_internal/operations/build/metadata_editable.py b/src/pip/_internal/operations/build/metadata_editable.py new file mode 100644 index 00000000000..321be2854d9 --- /dev/null +++ b/src/pip/_internal/operations/build/metadata_editable.py @@ -0,0 +1,32 @@ +"""Metadata generation logic for source distributions. +""" + +import os + +from pip._vendor.pep517.wrappers import Pep517HookCaller + +from pip._internal.build_env import BuildEnvironment +from pip._internal.utils.subprocess import runner_with_spinner_message +from pip._internal.utils.temp_dir import TempDirectory + + +def generate_editable_metadata( + build_env: BuildEnvironment, backend: Pep517HookCaller +) -> str: + """Generate metadata using mechanisms described in PEP 517 and PEP 660. + + Returns the generated metadata directory. + """ + metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True) + + metadata_dir = metadata_tmpdir.path + + with build_env: + # Note that Pep517HookCaller implements a fallback for + # prepare_metadata_for_build_wheel/editable, so we don't have to + # consider the possibility that this hook doesn't exist. + runner = runner_with_spinner_message("Preparing editable metadata") + with backend.subprocess_runner(runner): + distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir) + + return os.path.join(metadata_dir, distinfo_dir) diff --git a/src/pip/_internal/operations/build/wheel.py b/src/pip/_internal/operations/build/wheel.py index c913e22018a..6249a9bfdb1 100644 --- a/src/pip/_internal/operations/build/wheel.py +++ b/src/pip/_internal/operations/build/wheel.py @@ -2,7 +2,7 @@ import os from typing import Optional -from pip._vendor.pep517.wrappers import HookMissing, Pep517HookCaller +from pip._vendor.pep517.wrappers import Pep517HookCaller from pip._internal.utils.subprocess import runner_with_spinner_message @@ -14,7 +14,6 @@ def build_wheel_pep517( backend: Pep517HookCaller, metadata_directory: str, tempd: str, - editable: bool, ) -> Optional[str]: """Build one InstallRequirement using the PEP 517 build process. @@ -24,33 +23,13 @@ def build_wheel_pep517( try: logger.debug("Destination directory: %s", tempd) - artifact = "editable" if editable else "wheel" - pep = "660" if editable else "517" - runner = runner_with_spinner_message( - f"Building {artifact} for {name} (PEP {pep})" - ) + runner = runner_with_spinner_message(f"Building wheel for {name} (PEP 517)") with backend.subprocess_runner(runner): - if editable: - try: - wheel_name = backend.build_editable( - tempd, - metadata_directory=metadata_directory, - ) - except HookMissing as e: - logger.warning( - "Cannot build %s %s because the build " - "backend does not have the %s hook", - artifact, - name, - e, - ) - return None - else: - wheel_name = backend.build_wheel( - tempd, - metadata_directory=metadata_directory, - ) + wheel_name = backend.build_wheel( + tempd, + metadata_directory=metadata_directory, + ) except Exception: - logger.exception("Failed building %s for %s", artifact, name, exc_info=True) + logger.error("Failed building wheel for %s", name) return None return os.path.join(tempd, wheel_name) diff --git a/src/pip/_internal/operations/build/wheel_editable.py b/src/pip/_internal/operations/build/wheel_editable.py new file mode 100644 index 00000000000..52ef7018ad9 --- /dev/null +++ b/src/pip/_internal/operations/build/wheel_editable.py @@ -0,0 +1,46 @@ +import logging +import os +from typing import Optional + +from pip._vendor.pep517.wrappers import HookMissing, Pep517HookCaller + +from pip._internal.utils.subprocess import runner_with_spinner_message + +logger = logging.getLogger(__name__) + + +def build_editable_pep660( + name: str, + backend: Pep517HookCaller, + metadata_directory: str, + tempd: str, +) -> Optional[str]: + """Build one InstallRequirement using the PEP 660 build process. + + Returns path to wheel if successfully built. Otherwise, returns None. + """ + assert metadata_directory is not None + try: + logger.debug("Destination directory: %s", tempd) + + runner = runner_with_spinner_message( + f"Building editable for {name} (pyproject.toml)" + ) + with backend.subprocess_runner(runner): + try: + wheel_name = backend.build_editable( + tempd, + metadata_directory=metadata_directory, + ) + except HookMissing as e: + logger.warning( + "Cannot build editable %s because the build " + "backend does not have the %s hook", + name, + e, + ) + return None + except Exception: + logger.error("Failed building editable for %s", name) + return None + return os.path.join(tempd, wheel_name) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 0e360027bec..462b50a1bde 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -24,6 +24,7 @@ from pip._internal.locations import get_scheme from pip._internal.models.link import Link from pip._internal.operations.build.metadata import generate_metadata +from pip._internal.operations.build.metadata_editable import generate_editable_metadata from pip._internal.operations.build.metadata_legacy import ( generate_metadata as generate_metadata_legacy, ) @@ -195,7 +196,8 @@ def __init__( # Setting an explicit value before loading pyproject.toml is supported, # but after loading this flag should be treated as read only. self.use_pep517 = use_pep517 - # Supports_pep660 will be set to True or False when we try to prepare + + # supports_pep660 will be set to True or False when we try to prepare # editable metadata or build an editable wheel. None means "we don't know yet". self.supports_pep660: Optional[bool] = None @@ -501,18 +503,15 @@ def load_pyproject_toml(self) -> None: backend_path=backend_path, ) - def _generate_metadata_for_editable(self) -> str: + def _generate_editable_metadata(self) -> str: """Invokes metadata generator functions, with the required arguments.""" if self.use_pep517: assert self.pep517_backend is not None try: - metadata_directory = generate_metadata( + metadata_directory = generate_editable_metadata( build_env=self.build_env, backend=self.pep517_backend, - editable=True, ) - self.supports_pep660 = True - return metadata_directory except HookMissing as e: self.supports_pep660 = False if not os.path.exists(self.setup_py_path) and not os.path.exists( @@ -528,6 +527,9 @@ def _generate_metadata_for_editable(self) -> str: # At this point we have determined that the build_editable hook # is missing, and there is a setup.py or setup.cfg # so we fallback to the legacy metadata generation + else: + self.supports_pep660 = True + return metadata_directory else: if not os.path.exists(self.setup_py_path) and not os.path.exists( self.setup_cfg_path @@ -554,7 +556,6 @@ def _generate_metadata(self) -> str: return generate_metadata( build_env=self.build_env, backend=self.pep517_backend, - editable=False, ) except HookMissing as e: raise InstallationError( @@ -584,7 +585,7 @@ def prepare_metadata(self) -> None: with indent_log(): if self.editable and not self.no_build_editable: - self.metadata_directory = self._generate_metadata_for_editable() + self.metadata_directory = self._generate_editable_metadata() else: self.metadata_directory = self._generate_metadata() diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index e7e94c41b96..1fc335bbbfe 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -16,6 +16,7 @@ from pip._internal.models.link import Link from pip._internal.models.wheel import Wheel from pip._internal.operations.build.wheel import build_wheel_pep517 +from pip._internal.operations.build.wheel_editable import build_editable_pep660 from pip._internal.operations.build.wheel_legacy import build_wheel_legacy from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.logging import indent_log @@ -252,13 +253,20 @@ def _build_one_inside_env( logger.warning( "Ignoring --build-option when building %s using PEP 517", req.name ) - wheel_path = build_wheel_pep517( - name=req.name, - backend=req.pep517_backend, - metadata_directory=req.metadata_directory, - tempd=temp_dir.path, - editable=editable, - ) + if editable: + wheel_path = build_editable_pep660( + name=req.name, + backend=req.pep517_backend, + metadata_directory=req.metadata_directory, + tempd=temp_dir.path, + ) + else: + wheel_path = build_wheel_pep517( + name=req.name, + backend=req.pep517_backend, + metadata_directory=req.metadata_directory, + tempd=temp_dir.path, + ) else: wheel_path = build_wheel_legacy( name=req.name,