first commit
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,140 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
from types import TracebackType
|
||||
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
|
||||
target = os.environ
|
||||
|
||||
# Save values from the target and change them.
|
||||
non_existent_marker = object()
|
||||
saved_values: dict[str, object | str] = {}
|
||||
for name, new_value in changes.items():
|
||||
try:
|
||||
saved_values[name] = target[name]
|
||||
except KeyError:
|
||||
saved_values[name] = non_existent_marker
|
||||
target[name] = new_value
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
# Restore original values in the target.
|
||||
for name, original_value in saved_values.items():
|
||||
if original_value is non_existent_marker:
|
||||
del target[name]
|
||||
else:
|
||||
assert isinstance(original_value, str) # for mypy
|
||||
target[name] = original_value
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def get_build_tracker() -> Generator[BuildTracker, None, None]:
|
||||
root = os.environ.get("PIP_BUILD_TRACKER")
|
||||
with contextlib.ExitStack() as ctx:
|
||||
if root is None:
|
||||
root = ctx.enter_context(TempDirectory(kind="build-tracker")).path
|
||||
ctx.enter_context(update_env_context_manager(PIP_BUILD_TRACKER=root))
|
||||
logger.debug("Initialized build tracking at %s", root)
|
||||
|
||||
with BuildTracker(root) as tracker:
|
||||
yield tracker
|
||||
|
||||
|
||||
class TrackerId(str):
|
||||
"""Uniquely identifying string provided to the build tracker."""
|
||||
|
||||
|
||||
class BuildTracker:
|
||||
"""Ensure that an sdist cannot request itself as a setup requirement.
|
||||
|
||||
When an sdist is prepared, it identifies its setup requirements in the
|
||||
context of ``BuildTracker.track()``. If a requirement shows up recursively, this
|
||||
raises an exception.
|
||||
|
||||
This stops fork bombs embedded in malicious packages."""
|
||||
|
||||
def __init__(self, root: str) -> None:
|
||||
self._root = root
|
||||
self._entries: dict[TrackerId, InstallRequirement] = {}
|
||||
logger.debug("Created build tracker: %s", self._root)
|
||||
|
||||
def __enter__(self) -> BuildTracker:
|
||||
logger.debug("Entered build tracker: %s", self._root)
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
self.cleanup()
|
||||
|
||||
def _entry_path(self, key: TrackerId) -> str:
|
||||
hashed = hashlib.sha224(key.encode()).hexdigest()
|
||||
return os.path.join(self._root, hashed)
|
||||
|
||||
def add(self, req: InstallRequirement, key: TrackerId) -> None:
|
||||
"""Add an InstallRequirement to build tracking."""
|
||||
|
||||
# Get the file to write information about this requirement.
|
||||
entry_path = self._entry_path(key)
|
||||
|
||||
# Try reading from the file. If it exists and can be read from, a build
|
||||
# is already in progress, so a LookupError is raised.
|
||||
try:
|
||||
with open(entry_path) as fp:
|
||||
contents = fp.read()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
else:
|
||||
message = f"{req.link} is already being built: {contents}"
|
||||
raise LookupError(message)
|
||||
|
||||
# If we're here, req should really not be building already.
|
||||
assert key not in self._entries
|
||||
|
||||
# Start tracking this requirement.
|
||||
with open(entry_path, "w", encoding="utf-8") as fp:
|
||||
fp.write(str(req))
|
||||
self._entries[key] = req
|
||||
|
||||
logger.debug("Added %s to build tracker %r", req, self._root)
|
||||
|
||||
def remove(self, req: InstallRequirement, key: TrackerId) -> None:
|
||||
"""Remove an InstallRequirement from build tracking."""
|
||||
|
||||
# Delete the created file and the corresponding entry.
|
||||
os.unlink(self._entry_path(key))
|
||||
del self._entries[key]
|
||||
|
||||
logger.debug("Removed %s from build tracker %r", req, self._root)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
for key, req in list(self._entries.items()):
|
||||
self.remove(req, key)
|
||||
|
||||
logger.debug("Removed build tracker: %r", self._root)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def track(self, req: InstallRequirement, key: str) -> Generator[None, None, None]:
|
||||
"""Ensure that `key` cannot install itself as a setup requirement.
|
||||
|
||||
:raises LookupError: If `key` was already provided in a parent invocation of
|
||||
the context introduced by this method."""
|
||||
tracker_id = TrackerId(key)
|
||||
self.add(req, tracker_id)
|
||||
yield
|
||||
self.remove(req, tracker_id)
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Metadata generation logic for source distributions."""
|
||||
|
||||
import os
|
||||
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import (
|
||||
InstallationSubprocessError,
|
||||
MetadataGenerationFailed,
|
||||
)
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
|
||||
def generate_metadata(
|
||||
build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
|
||||
) -> str:
|
||||
"""Generate metadata using mechanisms described in PEP 517.
|
||||
|
||||
Returns the generated metadata directory.
|
||||
"""
|
||||
metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
|
||||
|
||||
metadata_dir = metadata_tmpdir.path
|
||||
|
||||
with build_env:
|
||||
# Note that BuildBackendHookCaller implements a fallback for
|
||||
# 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 (pyproject.toml)")
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
|
||||
except InstallationSubprocessError as error:
|
||||
raise MetadataGenerationFailed(package_details=details) from error
|
||||
|
||||
return os.path.join(metadata_dir, distinfo_dir)
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Metadata generation logic for source distributions."""
|
||||
|
||||
import os
|
||||
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import (
|
||||
InstallationSubprocessError,
|
||||
MetadataGenerationFailed,
|
||||
)
|
||||
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: BuildBackendHookCaller, details: str
|
||||
) -> str:
|
||||
"""Generate metadata using mechanisms described in 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 BuildBackendHookCaller 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 (pyproject.toml)"
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir)
|
||||
except InstallationSubprocessError as error:
|
||||
raise MetadataGenerationFailed(package_details=details) from error
|
||||
|
||||
assert distinfo_dir is not None
|
||||
return os.path.join(metadata_dir, distinfo_dir)
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def build_wheel_pep517(
|
||||
name: str,
|
||||
backend: BuildBackendHookCaller,
|
||||
metadata_directory: str,
|
||||
wheel_directory: str,
|
||||
) -> str | None:
|
||||
"""Build one InstallRequirement using the PEP 517 build process.
|
||||
|
||||
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||
"""
|
||||
assert metadata_directory is not None
|
||||
try:
|
||||
logger.debug("Destination directory: %s", wheel_directory)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Building wheel for {name} (pyproject.toml)"
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
wheel_name = backend.build_wheel(
|
||||
wheel_directory=wheel_directory,
|
||||
metadata_directory=metadata_directory,
|
||||
)
|
||||
except Exception:
|
||||
logger.error("Failed building wheel for %s", name)
|
||||
return None
|
||||
return os.path.join(wheel_directory, wheel_name)
|
||||
@@ -0,0 +1,47 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller, HookMissing
|
||||
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def build_wheel_editable(
|
||||
name: str,
|
||||
backend: BuildBackendHookCaller,
|
||||
metadata_directory: str,
|
||||
wheel_directory: str,
|
||||
) -> str | None:
|
||||
"""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", wheel_directory)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Building editable for {name} (pyproject.toml)"
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
wheel_name = backend.build_editable(
|
||||
wheel_directory=wheel_directory,
|
||||
metadata_directory=metadata_directory,
|
||||
)
|
||||
except HookMissing as e:
|
||||
logger.error(
|
||||
"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(wheel_directory, wheel_name)
|
||||
Reference in New Issue
Block a user