Fix: ERROR: Could not build wheels / Failed building wheel (pip)
Part of: Python Errors
Quick Answer
How to fix pip 'ERROR: Could not build wheels', 'Failed building wheel', 'No matching distribution found', and 'error: subprocess-exited-with-error'. Covers missing C compilers, build tools, system libraries, Python version issues, pre-built wheels, and platform-specific fixes for Linux, macOS, and Windows.
The Wheel That Never Got Built
Personally, I rate this as one of the most cryptic pip errors because the verbose output buries the actual cause. “Could not build wheels” almost always means “no pre-built binary for your platform, AND I cannot compile from source.” The fix depends on which half of that statement is true. I have learned to read the compiler output above the pip error, not the pip error itself. You run pip install and get one of these:
ERROR: Could not build wheels for numpy, which is required to install pyproject.toml-based projectsFailed building wheel for cryptographyerror: subprocess-exited-with-error
× Building wheel for PyYAML (pyproject.toml) did not run successfully.
│ exit code: 1
╰─> [25 lines of output]
...
error: command 'gcc' failed: No such file or directoryERROR: No matching distribution found for tensorflow==2.15.0These errors all point to the same core problem: pip can’t install a package because it can’t build it from source, can’t find a pre-built binary (wheel) for your platform, or the package doesn’t support your Python version.
Why pip Falls Back to Building from Source
Most Python packages on PyPI are distributed as wheels—pre-built binary packages that install instantly without compilation. But wheels are platform-specific. A wheel built for Linux on x86_64 won’t work on macOS on Apple Silicon.
When pip can’t find a compatible wheel, it falls back to building the package from source. This requires:
- A C/C++ compiler (gcc, clang, or MSVC)
- Python development headers (python3-dev / python3-devel)
- System libraries the package depends on (libffi, libssl, libxml2, etc.)
- Build tools (make, cmake, pkg-config)
If any of these are missing, the build fails. The error messages are often long and buried in compiler output, but the root cause is almost always one of the above.
“No matching distribution found” is different. It means pip can’t find any version of the package (source or wheel) that matches your Python version, OS, or architecture. This typically happens when:
- The package doesn’t support your Python version (e.g., Python 3.13 when the package only supports up to 3.12)
- The package doesn’t have builds for your platform (e.g., ARM64 / Apple Silicon)
- You misspelled the package name
- The package has been removed from PyPI
Quick Reference Before You Dive In
If you arrived here from Google with a fresh wheel build failure, the five facts that resolve roughly 90 percent of cases:
- READ ABOVE the
Could not build wheelsline. The actual compiler error (missing header, wrong toolchain, no Rust) is buried 20 to 50 lines above. The pip user guide and the PEP 517 build system spec are the canonical references. - Update pip FIRST.
python -m pip install --upgrade pip setuptools wheel. Old pip versions cannot see newermanylinux_2_28ormanylinux_2_34wheel tags and silently fall back to building from source. - Install build tools matching your OS. Linux:
build-essential+python3-dev. macOS:xcode-select --install. Windows: Visual Studio Build Tools with “Desktop development with C++.” --prefer-binaryor--only-binary :all:forces wheel-only install. Useful when you know wheels exist for your platform and want to skip the build path entirely.cryptography,tokenizers,polars,ruffneed a RUST compiler. Install rustup if you see “can’t find Rust compiler” or “Cargo, the Rust package manager, is not installed.”
The rest of this article walks through each cause in detail, plus the failure modes most other guides skip.
How Python’s Packaging Stack Changed: PEP 517/518, manylinux, and distutils Removal
The reason this error often spikes after a Python upgrade (even when nothing else on the system changed) is that Python’s packaging stack has moved through several major shifts in the last few years. Knowing which shift you crossed makes the failure obvious.
- PEP 517 / PEP 518 (pyproject.toml-based builds). Before 2017,
python setup.py installwas the canonical build path. PEP 517/518 introducedpyproject.toml-driven builds with isolated build environments. pip 21.3 (October 2021) made PEP 517 the default. The benefit: reproducible builds. The cost: when a build tool (setuptools, hatchling, poetry-core) is missing or pinned wrong, the error message ends up nested two levels deep and looks like a compiler failure when it is actually a build-backend failure. Always read above the “Could not build wheels” line for the real cause. - manylinux tags evolution. Linux wheels carry an ABI tag that says which glibc and which set of system libraries they target.
manylinux1(PEP 513, 2016): targeted CentOS 5, glibc 2.5. Effectively retired.manylinux2010(PEP 571, 2018): CentOS 6, glibc 2.12.manylinux2014(PEP 599, 2019): CentOS 7, glibc 2.17. Still the de facto baseline for older infrastructure.manylinux_2_28(PEP 600, 2019): based on AlmaLinux 8 / glibc 2.28. Most major projects (NumPy, SciPy, cryptography, lxml) moved here in 2023–2024.manylinux_2_34started appearing in late 2024 for projects that want modern glibc features. If your pip is older than 22.x, it does not understand_2_28or_2_34wheel tags and silently falls back to building from source. Alwayspip install --upgrade pipfirst.
- Python 3.12 removed
distutilsfrom the standard library (PEP 632). Setup scripts that still dofrom distutils.core import setupraiseModuleNotFoundError: No module named 'distutils'. The fix is to migrate the package tosetuptools, or install thesetuptoolscompatibility shim before running the build. Packages last released before 2023 are the usual offenders. - Python 3.13 disabled the GIL as an opt-in build (PEP 703, “free-threaded Python”) in October 2024. Wheels built for the standard 3.13 do not necessarily run on the free-threaded 3.13t. If you installed
python3.13tand a package fails to build, that is the reason; wait for the project to publishcp313twheels. - Rust as a build dependency became mainstream.
cryptography>= 3.4 (March 2021),pydanticv2 core,tokenizers,polars, andruffall need a Rust toolchain to build from source. On Python 3.13 release day in October 2024, none of them had matching wheels for hours — installing on day one required rustc. build-system.requiresis honored from pip 19.0+ only. Very old pip versions ignore the section and try to import setuptools that is not present.
If the error appeared after a Python minor-version upgrade, check whether the wheel tag for your version exists on the PyPI release page before doing anything else.
When to Use Which Fix
The next eight sections cover the fixes in detail. The table below maps your situation to the recommended fix.
| Your situation | Recommended fix | Why |
|---|---|---|
| No compiler installed | Fix 1: install build tools per OS | Cannot build C extensions |
Missing system library (libffi, libssl, etc.) | Fix 2: install -dev / -devel package | Headers required |
| Old pip cannot see new wheels | Fix 3: pip install --upgrade pip setuptools wheel | New manylinux tags |
| Want to skip source build entirely | Fix 4: --prefer-binary or --only-binary :all: | Wheel-only mode |
No matching distribution after Python upgrade | Fix 5: check Python version against package metadata | Version compatibility |
| Apple Silicon / ARM64 with no wheel | Fix 6: Conda / Miniforge, or Rosetta x86_64 Python | Architecture-specific |
Cython or setuptools mismatch | Fix 7: pin or upgrade explicitly | Build tool version drift |
| System Python permission issues | Fix 8: virtual environment | Clean state, no PEP 668 |
If multiple rows apply, pick the topmost one.
Fix 1: Install Build Tools and a C Compiler
The most common cause. You’re missing the compiler and development tools needed to build C extensions.
Linux (Debian/Ubuntu)
sudo apt update
sudo apt install build-essential python3-devbuild-essential installs gcc, g++, make, and other essential build tools. python3-dev installs the Python header files needed to compile C extensions.
For a specific Python version:
sudo apt install python3.12-devLinux (Fedora/RHEL/CentOS)
sudo dnf groupinstall "Development Tools"
sudo dnf install python3-develLinux (Arch)
sudo pacman -S base-devel pythonArch ships Python headers with the main python package.
macOS
Install Xcode Command Line Tools:
xcode-select --installThis gives you clang, make, and the standard C headers. You don’t need the full Xcode IDE—the command line tools are sufficient.
If you already have them installed but still get errors after a macOS upgrade, reinstall:
sudo rm -rf /Library/Developer/CommandLineTools
xcode-select --installWindows
Install Microsoft Visual Studio Build Tools:
- Download from https://visualstudio.microsoft.com/visual-cpp-build-tools/
- Run the installer
- Select “Desktop development with C++”
- Install and restart your terminal
Without these build tools, any package with C extensions will fail on Windows. The error usually looks like:
error: Microsoft Visual C++ 14.0 or greater is required.A small observation: installing build-essential and python3-dev is the single most effective preventive measure on any Linux dev machine. It costs almost nothing in disk space, fixes “Could not build wheels” pre-emptively for the entire ecosystem, and lets you skip half the fixes below. I add it to every Dockerfile and every Vagrant bootstrap script as a matter of course.
Fix 2: Install Missing System Libraries
Some packages need specific system libraries. The error output usually tells you which one is missing, but it’s buried in the compiler output. Look for lines like fatal error: ffi.h: No such file or directory or Package libssl was not found.
Common packages and their system dependencies
cryptography:
# Debian/Ubuntu
sudo apt install build-essential libssl-dev libffi-dev python3-dev
# Fedora
sudo dnf install openssl-devel libffi-devel python3-devel
# macOS
brew install openssl libffi
export LDFLAGS="-L$(brew --prefix openssl)/lib"
export CPPFLAGS="-I$(brew --prefix openssl)/include"lxml:
# Debian/Ubuntu
sudo apt install libxml2-dev libxslt1-dev python3-dev
# Fedora
sudo dnf install libxml2-devel libxslt-devel python3-devel
# macOS
brew install libxml2 libxsltPillow:
# Debian/Ubuntu
sudo apt install libjpeg-dev zlib1g-dev libfreetype-dev liblcms2-dev libwebp-dev
# Fedora
sudo dnf install libjpeg-devel zlib-devel freetype-devel lcms2-devel libwebp-devel
# macOS
brew install libjpeg zlib freetypepsycopg2:
# Debian/Ubuntu
sudo apt install libpq-dev python3-dev
# Fedora
sudo dnf install postgresql-devel python3-devel
# macOS
brew install postgresqlTip: if a package has a pure-Python or pre-built alternative, use that instead. For example, psycopg2-binary ships pre-built wheels and doesn’t require libpq-dev:
pip install psycopg2-binarymysqlclient:
# Debian/Ubuntu
sudo apt install default-libmysqlclient-dev build-essential python3-dev pkg-config
# Fedora
sudo dnf install mysql-devel python3-devel
# macOS
brew install mysql pkg-configFix 3: Upgrade pip, setuptools, and wheel
An outdated pip may not find wheels that exist on PyPI. Newer pip versions support more wheel formats (including newer manylinux tags) and handle build dependencies better.
python -m pip install --upgrade pip setuptools wheelThen retry your install:
pip install <package>This is especially important on older systems where the default pip might be years old. A pip from 2021 will not know about manylinux_2_28 wheels published in 2024.
Fix 4: Use Pre-built Wheels (—prefer-binary)
Force pip to prefer pre-built wheels over source distributions:
pip install --prefer-binary <package>This tells pip to choose a wheel even if a newer source distribution is available. Useful when a package has wheels for most versions but the very latest release only has a source dist.
You can also force pip to only install from wheels and never attempt a source build:
pip install --only-binary :all: <package>If no compatible wheel exists, this will fail with an error instead of attempting a doomed build.
Fix 5: Check Python Version Compatibility
The package may not support your Python version. This is the most common cause of “No matching distribution found.”
Check your Python version:
python --versionThen check what Python versions the package supports. Go to the package’s PyPI page (e.g., https://pypi.org/project/<package>/) and look at the “Requires Python” field and the available download files.
You can also check from the command line:
pip install <package>==This intentionally errors but shows all available versions. Pick a version that supports your Python.
If you’re on Python 3.13 and the package only supports up to 3.12, you have two options:
- Wait for the package to release a new version with 3.13 support
- Use an older Python version that the package supports
For managing multiple Python versions, use pyenv (Linux/macOS) or download specific versions from python.org (Windows).
Fix 6: Platform-Specific Wheels (Apple Silicon / ARM64)
If you’re on Apple Silicon (M1/M2/M3/M4) or ARM64 Linux, some packages don’t have pre-built wheels for your architecture.
Apple Silicon (macOS ARM64)
Older packages may only ship x86_64 wheels. Options:
Install Rosetta 2 and use an x86_64 Python:
softwareupdate --install-rosettaThen install the x86_64 version of Python from python.org (the “macOS 64-bit universal2 installer” supports both architectures).
Use Conda/Miniforge: Miniforge provides ARM64-native builds for most scientific Python packages:
# Install Miniforge (ARM64-native)
brew install miniforge
conda create -n myenv python=3.12
conda activate myenv
conda install numpy pandas scipyARM64 Linux (Raspberry Pi, AWS Graviton)
Many packages now have aarch64 wheels, but some still don’t. Install build dependencies and build from source:
sudo apt install build-essential python3-dev gfortran libopenblas-dev
pip install numpyFor scientific packages, Conda (via Miniforge) often has ARM64 builds before PyPI wheels are available.
A pattern I lean on whenever the toolchain looks daunting: pre-built alternative packages. psycopg2-binary replaces psycopg2, pymysql replaces mysqlclient, pillow ships wheels for almost every platform. The trade-off is usually a slightly larger install size and slightly less control over linking, which I happily pay to skip the C-toolchain debugging spiral.
Fix 7: Fix Cython / setuptools Version Issues
Some packages require specific versions of Cython or setuptools to build. If you see errors like Cython.Compiler.Errors.CompileError or setuptools.extern.packaging.version.InvalidVersion, try:
pip install --upgrade Cython setuptoolsSome older packages break with newer setuptools. If upgrading doesn’t help, try pinning an older version:
pip install "setuptools<71"
pip install <package>This is common with packages that haven’t updated their build configuration for newer setuptools versions, particularly after setuptools 71.0 removed deprecated features.
Fix 8: Use a Virtual Environment
If you’re installing into the system Python, you might have permission issues or conflicting versions that interfere with the build. A virtual environment gives you a clean slate.
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
pip install --upgrade pip setuptools wheel
pip install <package>This also avoids the externally-managed-environment error on modern Linux distributions.
Stranger Causes I Have Tracked Down
Read the actual error
The pip output is verbose, but the real error is usually near the end. Scroll up past the “ERROR: Could not build wheels” line and look for:
fatal error: *.h: No such file or directory: missing header file. Install the corresponding-dev/-develpackage.command 'gcc' failed: no C compiler installed.pkg-configerrors: installpkg-configand the library it’s looking for.LINK : fatal error LNK*: Windows linker error. Reinstall Visual Studio Build Tools.RuntimeError: CMake must be installed: install cmake (pip install cmakeor use your system package manager).
Install cmake or meson
Some packages (like dlib, llvmlite) need cmake or meson to build:
pip install cmake meson-python meson
# Then retry
pip install <package>Use Conda instead of pip
For scientific and data packages (NumPy, SciPy, TensorFlow, PyTorch, OpenCV), Conda often works when pip doesn’t. Conda ships its own pre-compiled binaries with all dependencies bundled, so you don’t need system libraries.
conda install numpy scipy pandas scikit-learnCheck for a binary variant of the package
Some hard-to-build packages offer a pre-built variant:
| Hard to build | Use this instead |
|---|---|
psycopg2 | psycopg2-binary |
mysqlclient | pymysql (pure Python) |
lxml | pre-built wheels usually available; update pip first |
opencv-python | pre-built on most platforms; check opencv-python-headless |
pillow | pre-built wheels usually available; update pip first |
The package is not on PyPI
“No matching distribution found” can also mean the package name is wrong or it’s hosted elsewhere. Check:
# Verify the package name
pip search <package> # deprecated, use the PyPI website instead
pip install <package> --dry-runSome packages are hosted on custom indexes. Check the project’s documentation for install instructions:
# Example: installing PyTorch from its own index
pip install torch --index-url https://download.pytorch.org/whl/cpuBuild isolation is interfering
pip builds packages in an isolated environment by default (PEP 517). Sometimes this causes issues if the package’s build dependencies have conflicts. Try disabling build isolation:
pip install --no-build-isolation <package>You’ll need to manually install the package’s build dependencies first (check pyproject.toml for [build-system] requires).
You’re behind a proxy or firewall
If pip can’t download build dependencies, the build will fail. Configure pip to use your proxy:
pip install --proxy http://user:password@proxy.example.com:8080 <package>Or set the environment variable:
export HTTPS_PROXY=http://proxy.example.com:8080
pip install <package>Rust compiler required
Some packages (like cryptography >= 3.4 and tokenizers) require a Rust compiler. If you see error: can't find Rust compiler:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
pip install <package>On Windows, download and run the Rust installer from https://rustup.rs.
ModuleNotFoundError: No module named 'distutils' on Python 3.12+
If the build log contains this line, the package’s setup.py still imports distutils, which was removed from Python 3.12. Install the compatibility shim:
pip install setuptoolsSetuptools 60+ injects a distutils shim back into sys.modules. If the project’s pyproject.toml does not list setuptools under [build-system] requires, you may also need --no-build-isolation for the install to see it. File an issue with the package upstream; they need to migrate off distutils.
Wheel Cache Has a Bad Build From Last Time
pip caches wheels under ~/.cache/pip/wheels (Linux/macOS) or %LOCALAPPDATA%\pip\Cache (Windows). A previous failed-but-partial build can poison the cache so every subsequent install replays the failure. Clear it:
pip cache purge
pip install --no-cache-dir <package>--no-cache-dir forces a clean download for that single install without affecting future ones.
uv or pip-tools Locked an Old Wheel Tag
If you use uv pip install or pip-tools with a requirements.lock, the lock can pin a wheel from a manylinux generation that no longer exists. Regenerate the lock on the target platform (or use uv lock --upgrade) so the newer manylinux_2_28 tag gets selected. See also Fix: uv not working for related uv troubleshooting.
What Other Tutorials Get Wrong About Wheel Build Failures
Most pip tutorials list the same fixes but frame them in ways that produce subtle bugs.
They miss the “read above the error” rule. “Could not build wheels” is the wrapper, not the cause. The actual compiler / linker / Rust error is 20 to 50 lines above. Articles that focus on the bottom line send readers chasing pip when the bug is in gcc, ld, or rustc output.
They omit the manylinux tag evolution. Old pip cannot see manylinux_2_28 wheels published in 2024. The same pip install numpy that worked on a coworker’s machine fails on yours because their pip is newer. Articles that say “wheels exist for this package” without flagging the pip-version dependency miss the most common silent fallback.
They recommend --user to fix permission issues. --user masks the real problem (no write access to system site-packages) and creates a parallel install location that confuses every subsequent shell session. Use a virtual environment instead; it solves the same problem cleanly.
They miss the Rust requirement for modern packages. cryptography, pydantic-core, tokenizers, polars, and ruff all need rustc to build from source. Articles that show only the C-compiler fixes leave Rust-dependent packages broken with confusing “can’t find Rust” errors.
They confuse Could not build wheels with No matching distribution found. They look similar but have different causes. The former means the build path failed; the latter means no wheel and no buildable source were found at all (often a Python version or platform mismatch). The fixes differ.
They omit Conda / Miniforge as the answer for scientific Python. NumPy, SciPy, TensorFlow, PyTorch, OpenCV all have pre-compiled Conda builds that skip every pip wheel-building problem entirely. Articles that insist on staying in pip for scientific stacks send readers into hours of debugging that Conda would skip.
Frequently Asked Questions
Why does the same package install on my coworker’s machine but fail on mine?
Three common reasons. First, their pip is newer and sees a wheel for your platform that yours does not. Second, they have build tools installed (Xcode CLT on macOS, build-essential on Linux) and you do not. Third, they are on a different architecture (x86_64 vs ARM64) and only one has wheels available. pip install --upgrade pip first; then check build tools.
What is the difference between Could not build wheels and No matching distribution found?
The former means pip found a source distribution and tried to compile it, but the build failed. The latter means pip could not find any installable distribution (no wheel, no source) for your Python version and platform. They have different causes (build tool issue vs metadata mismatch) and different fixes.
Should I use --no-build-isolation?
Only as a last resort. PEP 517 build isolation creates a clean environment for the build, which is good for reproducibility but breaks when build dependencies are in your active environment instead. --no-build-isolation makes the build use your env directly. If you use it, install the build deps from [build-system] requires yourself first.
Why do I need a Rust compiler now?
Since 2021, multiple high-profile Python packages adopted Rust for performance: cryptography 3.4+, pydantic v2 core, tokenizers, polars, ruff. When wheels are not available for your platform, pip falls back to building from source, which requires rustc. Install with curl https://sh.rustup.rs | sh (Linux / macOS) or rustup-init.exe (Windows).
Is --prefer-binary always safe?
Yes, with one caveat: it may pick an older version of a package that has wheels over a newer version that only has source. For most users that is fine; for users who specifically need bleeding-edge features, it can hide that the newer version is missing wheels on their platform. The flag is a useful default for CI and Docker builds.
Why does Conda often succeed when pip fails?
Conda ships pre-compiled binaries from its own channels (defaults, conda-forge) along with all required system libraries. When you conda install numpy, you get numpy plus the BLAS/LAPACK libraries it needs, all linked correctly. pip only ships the Python package and assumes the system libraries are present. For scientific Python especially, Conda’s bundled approach skips the entire build-tool dependency chain.
Related: Fix: ModuleNotFoundError: No module named | Fix: error: externally-managed-environment | Fix: IndentationError: unexpected indent
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: python: command not found (or python3: No such file or directory)
How to fix 'python: command not found', 'python3: command not found', and wrong Python version errors on Linux, macOS, Windows, and Docker. Covers PATH, symlinks, pyenv, update-alternatives, Homebrew, and more.
Fix: pip error: no such option: --break-system-packages
How to fix pip error no such option --break-system-packages caused by old pip versions, PEP 668 externally-managed environments, and virtual environment setup on modern Linux distributions.
Fix: error: externally-managed-environment (pip install)
How to fix the 'error: externally-managed-environment' and 'This environment is externally managed' error when running pip install on Python 3.11+ on Ubuntu, Debian, Fedora, and macOS.
Fix: Electron Forge Not Working — Makers, Code Signing, Native Modules, and Publishers
How to fix Electron Forge errors — forge.config.js makers per OS, code signing on macOS (notarytool) and Windows, native module rebuild via electron-rebuild, Vite/Webpack plugin, auto-updater, and GitHub publisher.