If you ran uv init any time since July, your pyproject.toml probably says build-backend = "uv_build", and you probably never typed that line. On October 12, Gentoo developer Michał Górny published a plea not to use uv-build, written after one dependency switched backends and "yet another dozen packages" lost keywords on Gentoo.
So which Python build backend should you actually ship: uv_build, hatchling, setuptools, flit-core or pdm-backend? I read the docs, the changelogs, the PyPI metadata and the lobste.rs thread, and ran the builds with uv 0.13.1. My answer: for a pure-Python library on PyPI, uv_build saves you a fraction of a second and pushes a Rust toolchain onto every source-based distro and anyone building your sdist on a platform without a prebuilt uv_build wheel. Switch to flit-core. For an internal app, keep it.
How uv_build became the default in uv init
It took two changelog entries. The uv 0.8.0 changelog says: "Make uv_build the default build backend in uv init". Before that, hatchling was the default, and only uv init --package and uv init --lib got a build backend at all. The backend had been declared stable in 0.7.19.
Then uv 0.12.0, released 2026-07-28, listed this as a breaking change: "Projects created with uv init now declare a build system and are packaged by default." The old unpackaged layout is now behind uv init --no-package.
With uv 0.13.1, a plain uv init gives you this:
[build-system]
requires = ["uv_build>=0.13.1,<0.14.0"]
build-backend = "uv_build"
The uv build backend docs recommend that upper bound. uv_build is versioned in lockstep with uv, so expect to bump it.
What the Gentoo maintainer objects to
Górny is explicit that this isn't a swipe at uv the tool, calling Astral's projects "good tools with a nice and responsive upstream." The complaint is narrower: uv_build is a Rust binary doing a pure-Python job. The numbers from the post:
- uv_build 0.13.0 wheels are 1.2 to 1.5 MiB depending on platform. flit-core's wheel is 45 KiB.
- uv_build shipped 12 releases in the 30 days before the post. On a source-based distro, each release means compiling a Rust crate graph again.
- The payoff is "in the ballpark of 100 ms" per build.
- Rust's platform support is narrower than "has a C compiler". The post points to rustix skipping SPARC, and to the toolchain running out of address space on 32-bit targets. Gentoo's uv ebuild already carries two hacks to build uv on 32-bit platforms.
In the lobste.rs thread, eschwartz, another Gentoo developer, lists architectures where Gentoo has no rustc package at all: 32-bit musl, i486, armv4/armv4t/armv5te, rv32imac, Gentoo Prefix on Darwin, hppa, m68k and alpha. You may not care about m68k, but someone running your library on an old 32-bit ARM board will.
The case for uv_build
yossarian, one of uv's maintainers, makes two fair points in the thread. First, backends are interchangeable: if uv_build doesn't fit, "you can use any number of other backends." Second, uv_build is intentionally compatible across uv versions, so a distro doesn't have to package every release. Updating weekly or less often would do.
uv_build also has a real advantage for uv users: the uv executable bundles a copy. When you run uv build and the bundled version satisfies your requires pin, nothing gets downloaded. The docs add the catch: other frontends such as python -m build always install the separately published uv_build package. Distro tooling goes down that path, and so does anyone without uv installed.
The sharper replies went the other way. eschwartz says they haven't seen a uv_build feature beyond "flit_core except branded with the Astral logo." ubernostrum reports that pdm build on their package takes between 0.75 and 0.79 seconds, which is not a number anyone is waiting on.
Is uv_build actually faster?
Yes, and it doesn't matter much. Every build-speed figure I could find with a source behind it:
- Charlie Marsh's benchmark, as reported by pydevtools in July 2025: uv_build is 10 to 35 times faster than flit, hatchling and setuptools.
- Górny: about 100 ms of real-world difference per build.
- ubernostrum: 0.75 to 0.79 s for a full
pdm build. - Mine: after switching a fresh
uv initproject to flit_core,uv build --wheeltook 0.14 s wall clock, uv's own overhead included.
The Astral docs just say "very fast". I believe the 10-35x number: a Rust binary that skips interpreter startup and imports will win a microbenchmark. But 35x of a sub-second step that runs once per release or once per CI job is a rounding error next to your test suite.
uv_build vs hatchling vs setuptools vs flit-core vs pdm-backend
Versions, wheel sizes and dependencies come from PyPI's JSON API for the current releases.
| Backend | Version | Wheel size | Runtime deps | Written in | Extension modules | Dynamic version / build hooks |
|---|---|---|---|---|---|---|
| uv_build | 0.13.1 | 1.2–1.5 MiB per platform (18 wheels) | none | Rust | No | No / No |
| flit-core | 4.1.0 | 45 KB | none | Python | No | From __version__ / No |
| hatchling | 1.32.4 | 79 KB | packaging, pathspec, pluggy, tomlkit, trove-classifiers (+ tomli < 3.11) | Python | Through build hooks/plugins | Yes / Yes |
| pdm-backend | 2.5.0 | 200 KB | none (importlib-metadata < 3.10) | Python | Through pdm_build.py hooks |
Yes / Yes |
| setuptools | 84.0.0 | 818 KB | none | Python | Yes, native | Yes / Yes |
The limits that bite in practice, from the pydevtools uv_build reference and the uv docs:
- uv_build is pure Python only. For C, C++ or Rust extensions you need maturin, scikit-build-core or setuptools.
-
dynamic = ["version"]fails with "missing field version". If you version from git tags, uv_build is out. - There's no plugin system and no build scripts, so generated files have to exist before you build.
- The layout is strict: it expects
src/<name>/__init__.pyand fails with "Expected a Python module at: ..." otherwise. You can change it withmodule-nameandmodule-root, but legacy repos will hit this first.
For bootstrapping, flit-core wins easily. It has zero dependencies and installs with nothing but Python, which is why distros use it to bootstrap everything else. Hatchling needs five packages before it can build anything. Setuptools is an 818 KB wheel with its own dependencies vendored, so it needs nothing else. uv_build needs a Rust toolchain on any platform without a prebuilt wheel, and on a source distro that means every platform.
Which build backend to pick, by project type
- Pure-Python library on PyPI: flit-core. It's tiny, has no deps, and no packager will ever email you about it.
- Pure-Python library that needs git-tag versioning or generated files: hatchling (with hatch-vcs) or pdm-backend.
- C/C++ extensions: setuptools for legacy code, scikit-build-core if you already use CMake.
- Rust extensions: maturin. That needs Rust too, but there the Rust is the point.
- Internal app that never ships an sdist: uv_build is fine. Your only downstream is your own CI, and the bundled copy means no extra download.
That last case is why I think Górny is right about libraries and overreaching about everything else. The cost of uv_build falls on people building your sdist, and an internal service has none.
Switching from uv_build to flit-core
For new projects, pass the flag:
uv init --lib --build-backend flit my-lib
--build-backend also accepts uv, hatch, pdm, poetry, setuptools, maturin and scikit. One gotcha: with uv 0.13.1 the flit template still writes requires = ["flit_core>=3.2,<4"], which caps you below the current flit-core 4.1.0. Widen it.
For an existing uv_build project with the default src/ layout, swap the block:
[build-system]
requires = ["flit_core>=3.12,<5"]
build-backend = "flit_core.buildapi"
Then delete any [tool.uv.build-backend] table, run uv build, and compare the file lists of the old and new wheel and sdist before you publish. I did this on a fresh uv init project and it built first try with the same module and entry point in the wheel. flit-core can read the version and description from __version__ and the module docstring if you mark them dynamic, but the static version = "..." that uv writes into [project] works as is. uv sync, uv lock and uv run keep working the same; the frontend and the backend were always separate choices.
Sources
- Michał Górny, "A plea not to use uv-build" (2026-10-12)
- lobste.rs discussion
- uv docs: build backend
- uv 0.8.x changelog, uv 0.12.x changelog
- pydevtools: uv_build reference, The uv build backend is now stable
- PyPI: uv-build, flit-core, hatchling, pdm-backend, setuptools
Which build backend is in your pyproject.toml right now, and did you pick it or did uv init? If you're keeping uv_build for a library on PyPI, I'd like to hear what you get for it besides the 100 ms.
Top comments (0)