2016-02-18 06:00:57 -05:00
|
|
|
|
PEP: 517
|
|
|
|
|
Title: A build-system independent format for source trees
|
|
|
|
|
Version: $Revision$
|
|
|
|
|
Last-Modified: $Date$
|
2016-09-22 07:39:58 -04:00
|
|
|
|
Author: Nathaniel J. Smith <njs@pobox.com>,
|
|
|
|
|
Thomas Kluyver <thomas@kluyver.me.uk>
|
2016-02-18 06:00:57 -05:00
|
|
|
|
BDFL-Delegate: Nick Coghlan <ncoghlan@gmail.com>
|
|
|
|
|
Discussions-To: <distutils-sig@python.org>
|
2021-06-30 15:51:34 -04:00
|
|
|
|
Status: Final
|
2016-02-18 06:00:57 -05:00
|
|
|
|
Type: Standards Track
|
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
|
Created: 30-Sep-2015
|
2017-09-29 02:42:07 -04:00
|
|
|
|
Post-History: 1 Oct 2015, 25 Oct 2015, 19 May 2017, 11 Sep 2017
|
|
|
|
|
Resolution: https://mail.python.org/pipermail/distutils-sig/2017-September/031548.html
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
==========
|
|
|
|
|
Abstract
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
While ``distutils`` / ``setuptools`` have taken us a long way, they
|
|
|
|
|
suffer from three serious problems: (a) they're missing important
|
|
|
|
|
features like usable build-time dependency declaration,
|
|
|
|
|
autoconfiguration, and even basic ergonomic niceties like `DRY
|
|
|
|
|
<https://en.wikipedia.org/wiki/Don%27t_repeat_yourself>`_-compliant
|
|
|
|
|
version number management, and (b) extending them is difficult, so
|
|
|
|
|
while there do exist various solutions to the above problems, they're
|
|
|
|
|
often quirky, fragile, and expensive to maintain, and yet (c) it's
|
|
|
|
|
very difficult to use anything else, because distutils/setuptools
|
|
|
|
|
provide the standard interface for installing packages expected by
|
|
|
|
|
both users and installation tools like ``pip``.
|
|
|
|
|
|
|
|
|
|
Previous efforts (e.g. distutils2 or setuptools itself) have attempted
|
|
|
|
|
to solve problems (a) and/or (b). This proposal aims to solve (c).
|
|
|
|
|
|
|
|
|
|
The goal of this PEP is get distutils-sig out of the business of being
|
|
|
|
|
a gatekeeper for Python build systems. If you want to use distutils,
|
|
|
|
|
great; if you want to use something else, then that should be easy to
|
|
|
|
|
do using standardized methods. The difficulty of interfacing with
|
|
|
|
|
distutils means that there aren't many such systems right now, but to
|
|
|
|
|
give a sense of what we're thinking about see `flit
|
|
|
|
|
<https://github.com/takluyver/flit>`_ or `bento
|
|
|
|
|
<https://cournape.github.io/Bento/>`_. Fortunately, wheels have now
|
|
|
|
|
solved many of the hard problems here -- e.g. it's no longer necessary
|
|
|
|
|
that a build system also know about every possible installation
|
|
|
|
|
configuration -- so pretty much all we really need from a build system
|
|
|
|
|
is that it have some way to spit out standard-compliant wheels and
|
|
|
|
|
sdists.
|
|
|
|
|
|
|
|
|
|
We therefore propose a new, relatively minimal interface for
|
|
|
|
|
installation tools like ``pip`` to interact with package source trees
|
|
|
|
|
and source distributions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
=======================
|
|
|
|
|
Terminology and goals
|
|
|
|
|
=======================
|
|
|
|
|
|
|
|
|
|
A *source tree* is something like a VCS checkout. We need a standard
|
|
|
|
|
interface for installing from this format, to support usages like
|
|
|
|
|
``pip install some-directory/``.
|
|
|
|
|
|
|
|
|
|
A *source distribution* is a static snapshot representing a particular
|
2018-12-03 12:14:23 -05:00
|
|
|
|
release of some source code, like ``lxml-3.4.4.tar.gz``. Source
|
2016-02-18 06:00:57 -05:00
|
|
|
|
distributions serve many purposes: they form an archival record of
|
|
|
|
|
releases, they provide a stupid-simple de facto standard for tools
|
|
|
|
|
that want to ingest and process large corpora of code, possibly
|
|
|
|
|
written in many languages (e.g. code search), they act as the input to
|
|
|
|
|
downstream packaging systems like Debian/Fedora/Conda/..., and so
|
|
|
|
|
forth. In the Python ecosystem they additionally have a particularly
|
|
|
|
|
important role to play, because packaging tools like ``pip`` are able
|
|
|
|
|
to use source distributions to fulfill binary dependencies, e.g. if
|
|
|
|
|
there is a distribution ``foo.whl`` which declares a dependency on
|
|
|
|
|
``bar``, then we need to support the case where ``pip install bar`` or
|
|
|
|
|
``pip install foo`` automatically locates the sdist for ``bar``,
|
|
|
|
|
downloads it, builds it, and installs the resulting package.
|
|
|
|
|
|
|
|
|
|
Source distributions are also known as *sdists* for short.
|
|
|
|
|
|
|
|
|
|
A *build frontend* is a tool that users might run that takes arbitrary
|
|
|
|
|
source trees or source distributions and builds wheels from them. The
|
|
|
|
|
actual building is done by each source tree's *build backend*. In a
|
|
|
|
|
command like ``pip wheel some-directory/``, pip is acting as a build
|
|
|
|
|
frontend.
|
|
|
|
|
|
|
|
|
|
An *integration frontend* is a tool that users might run that takes a
|
|
|
|
|
set of package requirements (e.g. a requirements.txt file) and
|
|
|
|
|
attempts to update a working environment to satisfy those
|
|
|
|
|
requirements. This may require locating, building, and installing a
|
|
|
|
|
combination of wheels and sdists. In a command like ``pip install
|
|
|
|
|
lxml==2.4.0``, pip is acting as an integration frontend.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
==============
|
|
|
|
|
Source trees
|
|
|
|
|
==============
|
|
|
|
|
|
|
|
|
|
There is an existing, legacy source tree format involving
|
|
|
|
|
``setup.py``. We don't try to specify it further; its de facto
|
|
|
|
|
specification is encoded in the source code and documentation of
|
|
|
|
|
``distutils``, ``setuptools``, ``pip``, and other tools. We'll refer
|
|
|
|
|
to it as the ``setup.py``\-style.
|
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
Here we define a new style of source tree based around the
|
2022-01-21 06:03:51 -05:00
|
|
|
|
``pyproject.toml`` file defined in :pep:`518`, extending the
|
2016-09-22 07:39:58 -04:00
|
|
|
|
``[build-system]`` table in that file with one additional key,
|
2016-11-28 13:28:44 -05:00
|
|
|
|
``build-backend``. Here's an example of how it would look::
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
[build-system]
|
|
|
|
|
# Defined by PEP 518:
|
|
|
|
|
requires = ["flit"]
|
|
|
|
|
# Defined by this PEP:
|
2016-11-28 13:28:44 -05:00
|
|
|
|
build-backend = "flit.api:main"
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-11-28 13:28:44 -05:00
|
|
|
|
``build-backend`` is a string naming a Python object that will be
|
2016-09-22 07:39:58 -04:00
|
|
|
|
used to perform the build (see below for details). This is formatted
|
|
|
|
|
following the same ``module:object`` syntax as a ``setuptools`` entry
|
|
|
|
|
point. For instance, if the string is ``"flit.api:main"`` as in the
|
|
|
|
|
example above, this object would be looked up by executing the
|
|
|
|
|
equivalent of::
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
import flit.api
|
|
|
|
|
backend = flit.api.main
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
It's also legal to leave out the ``:object`` part, e.g. ::
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-11-28 13:28:44 -05:00
|
|
|
|
build-backend = "flit.api"
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
which acts like::
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
import flit.api
|
|
|
|
|
backend = flit.api
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
Formally, the string should satisfy this grammar::
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
identifier = (letter | '_') (letter | '_' | digit)*
|
|
|
|
|
module_path = identifier ('.' identifier)*
|
|
|
|
|
object_path = identifier ('.' identifier)*
|
|
|
|
|
entry_point = module_path (':' object_path)?
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-09-22 07:39:58 -04:00
|
|
|
|
And we import ``module_path`` and then lookup
|
|
|
|
|
``module_path.object_path`` (or just ``module_path`` if
|
|
|
|
|
``object_path`` is missing).
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-09-01 06:23:55 -04:00
|
|
|
|
When importing the module path, we do *not* look in the directory containing the
|
|
|
|
|
source tree, unless that would be on ``sys.path`` anyway (e.g. because it is
|
|
|
|
|
specified in PYTHONPATH). Although Python automatically adds the working
|
|
|
|
|
directory to ``sys.path`` in some situations, code to resolve the backend should
|
|
|
|
|
not be affected by this.
|
|
|
|
|
|
2016-11-28 13:28:44 -05:00
|
|
|
|
If the ``pyproject.toml`` file is absent, or the ``build-backend``
|
2016-09-22 07:39:58 -04:00
|
|
|
|
key is missing, the source tree is not using this specification, and
|
2019-05-07 15:37:29 -04:00
|
|
|
|
tools should revert to the legacy behaviour of running ``setup.py`` (either
|
|
|
|
|
directly, or by implicitly invoking the ``setuptools.build_meta:__legacy__``
|
|
|
|
|
backend).
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2019-04-10 09:53:29 -04:00
|
|
|
|
Where the ``build-backend`` key exists, this takes precedence and the source tree follows the format and
|
|
|
|
|
conventions of the specified backend (as such no ``setup.py`` is needed unless the backend requires it).
|
|
|
|
|
Projects may still wish to include a ``setup.py`` for compatibility with tools that do not use this spec.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2019-03-02 07:04:23 -05:00
|
|
|
|
This PEP also defines a ``backend-path`` key for use in ``pyproject.toml``, see
|
|
|
|
|
the "In-Tree Build Backends" section below. This key would be used as follows::
|
|
|
|
|
|
|
|
|
|
[build-system]
|
|
|
|
|
# Defined by PEP 518:
|
|
|
|
|
requires = ["flit"]
|
|
|
|
|
# Defined by this PEP:
|
|
|
|
|
build-backend = "local_backend"
|
|
|
|
|
backend-path = ["backend"]
|
|
|
|
|
|
|
|
|
|
|
2019-10-29 19:11:57 -04:00
|
|
|
|
Build requirements
|
2019-03-02 07:04:23 -05:00
|
|
|
|
==================
|
|
|
|
|
|
|
|
|
|
This PEP places a number of additional requirements on the "build requirements"
|
|
|
|
|
section of ``pyproject.toml``. These are intended to ensure that projects do
|
|
|
|
|
not create impossible to satisfy conditions with their build requirements.
|
|
|
|
|
|
|
|
|
|
- Project build requirements will define a directed graph of requirements
|
|
|
|
|
(project A needs B to build, B needs C and D, etc.) This graph MUST NOT
|
|
|
|
|
contain cycles. If (due to lack of co-ordination between projects, for
|
|
|
|
|
example) a cycle is present, front ends MAY refuse to build the project.
|
|
|
|
|
- Where build requirements are available as wheels, front ends SHOULD use these
|
|
|
|
|
where practical, to avoid deeply nested builds. However front ends 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.
|
|
|
|
|
- Front ends SHOULD check explicitly for requirement cycles, and terminate
|
|
|
|
|
the build with an informative message if one is found.
|
|
|
|
|
|
|
|
|
|
Note in particular that 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).
|
|
|
|
|
|
|
|
|
|
|
2016-02-18 06:00:57 -05:00
|
|
|
|
=========================
|
|
|
|
|
Build backend interface
|
|
|
|
|
=========================
|
|
|
|
|
|
|
|
|
|
The build backend object is expected to have attributes which provide
|
|
|
|
|
some or all of the following hooks. The common ``config_settings``
|
2017-07-14 03:25:30 -04:00
|
|
|
|
argument is described after the individual hooks.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-07-14 03:25:30 -04:00
|
|
|
|
Mandatory hooks
|
|
|
|
|
===============
|
2017-06-24 10:16:21 -04:00
|
|
|
|
|
2017-07-14 03:25:30 -04:00
|
|
|
|
build_wheel
|
|
|
|
|
-----------
|
2017-06-24 10:16:21 -04:00
|
|
|
|
|
2016-02-18 06:00:57 -05:00
|
|
|
|
::
|
|
|
|
|
|
2017-08-25 08:36:55 -04:00
|
|
|
|
build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
|
2017-07-14 03:25:30 -04:00
|
|
|
|
...
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-07-14 03:25:30 -04:00
|
|
|
|
Must build a .whl file, and place it in the specified ``wheel_directory``. It
|
|
|
|
|
must return the basename (not the full path) of the ``.whl`` file it creates,
|
|
|
|
|
as a unicode string.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-07-20 08:12:48 -04:00
|
|
|
|
If the build frontend has previously called ``prepare_metadata_for_build_wheel``
|
|
|
|
|
and depends on the wheel resulting from this call to have metadata
|
2017-06-16 02:59:09 -04:00
|
|
|
|
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
|
2016-02-18 06:00:57 -05:00
|
|
|
|
metadata. The directory passed in by the build frontend MUST be
|
2017-07-20 08:12:48 -04:00
|
|
|
|
identical to the directory created by ``prepare_metadata_for_build_wheel``,
|
2016-02-18 06:00:57 -05:00
|
|
|
|
including any unrecognized files it created.
|
|
|
|
|
|
2017-07-20 08:12:48 -04:00
|
|
|
|
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``.
|
2017-07-16 01:12:00 -04:00
|
|
|
|
|
2017-09-01 06:23:55 -04:00
|
|
|
|
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. But if the backend indicates that it is missing some requirements for
|
|
|
|
|
creating an sdist (see below), the frontend will fall back to calling
|
|
|
|
|
``build_wheel`` in the source directory.
|
2017-08-25 08:36:55 -04:00
|
|
|
|
|
|
|
|
|
The source directory may be read-only. Backends should therefore be
|
|
|
|
|
prepared to build without creating or modifying any files in the source
|
|
|
|
|
directory, but they may opt not to handle this case, 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
|
2017-07-16 01:12:00 -04:00
|
|
|
|
make a material difference to the final result of the build.
|
2017-07-14 03:25:30 -04:00
|
|
|
|
|
|
|
|
|
build_sdist
|
|
|
|
|
-----------
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-06-24 10:16:21 -04:00
|
|
|
|
::
|
|
|
|
|
|
2017-07-14 03:25:30 -04:00
|
|
|
|
def build_sdist(sdist_directory, config_settings=None):
|
|
|
|
|
...
|
2017-06-05 05:44:47 -04:00
|
|
|
|
|
2017-06-14 07:38:14 -04:00
|
|
|
|
Must build a .tar.gz source distribution and place it in the specified
|
2017-07-14 03:25:30 -04:00
|
|
|
|
``sdist_directory``. It must return the basename (not the full path) of the
|
|
|
|
|
``.tar.gz`` file it creates, as a unicode string.
|
2017-06-05 05:44:47 -04:00
|
|
|
|
|
2017-06-14 07:38:14 -04:00
|
|
|
|
A .tar.gz source distribution (sdist) contains a single top-level directory called
|
2017-06-05 05:44:47 -04:00
|
|
|
|
``{name}-{version}`` (e.g. ``foo-1.0``), containing the source files of the
|
|
|
|
|
package. This directory must also contain the
|
|
|
|
|
``pyproject.toml`` from the build directory, and a PKG-INFO file containing
|
|
|
|
|
metadata in the format described in
|
2022-01-21 06:03:51 -05:00
|
|
|
|
:pep:`345`. Although historically
|
2017-06-14 07:38:14 -04:00
|
|
|
|
zip files have also been used as sdists, this hook should produce a gzipped
|
|
|
|
|
tarball. This is already the more common format for sdists, and having a
|
|
|
|
|
consistent format makes for simpler tooling.
|
|
|
|
|
|
|
|
|
|
The generated tarball should use the modern POSIX.1-2001 pax tar format, which
|
|
|
|
|
specifies UTF-8 based file names. This is not yet the default for the tarfile
|
|
|
|
|
module shipped with Python 3.6, so backends using the tarfile module need to
|
|
|
|
|
explicitly pass ``format=tarfile.PAX_FORMAT``.
|
|
|
|
|
|
2017-09-01 06:23:55 -04:00
|
|
|
|
Some backends may have extra requirements for creating sdists, such as version
|
|
|
|
|
control tools. However, some frontends may prefer to make intermediate sdists
|
|
|
|
|
when producing wheels, to ensure consistency.
|
|
|
|
|
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.
|
2017-07-14 03:25:30 -04:00
|
|
|
|
|
|
|
|
|
Optional hooks
|
|
|
|
|
==============
|
|
|
|
|
|
|
|
|
|
get_requires_for_build_wheel
|
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
def get_requires_for_build_wheel(config_settings=None):
|
|
|
|
|
...
|
|
|
|
|
|
2022-01-21 06:03:51 -05:00
|
|
|
|
This hook MUST return an additional list of strings containing :pep:`508`
|
2017-07-14 03:25:30 -04:00
|
|
|
|
dependency specifications, above and beyond those specified in the
|
|
|
|
|
``pyproject.toml`` 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, the default implementation is equivalent to ``return []``.
|
|
|
|
|
|
|
|
|
|
prepare_metadata_for_build_wheel
|
|
|
|
|
--------------------------------
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
Must create a ``.dist-info`` directory containing wheel metadata
|
|
|
|
|
inside the specified ``metadata_directory`` (i.e., creates a directory
|
|
|
|
|
like ``{metadata_directory}/{package}-{version}.dist-info/``). This
|
|
|
|
|
directory 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 unicode 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.
|
|
|
|
|
|
|
|
|
|
get_requires_for_build_sdist
|
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
def get_requires_for_build_sdist(config_settings=None):
|
|
|
|
|
...
|
|
|
|
|
|
2022-01-21 06:03:51 -05:00
|
|
|
|
This hook MUST return an additional list of strings containing :pep:`508`
|
2017-07-14 03:25:30 -04:00
|
|
|
|
dependency specifications, above and beyond those specified in the
|
|
|
|
|
``pyproject.toml`` file. These dependencies will be installed when calling the
|
|
|
|
|
``build_sdist`` hook.
|
|
|
|
|
|
|
|
|
|
If not defined, the default implementation is equivalent to ``return []``.
|
2017-06-05 05:44:47 -04:00
|
|
|
|
|
|
|
|
|
|
2016-11-28 13:27:07 -05:00
|
|
|
|
.. note:: Editable installs
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-06-05 05:44:47 -04:00
|
|
|
|
This PEP originally specified another hook, ``install_editable``, to do an
|
2016-11-28 13:27:07 -05:00
|
|
|
|
editable install (as with ``pip install -e``). It was removed due to the
|
|
|
|
|
complexity of the topic, but may be specified in a later PEP.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2016-11-28 13:27:07 -05:00
|
|
|
|
Briefly, the questions to be answered include: what reasonable ways existing
|
|
|
|
|
of implementing an 'editable install'? Should the backend or the frontend
|
|
|
|
|
pick how to make an editable install? And if the frontend does, what does it
|
|
|
|
|
need from the backend to do so.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-07-15 00:31:54 -04:00
|
|
|
|
Config settings
|
|
|
|
|
===============
|
|
|
|
|
|
2016-02-18 06:00:57 -05:00
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
config_settings
|
|
|
|
|
|
|
|
|
|
This 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 a mix of modern and legacy command line arguments
|
|
|
|
|
like::
|
|
|
|
|
|
|
|
|
|
pip install \
|
|
|
|
|
--package-config CC=gcc \
|
|
|
|
|
--global-option="--some-global-option" \
|
|
|
|
|
--build-option="--build-option1" \
|
|
|
|
|
--build-option="--build-option2"
|
|
|
|
|
|
|
|
|
|
into a ``config_settings`` dictionary like::
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
"CC": "gcc",
|
|
|
|
|
"--global-option": ["--some-global-option"],
|
|
|
|
|
"--build-option": ["--build-option1", "--build-option2"],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Of course, it's up to users to make sure that they pass options which
|
|
|
|
|
make sense for the particular build backend and package that they are
|
|
|
|
|
building.
|
|
|
|
|
|
2017-06-14 07:38:14 -04:00
|
|
|
|
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.
|
|
|
|
|
|
2016-02-18 06:00:57 -05:00
|
|
|
|
All hooks are run with working directory set to the root of the source
|
|
|
|
|
tree, and MAY print arbitrary informational text on stdout and
|
|
|
|
|
stderr. They MUST NOT read from stdin, and the build frontend MAY
|
|
|
|
|
close stdin before invoking the hooks.
|
|
|
|
|
|
2017-05-28 21:23:39 -04:00
|
|
|
|
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 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.
|
|
|
|
|
|
2016-02-18 06:00:57 -05:00
|
|
|
|
If a hook raises an exception, or causes the process to terminate,
|
|
|
|
|
then this indicates an error.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Build environment
|
|
|
|
|
=================
|
|
|
|
|
|
|
|
|
|
One of the responsibilities of a build frontend is to set up the
|
|
|
|
|
Python environment in which the build backend will run.
|
|
|
|
|
|
|
|
|
|
We do not require that any particular "virtual environment" mechanism
|
|
|
|
|
be used; a build frontend might use virtualenv, or venv, or no special
|
|
|
|
|
mechanism at all. But whatever mechanism is used MUST meet the
|
|
|
|
|
following criteria:
|
|
|
|
|
|
|
|
|
|
- All requirements specified by the project's build-requirements must
|
|
|
|
|
be available for import from Python. In particular:
|
|
|
|
|
|
2017-07-14 03:25:30 -04:00
|
|
|
|
- The ``get_requires_for_build_wheel`` and ``get_requires_for_build_sdist`` hooks are
|
2017-06-24 10:16:21 -04:00
|
|
|
|
executed in an environment which contains the bootstrap requirements
|
|
|
|
|
specified in the ``pyproject.toml`` file.
|
|
|
|
|
|
2017-07-14 03:25:30 -04:00
|
|
|
|
- The ``prepare_metadata_for_build_wheel`` and ``build_wheel`` hooks are
|
|
|
|
|
executed in an environment which contains the
|
2017-06-24 10:16:21 -04:00
|
|
|
|
bootstrap requirements from ``pyproject.toml`` and those specified by the
|
2017-07-14 03:25:30 -04:00
|
|
|
|
``get_requires_for_build_wheel`` hook.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-06-24 10:16:21 -04:00
|
|
|
|
- The ``build_sdist`` hook is executed in an environment which contains the
|
|
|
|
|
bootstrap requirements from ``pyproject.toml`` and those specified by the
|
2017-07-14 03:25:30 -04:00
|
|
|
|
``get_requires_for_build_sdist`` hook.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
- This must remain true even for new Python subprocesses spawned by
|
|
|
|
|
the build environment, e.g. code like::
|
|
|
|
|
|
|
|
|
|
import sys, subprocess
|
|
|
|
|
subprocess.check_call([sys.executable, ...])
|
|
|
|
|
|
|
|
|
|
must spawn a Python process which has access to all the project's
|
|
|
|
|
build-requirements. This is necessary e.g. for build backends that
|
|
|
|
|
want to run legacy ``setup.py`` scripts in a subprocess.
|
|
|
|
|
|
|
|
|
|
- All command-line scripts provided by the build-required packages
|
|
|
|
|
must be present in the build environment's PATH. For example, if a
|
|
|
|
|
project declares a build-requirement on `flit
|
2016-02-18 07:02:27 -05:00
|
|
|
|
<https://flit.readthedocs.org/en/latest/>`__, then the following must
|
2016-02-18 06:00:57 -05:00
|
|
|
|
work as a mechanism for running the flit command-line tool::
|
|
|
|
|
|
|
|
|
|
import subprocess
|
2020-12-14 15:13:50 -05:00
|
|
|
|
import shutil
|
|
|
|
|
subprocess.check_call([shutil.which("flit"), ...])
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
stdlib, or that are explicitly declared as build-requirements.
|
|
|
|
|
|
2017-07-15 00:31:54 -04:00
|
|
|
|
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.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
Recommendations for build frontends (non-normative)
|
|
|
|
|
---------------------------------------------------
|
|
|
|
|
|
|
|
|
|
A build frontend MAY use any mechanism for setting up a build
|
|
|
|
|
environment that meets the above criteria. For example, simply
|
|
|
|
|
installing all build-requirements into the global environment would be
|
|
|
|
|
sufficient to build any compliant package -- but this would be
|
|
|
|
|
sub-optimal for a number of reasons. This section contains
|
|
|
|
|
non-normative advice to frontend implementors.
|
|
|
|
|
|
|
|
|
|
A build frontend SHOULD, by default, create an isolated environment
|
|
|
|
|
for each build, containing only the standard library and any
|
|
|
|
|
explicitly requested build-dependencies. This has two benefits:
|
|
|
|
|
|
|
|
|
|
- It allows for a single installation run to build multiple packages
|
|
|
|
|
that have contradictory build-requirements. E.g. if package1
|
|
|
|
|
build-requires pbr==1.8.1, and package2 build-requires pbr==1.7.2,
|
|
|
|
|
then these cannot both be installed simultaneously into the global
|
|
|
|
|
environment -- which is a problem when the user requests ``pip
|
|
|
|
|
install package1 package2``. Or if the user already has pbr==1.8.1
|
|
|
|
|
installed in their global environment, and a package build-requires
|
|
|
|
|
pbr==1.7.2, then downgrading the user's version would be rather
|
|
|
|
|
rude.
|
|
|
|
|
|
|
|
|
|
- It acts as a kind of public health measure to maximize the number of
|
|
|
|
|
packages that actually do declare accurate build-dependencies. We
|
|
|
|
|
can write all the strongly worded admonitions to package authors we
|
|
|
|
|
want, but if build frontends don't enforce isolation by default,
|
|
|
|
|
then we'll inevitably end up with lots of packages on PyPI that
|
|
|
|
|
build fine on the original author's machine and nowhere else, which
|
|
|
|
|
is a headache that no-one needs.
|
|
|
|
|
|
|
|
|
|
However, there will also be situations where build-requirements are
|
|
|
|
|
problematic in various ways. For example, a package author might
|
|
|
|
|
accidentally leave off some crucial requirement despite our best
|
2016-09-22 07:39:58 -04:00
|
|
|
|
efforts; or, a package might declare a build-requirement on ``foo >=
|
|
|
|
|
1.0`` which worked great when 1.0 was the latest version, but now 1.1
|
2016-02-18 06:00:57 -05:00
|
|
|
|
is out and it has a showstopper bug; or, the user might decide to
|
|
|
|
|
build a package against numpy==1.7 -- overriding the package's
|
|
|
|
|
preferred numpy==1.8 -- to guarantee that the resulting build will be
|
|
|
|
|
compatible at the C ABI level with an older version of numpy (even if
|
|
|
|
|
this means the resulting build is unsupported upstream). Therefore,
|
|
|
|
|
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-requirements.
|
|
|
|
|
|
|
|
|
|
The general principle here is that we want to enforce hygiene on
|
|
|
|
|
package *authors*, while still allowing *end-users* to open up the
|
|
|
|
|
hood and apply duct tape when necessary.
|
|
|
|
|
|
|
|
|
|
|
2019-10-29 19:11:57 -04:00
|
|
|
|
In-tree build backends
|
2019-03-02 07:04:23 -05:00
|
|
|
|
======================
|
|
|
|
|
|
|
|
|
|
In certain circumstances, projects may wish to include the source code for the
|
|
|
|
|
build backend directly in the source tree, rather than referencing the backend
|
|
|
|
|
via the ``requires`` key. Two specific situations where this would be expected
|
|
|
|
|
are:
|
|
|
|
|
|
|
|
|
|
- Backends themselves, which want to use their own features for building
|
|
|
|
|
themselves ("self-hosting backends")
|
|
|
|
|
- Project-specific backends, typically consisting of a custom wrapper around a
|
|
|
|
|
standard backend, where the wrapper is too project-specific to be worth
|
|
|
|
|
distributing independently ("in-tree backends")
|
|
|
|
|
|
|
|
|
|
Projects can specify that their backend code is hosted in-tree by including the
|
|
|
|
|
``backend-path`` key in ``pyproject.toml``. This key contains a list of
|
|
|
|
|
directories, which the frontend will add to the start of ``sys.path`` when
|
|
|
|
|
loading the backend, and running the backend hooks.
|
|
|
|
|
|
|
|
|
|
There are two restrictions on the content of the ``backend-path`` key:
|
|
|
|
|
|
|
|
|
|
- Directories in ``backend-path`` are interpreted as relative to the project
|
|
|
|
|
root, and MUST refer to a location within the source tree (after relative
|
|
|
|
|
paths and symbolic links have been resolved).
|
|
|
|
|
- The backend code MUST be loaded from one of the directories specified in
|
|
|
|
|
``backend-path`` (i.e., it is not permitted to specify ``backend-path`` and
|
|
|
|
|
*not* have in-tree backend code).
|
|
|
|
|
|
|
|
|
|
The first restriction is to ensure that source trees remain self-contained,
|
|
|
|
|
and cannot refer to locations outside of the source tree. Frontends SHOULD
|
|
|
|
|
check this condition (typically by resolving the location to an absolute path
|
|
|
|
|
and resolving symbolic links, and then checking it against the project root),
|
|
|
|
|
and fail with an error message if it is violated.
|
|
|
|
|
|
|
|
|
|
The ``backend-path`` feature is intended to support the implementation of
|
|
|
|
|
in-tree backends, and not to allow configuration of existing backends. The
|
|
|
|
|
second restriction above is specifically to ensure that this is how the feature
|
|
|
|
|
is used. Front ends MAY enforce this check, but are not required to. Doing so
|
|
|
|
|
would typically involve checking the backend's ``__file__`` attribute against
|
|
|
|
|
the locations in ``backend-path``.
|
|
|
|
|
|
|
|
|
|
|
2016-02-18 06:00:57 -05:00
|
|
|
|
======================
|
|
|
|
|
Source distributions
|
|
|
|
|
======================
|
|
|
|
|
|
2017-07-14 03:25:30 -04:00
|
|
|
|
We continue with the legacy sdist format, adding some new restrictions.
|
|
|
|
|
This format is mostly
|
2016-02-18 06:00:57 -05:00
|
|
|
|
undefined, but basically comes down to: a file named
|
|
|
|
|
``{NAME}-{VERSION}.{EXT}``, which unpacks into a buildable source tree
|
|
|
|
|
called ``{NAME}-{VERSION}/``. Traditionally these have always
|
|
|
|
|
contained ``setup.py``\-style source trees; we now allow them to also
|
2016-09-22 07:39:58 -04:00
|
|
|
|
contain ``pyproject.toml``\-style source trees.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
Integration frontends require that an sdist named
|
|
|
|
|
``{NAME}-{VERSION}.{EXT}`` will generate a wheel named
|
|
|
|
|
``{NAME}-{VERSION}-{COMPAT-INFO}.whl``.
|
|
|
|
|
|
2022-01-21 06:03:51 -05:00
|
|
|
|
The new restrictions for sdists built by :pep:`517` backends are:
|
2017-07-14 03:25:30 -04:00
|
|
|
|
|
|
|
|
|
- They will be gzipped tar archives, with the ``.tar.gz`` extension. Zip
|
|
|
|
|
archives, or other compression formats for tarballs, are not allowed at
|
|
|
|
|
present.
|
|
|
|
|
- Tar archives must be created in the modern POSIX.1-2001 pax tar format, which
|
|
|
|
|
uses UTF-8 for file names.
|
|
|
|
|
- The source tree contained in an sdist is expected to include the
|
|
|
|
|
``pyproject.toml`` file.
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-07-20 09:37:31 -04:00
|
|
|
|
====================
|
|
|
|
|
Evolutionary notes
|
|
|
|
|
====================
|
|
|
|
|
|
|
|
|
|
A goal here is to make it as simple as possible to convert old-style
|
|
|
|
|
sdists to new-style sdists. (E.g., this is one motivation for
|
|
|
|
|
supporting dynamic build requirements.) The ideal would be that there
|
|
|
|
|
would be a single static ``pyproject.toml`` that could be dropped into any
|
|
|
|
|
"version 0" VCS checkout to convert it to the new shiny. This is
|
|
|
|
|
probably not 100% possible, but we can get close, and it's important
|
|
|
|
|
to keep track of how close we are... hence this section.
|
|
|
|
|
|
|
|
|
|
A rough plan would be: Create a build system package
|
|
|
|
|
(``setuptools_pypackage`` or whatever) that knows how to speak
|
|
|
|
|
whatever hook language we come up with, and convert them into calls to
|
|
|
|
|
``setup.py``. This will probably require some sort of hooking or
|
|
|
|
|
monkeypatching to setuptools to provide a way to extract the
|
|
|
|
|
``setup_requires=`` argument when needed, and to provide a new version
|
|
|
|
|
of the sdist command that generates the new-style format. This all
|
|
|
|
|
seems doable and sufficient for a large proportion of packages (though
|
|
|
|
|
obviously we'll want to prototype such a system before we finalize
|
|
|
|
|
anything here). (Alternatively, these changes could be made to
|
|
|
|
|
setuptools itself rather than going into a separate package.)
|
|
|
|
|
|
|
|
|
|
But there remain two obstacles that mean we probably won't be able to
|
|
|
|
|
automatically upgrade packages to the new format:
|
|
|
|
|
|
|
|
|
|
1) There currently exist packages which insist on particular packages
|
|
|
|
|
being available in their environment before setup.py is
|
|
|
|
|
executed. This means that if we decide to execute build scripts in
|
|
|
|
|
an isolated virtualenv-like environment, then projects will need to
|
|
|
|
|
check whether they do this, and if so then when upgrading to the
|
|
|
|
|
new system they will have to start explicitly declaring these
|
|
|
|
|
dependencies (either via ``setup_requires=`` or via static
|
|
|
|
|
declaration in ``pyproject.toml``).
|
|
|
|
|
|
|
|
|
|
2) There currently exist packages which do not declare consistent
|
|
|
|
|
metadata (e.g. ``egg_info`` and ``bdist_wheel`` might get different
|
|
|
|
|
``install_requires=``). When upgrading to the new system, projects
|
|
|
|
|
will have to evaluate whether this applies to them, and if so they
|
|
|
|
|
will need to stop doing that.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
==================
|
|
|
|
|
Rejected options
|
|
|
|
|
==================
|
|
|
|
|
|
|
|
|
|
* We discussed making the wheel and sdist hooks build unpacked directories
|
|
|
|
|
containing the same contents as their respective archives. In some cases this
|
|
|
|
|
could avoid the need to pack and unpack an archive, but this seems like
|
|
|
|
|
premature optimisation. It's advantageous for tools to work with archives
|
|
|
|
|
as the canonical interchange formats (especially for wheels, where the archive
|
|
|
|
|
format is already standardised). Close control of archive creation is
|
|
|
|
|
important for reproducible builds. And it's not clear that tasks requiring an
|
|
|
|
|
unpacked distribution will be more common than those requiring an archive.
|
|
|
|
|
* We considered an extra hook to copy files to a build directory before invoking
|
|
|
|
|
``build_wheel``. Looking at existing build systems, we found that passing
|
2017-09-01 06:23:55 -04:00
|
|
|
|
a build directory into ``build_wheel`` made more sense for many tools than
|
2017-07-20 09:37:31 -04:00
|
|
|
|
pre-emptively copying files into a build directory.
|
2017-09-01 06:23:55 -04:00
|
|
|
|
* The idea of passing ``build_wheel`` a build directory was then also deemed an
|
|
|
|
|
unnecessary complication. Build tools can use a temporary directory or a cache
|
|
|
|
|
directory to store intermediate files while building. If there is a need, a
|
|
|
|
|
frontend-controlled cache directory could be added in the future.
|
|
|
|
|
* For ``build_sdist`` to signal a failure for an expected reason, various
|
|
|
|
|
options were debated at great length, including raising
|
|
|
|
|
``NotImplementedError`` and returning either ``NotImplemented`` or ``None``.
|
|
|
|
|
Please do not attempt to reopen this discussion without an *extremely* good
|
|
|
|
|
reason, because we are quite tired of it.
|
|
|
|
|
* Allowing the backend to be imported from files in the source tree would be
|
|
|
|
|
more consistent with the way Python imports often work. However, not allowing
|
2019-03-02 07:04:23 -05:00
|
|
|
|
this prevents confusing errors from clashing module names. The initial
|
|
|
|
|
version of this PEP did not provide a means to allow backends to be
|
|
|
|
|
imported from files within the source tree, but the ``backend-path`` key
|
|
|
|
|
was added in the next revision to allow projects to opt into this behaviour
|
|
|
|
|
if needed.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
===============================
|
2019-10-29 19:11:57 -04:00
|
|
|
|
Summary of changes to PEP 517
|
2019-03-02 07:04:23 -05:00
|
|
|
|
===============================
|
|
|
|
|
|
|
|
|
|
The following changes were made to this PEP after the initial reference
|
|
|
|
|
implementation was released in pip 19.0.
|
|
|
|
|
|
|
|
|
|
* Cycles in build requirements were explicitly prohibited.
|
|
|
|
|
* Support for in-tree backends and self-hosting of backends was added by
|
|
|
|
|
the introduction of the ``backend-path`` key in the ``[build-system]``
|
|
|
|
|
table.
|
2022-01-21 06:03:51 -05:00
|
|
|
|
* Clarified that the ``setuptools.build_meta:__legacy__`` :pep:`517` backend is
|
2019-05-07 15:37:29 -04:00
|
|
|
|
an acceptable alternative to directly invoking ``setup.py`` for source trees
|
|
|
|
|
that don't specify ``build-backend`` explicitly.
|
2019-03-02 07:04:23 -05:00
|
|
|
|
|
2017-07-20 09:37:31 -04:00
|
|
|
|
|
2016-02-18 06:00:57 -05:00
|
|
|
|
===================================
|
2017-07-20 09:37:31 -04:00
|
|
|
|
Appendix A: Comparison to PEP 516
|
2016-02-18 06:00:57 -05:00
|
|
|
|
===================================
|
|
|
|
|
|
2017-07-20 09:37:31 -04:00
|
|
|
|
:pep:`516` is a competing proposal to specify a build system interface, which
|
|
|
|
|
has now been rejected in favour of this PEP. The primary difference is
|
2016-02-18 06:00:57 -05:00
|
|
|
|
that our build backend is defined via a Python hook-based interface
|
|
|
|
|
rather than a command-line based interface.
|
|
|
|
|
|
2022-01-21 06:03:51 -05:00
|
|
|
|
This appendix documents the arguments advanced for this PEP over :pep:`516`.
|
2017-07-20 09:37:31 -04:00
|
|
|
|
|
|
|
|
|
We do *not* expect that specifying Python hooks rather than command line
|
|
|
|
|
interfaces will, by itself, reduce the
|
|
|
|
|
complexity of calling into the backend, because build frontends will
|
2016-02-18 06:00:57 -05:00
|
|
|
|
in any case want to run hooks inside a child -- this is important to
|
|
|
|
|
isolate the build frontend itself from the backend code and to better
|
|
|
|
|
control the build backends execution environment. So under both
|
|
|
|
|
proposals, there will need to be some code in ``pip`` to spawn a
|
|
|
|
|
subprocess and talk to some kind of command-line/IPC interface, and
|
|
|
|
|
there will need to be some code in the subprocess that knows how to
|
|
|
|
|
parse these command line arguments and call the actual build backend
|
|
|
|
|
implementation. So this diagram applies to all proposals equally::
|
|
|
|
|
|
|
|
|
|
+-----------+ +---------------+ +----------------+
|
|
|
|
|
| frontend | -spawn-> | child cmdline | -Python-> | backend |
|
|
|
|
|
| (pip) | | interface | | implementation |
|
|
|
|
|
+-----------+ +---------------+ +----------------+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The key difference between the two approaches is how these interface
|
|
|
|
|
boundaries map onto project structure::
|
|
|
|
|
|
|
|
|
|
.-= This PEP =-.
|
|
|
|
|
|
|
|
|
|
+-----------+ +---------------+ | +----------------+
|
|
|
|
|
| frontend | -spawn-> | child cmdline | -Python-> | backend |
|
|
|
|
|
| (pip) | | interface | | | implementation |
|
|
|
|
|
+-----------+ +---------------+ | +----------------+
|
|
|
|
|
|
|
|
|
|
|
|______________________________________| |
|
|
|
|
|
Owned by pip, updated in lockstep |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PEP-defined interface boundary
|
|
|
|
|
Changes here require distutils-sig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.-= Alternative =-.
|
|
|
|
|
|
|
|
|
|
+-----------+ | +---------------+ +----------------+
|
|
|
|
|
| frontend | -spawn-> | child cmdline | -Python-> | backend |
|
|
|
|
|
| (pip) | | | interface | | implementation |
|
|
|
|
|
+-----------+ | +---------------+ +----------------+
|
|
|
|
|
|
|
|
|
|
|
| |____________________________________________|
|
|
|
|
|
| Owned by build backend, updated in lockstep
|
|
|
|
|
|
|
|
|
|
|
PEP-defined interface boundary
|
|
|
|
|
Changes here require distutils-sig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
By moving the PEP-defined interface boundary into Python code, we gain
|
|
|
|
|
three key advantages.
|
|
|
|
|
|
|
|
|
|
**First**, because there will likely be only a small number of build
|
|
|
|
|
frontends (``pip``, and... maybe a few others?), while there will
|
|
|
|
|
likely be a long tail of custom build backends (since these are chosen
|
|
|
|
|
separately by each package to match their particular build
|
|
|
|
|
requirements), the actual diagrams probably look more like::
|
|
|
|
|
|
|
|
|
|
.-= This PEP =-.
|
|
|
|
|
|
|
|
|
|
+-----------+ +---------------+ +----------------+
|
|
|
|
|
| frontend | -spawn-> | child cmdline | -Python+> | backend |
|
|
|
|
|
| (pip) | | interface | | | implementation |
|
|
|
|
|
+-----------+ +---------------+ | +----------------+
|
|
|
|
|
|
|
|
|
|
|
| +----------------+
|
|
|
|
|
+> | backend |
|
|
|
|
|
| | implementation |
|
|
|
|
|
| +----------------+
|
|
|
|
|
:
|
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
.-= Alternative =-.
|
|
|
|
|
|
|
|
|
|
+-----------+ +---------------+ +----------------+
|
|
|
|
|
| frontend | -spawn+> | child cmdline | -Python-> | backend |
|
|
|
|
|
| (pip) | | | interface | | implementation |
|
|
|
|
|
+-----------+ | +---------------+ +----------------+
|
|
|
|
|
|
|
|
|
|
|
| +---------------+ +----------------+
|
|
|
|
|
+> | child cmdline | -Python-> | backend |
|
|
|
|
|
| | interface | | implementation |
|
|
|
|
|
| +---------------+ +----------------+
|
|
|
|
|
:
|
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
That is, this PEP leads to less total code in the overall
|
|
|
|
|
ecosystem. And in particular, it reduces the barrier to entry of
|
|
|
|
|
making a new build system. For example, this is a complete, working
|
|
|
|
|
build backend::
|
|
|
|
|
|
|
|
|
|
# mypackage_custom_build_backend.py
|
|
|
|
|
import os.path
|
2017-06-14 07:38:14 -04:00
|
|
|
|
import pathlib
|
2017-07-16 01:12:00 -04:00
|
|
|
|
import shutil
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-07-16 01:12:00 -04:00
|
|
|
|
SDIST_NAME = "mypackage-0.1"
|
|
|
|
|
SDIST_FILENAME = SDIST_NAME + ".tar.gz"
|
|
|
|
|
WHEEL_FILENAME = "mypackage-0.1-py2.py3-none-any.whl"
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
2017-07-16 01:12:00 -04:00
|
|
|
|
#################
|
|
|
|
|
# sdist creation
|
|
|
|
|
#################
|
2017-06-14 07:38:14 -04:00
|
|
|
|
|
|
|
|
|
def _exclude_hidden_and_special_files(archive_entry):
|
|
|
|
|
"""Tarfile filter to exclude hidden and special files from the archive"""
|
2019-10-29 19:11:57 -04:00
|
|
|
|
if archive_entry.isfile() or archive_entry.isdir():
|
2017-06-14 07:38:14 -04:00
|
|
|
|
if not os.path.basename(archive_entry.name).startswith("."):
|
|
|
|
|
return archive_entry
|
|
|
|
|
|
2017-07-16 01:12:00 -04:00
|
|
|
|
def _make_sdist(sdist_dir):
|
|
|
|
|
"""Make an sdist and return both the Python object and its filename"""
|
|
|
|
|
sdist_path = pathlib.Path(sdist_dir) / SDIST_FILENAME
|
2017-06-14 07:38:14 -04:00
|
|
|
|
sdist = tarfile.open(sdist_path, "w:gz", format=tarfile.PAX_FORMAT)
|
|
|
|
|
# Tar up the whole directory, minus hidden and special files
|
2017-07-16 01:12:00 -04:00
|
|
|
|
sdist.add(os.getcwd(), arcname=SDIST_NAME,
|
2017-06-14 07:38:14 -04:00
|
|
|
|
filter=_exclude_hidden_and_special_files)
|
2017-07-16 01:12:00 -04:00
|
|
|
|
return sdist, SDIST_FILENAME
|
|
|
|
|
|
|
|
|
|
def build_sdist(sdist_dir, config_settings):
|
|
|
|
|
"""PEP 517 sdist creation hook"""
|
|
|
|
|
sdist, sdist_filename = _make_sdist(sdist_dir)
|
|
|
|
|
return sdist_filename
|
|
|
|
|
|
|
|
|
|
#################
|
|
|
|
|
# wheel creation
|
|
|
|
|
#################
|
|
|
|
|
|
|
|
|
|
def get_requires_for_build_wheel(config_settings):
|
|
|
|
|
"""PEP 517 wheel building dependency definition hook"""
|
|
|
|
|
# As a simple static requirement, this could also just be
|
|
|
|
|
# listed in the project's build system dependencies instead
|
|
|
|
|
return ["wheel"]
|
|
|
|
|
|
2017-08-25 08:36:55 -04:00
|
|
|
|
def build_wheel(wheel_directory,
|
2017-07-16 01:12:00 -04:00
|
|
|
|
metadata_directory=None, config_settings=None):
|
|
|
|
|
"""PEP 517 wheel creation hook"""
|
|
|
|
|
from wheel.archive import archive_wheelfile
|
|
|
|
|
path = os.path.join(wheel_directory, WHEEL_FILENAME)
|
|
|
|
|
archive_wheelfile(path, "src/")
|
|
|
|
|
return WHEEL_FILENAME
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
Of course, this is a *terrible* build backend: it requires the user to
|
|
|
|
|
have manually set up the wheel metadata in
|
|
|
|
|
``src/mypackage-0.1.dist-info/``; when the version number changes it
|
2017-06-14 07:38:14 -04:00
|
|
|
|
must be manually updated in multiple places... but it works, and more features
|
|
|
|
|
could be added incrementally. Much experience suggests that large successful
|
2016-02-18 06:00:57 -05:00
|
|
|
|
projects often originate as quick hacks (e.g., Linux -- "just a hobby,
|
|
|
|
|
won't be big and professional"; `IPython/Jupyter
|
|
|
|
|
<https://en.wikipedia.org/wiki/IPython#Grants_and_awards>`_ -- `a grad
|
2021-06-17 18:49:29 -04:00
|
|
|
|
student's $PYTHONSTARTUP file
|
2016-02-18 06:00:57 -05:00
|
|
|
|
<http://blog.fperez.org/2012/01/ipython-notebook-historical.html>`_),
|
|
|
|
|
so if our goal is to encourage the growth of a vibrant ecosystem of
|
|
|
|
|
good build tools, it's important to minimize the barrier to entry.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Second**, because Python provides a simpler yet richer structure for
|
|
|
|
|
describing interfaces, we remove unnecessary complexity from the
|
|
|
|
|
specification -- and specifications are the worst place for
|
|
|
|
|
complexity, because changing specifications requires painful
|
|
|
|
|
consensus-building across many stakeholders. In the command-line
|
|
|
|
|
interface approach, we have to come up with ad hoc ways to map
|
|
|
|
|
multiple different kinds of inputs into a single linear command line
|
|
|
|
|
(e.g. how do we avoid collisions between user-specified configuration
|
|
|
|
|
arguments and PEP-defined arguments? how do we specify optional
|
|
|
|
|
arguments? when working with a Python interface these questions have
|
|
|
|
|
simple, obvious answers). When spawning and managing subprocesses,
|
|
|
|
|
there are many fiddly details that must be gotten right, subtle
|
|
|
|
|
cross-platform differences, and some of the most obvious approaches --
|
|
|
|
|
e.g., using stdout to return data for the ``build_requires`` operation
|
|
|
|
|
-- can create unexpected pitfalls (e.g., what happens when computing
|
|
|
|
|
the build requirements requires spawning some child processes, and
|
|
|
|
|
these children occasionally print an error message to stdout?
|
|
|
|
|
obviously a careful build backend author can avoid this problem, but
|
|
|
|
|
the most obvious way of defining a Python interface removes this
|
|
|
|
|
possibility entirely, because the hook return value is clearly
|
|
|
|
|
demarcated).
|
|
|
|
|
|
|
|
|
|
In general, the need to isolate build backends into their own process
|
|
|
|
|
means that we can't remove IPC complexity entirely -- but by placing
|
|
|
|
|
both sides of the IPC channel under the control of a single project,
|
2016-05-03 05:03:16 -04:00
|
|
|
|
we make it much cheaper to fix bugs in the IPC interface than if
|
2016-02-18 06:00:57 -05:00
|
|
|
|
fixing bugs requires coordinated agreement and coordinated changes
|
|
|
|
|
across the ecosystem.
|
|
|
|
|
|
|
|
|
|
**Third**, and most crucially, the Python hook approach gives us much
|
|
|
|
|
more powerful options for evolving this specification in the future.
|
|
|
|
|
|
|
|
|
|
For concreteness, imagine that next year we add a new
|
2017-07-20 08:12:48 -04:00
|
|
|
|
``build_sdist_from_vcs`` hook, which provides an alternative to the current
|
|
|
|
|
``build_sdist`` hook where the frontend is responsible for passing
|
|
|
|
|
version control tracking metadata to backends (including indicating when all
|
|
|
|
|
on disk files are tracked), rather than individual backends having to query that
|
|
|
|
|
information themselves. In order to manage the transition, we'd want it to be
|
|
|
|
|
possible for build frontends to transparently use ``build_sdist_from_vcs`` when
|
|
|
|
|
available and fall back onto ``build_sdist`` otherwise; and we'd want it to be
|
2016-02-18 06:00:57 -05:00
|
|
|
|
possible for build backends to define both methods, for compatibility
|
|
|
|
|
with both old and new build frontends.
|
|
|
|
|
|
|
|
|
|
Furthermore, our mechanism should also fulfill two more goals: (a) If
|
|
|
|
|
new versions of e.g. ``pip`` and ``flit`` are both updated to support
|
|
|
|
|
the new interface, then this should be sufficient for it to be used;
|
|
|
|
|
in particular, it should *not* be necessary for every project that
|
2016-09-22 07:39:58 -04:00
|
|
|
|
*uses* ``flit`` to update its individual ``pyproject.toml`` file. (b)
|
2016-02-18 06:00:57 -05:00
|
|
|
|
We do not want to have to spawn extra processes just to perform this
|
|
|
|
|
negotiation, because process spawns can easily become a bottleneck when
|
|
|
|
|
deploying large multi-package stacks on some platforms (Windows).
|
|
|
|
|
|
|
|
|
|
In the interface described here, all of these goals are easy to
|
|
|
|
|
achieve. Because ``pip`` controls the code that runs inside the child
|
|
|
|
|
process, it can easily write it to do something like::
|
|
|
|
|
|
|
|
|
|
command, backend, args = parse_command_line_args(...)
|
2017-07-20 08:12:48 -04:00
|
|
|
|
if command == "build_sdist":
|
|
|
|
|
if hasattr(backend, "build_sdist_from_vcs"):
|
|
|
|
|
backend.build_sdist_from_vcs(...)
|
|
|
|
|
elif hasattr(backend, "build_sdist"):
|
|
|
|
|
backend.build_sdist(...)
|
2016-02-18 06:00:57 -05:00
|
|
|
|
else:
|
|
|
|
|
# error handling
|
|
|
|
|
|
|
|
|
|
In the alternative where the public interface boundary is placed at
|
|
|
|
|
the subprocess call, this is not possible -- either we need to spawn
|
|
|
|
|
an extra process just to query what interfaces are supported (as was
|
2022-01-21 06:03:51 -05:00
|
|
|
|
included in an earlier draft of :pep:`516`, an alternative to this), or
|
2016-02-18 06:00:57 -05:00
|
|
|
|
else we give up on autonegotiation entirely (as in the current version
|
|
|
|
|
of that PEP), meaning that any changes in the interface will require
|
2016-09-22 07:39:58 -04:00
|
|
|
|
N individual packages to update their ``pyproject.toml`` files before
|
2016-02-18 06:00:57 -05:00
|
|
|
|
any change can go live, and that any changes will necessarily be
|
|
|
|
|
restricted to new releases.
|
|
|
|
|
|
|
|
|
|
One specific consequence of this is that in this PEP, we're able to
|
2017-07-20 08:12:48 -04:00
|
|
|
|
make the ``prepare_metadata_for_build_wheel`` command optional. In our design,
|
|
|
|
|
this can be readily handled by build frontends, which can put code in
|
|
|
|
|
their subprocess runner like::
|
|
|
|
|
|
2017-07-20 09:09:00 -04:00
|
|
|
|
def dump_wheel_metadata(backend, working_dir):
|
|
|
|
|
"""Dumps wheel metadata to working directory.
|
|
|
|
|
|
|
|
|
|
Returns absolute path to resulting metadata directory
|
|
|
|
|
"""
|
2017-07-20 08:12:48 -04:00
|
|
|
|
if hasattr(backend, "prepare_metadata_for_build_wheel"):
|
2017-07-20 09:09:00 -04:00
|
|
|
|
subdir = backend.prepare_metadata_for_build_wheel(working_dir)
|
2017-07-20 08:12:48 -04:00
|
|
|
|
else:
|
2019-10-29 19:11:57 -04:00
|
|
|
|
wheel_fname = backend.build_wheel(working_dir)
|
2017-07-20 09:09:00 -04:00
|
|
|
|
already_built = os.path.join(working_dir, "ALREADY_BUILT_WHEEL")
|
|
|
|
|
with open(already_built, "w") as f:
|
|
|
|
|
f.write(wheel_fname)
|
|
|
|
|
subdir = unzip_metadata(os.path.join(working_dir, wheel_fname))
|
|
|
|
|
return os.path.join(working_dir, subdir)
|
|
|
|
|
|
|
|
|
|
def ensure_wheel_is_built(backend, output_dir, working_dir, metadata_dir):
|
|
|
|
|
"""Ensures built wheel is available in output directory
|
|
|
|
|
|
|
|
|
|
Returns absolute path to resulting wheel file
|
|
|
|
|
"""
|
|
|
|
|
already_built = os.path.join(working_dir, "ALREADY_BUILT_WHEEL")
|
|
|
|
|
if os.path.exists(already_built):
|
|
|
|
|
with open(already_built, "r") as f:
|
|
|
|
|
wheel_fname = f.read().strip()
|
|
|
|
|
working_path = os.path.join(working_dir, wheel_fname)
|
|
|
|
|
final_path = os.path.join(output_dir, wheel_fname)
|
|
|
|
|
os.rename(working_path, final_path)
|
|
|
|
|
os.remove(already_built)
|
2017-07-20 08:12:48 -04:00
|
|
|
|
else:
|
2017-07-20 09:09:00 -04:00
|
|
|
|
wheel_fname = backend.build_wheel(output_dir, metadata_dir=metadata_dir)
|
|
|
|
|
return os.path.join(output_dir, wheel_fname)
|
2017-07-20 08:12:48 -04:00
|
|
|
|
|
|
|
|
|
and thus expose a totally uniform interface to the rest of the frontend,
|
2016-02-18 06:00:57 -05:00
|
|
|
|
with no extra subprocess calls, no duplicated builds, etc. But
|
|
|
|
|
obviously this is the kind of code that you only want to write as part
|
2017-07-20 09:09:00 -04:00
|
|
|
|
of a private, within-project interface (e.g. the given example requires that
|
|
|
|
|
the working directory be shared between the two calls, but not with any
|
|
|
|
|
other wheel builds, and that the return value from the metadata helper function
|
|
|
|
|
will be passed back in to the wheel building one).
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
(And, of course, making the ``metadata`` command optional is one piece
|
2017-07-20 09:09:00 -04:00
|
|
|
|
of lowering the barrier to entry for developing new backends, as discussed
|
|
|
|
|
above.)
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Other differences
|
2016-02-18 07:02:27 -05:00
|
|
|
|
=================
|
2016-02-18 06:00:57 -05:00
|
|
|
|
|
|
|
|
|
Besides the key command line versus Python hook difference described
|
|
|
|
|
above, there are a few other differences in this proposal:
|
|
|
|
|
|
|
|
|
|
* Metadata command is optional (as described above).
|
|
|
|
|
|
|
|
|
|
* We return metadata as a directory, rather than a single METADATA
|
|
|
|
|
file. This aligns better with the way that in practice wheel metadata
|
|
|
|
|
is distributed across multiple files (e.g. entry points), and gives us
|
|
|
|
|
more options in the future. (For example, instead of following the PEP
|
|
|
|
|
426 proposal of switching the format of METADATA to JSON, we might
|
|
|
|
|
decide to keep the existing METADATA the way it is for backcompat,
|
|
|
|
|
while adding new extensions as JSON "sidecar" files inside the same
|
|
|
|
|
directory. Or maybe not; the point is it keeps our options more open.)
|
|
|
|
|
|
|
|
|
|
* We provide a mechanism for passing information between the metadata
|
|
|
|
|
step and the wheel building step. I guess everyone probably will
|
|
|
|
|
agree this is a good idea?
|
|
|
|
|
|
|
|
|
|
* We provide more detailed recommendations about the build environment,
|
|
|
|
|
but these aren't normative anyway.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
===========
|
|
|
|
|
Copyright
|
|
|
|
|
===========
|
|
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
coding: utf-8
|
|
|
|
|
End:
|