Declaring a project’s build system¶
As originally specified in PEP 517 and PEP 518, projects may define a [build-system] table in their pyproject.toml config file to declare their Build Backend (as specified in the Build frontend-backend interface specification) and its dependencies.
[build-system] table¶
The [build-system] table is used to store build-related configuration.
Tools SHOULD NOT require the existence of the [build-system] table.
If it or a pyproject.toml file is not present,
tools SHOULD assume the following default value:
[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools"]
# The build backend Python object to invoke.
build-system = "setuptools.build_meta:__legacy__"
If the table is present but is missing a value for the mandatory requires key, tools SHOULD consider it an error.
The valid top-level keys are listed below. Keys not defined in this specification MUST NOT be added to this table.
requires key¶
The requires key is used to declare the Python-level dependencies
that must be installed in order to run
the project’s Build Backend successfully.
The key’s value MUST be a list of valid string Dependency specifiers required to execute the specified build backend.
For a build tool such as flit with a backend package flit_core,
an example requires value specifying a particular version range might be:
[build-system]
requires = ["flit_core >=3.2,<4"]
Projects still relying on a legacy implicit setup.py invocation
can specify the following value for the requires key:
[build-system]
requires = ["setuptools"]
This is the default value for this key
if the [build-system] table is not present in a pyproject.toml.
If the table is defined but is missing a value for the requires key,
tools SHOULD consider it an error.
The following requirements also apply:
Project build requirements will define a directed graph of requirements (project
AneedsBto build,BneedsCandD, etc.). This graph MUST NOT contain cycles. If (due to lack of co-ordination between projects, for example) a cycle is present, Build Frontends MAY refuse to build the project.Where build requirements are available as Wheels, frontends SHOULD use these where practical, to avoid deeply nested builds. However, frontends MAY have modes where they do not consider wheels when locating build requirements, and so projects MUST NOT assume that publishing wheels is sufficient to break a requirement cycle.
Frontends SHOULD check explicitly for requirement cycles, and SHOULD terminate the build with an informative message if one is found.
Note
The requirement for no requirement cycles means that backends wishing to self-host (i.e., building a wheel for a backend uses that backend for the build) need to make special provision to avoid causing cycles. Typically, this will involve specifying themselves as an in-tree backend, and avoiding external build dependencies (usually by vendoring them).
build-backend key¶
The build-backend key specifies the project’s Build Backend.
Its value MUST be a string naming the Python object
that exposes attributes with callables for each of
the Build frontend-backend interface hooks supported by the backend.
This is formatted following the same module:object syntax as
an entry point.
For example, with the value:
[build-system]
build-backend = "flit_core.buildapi:main"
then the module would be flit_core.buildapi
and the object would be main,
so the backend would be looked up by executing the equivalent of:
import flit_core.buildapi
backend = flit_core.buildapi.main
The object part MAY be omitted,
for cases where the importable module is the top-level backend object.
For example, with the value:
[build-system]
build-backend = "flit_core.buildapi"
then the module would still be flit_core.buildapi
and the object part not specified,
so the backend would be looked up by executing the equivalent of:
import flit_core.buildapi
backend = flit_core.buildapi
Formally, the string SHOULD satisfy the grammar:
identifier = (letter | '_') (letter | '_' | digit)*
module_path = identifier ('.' identifier)*
object_path = identifier ('.' identifier)*
entry_point = module_path (':' object_path)?
which would import module_path
and then look up module_path.object_path
(or just module_path, if no object_path is specified).
When importing the module,
the directory containing the Source Tree
MUST NOT be added to sys.path and searched for the module,
including by Python’s automatic behavior of adding
the working directory or script directory to the path,
unless present anyway due to site or PYTHONPATH.
If a build-backend key is not present within
a [build-system] table of a pyproject.toml file,
Build Frontends SHOULD assume a default value for it of:
[build-system]
build-backend = "setuptools.build_meta:__legacy__"
or else MAY revert to the legacy behaviour of directly executing
a setup.py script at the root of the project’s source tree.
Projects MAY still include a setup.py
for compatibility with legacy tools that do not conform to this specification.
backend-path key¶
The optional backend-path key specifies where
a local Build Backend can be loaded from,
for projects that may wish to include the source code for their build backend
directly in their Source Tree,
rather than referencing the backend via the requires key.
Its value is a list of string paths to the directories which should be
inserted at the beginning of sys.path to import the module
specified in the build-backend key.
For example, suppose a project has a backend object named backend_object
located inside a Python module located at
project_subdirectory/backend_directory/backend_package/backend_module.py
relative to the project source tree root directory
(i.e. the directory in which the pyproject.toml is located),
and with backend_package being a Python Import Package
(i.e. with a __init__.py file inside it).
Therefore, the build-backend and backend-path configuration would be:
[build-system]
build-backend = "backend_package.backend_module:backend_object"
backend-path = ["project_subdirectory/backend_directory"]
Accordingly, project_subdirectory/backend_directory would be
inserted at the beginning of sys.path
and backend_package.backend_module would be imported from there,
with its backend_object attribute looked up as the backend object.
This is roughly equivalent to:
import sys
sys.path.insert(0, "project_subdirectory/backend_directory")
import backend_package.backend_module
backend = backend_package.backend_module.backend_object
There are restrictions on the content of the backend-path key:
Directories in
backend-pathare interpreted as relative to the project root (i.e. thepyproject.tomldirectory), and MUST refer to a location within the Source Tree (after relative paths and symbolic links have been resolved). Build Frontends SHOULD check this condition (typically by resolving the location to an absolute path and resolving symbolic links, and then checking that it is within the project root) and fail with an error message if it is violated.The backend code MUST be loaded from one of the directories specified in
backend-path(i.e.,backend-pathMUST NOT be specified without in-tree backend code). Frontends MAY enforce this check, but are not required to. Doing so would typically involve checking the backend’s__file__attribute against the locations inbackend-path.