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:

  • 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.py scripts 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.