Permalink
Cannot retrieve contributors at this time
497 lines (366 sloc)
19.4 KB
| PEP: 516 | |
| Title: Build system abstraction for pip/conda etc | |
| Version: $Revision$ | |
| Last-Modified: $Date$ | |
| Author: Robert Collins <rbtcollins@hp.com>, | |
| Nathaniel Smith <njs@pobox.com> | |
| BDFL-Delegate: Nick Coghlan <ncoghlan@gmail.com> | |
| Discussions-To: distutils-sig <distutils-sig@python.org> | |
| Status: Rejected | |
| Type: Standards Track | |
| Content-Type: text/x-rst | |
| Created: 26-Oct-2015 | |
| Resolution: https://mail.python.org/pipermail/distutils-sig/2017-May/030517.html | |
| 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 interface needed to permit pip to install build systems 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 and general project metadata. PEP-0508 [#pep508] provides a | |
| self-contained language for describing a dependency, which we encapsulate in a | |
| thin JSON schema to describe bootstrap dependencies. | |
| Since Python sdists specified in PEP-0314 [#pep314] are also source trees, this | |
| PEP is updating the definition of sdists. | |
| PEP Rejection | |
| ============= | |
| The CLI based approach proposed in this PEP has been rejected in favour of the | |
| Python API based approach proposed in PEP 517. The specific CLI used to | |
| communicate with build backends running as isolated subprocesses will be | |
| considered an implementation detail of front-end developer tool implementations. | |
| 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 | |
| and the name of the command to run to invoke the tool. | |
| All tools will be expected to conform to a single command line interface | |
| modelled on pip's existing use of the setuptools setup.py interface. | |
| pypa.json | |
| --------- | |
| The file ``pypa.json`` acts as a neutral configuration file for pip and other | |
| tools that want to build source trees to consult for configuration. The | |
| absence of a ``pypa.json`` file in a Python source tree implies a setuptools | |
| or setuptools compatible build system. | |
| The JSON has the following schema. Extra keys are ignored, which permits the | |
| use of ``pypa.json`` as a configuration file for other related tools. If doing | |
| that the chosen keys must be namespaced under ``tools``:: | |
| {"tools": {"flit": ["Flits content here"]}} | |
| schema | |
| The version of the schema. This PEP defines version "1". Defaults to "1" | |
| when absent. All tools reading the file must error on an unrecognised | |
| schema version. | |
| bootstrap_requires | |
| Optional list of dependency specifications [#pep508] that must be | |
| installed before running the build tool. For instance, if using flit, then | |
| the requirements might be:: | |
| bootstrap_requires: ["flit"] | |
| build_command | |
| A mandatory key, this is a list of Python format strings [#strformat]_ | |
| describing the command to run. For instance, if using flit then the build | |
| command might be:: | |
| build_command: ["flit"] | |
| If using a command which is a runnable module fred:: | |
| build_command: ["{PYTHON}", "-m", "fred"] | |
| Process interface | |
| ----------------- | |
| The command to run is defined by a simple Python format string [#strformat]_. | |
| This permits build systems with dedicated scripts and those that are invoked | |
| using "python -m somemodule". | |
| Processes will be run with the current working directory set to the root of | |
| the source tree. | |
| When run, processes should not read from stdin - while pip currently runs | |
| build systems with stdin connected to its own stdin, stdout and stderr are | |
| redirected and no communication with the user is possible. | |
| As usual with processes, a non-zero exit status indicates an error. | |
| Available format variables | |
| -------------------------- | |
| PYTHON | |
| The Python interpreter in use. This is important to enable calling things | |
| which are just Python entry points. | |
| {PYTHON} -m foo | |
| Available environment variables | |
| ------------------------------- | |
| These variables are set by the caller of the build system and will always be | |
| available. | |
| PATH | |
| The standard system path. | |
| PYTHON | |
| As for format variables. | |
| PYTHONPATH | |
| Used to control sys.path per the normal Python mechanisms. | |
| Subcommands | |
| ----------- | |
| There are a number of separate subcommands that build systems must support. | |
| The examples below use a build_command of ``flit`` for illustrative purposes. | |
| build_requires | |
| Query build requirements. Build requirements are returned as a UTF-8 | |
| encoded JSON document with one key ``build_requires`` consisting of a list | |
| of dependency specifications [#pep508]_. Additional keys must be | |
| ignored. The build_requires command is the only command run without | |
| setting up a build environment. | |
| Example command:: | |
| flit build_requires | |
| metadata | |
| Query project metadata. The metadata and only the metadata should | |
| be output on stdout in UTF-8 encoding. 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. | |
| Example command:: | |
| flit metadata | |
| wheel -d OUTPUT_DIR | |
| Command to run to build a wheel of the project. OUTPUT_DIR will point to | |
| an existing directory where the wheel should be output. Stdout and stderr | |
| have no semantic meaning. Only one file should be output - if more are | |
| output then pip would pick an arbitrary one to consume. | |
| Example command:: | |
| flit wheel -d /tmp/pip-build_1234 | |
| develop [--prefix PREFIX] | |
| Command to do an in-place 'development' installation of the project. | |
| Stdout and stderr have no semantic meaning. | |
| Not all build systems will be able to perform develop installs. If a build | |
| system cannot do develop installs, then it should error when run. Note | |
| that doing so will cause use operations like ``pip install -e foo`` to | |
| fail. | |
| The prefix option is used for defining an alternative prefix for the | |
| installation. While setuptools has ``--root`` and ``--user`` options, | |
| they can be done equivalently using ``--prefix``, and pip or other | |
| tools that accept ``--root`` or ``--user`` options should translate | |
| appropriately. | |
| The root option is used to define an alternative root within which the | |
| command should operate. | |
| For instance:: | |
| flit develop --root /tmp/ --prefix /usr/local | |
| Should install scripts within `/tmp/usr/local/bin`, even if the Python | |
| environment in use reports that the sys.prefix is `/usr/` which would lead | |
| to using `/tmp/usr/bin/`. Similar logic applies for package files etc. | |
| The build environment | |
| --------------------- | |
| Except for the build_requires command, all commands are run within a build | |
| environment. No specific implementation is required, but a build environment | |
| must achieve the following requirements. | |
| 1. All dependencies specified by the project's build_requires must be | |
| available for import from within ``$PYTHON``. | |
| 1. All command-line scripts provided by the build-required packages must be | |
| present in ``$PATH``. | |
| A corollary of this is that build systems cannot assume access to any Python | |
| package that is not declared as a build_requires or in the Python standard | |
| library. | |
| 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, and thus is not PEP material. | |
| Upgrades | |
| -------- | |
| 'pypa.json' is versioned to permit future changes without requiring | |
| compatibility. | |
| The sequence for upgrading either of schemas in a new PEP will be: | |
| 1. Issue new PEP defining an updated schema. If the schema is not entirely | |
| backward compatible then a new version number must be defined. | |
| 2. Consumers (e.g. pip) implement support for the new schema version. | |
| 3. 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 propagation 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) without affecting interoperability. | |
| In the long term, wheels should be able to express the difference between | |
| wheels built with one compiler or options vs another, and that is PEP | |
| material. | |
| Examples | |
| ======== | |
| An example 'pypa.json' for using flit:: | |
| {"bootstrap_requires": ["flit"], | |
| "build_command": "flit"} | |
| When 'pip' reads this it would prepare an environment with flit in it before | |
| trying to use flit. | |
| Because flit doesn't have setup-requires support today, | |
| `flit build_requires` would just output a constant string:: | |
| {"build_requires": []} | |
| `flit metadata` would interrogate `flit.ini` and marshal the metadata into | |
| a wheel METADATA file and output that on stdout. | |
| `flit wheel` would need to accept 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 an 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 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 deferred 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 that's 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 the pip | |
| maintainers have spoken strongly in favour of retaining a process based | |
| interface - something that is mature and robust in pip today. | |
| 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. | |
| The metadata and wheel commands are required to have consistent metadata to | |
| avoid a race condition that could otherwise happen where pip reads the | |
| metadata, acts on it, and then the resulting wheel has incompatible | |
| requirements. That race is exploited today by packages using PEP-426 | |
| environment markers, to work with older pip versions that do not support | |
| environment markers. That exploit is not needed with this PEP, because either | |
| the setuptools shim is in use (with older pip versions), or an environment | |
| marker ready pip is in use. The setuptools shim can take care of exploiting | |
| the difference older pip versions require. | |
| We discussed having an sdist verb. The main driver for this was to make sure | |
| that build systems were able to produce sdists that pip can build - but this is | |
| circular: the whole point of this PEP is to let pip consume such sdists or VCS | |
| source trees reliably and without requiring an implementation of setuptools. | |
| Being able to create new sdists from existing source trees isn't a thing pip | |
| does today, and while there is a PR to do that as part of building from | |
| source, it is contentious and lacks consensus. Rather than impose a | |
| requirement on all build systems, we are treating it as a YAGNI, and will add | |
| such a verb in a future version of the interface if required. The existing | |
| PEP-314 [#pep314] requirements for sdists still apply, and distutils or setuptools | |
| users can use ``setup.py sdist`` to create an sdist. Other tools should create | |
| sdists compatible with PEP-314 [#pep314]. Note that pip itself does not require | |
| PEP-314 compatibility - it does not use any of the metadata from sdists - they | |
| are treated like source trees from disk or version control. | |
| References | |
| ========== | |
| .. [#pip] pip, the recommended installer for Python packages | |
| (http://pip.readthedocs.org/en/stable/) | |
| .. [#setuptools] setuptools, the de facto 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) | |
| .. [#strformat] The Python string formatting syntax. | |
| (https://docs.python.org/3.1/library/string.html#format-string-syntax) | |
| .. [#pep314] Metadata for Python Software Packages v1.1 | |
| (https://www.python.org/dev/peps/pep-0314/) | |
| .. [#pep508] Dependency specification language PEP. | |
| (https://www.python.org/dev/peps/pep-0508/) | |
| 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: |