Skip to content

Commit

Permalink
fixup! ae0bda5
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed Sep 24, 2021
1 parent 134fa1d commit e9d570e
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 84 deletions.
72 changes: 43 additions & 29 deletions src/pip/_internal/distributions/sdist.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.",
Expand All @@ -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)
17 changes: 5 additions & 12 deletions src/pip/_internal/operations/build/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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)
32 changes: 32 additions & 0 deletions src/pip/_internal/operations/build/metadata_editable.py
Original file line number Diff line number Diff line change
@@ -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)
35 changes: 7 additions & 28 deletions src/pip/_internal/operations/build/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand All @@ -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)
46 changes: 46 additions & 0 deletions src/pip/_internal/operations/build/wheel_editable.py
Original file line number Diff line number Diff line change
@@ -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)
17 changes: 9 additions & 8 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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()

Expand Down
22 changes: 15 additions & 7 deletions src/pip/_internal/wheel_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit e9d570e

Please sign in to comment.