Build frontend-backend interface¶
This specification defines a standard interface between Build Frontends and Build Backends to generate Distribution Package artifacts from Source Trees of Python Projects. It was originally introduced in PEP 517 and revised to include editable support in PEP 660.
The build backend Python object specified in the
build-backend key of the [build-system] table
in the pyproject.toml config file
MUST have attributes which provide some or all of the following hooks
(as Python callables)
that can be invoked by the build frontend.
The build_wheel and build_sdist
hooks MUST be provided by any build backend implementing this specification;
all other listed hooks ore optional.
The common config_settings argument is
described after the individual hooks.
Wheel hooks¶
The following backend hooks relate to building a Wheel Built Distribution.
build_wheel¶
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
...
Mandatory hook.
MUST build a Wheel .whl file,
as defined in the Wheel specification,
and place it in the specified wheel_directory.
MUST return the basename (not the full path) of the .whl file it creates
as a string.
If the Build Frontend has previously called
prepare_metadata_for_build_wheel
and depends on the wheel resulting from this call
to have metadata matching this earlier call,
then it SHOULD provide the path to the created .dist-info directory
as the metadata_directory argument.
If this argument is provided,
then build_wheel MUST produce a wheel with identical metadata.
The directory passed in by the build frontend MUST be identical
to the directory created by
prepare_metadata_for_build_wheel,
including any unrecognized files it created.
Build Backends which do not provide
the prepare_metadata_for_build_wheel hook
MAY either silently ignore
the metadata_directory parameter to build_wheel,
or else raise an exception when it is set to anything other than None.
To ensure that wheels from different sources are built the same way,
frontends MAY call build_sdist first,
and then call build_wheel in the unpacked Sdist.
However, if the backend
indicates that it is missing some requirements for creating an sdist,
the frontend SHOULD fall back to calling build_wheel
in the Source Tree.
The source tree MAY be read-only. Backends SHOULD therefore be prepared to build without creating or modifying any files in the source tree, but they MAY opt not to handle this situation, in which case failures will be visible to the user. Frontends are not responsible for any special handling of read-only source directories.
The backend MAY store intermediate artifacts in cache locations or temporary directories. The presence or absence of any caches SHOULD not make a material difference to the final result of the build.
get_requires_for_build_wheel¶
def get_requires_for_build_wheel(config_settings=None):
...
Optional hook. This hook MUST return a list of strings containing Dependency specifiers, above and beyond those specified in the build-system.requires key of The pyproject.toml configuration file, to be installed when calling the build_wheel or prepare_metadata_for_build_wheel hooks.
Example:
def get_requires_for_build_wheel(config_settings):
return ["wheel >= 0.25", "setuptools"]
If not defined by the Build Backend,
the default implementation is equivalent to return [].
prepare_metadata_for_build_wheel¶
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
...
Optional hook.
MUST create a .dist-info directory containing Wheel metadata
inside the specified metadata_directory;
i.e., a directory like
metadata_directory/package-version.dist-info/.
This MUST be a valid .dist-info directory
as defined in the wheel specification,
except that it need not contain RECORD
or signatures.
The hook MAY also create other files inside this directory,
and a Build Frontend MUST preserve, but otherwise ignore, such files;
the intention here is that in cases where
the metadata depends on build-time decisions,
the Build Backend may need to record these decisions
in some convenient format for re-use by the actual wheel-building step.
This MUST return the basename (not the full path)
of the .dist-info directory it creates as a string.
If a build frontend needs this information and the method is not defined, it SHOULD call build_wheel and look at the resulting metadata directly.
Sdist hooks¶
The following backend hooks relate to building a Sdist Source Distribution.
build_sdist¶
def build_sdist(sdist_directory, config_settings=None):
...
Mandatory hook.
MUST build a Sdist Source Distribution,
as defined in the Sdist specification,
and place it in the specified sdist_directory.
MUST return the basename (not the full path) of the sdist file it creates
as a string.
Build Frontends MAY prefer produce wheels
from intermediate sdists, to ensure consistency.
However, some Build Backends
MAY have extra requirements for creating sdists,
such as version control tools.
If the backend cannot produce an sdist because a dependency is missing,
or for another well understood reason,
it SHOULD raise an exception of a specific type
which it makes available as UnsupportedOperation on the backend object.
If the frontend gets this exception while building an sdist
as an intermediate for a wheel,
it SHOULD fall back to building a wheel directly.
The backend does not need to define this exception type
if it would never raise it.
get_requires_for_build_sdist¶
def get_requires_for_build_sdist(config_settings=None):
...
Optional hook. This hook MUST return a list of strings containing Dependency specifiers, above and beyond those specified in the build-system.requires key of The pyproject.toml configuration file, to be installed when calling the build_sdist hook.
If not defined, the default implementation is equivalent to return [].
Editable hooks¶
The following backend hooks relate to building an Editable Installation. These hooks are used to build a Wheel that, when installed, allows that Distribution to be imported from its Source Tree directory.
build_editable¶
def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
...
Optional hook.
MUST build a Wheel .whl file,
as defined in the Wheel specification,
and place it in the specified wheel_directory.
MUST return the basename (not the full path) of the .whl file it creates
as a string.
Build backends MUST populate the generated wheel with files that, when installed, will result in a working Editable Installation. Backends MAY use various techniques to achieve this goal, such as those suggested in PEP 660.
Backends MAY do an in-place build of the distribution as a side effect so that any extension modules or other built artifacts are ready to be used.
Runtime dependencies (Requires-Dist)
and other core metadata of the built wheel
MUST be identical to that produced by build_wheel
or prepare_metadata_for_build_wheel;
with the exception that for build_editable,
Build Backends MAY add dependencies (such as editables)
that are necessary for their editable mechanism to function at runtime.
The filename for the “editable” wheel MUST follow the wheel File name convention; it MAY use different Platform compatibility tags than for build_wheel, but its tags MUST be compatible with the platform this hook is executed on.
If the Build Frontend has previously called
prepare_metadata_for_build_editable
and depends on the wheel resulting from this call
to have metadata matching this earlier call,
then it SHOULD provide the path to the created .dist-info directory
as the metadata_directory argument.
If this argument is provided,
then build_editable MUST produce a wheel with identical metadata.
The directory passed in by the build frontend MUST be identical
to the directory created by
prepare_metadata_for_build_editable,
including any unrecognized files it created.
An “editable” wheel uses the wheel format not for distribution but as ephemeral communication between the build system and the front end. This wheel MUST NOT be exposed to end users, nor cached, nor distributed.
get_requires_for_build_editable¶
def get_requires_for_build_editable(config_settings=None):
...
Optional hook. This hook MUST return a list of strings containing Dependency specifiers, above and beyond those specified in the build-system.requires key of The pyproject.toml configuration file, to be installed when calling the build_editable or prepare_metadata_for_build_editable hooks.
If not defined by the Build Backend,
the default implementation is equivalent to return [].
prepare_metadata_for_build_editable¶
def prepare_metadata_for_build_editable(metadata_directory, config_settings=None):
...
Optional hook.
MUST create a .dist-info directory containing Wheel metadata
inside the specified metadata_directory;
i.e., a directory like
metadata_directory/package-version.dist-info/.
This MUST be a valid .dist-info directory
as defined in the wheel specification,
except that it need not contain RECORD
or signatures.
The hook MAY also create other files inside this directory,
and a Build Frontend MUST preserve, but otherwise ignore, such files;
the intention here is that in cases where
the metadata depends on build-time decisions,
the Build Backend may need to record these decisions
in some convenient format for re-use by the actual wheel-building step.
This MUST return the basename (not the full path)
of the .dist-info directory it creates as a string.
If a build frontend needs this information and the method is not defined, it SHOULD call build_editable and look at the resulting metadata directly.
Hook invocation¶
The hooks MAY be called with positional or keyword arguments, so backends implementing them SHOULD be careful to make sure that their signatures match both the order and the names of the arguments above.
All hooks MUST be run with the working directory set to the
root of the Source Tree
(or unpacked sdist),
and MAY print arbitrary informational text to stdout and stderr.
They MUST NOT read from stdin,
and the build frontend MAY close stdin before invoking the hooks.
The build frontend MAY capture stdout and/or stderr from the backend.
If the backend detects that an output stream is not a terminal/console
(e.g. not sys.stdout.isatty()),
it SHOULD ensure that any output it writes to that stream is UTF-8 encoded.
The build frontend MUST NOT fail if captured output is not valid UTF-8,
but it MAY not preserve all the information in that case
(e.g. it may decode output using the 'replace' error handler in Python).
If the output stream is a terminal,
the build backend is responsible for presenting its output accurately,
as for any program running in a terminal.
If a hook raises an exception, or causes the process to terminate, then this indicates an error.
Config settings¶
The config_settings argument, which is passed to all hooks,
is an arbitrary dictionary provided as an “escape hatch”
for users to pass ad-hoc configuration into individual package builds.
Build Backends MAY assign any semantics they like to this dictionary.
Build Frontends SHOULD provide some mechanism for users to specify
arbitrary string-key/string-value pairs to be placed in this dictionary.
For example, they might support some syntax like --package-config CC=gcc.
Build frontends MAY also provide arbitrary other mechanisms
for users to place entries in this dictionary.
For example, pip might choose to map the following mix
of modern and legacy command line arguments:
pip install \
--package-config CC=gcc \
--global-option="--some-global-option" \
--build-option="--build-option1" \
--build-option="--build-option2"
into a config_settings dictionary as:
{
"CC": "gcc",
"--global-option": ["--some-global-option"],
"--build-option": ["--build-option1", "--build-option2"],
}
Of course, it is up to users to ensure that they pass options which make sense for the particular build backend and package that they are building.
Build environment¶
One of the responsibilities of a Build Frontend is to set up the Python environment in which the Build Backend will run.
A build frontend MAY use any “virtual environment” mechanism it chooses; such as virtualenv, venv, or no special mechanism at all. However, whatever mechanism is used MUST meet the following criteria:
All dependencies required by the build backend MUST be available for import from Python. In particular:
The get_requires_for_build_wheel, get_requires_for_build_sdist and get_requires_for_build_editable hooks MUST be executed in an environment which contains the requires specified in build-system.requires in pyproject.toml.
The prepare_metadata_for_build_wheel and build_wheel hooks MUST be executed in an environment which contains the
build-system.requiresrequirements and those specified by the get_requires_for_build_wheel hook.The build_sdist hook MUST be executed in an environment which contains the
build-system.requiresrequirements and those specified by the get_requires_for_build_sdist hook.The prepare_metadata_for_build_editable and build_editable hooks MUST be executed in an environment which contains the
build-system.requiresrequirements and those specified by the get_requires_for_build_editable hook.
This MUST remain true even for new Python subprocesses spawned by the build environment. For example, code like:
import subprocess, sys subprocess.run([sys.executable, ...])
MUST spawn a Python process which has access to all the project’s build requirements. This is necessary for build backends that want to e.g. run legacy
setup.pyscripts in a subprocess.All command-line scripts provided by the build requirements MUST be present in the build environment’s
PATH. For example, if a project declares a build-requirement on flit, then the following MUST work as a mechanism for running the Flit command-line tool:import shutil, subprocess subprocess.run([shutil.which("flit"), ...])
A build backend MUST be prepared to function in any environment which meets the above criteria. In particular, it MUST NOT assume that it has access to any packages except those that are present in the Python standard library, or that are explicitly declared as build requirements.
Frontends SHOULD call each hook in a fresh subprocess, so that backends are free to change process global state (such as environment variables or the working directory). A Python library will be provided which frontends can use to easily call hooks this way.
Frontends MAY use any mechanism for setting up a build environment that meets the above criteria, including simply installing all build requirements into the global environment. However, a build frontend SHOULD, by default, create an isolated environment for each build, containing only the Python standard library and any explicitly requested build dependencies.
Build frontends SHOULD provide some mechanism for users to override
the above defaults.
For example, a build frontend could have a
--build-with-system-site-packages option that causes the
--system-site-packages option to be passed to
virtualenv-or-equivalent when creating build environments,
or a --build-requirements-override=my-requirements.txt option that
overrides the project’s normal build-time requirements.
Frontend requirements for editable installs¶
Build Frontends MUST install “editable” wheels built with the build_editable hook in the same way as normal Wheels built with the build_wheel hook. This also means uninstallation of Editable Installations MUST NOT require any special treatment.
Frontends MUST create a direct_url.json file
in the .dist-info directory of the installed distribution,
as specified in the Recording the Direct URL Origin of installed distributions specification.
The url value MUST be a file:// URI to the Project directory
(i.e. the directory containing the project’s pyproject.toml),
and the dir_info value MUST be {'editable': true}.
Frontends MUST execute get_requires_for_build_editable hooks in an environment which contains the build system requirements specified in The pyproject.toml configuration file.
Frontends MUST execute the
prepare_metadata_for_build_editable
and build_editable hooks
in an environment which contains
the build system requirements from pyproject.toml
and those specified by the
get_requires_for_build_editable hook.
Frontends MUST NOT expose the wheel obtained from build_editable to end users. The wheel MUST be discarded after installation and MUST NOT be cached nor distributed.