pypackaging-native has a very good writeup “Unsuspecting users getting failing from-source builds” that describes one of the practical usability problems with the pip/PyPI ecosystem. In brief, there are several popular packages where building from source is terribly complicated and unlikely to work unless you are intentionally building from source and have carefully set upa build environment, and where the preferred option for users of the software is to install a wheel. But there are a few reasons your resolver might fail to find a wheel, get an sdist instead, and attempt in vain to build it:
- You’re on a platform / Python version that the authors don’t (yet) build wheels for.
- You’re on a platform they do build wheels for, but your installer doesn’t know that (e.g., there’s a new manylinux tag).
- sdists and wheels are not uploaded at the same time, and you happened to pip install between those and lost a race condition.
- There’s an incompatible change in the wheel format, as is being proposed over in How to reinvent the wheel , and your installer can’t handle those wheels.
In these cases it would be better to get an error message up front mentioning the lack of a wheel than to imply to users that they should debug why the build failed.
A couple of very popular projects have started addressing this by not uploading sdists at all, which is unfortunate.
pypa/pip-#9140 “–only-binary by default?” proposes to address this by changing pip’s behavior to ignore sdists if it sees any wheels at all for a package, or some similar heuristic. But, being a heuristic, it seems like it’s hard to roll out. without breaking some existing use case.
I think the following design is fully backwards-compatible and accomplishes the goal:
- Define a new type of sdist, which I’m going to call a “manual-build sdist” but we can probably find a better name. A manual-build sdist is just like any other sdist, except that the filename ends in
.manualbuild
, e.g.,foo-1.0.tar.gz.manualbuild
. The unsuffixed filename and the contents can be treated like any other sdist. - Extend PyPI to accept files ending in
.manualbuild
, if it doesn’t already accept them. - Extend the code in pip, uv, etc. to download manual-build sdists as if they were normal sdists when some command-line flag / configuration is enabled (e.g.,
pip install --manually-build :all: numpy
). If that flag is not specified, they keep their current behavior, which should be to ignore these files - Ask the maintainers of projects that have complicated build processes and that intend to comprehensively provide wheels to rename their sdists to
.manualbuild
before uploading them. - Ideally, ask the maintainers of projects that currently aren’t uploading sdists at all to start uploading manual-build sdists.
This is opt-in on a per-project basis, and therefore it is fully backwards-compatible: any project that is not uploading manual-build sdists gets versions resolved exactly like they do today. A package that only has sdists has users build sdists; a package that only has wheels has users build wheels; a package that has an sdist for a new version but a wheel for an old or outdated version has users install the latest version.
Essentially this recognizes, as pynative-packaging puts it, the dual purposes of pypi. A regular (auto-build) sdist now conveys the intention that it’s meant for users to actually build under normal circumstances, which has so far been the implicit effect. A manual-build sdist is for publication and for consumption by redistributors or people with unusual use cases, and is just as easy for someone to download from the web interface or for a tool to grab, but explicitly conveys the intention that it’s not for routine installation.
This even is backwards compatible with the use case of build pipelines that do a pip install xyz
in CI to produce the official wheel for that version of xyz
from its sdist. If they don’t hear about this new feature, they’ll continue to upload normal (automatic-build) sdists, and pip install xyz
will build it. If they do switch to uploading manual-build sdists, then they’re aware of this new feature, and they can adjust their CI to use pip install --manually-build :all: xyz
; there’s no behavior change that they’re not aware of.
No changes to build tools are strictly required (as with the proposals to avoid heuristics by adding metadata); all you need to do is rename the file. We certainly could make this nicer but it’s not a hard requirement.
This is backwards-compatible with existing versions of pip (and other installers): for projects that only provide manual-build sdists, existing versions of installers will just ignore the .manualbuild
files that they don’t recognize, and they’ll gracefully degrade to the same behavior as if sdists are not uploaded at all, which is what we want. But this will only happen for projects that explicitly choose to switch to manual-build sdists.
So I think this is a much easier rollout than any of the other proposals to address this problem: if no projects made any behavior changes, the new versions of pip etc. would not change any behavior either, so there’s no coordination problem. [1] There will be some work in encouraging popular projects to adopt this scheme for it to be actually useful, but it’s in their own interest to not have users post build failures in their support forums, so I think that’s easy. After all, a couple of major projects are already doing the worse version of this,
One thing that might break is automation by redistributors to find and download an sdist that they specifically intend to build from source - pypi2deb, pyp2rpm, etc. But those are already broken by the projects that choose to stop uploading sdists, and it’s an easy fix, and it only needs to be fixed once per tool. It is also most likely to affect those packages where the redistributors know they will have to spend manual effort getting the packaging right. So I think this is an acceptable impact.
By contrast, I think every proposal on pip#9140 to make a change in pip alone has some edge case where the heuristics do the wrong thing in a use case where people are not well equipped to figure out what happened, and so there’s some hesitation to make a change without a lot of care and planning and publicity. Some of the proposals also involve implementing better error reporting inside pip in a way that would be difficult to get right. Alternatively, there are proposals to accomplish roughly the same thing as this proposal via metadata, which would be a more involved change and also wouldn’t be as backwards-compatible. Empirically - there’s been a quick consensus on the issue that the change is a good idea, but no movement for a couple of years.
I welcome feedback about whether I’m missing anything that makes this approach complicated or unworkable - or whether one of the other proposals actually is straightforward. I’ll turn this proposal into a relatively short PEP if people feel this is a decent plan, and I’m happy to do the code changes needed, too.
There is a valid question about whether automated tools that generate and upload sdists should generate them as auto-buiild or manual-build sdists. But do any such tools exist? I was worried about cibuildwheel, but it just documents the CI steps you need, it doesn’t upload sdists on its own. ↩︎