|
| 1 | +PEP: 777 |
| 2 | +Title: How to Re-invent the Wheel |
| 3 | +Author: Ethan Smith < [email protected]> |
| 4 | +Sponsor: Barry Warsaw < [email protected]> |
| 5 | +PEP-Delegate: Paul Moore < [email protected]> |
| 6 | +Status: Draft |
| 7 | +Type: Standards Track |
| 8 | +Topic: Packaging |
| 9 | +Created: 09-Oct-2024 |
| 10 | +Post-History: |
| 11 | + |
| 12 | +Abstract |
| 13 | +======== |
| 14 | + |
| 15 | +The current :pep:`wheel 1.0 specification <427>` was written over a decade ago, |
| 16 | +and has been extremely robust to changes in the Python packaging ecosystem. |
| 17 | +Previous efforts to improve the wheel specification |
| 18 | +:pep:`were deferred <491#pep-deferral>` to focus on other packaging |
| 19 | +specifications. Meanwhile, the use of wheels has changed dramatically in the |
| 20 | +last decade. There have been many requests for new wheel features over the |
| 21 | +years; however, a fundamental obstacle to evolving the wheel specification has |
| 22 | +been that there is no defined process for how to handle adding |
| 23 | +backwards-incompatible features to wheels. Therefore, to enable other PEPs to |
| 24 | +describe new enhancements to the wheel specification, **this PEP prescribes** |
| 25 | +**compatibility requirements on future wheel revisions**. This PEP does *not* |
| 26 | +specify a new wheel revision. The specification of a new wheel format |
| 27 | +(“Wheel 2.0”) is left to a future PEP. |
| 28 | + |
| 29 | +Rationale |
| 30 | +========= |
| 31 | + |
| 32 | +Currently, wheel specification changes that require new installer behavior are backwards incompatible and require a major version increase in |
| 33 | +the wheel metadata format. An increase of the wheel major version has yet to |
| 34 | +happen, partially because such a change has the potential to be |
| 35 | +catastrophically disruptive. Per |
| 36 | +`the wheel specification <https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl>`_, |
| 37 | +any installer that does not support the new major version must abort at install |
| 38 | +time. This means that if the major version were to be incremented without |
| 39 | +further planning, many users would see installation failures as older installers reject new wheels |
| 40 | +uploaded to public package indices like the Python Package Index (PyPI). It is |
| 41 | +critically important to carefully plan the interactions between build tools, |
| 42 | +package indices, and package installers to avoid incompatibility issues, |
| 43 | +especially considering the long tail of users who are slow to update their |
| 44 | +installers. |
| 45 | + |
| 46 | +The backward compatibility concerns have prevented valuable improvements |
| 47 | +to the wheel file format, such as |
| 48 | +`better compression <https://discuss.python.org/t/improving-wheel-compression-by-nesting-data-as-a-second-zip/1747>`_, |
| 49 | +`wheel data format improvements <https://discuss.python.org/t/should-there-be-a-new-standard-for-installing-arbitrary-data-files/7853/7>`_, |
| 50 | +`better information about what is included in a wheel <https://discuss.python.org/t/record-the-top-level-names-of-a-wheel-in-metadata/29494>`_, |
| 51 | +and `JSON formatted metadata in the ".dist-info" folder <https://discuss.python.org/t/is-was-there-a-goal-with-pep-566s-json-encoding-section/12324/3>`_. |
| 52 | + |
| 53 | +This PEP describes constraints and behavior for new wheel revisions to preserve |
| 54 | +stability for existing tools that do not support a new major version of the wheel format. |
| 55 | +This ensures that backwards incompatible changes to the wheel specification |
| 56 | +will only affect users and tools that are properly set up to use the newer |
| 57 | +wheels. With a clear path for evolving the wheel specification, future PEPs |
| 58 | +will be able to improve the wheel format without needing to re-define a |
| 59 | +completely new compatibility story. |
| 60 | + |
| 61 | +Specification |
| 62 | +============= |
| 63 | + |
| 64 | +Add Wheel-Version Metadata Field to Core Metadata |
| 65 | +------------------------------------------------- |
| 66 | + |
| 67 | +Currently, the :pep:`wheel 1.0 PEP <427>`, PEP 427, specifies that wheel files |
| 68 | +must contain a ``WHEEL`` metadata file that contains the version of the wheel |
| 69 | +specification that the file conforms to. PEP 427 stipulates that installers |
| 70 | +MUST warn on installation of a wheel with a minor version greater than supported, |
| 71 | +and MUST abort on installation of wheels with a major version that is greater than |
| 72 | +what the installer supports. This ensures that users do not get invalid |
| 73 | +installations from wheels that installers cannot properly install. |
| 74 | + |
| 75 | +However, resolvers do not currently exclude wheels with an incompatible wheel |
| 76 | +version. There is also currently no way for a resolver to check a wheel's |
| 77 | +version without downloading the wheel directly. To make wheel version filtering |
| 78 | +easy for resolvers, the wheel version **MUST** be included in the relevant |
| 79 | +metadata file (currently METADATA). This will allow resolvers to efficiently |
| 80 | +check the wheel version using the :pep:`658` metadata API without needing to |
| 81 | +download and inspect the ``.dist-info/WHEEL`` file. |
| 82 | + |
| 83 | +To accomplish this, a new core metadata field is introduced called |
| 84 | +``Wheel-Version``. While this field is optional for metadata included in a |
| 85 | +wheel of major version 1, it is a mandatory field for metadata in wheels of major |
| 86 | +version 2 or higher. This enforces that future revisions of the wheel |
| 87 | +specification can rely on resolvers skipping incompatible wheels by checking |
| 88 | +the ``Wheel-Version`` field. |
| 89 | + |
| 90 | +The ``Wheel-Version`` field in the metadata file shall contain the exact same entry as the |
| 91 | +``Wheel-Version`` entry in the ``WHEEL`` file, or any future replacement file |
| 92 | +defining metadata about the wheel file. Installers **MUST** verify that these |
| 93 | +entries match when installing a wheel. If ``Wheel-Version`` is absent from the |
| 94 | +metadata file, then the implied major version of the wheel is 1. |
| 95 | + |
| 96 | +Resolver Behavior Regarding ``Wheel-Version`` |
| 97 | +--------------------------------------------- |
| 98 | + |
| 99 | +Resolvers, in the process of selecting a wheel to install, **MUST** check a |
| 100 | +candidate wheel's ``Wheel-Version``, and ignore incompatible wheel files. |
| 101 | +Without ignoring these files, older installers might select a wheel that uses |
| 102 | +an unsupported wheel version for that installer, and force the installer to |
| 103 | +abort per :pep:`427`. By skipping incompatible wheel files, users will not see |
| 104 | +installation errors when a project adopts a new wheel major version. As already |
| 105 | +specified in PEP 427, installers **MUST** abort if a user tries to directly |
| 106 | +install a wheel that is incompatible. If, in the process of resolving packages |
| 107 | +found in multiple indices, a resolver comes across two wheels of the same |
| 108 | +distribution and version, resolvers should prioritize the wheel of the highest |
| 109 | +compatible version. |
| 110 | + |
| 111 | +While the above protects users from unexpected breakages, users may miss a new |
| 112 | +release of a distribution if their installer does not support the wheel version |
| 113 | +used in the release. Imagine in the future that a package publishes 3.0 wheel |
| 114 | +files. Downstream users won't see that there is a new release available if |
| 115 | +their installers only support 2.x wheels. Therefore, installers **SHOULD** emit |
| 116 | +a warning if, in the process of resolving packages, they come across an incompatible wheel |
| 117 | +and skip it. |
| 118 | + |
| 119 | +First Major Version Bump Must Change File Extension |
| 120 | +--------------------------------------------------- |
| 121 | + |
| 122 | +Unfortunately, existing resolvers do not check the compatibility of wheels |
| 123 | +before selecting them as installation candidates. Until a majority of users |
| 124 | +update to installers that properly check for wheel compatibility, it is unsafe |
| 125 | +to allow publishing wheels of a new major version that existing resolvers might |
| 126 | +select. It could take upwards of four years before the majority of users are on |
| 127 | +updated resolvers, based on current data about PyPI installer usage (See the |
| 128 | +:ref:`777-pypi-download-analysis`, for |
| 129 | +details). To allow for experimentation and faster adoption of 2.0 wheels, |
| 130 | +this PEP proposes a one time change to the file extension of the |
| 131 | +wheel file format, from ``.whl`` to ``.whlx``. This resolves the initial |
| 132 | +transition issue of 2.0 wheels breaking users on existing installers that do |
| 133 | +not implement ``Wheel-Version`` checks. By using a different file extension, |
| 134 | +2.0 wheels can immediately be uploaded to PyPI, and users will be able to |
| 135 | +experiment with the new features right away. Users on older installers will |
| 136 | +simply ignore these new files. |
| 137 | + |
| 138 | +One rejected alternative would be to keep the ``.whl`` extension, but delay the |
| 139 | +publishing of wheel 2.0 to PyPI. For more on that, please see Rejected Ideas. |
| 140 | + |
| 141 | +Recommended Build Backend Behavior with New Wheel Formats |
| 142 | +--------------------------------------------------------- |
| 143 | + |
| 144 | +Build backends are recommended to generate the most compatible wheel based on |
| 145 | +features a project uses. For example, if a wheel does not use symbolic links, |
| 146 | +and such a feature was introduced in wheel 5.0, the build backend could |
| 147 | +generate a wheel of version 4.0. On the other hand, some features will want to |
| 148 | +be adopted by default. For example, if wheel 3.0 introduces better compression, |
| 149 | +the build backend may wish to enable this feature by default to improve the |
| 150 | +wheel size and download performance. |
| 151 | + |
| 152 | +Limitations on Future Wheel Revisions |
| 153 | +------------------------------------- |
| 154 | + |
| 155 | +While it is difficult to know what future features may be planned for the wheel |
| 156 | +format, it is important that certain compatibility promises are maintained. |
| 157 | + |
| 158 | +Wheel files, when installed, **MUST** stay compatible with the Python standard |
| 159 | +library's ``importlib.metadata`` for all supported CPython versions. For |
| 160 | +example, replacing ``.dist-info/METADATA`` with a JSON formatted metadata file |
| 161 | +MUST be a multi-major version migration with one version introducing the new |
| 162 | +JSON file alongside the existing email header format, and another future |
| 163 | +version removing the email header format metadata file. The version to remove |
| 164 | +``.dist-info/METADATA`` also **MUST** be adopted only after the last CPython |
| 165 | +release that lacked support for the new file reaches end of life. This ensures |
| 166 | +that code using ``importlib.metadata`` will not break with wheel major version |
| 167 | +revisions. |
| 168 | + |
| 169 | +Wheel files **MUST** remain ZIP format files as the outer container format. |
| 170 | +Additionally, the ``.dist-info`` metadata directory **MUST** be placed at the |
| 171 | +root of the archive without any compression, so that unpacking the wheel file |
| 172 | +produces a normal ``.dist-info`` directory holding any metadata for the wheel. |
| 173 | +Future wheel revisions **MAY** modify the layout, compression, and other |
| 174 | +attributes about non-metadata components of a wheel such as data and code. This |
| 175 | +assures that future wheel revisions remain compatible with tools operating on |
| 176 | +package metadata, while allowing for improvements to code storage in the wheel, |
| 177 | +such as adopting compression. |
| 178 | + |
| 179 | +Package tooling **MUST NOT** assume that the contents and format of the wheel |
| 180 | +file will remain the same for future wheel major versions beyond the |
| 181 | +limitations above about metadata folder contents and outer container format. |
| 182 | +For example, newer wheel major versions may add or remove filename components, |
| 183 | +such as the build tag or the platform tag. Therefore it is incumbent upon |
| 184 | +tooling to check the metadata for the ``Wheel-Version`` before attempting to |
| 185 | +install a wheel. |
| 186 | + |
| 187 | +Finally, future wheel revisions **MUST NOT** use any compression formats not in |
| 188 | +the CPython standard library of at least the latest release. Wheels generated |
| 189 | +using any new compression format should be tagged as requiring at least the |
| 190 | +first released version of CPython to support the new compression format, |
| 191 | +regardless of the Python API compatibility of the code within the wheel. |
| 192 | + |
| 193 | +Backwards Compatibility |
| 194 | +======================= |
| 195 | + |
| 196 | +Backwards compatibility is an incredibly important issue for evolving the wheel |
| 197 | +format. If adopting a new wheel revision is painful for downstream users, |
| 198 | +package creators will hesitate to adopt the new standards, and users will be |
| 199 | +stuck with failed CI pipelines and other installation woes. |
| 200 | + |
| 201 | +Several choices in the above specification are made so that the adoption of a |
| 202 | +new feature is less painful. For example, today wheels of an incompatible major |
| 203 | +version are still selected by pip as installation candidates, which causes |
| 204 | +installer failures if a project starts publishing 2.0 wheels. To avoid this |
| 205 | +issue, this PEP requires resolvers to filter out wheels with major versions or |
| 206 | +features incompatible with the installer. |
| 207 | + |
| 208 | +This PEP also defines constraints on future wheel revisions, with the goal of |
| 209 | +maintaining compatibility with CPython, but allowing evolution of wheel |
| 210 | +contents. Wheel revisions shouldn't cause package installations to break on |
| 211 | +older CPython revisions, as not only would it be frustrating, it would be |
| 212 | +incredibly hard to debug for users. |
| 213 | + |
| 214 | +The main compatibility limitation of this PEP is for projects that start |
| 215 | +publishing solely new wheels alongside a source distribution. If a user on an |
| 216 | +older installer tries to install the package, it will fall back to the source |
| 217 | +distribution, because the resolver will skip all newer wheels. Users are often |
| 218 | +poorly set up to build projects from source, so this could lead to some failed |
| 219 | +builds users would not see otherwise. There are several approaches to resolving |
| 220 | +this issue, such as allowing dual-publishing for the initial migration, or |
| 221 | +marking source distributions as not intended to be built. |
| 222 | + |
| 223 | +Rejected Ideas |
| 224 | +============== |
| 225 | + |
| 226 | +The Wheel Format is Perfect and Does not Need to be Changed |
| 227 | +----------------------------------------------------------- |
| 228 | +The wheel format has been around for over 10 years, and in that time, Python |
| 229 | +packages have changed a lot. It is much more common for packages to include |
| 230 | +Rust or C extension modules, increasing the size of packages. Better |
| 231 | +compression, such as lzma or zstd, could save a lot of time and bandwidth for |
| 232 | +PyPI and its users. Compatibility tags cannot express the wide variety of |
| 233 | +hardware used to accelerate Python code today, nor encode shared library |
| 234 | +compatibility information. In order to address these issues, evolution of the |
| 235 | +wheel package format is necessary. |
| 236 | + |
| 237 | +Wheel Format Changes Should be Tied to CPython Releases |
| 238 | +------------------------------------------------------- |
| 239 | +I do not believe that tying wheel revisions to CPython |
| 240 | +releases is beneficial. The main benefit of doing so is to make adoption of new |
| 241 | +wheels predictable - users with the latest CPython get the latest package |
| 242 | +format! This choice has several issues however. First, tying the new format |
| 243 | +to the latest CPython makes adoption much slower. Users on LTS versions of |
| 244 | +Linux with older Python installations are free to update their pip in a virtual |
| 245 | +environment, but cannot update the version of Python as easily. While some |
| 246 | +changes to the wheel format must be tied to CPython changes necessarily, such |
| 247 | +as adding new compression formats or changing the metadata format, many changes |
| 248 | +do not need to be tied to the Python version, such as symlinks, enhanced |
| 249 | +compatibility tags, and new formats that use existing compression formats in |
| 250 | +the standard library. Additionally, wheels are used across multiple different |
| 251 | +language implementations, which lag behind the CPython version. It seems unfair |
| 252 | +to prevent their users from using a feature due to the Python version. Lastly, |
| 253 | +while this PEP does not suggest tying the wheel version to CPython releases, a |
| 254 | +future PEP may still do so at any time, so this choice does not need to be made |
| 255 | +in this PEP. |
| 256 | + |
| 257 | +Keep Using ``.whl`` as the File Extension |
| 258 | +----------------------------------------- |
| 259 | +While keeping the extension ``.whl`` is appealing for many reasons, it presents |
| 260 | +several problems that are difficult to surmount. First, current installers |
| 261 | +would still pick a new wheel and fail to install the package. Furthermore, |
| 262 | +the file name of a wheel would not be able to change without breaking existing |
| 263 | +installers that expect a set wheel file name format. While the current filename |
| 264 | +specification for wheels is sufficient for current usage, the optional |
| 265 | +build tag in the middle of the file name makes any extensions ambiguous (i.e. |
| 266 | +``foo-0.3-py3-none-any-fancy_new_tag.whl`` would parse as the build tag being |
| 267 | +``py3``). This limits changes to information stored in the wheel file name. |
| 268 | + |
| 269 | +Discussion Topics |
| 270 | +================= |
| 271 | + |
| 272 | +Should Indices Support Dual-publishing for the First Migration? |
| 273 | +--------------------------------------------------------------- |
| 274 | +Since ``.whl`` and ``.whlx`` will look different in file name, they could be |
| 275 | +uploaded side-by-side to package indices like PyPI. This has some nice |
| 276 | +benefits, like dual-support for older and newer installers, so users who can |
| 277 | +get the latest features, while users who don't upgrade still can install the |
| 278 | +latest version of a package. |
| 279 | + |
| 280 | +There are many complications however. Should we allow wheel 2 uploads to |
| 281 | +existing wheel 1-only releases? Should we put any requirements on the |
| 282 | +side-by-side wheels, such as: |
| 283 | + |
| 284 | +.. admonition:: Constraints on dual-published wheels |
| 285 | + |
| 286 | + A given index may contain identical-content wheels with different wheel |
| 287 | + versions, and installers should prefer the newest-available wheel format, |
| 288 | + with all other factors held constant. |
| 289 | + |
| 290 | +Should we only allow uploading both with :pep:`694` allowing "atomic" |
| 291 | +dual-publishing? |
| 292 | + |
| 293 | +Acknowledgements |
| 294 | +================ |
| 295 | + |
| 296 | +The author of this PEP is greatly indebted to the incredibly valuable review, |
| 297 | +advice, and feedback of Barry Warsaw and Michael Sarahan. |
| 298 | + |
| 299 | +Copyright |
| 300 | +========= |
| 301 | + |
| 302 | +This document is placed in the public domain or under the |
| 303 | +CC0-1.0-Universal license, whichever is more permissive. |
0 commit comments