Why does pip sometimes compile a package from source instead of installing a prebuilt wheel?
answer
- Installing a wheel is a copy
- A source distribution has to be built
- Nothing matched the interpreter's tag set
- New Python minor release, no binaries yet
- Make it fail loudly with only-binary
basics
~20 sBecause no published wheel's tags match this interpreter, ABI and platform, so pip falls back to the source distribution and builds it. Usual causes: a brand-new Python release, an unusual CPU or C library, or a project shipping only an sdist.
solid answer
~50 sInstalling a wheel is a copy; installing a source distribution is a build. pip prefers wheels, and only reaches for the sdist when nothing in the index matches the tag set the running interpreter accepts. The common triggers are all tag misses: you upgraded to a Python minor version the project has not published binaries for yet, you are on an architecture or C library the project does not build for, or the project ships no wheels at all. A source build then needs whatever toolchain that project requires, which is why the failure usually surfaces as a compiler error rather than a packaging error. Two flags make the behaviour explicit: `--only-binary=:all:` refuses to build and fails loudly instead, and `--no-binary=:all:` forces the source path deliberately. A successful build is cached locally as a wheel, so the second install on that machine is fast again.
code
console · 1 linepython -m pip debug --verbosego deeper
Recognise the symptom: long install, compiler output, an error that is not about versions. Know that it means no ready-built file matched your Python and machine, and that upgrading pip will not conjure one.
Explain the fallback precisely and name which of the three tag fields missed in a given case. Know the flags that force or forbid the source path and what the local wheel cache does after a build.
Treat it as an operational risk: pin interpreters and base images to what your dependency set actually ships binaries for, fail builds fast rather than compiling in production images, and know when to publish an internal wheel instead.
Set the policy on interpreter adoption pace and supported architectures, weighing the cost of waiting for the ecosystem's binaries against running an internal build-and-host pipeline for the dependencies that lag.
## Two different install paths pip has two ways to install a project. Given a wheel, it verifies the file's compatibility tags, unpacks the archive into the environment and writes the metadata — no code from the project runs, and no compiler is involved. Given a source distribution, it must *build* one: set up an isolated build environment, install the declared build backend and its requirements, and ask that backend to produce a wheel, which it then installs. When the project contains C, Rust or Fortran sources, that step invokes a compiler on your machine. So "pip started compiling" always means the same thing: **no candidate wheel's tags matched**, and pip fell back. ## What "matched" means The running interpreter defines an ordered set of acceptable tag triples — interpreter tag, ABI tag, platform tag. pip compares each available file's tags against that set. A miss on any one of the three disqualifies the file: - **Interpreter or ABI miss.** The project publishes `cp313` wheels and you have just moved to CPython 3.14. This is the single most common cause, and it appears in a wave every October when a new Python minor release lands and the compiled-extension ecosystem has not caught up. - **Platform miss.** You are on ARM64 and only x86-64 wheels exist; or you are on a musl-based image while the project publishes only glibc-baseline Linux wheels; or the wheels' libc baseline is newer than your host's. - **No wheels at all.** Some projects publish only an sdist, by choice or because they never set up a build matrix. A fourth, non-tag cause: you asked for it. `--no-binary=:all:` forces the source path, and installing from a directory, a VCS URL or a plain `.tar.gz` skips wheels by definition. ## Why it hurts A source build is slow — seconds for a pure-Python project, minutes for a large compiled one — and it is fragile in a way a wheel install is not, because it depends on the machine having a compiler, the right headers and enough memory. A container image or CI job that quietly worked because a wheel existed will break the day the wheel stops matching, and the error text will come from a compiler rather than from pip, which is why the cause is so often misdiagnosed. ## Diagnosing it Read the tags. `python -m pip debug --verbose` prints the compatible tag list for the interpreter you are running, most specific first. Then look at what the project actually publishes: if the newest files are `cp313-cp313-manylinux_2_28_x86_64` and you are on CPython 3.14, the mismatch is in the interpreter field. If they are `manylinux` and you are on a musl-based image, the mismatch is in the platform field. One comparison names the cause. ## Controlling it Make the behaviour a decision rather than an accident: - `pip install --only-binary=:all: <name>` fails immediately when no wheel matches, instead of compiling. In CI and in image builds this turns a ten-minute surprise into an instant, readable failure. - `pip install --no-binary=:all: <name>` forces the source path — occasionally wanted when you must build against local libraries or a specific instruction set. - `--prefer-binary` biases the resolver towards versions that have wheels, which sometimes means picking a slightly older release that does have one. - Pin the interpreter version your project's dependencies actually ship binaries for, rather than tracking the newest release on day one. ## Reading the failure correctly The error text misleads people, because it comes from the wrong layer. A missing header, an unknown compiler flag or a linker error are all *symptoms* of the fallback, not the cause; nothing is wrong with your compiler setup, and installing build tools to make the message go away treats a packaging problem as a toolchain problem. The question to ask first is always "why was there no wheel for me", and the answer is one of the three tag fields or an index that publishes no wheels at all. ## The cache When pip does build a source distribution, it stores the resulting wheel in its local cache and reuses it for later installs of the same project, version and tag set on that machine. That is why the compile appears to happen only once locally, and why it appears *every* time in a fresh container: the cache is not in the image unless you deliberately mount or preserve it. The correct fix is rarely to warm the cache; it is to install an interpreter and base image for which the wheels you need actually exist, or to build the wheel once yourself and install that artefact everywhere.
- How would you stop a container image build from silently spending minutes compiling a dependency?Install with `--only-binary=:all:` so a missing wheel fails the build immediately with a clear message instead of invoking a compiler. Then fix the cause: choose a base image and interpreter version the dependency publishes wheels for, or build that wheel once yourself in a separate step and install the artefact. Relying on a warm pip cache does not help, because a fresh image starts with an empty one.
- Your install compiles on an ARM64 laptop but not on the x86-64 CI runner. What does that tell you?That the project publishes x86-64 wheels but no ARM64 ones for your interpreter, so only the laptop falls back to the source path. The mismatch is in the platform field of the tag, not in the interpreter. Confirm by listing the project's published files and comparing them with the tag list the laptop's interpreter accepts.
saying these in an interview costs you the question
- Says pip chose to build because it is faster
- Blames a corrupt or missing pip cache
- Thinks upgrading pip will produce the missing wheel
- Assumes every project publishes wheels for every Python
- Confuses a compiler error with a dependency-resolution error