mrkeyoor.com_
Thu 06 Aug 02:41 UTC
PyPICLI & Toolingupdated 06 Aug 2026

build

build is the PyPA's build frontend: the tool you run to turn a source tree with a pyproject.toml into an sdist and a wheel in dist/. It does not know how to compile anything itself. It reads the [build-system] table, creates an isolated virtual environment, installs the requires listed there, then calls the PEP 517 hooks on whichever backend you named (setuptools, hatchling, flit-core, poetry-core, maturin, scikit-build-core). That indirection is the whole point: python -m build produces the same artifacts regardless of which backend the project chose, which is why it replaced the old python setup.py sdist bdist_wheel invocation.

Verdict

The correct default way to produce Python distributions when you are not already inside a project manager that builds for you. Boring, tiny, and maintained by the people who write the packaging specs, which is exactly what you want from this part of a release pipeline.

API stability5/5The CLI has been stable since 1.0 in 2021 and the Python API is a small documented surface (ProjectBuilder, DefaultIsolatedEnv, util.project_wheel_metadata). 1.5.0 added options and dropped Python 3.9 rather than changing behavior.
Docs4/5build.pypa.io covers the CLI, the Python API, isolation, and differences between backends, with a clear changelog. It leans on the PEP 517 and PEP 621 specs for anything conceptual, so newcomers still need to read those separately.
Maintenance5/5Maintained by PyPA members alongside the specs themselves, pushed within the last few days, releasing regularly, and carrying just 1 open issue (4 including PRs), which is close to unheard of for a tool at this download volume.
Ecosystem5/536.9M weekly downloads, the default build frontend in cibuildwheel 3.0+, standard in GitHub Actions release workflows, and packaged on conda-forge as python-build.

Use it if

  • You publish packages and want one command that works the same across setuptools, hatchling, flit-core, poetry-core, and maturin projects
  • You need reproducible CI artifacts: the default isolated build means your local site-packages cannot leak into the wheel, which is the classic cause of 'works on my machine' releases
  • You want to read a project's wheel metadata (name, version, requires-dist) without installing it, either via --metadata on the CLI or build.util.project_wheel_metadata in Python
  • You are still running python setup.py sdist bdist_wheel: that path is deprecated and this is the direct replacement
Skip it if

Setup reality

pip install build is trivial (packaging, pyproject_hooks, plus tomli on Python below 3.11) and needs Python 3.10 or newer as of 1.5.0. Most people should not install it into the project environment at all: pipx run build or uvx --from build pyproject-build keeps it out of your dependency tree. The friction shows up in isolation. Every run creates a temporary venv and downloads build requirements, so cold builds are slow and offline builds fail. Passing -n disables isolation but then the backend must already be importable in the current interpreter, and -x additionally skips the dependency check that would have told you what was missing. Backend options go through -Csetting=value, which setuptools supports only partially, so a flag that works with hatchling may be quietly ignored elsewhere.

Patterns

Build both distributionsbuild-sdist-and-wheel

$ python -m build

# dist/mypkg-1.2.0.tar.gz
# dist/mypkg-1.2.0-py3-none-any.whl

The default builds the sdist first and then builds the wheel from the unpacked sdist, which catches missing files in MANIFEST.in before your users do.

Build just one artifactbuild-wheel-only

$ python -m build --wheel        # or -w
$ python -m build --sdist        # or -s
$ python -m build --sdist --wheel  # -sw: both, wheel built from the source tree

-sw is not the same as the default. It builds both from the working directory, so a file missing from the sdist will not show up as a broken wheel.

Write artifacts somewhere other than dist/choose-output-directory

$ python -m build --outdir build-artifacts/
$ python -m build -o "$RUNNER_TEMP/wheels" .

The final positional argument is the source directory and defaults to the current directory, so ordering matters when you pass both -o and a path.

Run build without adding it to your projectrun-without-installing

$ pipx run build
$ uvx --from build pyproject-build --installer uv
$ pipx run 'build[uv]' --installer uv

Keeping the frontend out of the project environment avoids version conflicts with your own dependencies. pyproject-build is the console script name; python -m build is the same code.

Use uv to create the isolated environmentspeed-up-with-uv

$ python -m build --installer uv

# in pyproject.toml for cibuildwheel:
# [tool.cibuildwheel]
# build-frontend = "build[uv]"

--installer only changes how the isolated env is created and populated. It does not change the backend or the resulting artifacts, and uv must already be on PATH unless you installed the [uv] extra.

Build against the current environmentdisable-isolation

$ python -m build --no-isolation          # -n
$ python -m build -nx                      # also skip the dependency check

# equivalent to pip's and uv's --no-build-isolation

With -n the backend and every build requirement must already be installed in the active interpreter, or the run fails with an import error from the hook. -x hides the check that would have named the missing package.

Pass config settings to the backendpass-backend-options

$ python -m build -Cbuild-arg=--verbose -Cbuild-arg=--jobs=4
$ python -m build --config-json '{"builddir": "out", "setup-args": ["-Dopt=1"]}'

Repeating the same -C key collects values into a list. Backends differ wildly in what they accept, setuptools support is very limited, and unknown keys are usually ignored rather than rejected.

Get project metadata without building a wheelread-metadata-only

$ python -m build --metadata | jq -r .version
$ python -m build --metadata dist/mypkg-1.2.0-py3-none-any.whl

This uses the backend's prepare_metadata_for_build_wheel hook when available and falls back to a full wheel build when not, so it is not always fast. --metadata cannot be combined with --sdist, --wheel, or --outdir, and the source argument can be an already-built .whl to read metadata straight from it.

Build from Python with an isolated environmentpython-api-build

from build import ProjectBuilder
from build.env import DefaultIsolatedEnv

with DefaultIsolatedEnv(installer='uv') as env:
    builder = ProjectBuilder.from_isolated_env(env, '.')
    env.install(builder.build_system_requires)
    env.install(builder.get_requires_for_build('wheel'))
    path = builder.build('wheel', 'dist/')

print(path)

You have to install build_system_requires and get_requires_for_build yourself; from_isolated_env only wires the runner to the venv. Skipping either step makes the backend hook fail with a bare ImportError.

Read wheel metadata from Pythonpython-api-metadata

from build.util import project_wheel_metadata

meta = project_wheel_metadata('.', isolated=True)
print(meta['Name'], meta['Version'])
print(meta.get_all('Requires-Dist'))

The return value is an importlib.metadata PackageMetadata, so repeated headers such as Requires-Dist need get_all(); plain indexing gives you only the first one.

Find out what a non-isolated build is missingcheck-build-dependencies

from build import ProjectBuilder

builder = ProjectBuilder('.')
unmet = builder.check_dependencies('wheel')
if unmet:
    for chain in unmet:
        print(' -> '.join(chain))

Each entry is a chain showing which requirement pulled in the unmet one. This is exactly the check that -x turns off on the CLI.

Build and upload in a release workflowrelease-in-ci

- run: pipx run build
- run: pipx run twine check --strict dist/*
- uses: pypa/gh-action-pypi-publish@release/v1  # trusted publishing, no token

twine check --strict catches a README that PyPI will reject, which build itself never validates. build stops at dist/ and deliberately has no upload command.

Alternatives

PackageRegistryPick it when
uvPyPIYou want uv build plus publish in one fast Rust binary and are already using uv to manage the project
hatchPyPIYou want an opinionated project manager where building, versioning, environments, and publishing are one tool
twinePyPIThe artifacts already exist in dist/ and you need to check and upload them, which build deliberately does not do
flitPyPIThe project is a simple pure-Python package and you want build plus publish with almost no configuration