first commit
This commit is contained in:
commit
417e54da96
5696 changed files with 900003 additions and 0 deletions
|
@ -0,0 +1,31 @@
|
|||
"""Wrappers to call pyproject.toml-based build backend hooks.
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ._impl import (
|
||||
BackendUnavailable,
|
||||
BuildBackendHookCaller,
|
||||
HookMissing,
|
||||
UnsupportedOperation,
|
||||
default_subprocess_runner,
|
||||
quiet_subprocess_runner,
|
||||
)
|
||||
|
||||
__version__ = "1.1.0"
|
||||
__all__ = [
|
||||
"BackendUnavailable",
|
||||
"BackendInvalid",
|
||||
"HookMissing",
|
||||
"UnsupportedOperation",
|
||||
"default_subprocess_runner",
|
||||
"quiet_subprocess_runner",
|
||||
"BuildBackendHookCaller",
|
||||
]
|
||||
|
||||
BackendInvalid = BackendUnavailable # Deprecated alias, previously a separate exception
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._impl import SubprocessRunner
|
||||
|
||||
__all__ += ["SubprocessRunner"]
|
Binary file not shown.
Binary file not shown.
410
kivy_venv/lib/python3.11/site-packages/pyproject_hooks/_impl.py
Normal file
410
kivy_venv/lib/python3.11/site-packages/pyproject_hooks/_impl.py
Normal file
|
@ -0,0 +1,410 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from contextlib import contextmanager
|
||||
from os.path import abspath
|
||||
from os.path import join as pjoin
|
||||
from subprocess import STDOUT, check_call, check_output
|
||||
from typing import TYPE_CHECKING, Any, Iterator, Mapping, Optional, Sequence
|
||||
|
||||
from ._in_process import _in_proc_script_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
||||
class SubprocessRunner(Protocol):
|
||||
"""A protocol for the subprocess runner."""
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
cmd: Sequence[str],
|
||||
cwd: Optional[str] = None,
|
||||
extra_environ: Optional[Mapping[str, str]] = None,
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
def write_json(obj: Mapping[str, Any], path: str, **kwargs) -> None:
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(obj, f, **kwargs)
|
||||
|
||||
|
||||
def read_json(path: str) -> Mapping[str, Any]:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
class BackendUnavailable(Exception):
|
||||
"""Will be raised if the backend cannot be imported in the hook process."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
traceback: str,
|
||||
message: Optional[str] = None,
|
||||
backend_name: Optional[str] = None,
|
||||
backend_path: Optional[Sequence[str]] = None,
|
||||
) -> None:
|
||||
# Preserving arg order for the sake of API backward compatibility.
|
||||
self.backend_name = backend_name
|
||||
self.backend_path = backend_path
|
||||
self.traceback = traceback
|
||||
super().__init__(message or "Error while importing backend")
|
||||
|
||||
|
||||
class HookMissing(Exception):
|
||||
"""Will be raised on missing hooks (if a fallback can't be used)."""
|
||||
|
||||
def __init__(self, hook_name: str) -> None:
|
||||
super().__init__(hook_name)
|
||||
self.hook_name = hook_name
|
||||
|
||||
|
||||
class UnsupportedOperation(Exception):
|
||||
"""May be raised by build_sdist if the backend indicates that it can't."""
|
||||
|
||||
def __init__(self, traceback: str) -> None:
|
||||
self.traceback = traceback
|
||||
|
||||
|
||||
def default_subprocess_runner(
|
||||
cmd: Sequence[str],
|
||||
cwd: Optional[str] = None,
|
||||
extra_environ: Optional[Mapping[str, str]] = None,
|
||||
) -> None:
|
||||
"""The default method of calling the wrapper subprocess.
|
||||
|
||||
This uses :func:`subprocess.check_call` under the hood.
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
if extra_environ:
|
||||
env.update(extra_environ)
|
||||
|
||||
check_call(cmd, cwd=cwd, env=env)
|
||||
|
||||
|
||||
def quiet_subprocess_runner(
|
||||
cmd: Sequence[str],
|
||||
cwd: Optional[str] = None,
|
||||
extra_environ: Optional[Mapping[str, str]] = None,
|
||||
) -> None:
|
||||
"""Call the subprocess while suppressing output.
|
||||
|
||||
This uses :func:`subprocess.check_output` under the hood.
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
if extra_environ:
|
||||
env.update(extra_environ)
|
||||
|
||||
check_output(cmd, cwd=cwd, env=env, stderr=STDOUT)
|
||||
|
||||
|
||||
def norm_and_check(source_tree: str, requested: str) -> str:
|
||||
"""Normalise and check a backend path.
|
||||
|
||||
Ensure that the requested backend path is specified as a relative path,
|
||||
and resolves to a location under the given source tree.
|
||||
|
||||
Return an absolute version of the requested path.
|
||||
"""
|
||||
if os.path.isabs(requested):
|
||||
raise ValueError("paths must be relative")
|
||||
|
||||
abs_source = os.path.abspath(source_tree)
|
||||
abs_requested = os.path.normpath(os.path.join(abs_source, requested))
|
||||
# We have to use commonprefix for Python 2.7 compatibility. So we
|
||||
# normalise case to avoid problems because commonprefix is a character
|
||||
# based comparison :-(
|
||||
norm_source = os.path.normcase(abs_source)
|
||||
norm_requested = os.path.normcase(abs_requested)
|
||||
if os.path.commonprefix([norm_source, norm_requested]) != norm_source:
|
||||
raise ValueError("paths must be inside source tree")
|
||||
|
||||
return abs_requested
|
||||
|
||||
|
||||
class BuildBackendHookCaller:
|
||||
"""A wrapper to call the build backend hooks for a source directory."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source_dir: str,
|
||||
build_backend: str,
|
||||
backend_path: Optional[Sequence[str]] = None,
|
||||
runner: Optional["SubprocessRunner"] = None,
|
||||
python_executable: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param source_dir: The source directory to invoke the build backend for
|
||||
:param build_backend: The build backend spec
|
||||
:param backend_path: Additional path entries for the build backend spec
|
||||
:param runner: The :ref:`subprocess runner <Subprocess Runners>` to use
|
||||
:param python_executable:
|
||||
The Python executable used to invoke the build backend
|
||||
"""
|
||||
if runner is None:
|
||||
runner = default_subprocess_runner
|
||||
|
||||
self.source_dir = abspath(source_dir)
|
||||
self.build_backend = build_backend
|
||||
if backend_path:
|
||||
backend_path = [norm_and_check(self.source_dir, p) for p in backend_path]
|
||||
self.backend_path = backend_path
|
||||
self._subprocess_runner = runner
|
||||
if not python_executable:
|
||||
python_executable = sys.executable
|
||||
self.python_executable = python_executable
|
||||
|
||||
@contextmanager
|
||||
def subprocess_runner(self, runner: "SubprocessRunner") -> Iterator[None]:
|
||||
"""A context manager for temporarily overriding the default
|
||||
:ref:`subprocess runner <Subprocess Runners>`.
|
||||
|
||||
:param runner: The new subprocess runner to use within the context.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
hook_caller = BuildBackendHookCaller(...)
|
||||
with hook_caller.subprocess_runner(quiet_subprocess_runner):
|
||||
...
|
||||
"""
|
||||
prev = self._subprocess_runner
|
||||
self._subprocess_runner = runner
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self._subprocess_runner = prev
|
||||
|
||||
def _supported_features(self) -> Sequence[str]:
|
||||
"""Return the list of optional features supported by the backend."""
|
||||
return self._call_hook("_supported_features", {})
|
||||
|
||||
def get_requires_for_build_wheel(
|
||||
self,
|
||||
config_settings: Optional[Mapping[str, Any]] = None,
|
||||
) -> Sequence[str]:
|
||||
"""Get additional dependencies required for building a wheel.
|
||||
|
||||
:param config_settings: The configuration settings for the build backend
|
||||
:returns: A list of :pep:`dependency specifiers <508>`.
|
||||
|
||||
.. admonition:: Fallback
|
||||
|
||||
If the build backend does not defined a hook with this name, an
|
||||
empty list will be returned.
|
||||
"""
|
||||
return self._call_hook(
|
||||
"get_requires_for_build_wheel", {"config_settings": config_settings}
|
||||
)
|
||||
|
||||
def prepare_metadata_for_build_wheel(
|
||||
self,
|
||||
metadata_directory: str,
|
||||
config_settings: Optional[Mapping[str, Any]] = None,
|
||||
_allow_fallback: bool = True,
|
||||
) -> str:
|
||||
"""Prepare a ``*.dist-info`` folder with metadata for this project.
|
||||
|
||||
:param metadata_directory: The directory to write the metadata to
|
||||
:param config_settings: The configuration settings for the build backend
|
||||
:param _allow_fallback:
|
||||
Whether to allow the fallback to building a wheel and extracting
|
||||
the metadata from it. Should be passed as a keyword argument only.
|
||||
|
||||
:returns: Name of the newly created subfolder within
|
||||
``metadata_directory``, containing the metadata.
|
||||
|
||||
.. admonition:: Fallback
|
||||
|
||||
If the build backend does not define a hook with this name and
|
||||
``_allow_fallback`` is truthy, the backend will be asked to build a
|
||||
wheel via the ``build_wheel`` hook and the dist-info extracted from
|
||||
that will be returned.
|
||||
"""
|
||||
return self._call_hook(
|
||||
"prepare_metadata_for_build_wheel",
|
||||
{
|
||||
"metadata_directory": abspath(metadata_directory),
|
||||
"config_settings": config_settings,
|
||||
"_allow_fallback": _allow_fallback,
|
||||
},
|
||||
)
|
||||
|
||||
def build_wheel(
|
||||
self,
|
||||
wheel_directory: str,
|
||||
config_settings: Optional[Mapping[str, Any]] = None,
|
||||
metadata_directory: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Build a wheel from this project.
|
||||
|
||||
:param wheel_directory: The directory to write the wheel to
|
||||
:param config_settings: The configuration settings for the build backend
|
||||
:param metadata_directory: The directory to reuse existing metadata from
|
||||
:returns:
|
||||
The name of the newly created wheel within ``wheel_directory``.
|
||||
|
||||
.. admonition:: Interaction with fallback
|
||||
|
||||
If the ``build_wheel`` hook was called in the fallback for
|
||||
:meth:`prepare_metadata_for_build_wheel`, the build backend would
|
||||
not be invoked. Instead, the previously built wheel will be copied
|
||||
to ``wheel_directory`` and the name of that file will be returned.
|
||||
"""
|
||||
if metadata_directory is not None:
|
||||
metadata_directory = abspath(metadata_directory)
|
||||
return self._call_hook(
|
||||
"build_wheel",
|
||||
{
|
||||
"wheel_directory": abspath(wheel_directory),
|
||||
"config_settings": config_settings,
|
||||
"metadata_directory": metadata_directory,
|
||||
},
|
||||
)
|
||||
|
||||
def get_requires_for_build_editable(
|
||||
self,
|
||||
config_settings: Optional[Mapping[str, Any]] = None,
|
||||
) -> Sequence[str]:
|
||||
"""Get additional dependencies required for building an editable wheel.
|
||||
|
||||
:param config_settings: The configuration settings for the build backend
|
||||
:returns: A list of :pep:`dependency specifiers <508>`.
|
||||
|
||||
.. admonition:: Fallback
|
||||
|
||||
If the build backend does not defined a hook with this name, an
|
||||
empty list will be returned.
|
||||
"""
|
||||
return self._call_hook(
|
||||
"get_requires_for_build_editable", {"config_settings": config_settings}
|
||||
)
|
||||
|
||||
def prepare_metadata_for_build_editable(
|
||||
self,
|
||||
metadata_directory: str,
|
||||
config_settings: Optional[Mapping[str, Any]] = None,
|
||||
_allow_fallback: bool = True,
|
||||
) -> Optional[str]:
|
||||
"""Prepare a ``*.dist-info`` folder with metadata for this project.
|
||||
|
||||
:param metadata_directory: The directory to write the metadata to
|
||||
:param config_settings: The configuration settings for the build backend
|
||||
:param _allow_fallback:
|
||||
Whether to allow the fallback to building a wheel and extracting
|
||||
the metadata from it. Should be passed as a keyword argument only.
|
||||
:returns: Name of the newly created subfolder within
|
||||
``metadata_directory``, containing the metadata.
|
||||
|
||||
.. admonition:: Fallback
|
||||
|
||||
If the build backend does not define a hook with this name and
|
||||
``_allow_fallback`` is truthy, the backend will be asked to build a
|
||||
wheel via the ``build_editable`` hook and the dist-info
|
||||
extracted from that will be returned.
|
||||
"""
|
||||
return self._call_hook(
|
||||
"prepare_metadata_for_build_editable",
|
||||
{
|
||||
"metadata_directory": abspath(metadata_directory),
|
||||
"config_settings": config_settings,
|
||||
"_allow_fallback": _allow_fallback,
|
||||
},
|
||||
)
|
||||
|
||||
def build_editable(
|
||||
self,
|
||||
wheel_directory: str,
|
||||
config_settings: Optional[Mapping[str, Any]] = None,
|
||||
metadata_directory: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Build an editable wheel from this project.
|
||||
|
||||
:param wheel_directory: The directory to write the wheel to
|
||||
:param config_settings: The configuration settings for the build backend
|
||||
:param metadata_directory: The directory to reuse existing metadata from
|
||||
:returns:
|
||||
The name of the newly created wheel within ``wheel_directory``.
|
||||
|
||||
.. admonition:: Interaction with fallback
|
||||
|
||||
If the ``build_editable`` hook was called in the fallback for
|
||||
:meth:`prepare_metadata_for_build_editable`, the build backend
|
||||
would not be invoked. Instead, the previously built wheel will be
|
||||
copied to ``wheel_directory`` and the name of that file will be
|
||||
returned.
|
||||
"""
|
||||
if metadata_directory is not None:
|
||||
metadata_directory = abspath(metadata_directory)
|
||||
return self._call_hook(
|
||||
"build_editable",
|
||||
{
|
||||
"wheel_directory": abspath(wheel_directory),
|
||||
"config_settings": config_settings,
|
||||
"metadata_directory": metadata_directory,
|
||||
},
|
||||
)
|
||||
|
||||
def get_requires_for_build_sdist(
|
||||
self,
|
||||
config_settings: Optional[Mapping[str, Any]] = None,
|
||||
) -> Sequence[str]:
|
||||
"""Get additional dependencies required for building an sdist.
|
||||
|
||||
:returns: A list of :pep:`dependency specifiers <508>`.
|
||||
"""
|
||||
return self._call_hook(
|
||||
"get_requires_for_build_sdist", {"config_settings": config_settings}
|
||||
)
|
||||
|
||||
def build_sdist(
|
||||
self,
|
||||
sdist_directory: str,
|
||||
config_settings: Optional[Mapping[str, Any]] = None,
|
||||
) -> str:
|
||||
"""Build an sdist from this project.
|
||||
|
||||
:returns:
|
||||
The name of the newly created sdist within ``wheel_directory``.
|
||||
"""
|
||||
return self._call_hook(
|
||||
"build_sdist",
|
||||
{
|
||||
"sdist_directory": abspath(sdist_directory),
|
||||
"config_settings": config_settings,
|
||||
},
|
||||
)
|
||||
|
||||
def _call_hook(self, hook_name: str, kwargs: Mapping[str, Any]) -> Any:
|
||||
extra_environ = {"_PYPROJECT_HOOKS_BUILD_BACKEND": self.build_backend}
|
||||
|
||||
if self.backend_path:
|
||||
backend_path = os.pathsep.join(self.backend_path)
|
||||
extra_environ["_PYPROJECT_HOOKS_BACKEND_PATH"] = backend_path
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
hook_input = {"kwargs": kwargs}
|
||||
write_json(hook_input, pjoin(td, "input.json"), indent=2)
|
||||
|
||||
# Run the hook in a subprocess
|
||||
with _in_proc_script_path() as script:
|
||||
python = self.python_executable
|
||||
self._subprocess_runner(
|
||||
[python, abspath(str(script)), hook_name, td],
|
||||
cwd=self.source_dir,
|
||||
extra_environ=extra_environ,
|
||||
)
|
||||
|
||||
data = read_json(pjoin(td, "output.json"))
|
||||
if data.get("unsupported"):
|
||||
raise UnsupportedOperation(data.get("traceback", ""))
|
||||
if data.get("no_backend"):
|
||||
raise BackendUnavailable(
|
||||
data.get("traceback", ""),
|
||||
message=data.get("backend_error", ""),
|
||||
backend_name=self.build_backend,
|
||||
backend_path=self.backend_path,
|
||||
)
|
||||
if data.get("hook_missing"):
|
||||
raise HookMissing(data.get("missing_hook_name") or hook_name)
|
||||
return data["return_val"]
|
|
@ -0,0 +1,21 @@
|
|||
"""This is a subpackage because the directory is on sys.path for _in_process.py
|
||||
|
||||
The subpackage should stay as empty as possible to avoid shadowing modules that
|
||||
the backend might import.
|
||||
"""
|
||||
|
||||
import importlib.resources as resources
|
||||
|
||||
try:
|
||||
resources.files
|
||||
except AttributeError:
|
||||
# Python 3.8 compatibility
|
||||
def _in_proc_script_path():
|
||||
return resources.path(__package__, "_in_process.py")
|
||||
|
||||
else:
|
||||
|
||||
def _in_proc_script_path():
|
||||
return resources.as_file(
|
||||
resources.files(__package__).joinpath("_in_process.py")
|
||||
)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,373 @@
|
|||
"""This is invoked in a subprocess to call the build backend hooks.
|
||||
|
||||
It expects:
|
||||
- Command line args: hook_name, control_dir
|
||||
- Environment variables:
|
||||
_PYPROJECT_HOOKS_BUILD_BACKEND=entry.point:spec
|
||||
_PYPROJECT_HOOKS_BACKEND_PATH=paths (separated with os.pathsep)
|
||||
- control_dir/input.json:
|
||||
- {"kwargs": {...}}
|
||||
|
||||
Results:
|
||||
- control_dir/output.json
|
||||
- {"return_val": ...}
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import traceback
|
||||
from glob import glob
|
||||
from importlib import import_module
|
||||
from importlib.machinery import PathFinder
|
||||
from os.path import join as pjoin
|
||||
|
||||
# This file is run as a script, and `import wrappers` is not zip-safe, so we
|
||||
# include write_json() and read_json() from wrappers.py.
|
||||
|
||||
|
||||
def write_json(obj, path, **kwargs):
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(obj, f, **kwargs)
|
||||
|
||||
|
||||
def read_json(path):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
class BackendUnavailable(Exception):
|
||||
"""Raised if we cannot import the backend"""
|
||||
|
||||
def __init__(self, message, traceback=None):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.traceback = traceback
|
||||
|
||||
|
||||
class HookMissing(Exception):
|
||||
"""Raised if a hook is missing and we are not executing the fallback"""
|
||||
|
||||
def __init__(self, hook_name=None):
|
||||
super().__init__(hook_name)
|
||||
self.hook_name = hook_name
|
||||
|
||||
|
||||
def _build_backend():
|
||||
"""Find and load the build backend"""
|
||||
backend_path = os.environ.get("_PYPROJECT_HOOKS_BACKEND_PATH")
|
||||
ep = os.environ["_PYPROJECT_HOOKS_BUILD_BACKEND"]
|
||||
mod_path, _, obj_path = ep.partition(":")
|
||||
|
||||
if backend_path:
|
||||
# Ensure in-tree backend directories have the highest priority when importing.
|
||||
extra_pathitems = backend_path.split(os.pathsep)
|
||||
sys.meta_path.insert(0, _BackendPathFinder(extra_pathitems, mod_path))
|
||||
|
||||
try:
|
||||
obj = import_module(mod_path)
|
||||
except ImportError:
|
||||
msg = f"Cannot import {mod_path!r}"
|
||||
raise BackendUnavailable(msg, traceback.format_exc())
|
||||
|
||||
if obj_path:
|
||||
for path_part in obj_path.split("."):
|
||||
obj = getattr(obj, path_part)
|
||||
return obj
|
||||
|
||||
|
||||
class _BackendPathFinder:
|
||||
"""Implements the MetaPathFinder interface to locate modules in ``backend-path``.
|
||||
|
||||
Since the environment provided by the frontend can contain all sorts of
|
||||
MetaPathFinders, the only way to ensure the backend is loaded from the
|
||||
right place is to prepend our own.
|
||||
"""
|
||||
|
||||
def __init__(self, backend_path, backend_module):
|
||||
self.backend_path = backend_path
|
||||
self.backend_module = backend_module
|
||||
self.backend_parent, _, _ = backend_module.partition(".")
|
||||
|
||||
def find_spec(self, fullname, _path, _target=None):
|
||||
if "." in fullname:
|
||||
# Rely on importlib to find nested modules based on parent's path
|
||||
return None
|
||||
|
||||
# Ignore other items in _path or sys.path and use backend_path instead:
|
||||
spec = PathFinder.find_spec(fullname, path=self.backend_path)
|
||||
if spec is None and fullname == self.backend_parent:
|
||||
# According to the spec, the backend MUST be loaded from backend-path.
|
||||
# Therefore, we can halt the import machinery and raise a clean error.
|
||||
msg = f"Cannot find module {self.backend_module!r} in {self.backend_path!r}"
|
||||
raise BackendUnavailable(msg)
|
||||
|
||||
return spec
|
||||
|
||||
|
||||
def _supported_features():
|
||||
"""Return the list of options features supported by the backend.
|
||||
|
||||
Returns a list of strings.
|
||||
The only possible value is 'build_editable'.
|
||||
"""
|
||||
backend = _build_backend()
|
||||
features = []
|
||||
if hasattr(backend, "build_editable"):
|
||||
features.append("build_editable")
|
||||
return features
|
||||
|
||||
|
||||
def get_requires_for_build_wheel(config_settings):
|
||||
"""Invoke the optional get_requires_for_build_wheel hook
|
||||
|
||||
Returns [] if the hook is not defined.
|
||||
"""
|
||||
backend = _build_backend()
|
||||
try:
|
||||
hook = backend.get_requires_for_build_wheel
|
||||
except AttributeError:
|
||||
return []
|
||||
else:
|
||||
return hook(config_settings)
|
||||
|
||||
|
||||
def get_requires_for_build_editable(config_settings):
|
||||
"""Invoke the optional get_requires_for_build_editable hook
|
||||
|
||||
Returns [] if the hook is not defined.
|
||||
"""
|
||||
backend = _build_backend()
|
||||
try:
|
||||
hook = backend.get_requires_for_build_editable
|
||||
except AttributeError:
|
||||
return []
|
||||
else:
|
||||
return hook(config_settings)
|
||||
|
||||
|
||||
def prepare_metadata_for_build_wheel(
|
||||
metadata_directory, config_settings, _allow_fallback
|
||||
):
|
||||
"""Invoke optional prepare_metadata_for_build_wheel
|
||||
|
||||
Implements a fallback by building a wheel if the hook isn't defined,
|
||||
unless _allow_fallback is False in which case HookMissing is raised.
|
||||
"""
|
||||
backend = _build_backend()
|
||||
try:
|
||||
hook = backend.prepare_metadata_for_build_wheel
|
||||
except AttributeError:
|
||||
if not _allow_fallback:
|
||||
raise HookMissing()
|
||||
else:
|
||||
return hook(metadata_directory, config_settings)
|
||||
# fallback to build_wheel outside the try block to avoid exception chaining
|
||||
# which can be confusing to users and is not relevant
|
||||
whl_basename = backend.build_wheel(metadata_directory, config_settings)
|
||||
return _get_wheel_metadata_from_wheel(
|
||||
whl_basename, metadata_directory, config_settings
|
||||
)
|
||||
|
||||
|
||||
def prepare_metadata_for_build_editable(
|
||||
metadata_directory, config_settings, _allow_fallback
|
||||
):
|
||||
"""Invoke optional prepare_metadata_for_build_editable
|
||||
|
||||
Implements a fallback by building an editable wheel if the hook isn't
|
||||
defined, unless _allow_fallback is False in which case HookMissing is
|
||||
raised.
|
||||
"""
|
||||
backend = _build_backend()
|
||||
try:
|
||||
hook = backend.prepare_metadata_for_build_editable
|
||||
except AttributeError:
|
||||
if not _allow_fallback:
|
||||
raise HookMissing()
|
||||
try:
|
||||
build_hook = backend.build_editable
|
||||
except AttributeError:
|
||||
raise HookMissing(hook_name="build_editable")
|
||||
else:
|
||||
whl_basename = build_hook(metadata_directory, config_settings)
|
||||
return _get_wheel_metadata_from_wheel(
|
||||
whl_basename, metadata_directory, config_settings
|
||||
)
|
||||
else:
|
||||
return hook(metadata_directory, config_settings)
|
||||
|
||||
|
||||
WHEEL_BUILT_MARKER = "PYPROJECT_HOOKS_ALREADY_BUILT_WHEEL"
|
||||
|
||||
|
||||
def _dist_info_files(whl_zip):
|
||||
"""Identify the .dist-info folder inside a wheel ZipFile."""
|
||||
res = []
|
||||
for path in whl_zip.namelist():
|
||||
m = re.match(r"[^/\\]+-[^/\\]+\.dist-info/", path)
|
||||
if m:
|
||||
res.append(path)
|
||||
if res:
|
||||
return res
|
||||
raise Exception("No .dist-info folder found in wheel")
|
||||
|
||||
|
||||
def _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, config_settings):
|
||||
"""Extract the metadata from a wheel.
|
||||
|
||||
Fallback for when the build backend does not
|
||||
define the 'get_wheel_metadata' hook.
|
||||
"""
|
||||
from zipfile import ZipFile
|
||||
|
||||
with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), "wb"):
|
||||
pass # Touch marker file
|
||||
|
||||
whl_file = os.path.join(metadata_directory, whl_basename)
|
||||
with ZipFile(whl_file) as zipf:
|
||||
dist_info = _dist_info_files(zipf)
|
||||
zipf.extractall(path=metadata_directory, members=dist_info)
|
||||
return dist_info[0].split("/")[0]
|
||||
|
||||
|
||||
def _find_already_built_wheel(metadata_directory):
|
||||
"""Check for a wheel already built during the get_wheel_metadata hook."""
|
||||
if not metadata_directory:
|
||||
return None
|
||||
metadata_parent = os.path.dirname(metadata_directory)
|
||||
if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)):
|
||||
return None
|
||||
|
||||
whl_files = glob(os.path.join(metadata_parent, "*.whl"))
|
||||
if not whl_files:
|
||||
print("Found wheel built marker, but no .whl files")
|
||||
return None
|
||||
if len(whl_files) > 1:
|
||||
print(
|
||||
"Found multiple .whl files; unspecified behaviour. "
|
||||
"Will call build_wheel."
|
||||
)
|
||||
return None
|
||||
|
||||
# Exactly one .whl file
|
||||
return whl_files[0]
|
||||
|
||||
|
||||
def build_wheel(wheel_directory, config_settings, metadata_directory=None):
|
||||
"""Invoke the mandatory build_wheel hook.
|
||||
|
||||
If a wheel was already built in the
|
||||
prepare_metadata_for_build_wheel fallback, this
|
||||
will copy it rather than rebuilding the wheel.
|
||||
"""
|
||||
prebuilt_whl = _find_already_built_wheel(metadata_directory)
|
||||
if prebuilt_whl:
|
||||
shutil.copy2(prebuilt_whl, wheel_directory)
|
||||
return os.path.basename(prebuilt_whl)
|
||||
|
||||
return _build_backend().build_wheel(
|
||||
wheel_directory, config_settings, metadata_directory
|
||||
)
|
||||
|
||||
|
||||
def build_editable(wheel_directory, config_settings, metadata_directory=None):
|
||||
"""Invoke the optional build_editable hook.
|
||||
|
||||
If a wheel was already built in the
|
||||
prepare_metadata_for_build_editable fallback, this
|
||||
will copy it rather than rebuilding the wheel.
|
||||
"""
|
||||
backend = _build_backend()
|
||||
try:
|
||||
hook = backend.build_editable
|
||||
except AttributeError:
|
||||
raise HookMissing()
|
||||
else:
|
||||
prebuilt_whl = _find_already_built_wheel(metadata_directory)
|
||||
if prebuilt_whl:
|
||||
shutil.copy2(prebuilt_whl, wheel_directory)
|
||||
return os.path.basename(prebuilt_whl)
|
||||
|
||||
return hook(wheel_directory, config_settings, metadata_directory)
|
||||
|
||||
|
||||
def get_requires_for_build_sdist(config_settings):
|
||||
"""Invoke the optional get_requires_for_build_wheel hook
|
||||
|
||||
Returns [] if the hook is not defined.
|
||||
"""
|
||||
backend = _build_backend()
|
||||
try:
|
||||
hook = backend.get_requires_for_build_sdist
|
||||
except AttributeError:
|
||||
return []
|
||||
else:
|
||||
return hook(config_settings)
|
||||
|
||||
|
||||
class _DummyException(Exception):
|
||||
"""Nothing should ever raise this exception"""
|
||||
|
||||
|
||||
class GotUnsupportedOperation(Exception):
|
||||
"""For internal use when backend raises UnsupportedOperation"""
|
||||
|
||||
def __init__(self, traceback):
|
||||
self.traceback = traceback
|
||||
|
||||
|
||||
def build_sdist(sdist_directory, config_settings):
|
||||
"""Invoke the mandatory build_sdist hook."""
|
||||
backend = _build_backend()
|
||||
try:
|
||||
return backend.build_sdist(sdist_directory, config_settings)
|
||||
except getattr(backend, "UnsupportedOperation", _DummyException):
|
||||
raise GotUnsupportedOperation(traceback.format_exc())
|
||||
|
||||
|
||||
HOOK_NAMES = {
|
||||
"get_requires_for_build_wheel",
|
||||
"prepare_metadata_for_build_wheel",
|
||||
"build_wheel",
|
||||
"get_requires_for_build_editable",
|
||||
"prepare_metadata_for_build_editable",
|
||||
"build_editable",
|
||||
"get_requires_for_build_sdist",
|
||||
"build_sdist",
|
||||
"_supported_features",
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
sys.exit("Needs args: hook_name, control_dir")
|
||||
hook_name = sys.argv[1]
|
||||
control_dir = sys.argv[2]
|
||||
if hook_name not in HOOK_NAMES:
|
||||
sys.exit("Unknown hook: %s" % hook_name)
|
||||
hook = globals()[hook_name]
|
||||
|
||||
hook_input = read_json(pjoin(control_dir, "input.json"))
|
||||
|
||||
json_out = {"unsupported": False, "return_val": None}
|
||||
try:
|
||||
json_out["return_val"] = hook(**hook_input["kwargs"])
|
||||
except BackendUnavailable as e:
|
||||
json_out["no_backend"] = True
|
||||
json_out["traceback"] = e.traceback
|
||||
json_out["backend_error"] = e.message
|
||||
except GotUnsupportedOperation as e:
|
||||
json_out["unsupported"] = True
|
||||
json_out["traceback"] = e.traceback
|
||||
except HookMissing as e:
|
||||
json_out["hook_missing"] = True
|
||||
json_out["missing_hook_name"] = e.hook_name or hook_name
|
||||
|
||||
write_json(json_out, pjoin(control_dir, "output.json"), indent=2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue