[Distutils] build system abstraction PEP
Robert Collins
robertc at robertcollins.net
Sun Nov 8 20:28:26 EST 2015
Now that the dependent spec is up for review, I've refreshed this
build system abstraction one.
Key changes:
- pep-426 -> wheel METADATA files as the distribution description interface
- dropped yaml for JSON as its in the stdlib
I've kept the indirection via a build system rather than adopting
setup.py as-is: I judge the risk for user confusion to be large enough
that its worth doing that - even though we recommend a setuptools
shim, forcing it on everyone is unpleasant.
commit 6de544a6e6aa2d0505e2ac8eb364b8b95e27790d
Author: Robert Collins <rbtcollins at hp.com>
Date: Wed Oct 28 19:21:48 2015 +1300
PEP for build system abstraction.
diff --git a/build-system-abstraction.rst b/build-system-abstraction.rst
new file mode 100644
index 0000000..b8232c8
--- /dev/null
+++ b/build-system-abstraction.rst
@@ -0,0 +1,419 @@
+:PEP: XX
+:Title: Build system abstraction for pip/conda etc
+:Version: $Revision$
+:Last-Modified: $Date$
+:Author: Robert Collins <rbtcollins at hp.com>,
+ Nathaniel Smith <njs at pobox.com>
+:BDFL-Delegate: Donald Stufft <donald at stufft.io>
+:Discussions-To: distutils-sig <distutils-sig at python.org>
+:Status: Draft
+:Type: Standards Track
+:Content-Type: text/x-rst
+:Created: 26-Oct-2015
+:Post-History: XX
+:Requires: The new dependency-specification PEP.
+
+
+Abstract
+========
+
+This PEP specifies a programmatic interface for pip [#pip]_ and other
+distribution or installation tools to use when working with Python
+source trees (both the developer tree - e.g. the git tree - and source
+distributions).
+
+The programmatic interface allows decoupling of pip from its current
+hard dependency on setuptools [#setuptools]_ able for two
+key reasons:
+
+1. It enables new build systems that may be much easier to use without
+ requiring them to even appear to be setuptools.
+
+2. It facilitates setuptools itself changing its user interface without
+ breaking pip, giving looser coupling.
+
+The programmatic interface also enables pip to install build time requirements
+for packages which is an important step in getting pip to full feature parity
+with the installation components of easy-install.
+
+As PEP-426 [#pep426]_ is draft, we cannot utilise the metadata format it
+defined. However PEP-427 wheels are in wide use and fairly well specified, so
+we have adopted the METADATA format from that for specifying distribution
+dependencies. However something was needed for communicating bootstrap
+requirements and build requirements - but a thin JSON schema is sufficient.
+
+Motivation
+==========
+
+There is significant pent-up frustration in the Python packaging ecosystem
+around the current lock-in between build system and pip. Breaking that lock-in
+is better for pip, for setuptools, and for other build systems like flit
+[#flit]_.
+
+Specification
+=============
+
+Overview
+--------
+
+Build tools will be located by reading a file ``pypa.json`` from the root
+directory of the source tree. That file describes how to get the build tool.
+The build tool is then self-describing. This avoids having stale descriptions
+of build tools encoded into sdists that have been uploaded to PyPI [#pypi]_.
+
+The interface involves executing processes rather than loading Python modules,
+because tools like pip will often interact with many different versions of the
+same build tool during a single invocation, and in-process APIs are much
+harder to manage in that situation.
+
+Process interface
+-----------------
+
+Where a process needs to be run, we need to be able to assemble the subprocess
+call to make. For this we use a simple command string with in-Python variable
+interpolation. Only variables defined in this PEP will be honoured: the use of
+additional variables is an error. Additional variables can only be added with
+a schema version increase. Basic variable expressions such as ``${PYTHON}``,
+``${PYTHON:-python}``, ``${PYTHON:+PYTHON -m coverage}`` can be used. An
+implementation of this is in shellvars [#shellvars]_.
+
+Processes will be run with the current working directory set to the root of
+the source tree.
+
+Available variables
+-------------------
+
+PYTHON
+ The Python interpreter in use. This is important to enable calling things
+ which are just Python entry points::
+
+ ${PYTHON} -m foo
+
+OUTPUT_DIR
+ Where to create requested output. If not set, outputting to the current
+ working directory is appropriate. This is required by ``pip`` which needs
+ to have wheels output in a known location.
+
+pypa.json
+---------
+
+The yaml file has the following schema. Extra keys are ignored.
+
+schema
+ The version of the schema. This PEP defines version 1.
+ Defaults to 1
+
+bootstrap-requires
+ A list of dependency specifications that must be installed before
+ running the build tool. For instance, if using flit, then the requirements
+ might be::
+
+ bootstrap-requires:
+ - flit
+
+build-tool
+ A command to run to query the build tool for its complete interface.
+ The build tool should output on stdout a build tool description JSON
+ document. Stdin may not be read from, and stderr may be handled however
+ the calling process desires.
+
+build tool description
+----------------------
+
+The build tool description schema. Extra keys are ignored.
+
+schema
+ The version of the schema. This PEP defines version 1.
+ Defaults to 1
+
+build-requires
+ Command to run to query build requirements. Build requirements are
+ returned as a JSON document with one key ``build_requires`` consisting of
+ a list of dependency specifications. Additional keys must be ignored.
+
+metadata
+ Command to run to generate metadata for the project. The metadata should
+ be output on stdout, stdin may not be consumed, and stderr handling is up
+ to the discretion of the calling process. The build-requires for the
+ project will be present in the Python environment when the metadata
+ command is run. pip would run metadata just once to determine what other
+ packages need to be downloaded and installed. The metadata is output as a
+ wheel METADATA file per PEP-427 [#pep427]_.
+
+ Note that the metadata generated by the metadata command, and the metadata
+ present in a generated wheel must be identical.
+
+wheel
+ Command to run to build a wheel of the project. OUTPUT_DIR will be set and
+ point to an existing directory where the wheel should be output. Stdin
+ may not be consumed, stdout and stderr handling is at the discretion of
+ the calling process. The build-requires for the project will be present in
+ the Python environment when the wheel command is run. Only one file
+ should be output - if more are output then pip would pick an arbitrary one
+ to consume.
+
+develop
+ Command to do an in-place 'development' installation of the project.
+ Stdin may not be consumed, stdout and stderr handling is at the discretion
+ of the calling process. The build-requires for the project will be
+ present in the Python environment when the develop command is run.
+
+ Not all build systems will be able to perform develop installs. If a build
+ system cannot do develop installs, then this key can be omitted.
+
+ Note that when it is omitted, commands like ``pip install -e foo``
+ will be unable to complete.
+
+provided-by
+ Optional distribution name that provides this build system.
+ This is used to facilitate caching the build tool description. If absent
+ then the build tool description cannot be cached, which will incure an
+ extra subprocess per package being built (to query the build tool).
+ Specifically, where the resolved bootstrap-requires results in the same
+ version of the named distribution being installed, the build tool
+ description is presumed to be identical.
+
+Python environments and hermetic builds
+---------------------------------------
+
+This specification does not prescribe whether builds should be hermetic or not.
+Existing build tools like setuptools will use installed versions of build time
+requirements (e.g. setuptools_scm) and only install other versions on version
+conflicts or missing dependencies. However its likely that better consistency
+can be created by always isolation builds and using only the
specified dependencies.
+
+However there are nuanced problems there - such as how can users force the
+avoidance of a bad version of a build requirement which meets some packages
+dependencies. Future PEPs may tackle this problem, but it is not currently in
+scope - it does not affect the metadata required to coordinate between build
+systems and things that need to do builds.
+
+Upgrades
+--------
+
+Both 'pypa.json' and the build tool description are versioned to permit future
+incompatible changes. There is a sequence dependency here.
+
+Upgrades to the schemas defined in this specification must proceed with the
+consumers first. To ensure that consumers should refuse to operate when
+'pypa.json' has a schema version that they do not recognise.
+
+Build tools listed in a 'pypa.json' with schema version 1 must not generate a
+build tool description with a version other than 1 (or absent, as the default
+is 1).
+
+Thus the sequence for upgrading either of schemas in a new PEP will be:
+
+1. Issue new PEP defining build tool description and 'pypa.json' schemas. The
+ 'pypa.yaml' schema version must change if either the 'pypa.yaml' schema has
+ changed or the build tool description schema has changed. The build tool
+ description schema version must change if the build tool description schema
+ has changed.
+2. Consumers (e.g. pip) implement support for the new schema version.
+3. Build tool authors implement the new schemas, and publish updated reference
+ 'pypa.json' files for their users. 4. Package authors opt into the new
+ schema when they are happy to introduce a dependency on the version of
+ 'pip' (and potentially other consumers) that introduced support for the new
+ schema version.
+
+The *same* process will take place for the initial deployment of this PEP:-
+the propogation of the capability to use this PEP without a `setuptools shim`_
+will be largely gated by the adoption rate of the first version of pip that
+supports it.
+
+Static metadata in sdists
+-------------------------
+
+This PEP does not tackle the current inability to trust static metadata in
+sdists. That is a separate problem to identifying and consuming the build
+system that is in use in a source tree, whether it came from an sdist or not.
+
+Handling of compiler options
+----------------------------
+
+Handling of different compiler options is out of scope for this specification.
+
+pip currently handles compiler options by appending user supplied strings to
+the command line it runs when running setuptools. This approach is sufficient
+to work with the build system interface defined in this PEP, with the
+exception that globally specified options will stop working globally as
+different build systems evolve. That problem can be solved in pip (or conda or
+other installers).
+
+In the long term, wheels should be able to express the difference between
+wheels built with one compiler or options vs another.
+
+Examples
+========
+
+An example 'pypa.json' for using flit::
+
+ bootstrap-requires:
+ - flit
+ build-tool: flit --dump-build-description
+
+When 'pip' reads this it would prepare an environment with flit in it and
+run `flit --dump-build-description` which would output something like::
+
+ build-requires: flit --dump-build-requires
+ metadata: flit --dump-metadata
+ provided-by: flit
+ wheel: flit wheel -d $OUTPUT_DIR
+
+The `--dump-` switches in this example would be needed to be added to
+flit (or someone else could write an adapter). Because flit doesn't have
+setup-requires support today, `--dump-build-requires` would just output a
+constant string::
+
+ {"build_requires": []}
+
+`--dump-metadata` would interrogate `flit.ini` and marshal the metadata into
+a wheel METADATA file and output that on stdout.
+
+flit wheel would need a `-d` parameter that tells it where to output the
+wheel (pip needs this).
+
+Backwards Compatibility
+=======================
+
+Older pips will remain unable to handle alternative build systems.
+This is no worse than the status quo - and individual build system
+projects can decide whether to include a shim ``setup.py`` or not.
+
+All existing build systems that can product wheels and do develop installs
+should be able to run under this abstraction and will only need a specific
+adapter for them constructed and published on PyPI.
+
+In the absence of a ``pypa.json`` file, tools like pip should assume a
+setuptools build system and use setuptools commands directly.
+
+
+Network effects
+---------------
+
+Projects that adopt build systems that are not setuptools compatible - that
+is that they have no setup.py, or the setup.py doesn't accept commands that
+existing tools try to use - will not be installable by those existing tools.
+
+Where those projects are used by other projects, this effect will cascade.
+
+In particular, because pip does not handle setup-requires today, any project
+(A) that adopts a setuptools-incompatible build system and is consumed as a
+setup-requirement by a second project (B) which has not itself transitioned to
+having a pypa.json will make B uninstallable by any version of pip. This is
+because setup.py in B will trigger easy-install when 'setup.py egg_info' is
+run by pip, and that will try and fail to install A.
+
+As such we recommend that tools which are currently used as setup-requires
+either ensure that they keep a `setuptools shim`_ or find their consumers and
+get them all to upgrade to the use of a `pypa.json` in advance of moving
+themselves. Pragmatically that is impossible, so the advice is to keep a
+setuptools shim indefinitely - both for projects like pbr, setuptools_scm and
+also projects like numpy.
+
+setuptools shim
+---------------
+
+It would be possible to write a generic setuptools shim that looks like
+``setup.py`` and under the hood uses ``pypa.json`` to drive the builds. This
+is not needed for pip to use the system, but would allow package authors to
+use the new features while still retaining compatibility with older pip
+versions.
+
+Rationale
+=========
+
+This PEP started with a long mailing list thread on distutils-sig [#thread]_.
+Subsequent to that a online meeting was held to debug all the positions folk
+had. Minutes from that were posted to the list [#minutes]_.
+
+This specification is a translation of the consensus reached there into PEP
+form, along with some arbitrary choices on the minor remaining questions.
+
+The basic heuristic for the design has to been to focus on introducing an
+abstraction without requiring development not strictly tied to the
+abstraction. Where the gap is small to improvements, or the cost of using the
+existing interface is very high, then we've taken on having the improvement as
+a dependency, but otherwise defered such to future iterations.
+
+We chose wheel METADATA files rather than defining a new specification,
+because pip can already handle wheel .dist-info directories which encode all
+the necessary data in a METADATA file. PEP-426 can't be used as it's still
+draft, and defining a new metadata format, while we should do that, is a
+separate problem. Using a directory on disk would not add any value to the
+interface (pip has to do that today due to limitations in the setuptools
+CLI).
+
+The use of 'develop' as a command is because there is no PEP specifying the
+interoperability of things that do what 'setuptools develop' does - so we'll
+need to define that before pip can take on the responsibility for doing the
+'develop' step. Once thats done we can issue a successor PEP to this one.
+
+The use of a command line API rather than a Python API is a little
+contentious. Fundamentally anything can be made to work, and Robert wants to
+pick something thats sufficiently lowest common denominator that
+implementation is straight forward on all sides. Picking a CLI for that makes
+sense because all build systems will need a CLI for end users to use anyway.
+
+The choice of JSON as a file format is a compromise between several
+constraints. Firstly there is no stdlib YAML interpreter, nor one for any of
+the other low-friction structured file formats. Secondly, INIParser is a poor
+format for a number of reasons, primarily that it has very minimal structure -
+but pip's maintainers are not fond of it. JSON is in the stdlib, has
+sufficient structure to permit embedding anything we want in future without
+requiring embedded DSL's.
+
+Donald suggested using ``setup.cfg`` and the existing setuptools command line
+rather than inventing something new. While that would permit interoperability
+with less visible changes, it requires nearly as much engineering on the pip
+side - looking for the new key in setup.cfg, implementing the non-installed
+environments to run the build in. And the desire from other build system
+authors not to confuse their users by delivering something that looks like but
+behaves quite differently to setuptools seems like a bigger issue than pip
+learning how to invoke a custom build tool.
+
+References
+==========
+
+.. [#pip] pip, the recommended installer for Python packages
+ (http://pip.readthedocs.org/en/stable/)
+
+.. [#setuptools] setuptools, the defacto Python package build system
+ (https://pythonhosted.org/setuptools/)
+
+.. [#flit] flit, a simple way to put packages in PyPI
+ (http://flit.readthedocs.org/en/latest/)
+
+.. [#pypi] PyPI, the Python Package Index
+ (https://pypi.python.org/)
+
+.. [#shellvars] Shellvars, an implementation of shell variable rules
for Python.
+ (https://github.com/testing-cabal/shellvars)
+
+.. [#pep426] PEP-426, Python distribution metadata.
+ (https://www.python.org/dev/peps/pep-0426/)
+
+.. [#pep427] PEP-427, Python distribution metadata.
+ (https://www.python.org/dev/peps/pep-0427/)
+
+.. [#thread] The kick-off thread.
+ (https://mail.python.org/pipermail/distutils-sig/2015-October/026925.html)
+
+.. [#minutes] The minutes.
+ (https://mail.python.org/pipermail/distutils-sig/2015-October/027214.html)
+
+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:
-Rob
--
Robert Collins <rbtcollins at hp.com>
Distinguished Technologist
HP Converged Cloud
More information about the Distutils-SIG
mailing list