BUILD(1)
β¨ NAME β¨
okay so welcome to BUILD(1) bestie, this is the manual for build β version 1.5.1 π
It's a simple, correct Python packaging frontend (link for the curious: https://packaging.python.org/). Like, that's its whole personality.
What does it actually do? build reads your project's pyproject.toml configuration file (https://packaging.python.org/en/latest/specifications/pyproject-toml/) and invokes build backends to create distribution packages (https://packaging.python.org/en/latest/glossary/#term-Distribution-Package) β the files you upload to PyPI (https://pypi.org/) or install with pip. And that's IT. It focuses solely on building packages. It does not manage dependencies. It does not manage virtual environments. It minds its own business, respectfully β¨
π MISSION STATEMENT π
So many Python tools try to do everything at once, and it's giving overachiever. Take pip (https://github.com/pypa/pip) β it both installs packages and can build them. Convenient? Sure. But this tight coupling isn't always the vibe.
Some users need standalone build tools for custom environments (outside PyPI β https://pypi.org/), or they manage packages themselves (the way Linux distributions do, iconic behaviour honestly).
This project fills that gap: it's a standalone build tool that follows modern Python packaging standards β for how build tools communicate with backends, and how pyproject.toml defines build requirements.
We keep dependencies minimal so build stays easy to install and use in restricted environments. Lowkey a hero for that π«Ά
π₯ DIFFERENCES FROM OTHER TOOLS π₯
Plot twist: thanks to standardization, all compliant build frontends produce the same outputs β source distributions (https://packaging.python.org/en/latest/specifications/source-distribution-format/) and wheels (https://packaging.python.org/en/latest/specifications/binary-distribution-format/) β from the same project. The differences are mainly in scope, dependencies, and extra features. So like, everyone's wearing the same outfit but accessorising differently π
β uv build
uv build (https://docs.astral.sh/uv/) is essentially equivalent to python -m build --installer=uv. Both follow packaging standards, no cap.
But build offers features like --config-json for passing complex nested configuration to backends, and the pip installer works on systems that don't have pre-compiled uv wheels. iykyk.
β setup.py sdist bdist_wheel
build is the modern equivalent of setup.py sdist bdist_wheel, supporting any backend β not just setuptools (https://setuptools.pypa.io/). Glow up fr fr.
β hatch build
hatch build (https://hatch.pypa.io/) is the build command from the Hatch project management tool. It's a convenient wrapper around the build process as part of the larger Hatch ecosystem for managing Python projects. Meanwhile build is a standalone tool focused solely on building. Main character energy, but for one specific thing β¨
β flit build
flit build (https://flit.pypa.io/) is the build command from the Flit project. One important difference, and listen up: flit-core produces slightly different source distributions when built by flit itself compared to other frontends. Using build (or any standards-compliant frontend) ensures consistent outputs regardless of the backend. Consistency ate π
β cibuildwheel
cibuildwheel (https://cibuildwheel.pypa.io/) is a different kind of tool entirely. While build creates a single wheel for the current platform, cibuildwheel orchestrates building wheels across many platforms and Python versions in CI. It actually calls a build frontend (like build or pip) internally for each platform.
So: use build to create a pure-Python wheel or a single native wheel; use cibuildwheel when you need to produce native wheels for many platforms. Different jobs, bestie π
πΈ WHERE TO START πΈ
First time using build? Start with the Getting Started <> to create your first package.
Need to solve a specific problem? Check the Basic Usage <> for common workflows, or browse the how-to guides below for your specific scenario.
Looking for technical details? The Command-Line Interface <> documents all command-line options, and the API Documentation <> covers the Python API.
Want to understand how it works? Read How it Works <> to learn about the build process and isolation.
π¦ Getting Started
This tutorial will guide you through installing build and creating your first Python package ready for distribution. Let's go bestie, we're doing this together π«Ά
Note: New to Python packaging? Start with the Python Packaging User Guide tutorial (https://packaging.python.org/en/latest/tutorials/packaging-projects/) to learn how to structure a Python project. This tutorial assumes you already have a project with apyproject.tomlfile.
π Prerequisites
You need Python 3.10 or later installed on your system. Check your Python version:
$ python --version
Python 3.11.0
π Installation
The recommended way to install build is using pip:
$ pip install build
Or, if you prefer to install it in an isolated environment using pipx:
$ pipx install build
For corporate environments or systems with restricted internet access, see Corporate Environments <>.
π Creating a simple package
Let's create a minimal Python package to demonstrate how build works. Tiny project, big lessons β¨
- Create a project directory:
$ mkdir mypackage
$ cd mypackage
- Create a
pyproject.tomlfile:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mypackage"
version = "0.1.0"
description = "A simple example package"
readme = "README.md"
requires-python = ">=3.8"
- Create a
README.mdfile:
# My Package
This is a simple example package.
- Create your Python package:
$ mkdir src/mypackage
$ touch src/mypackage/__init__.py
- Add some code to
src/mypackage/__init__.py:
def hello():
return "Hello from mypackage!"
π Building your package
Now you're ready to build your package:
$ python -m build
You should see output like:
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- hatchling
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- hatchling
* Getting build dependencies for wheel...
* Building wheel...
Successfully built mypackage-0.1.0.tar.gz and mypackage-0.1.0-py3-none-any.whl
The built packages are now in the dist/ directory:
$ ls dist/
mypackage-0.1.0-py3-none-any.whl
mypackage-0.1.0.tar.gz
π Understanding what happened
build created two distribution files (https://packaging.python.org/en/latest/glossary/#term-Distribution-Package) β packages ready for distribution. Two for the price of one, we love to see it π
- Source distribution:
mypackage-0.1.0.tar.gz
This tarball (https://en.wikipedia.org/wiki/Tar_(computing)) (a compressed archive) contains your source code. Anyone can download it and build your package on their system. Learn more in the source distribution specification (https://packaging.python.org/en/latest/specifications/source-distribution-format/).
- Wheel:
mypackage-0.1.0-py3-none-any.whl
This wheel (https://packaging.python.org/en/latest/specifications/binary-distribution-format/) is a pre-built package that pip (https://pip.pypa.io/) can install directly without needing to build anything. This makes installation much faster. Fast = iconic.
build used an isolated environment to ensure your package builds consistently regardless of what you have installed on your computer. It:
- Created a temporary virtual environment (
https://docs.python.org/3/tutorial/venv.html) - Installed only your build dependencies (
hatchlingβhttps://hatch.pypa.io/latest/) - Invoked the build backend to create the distribution files
- Cleaned up the temporary environment when done
So tidy. Left no crumbs π§
π Seeing which backend versions were used
build deletes the isolated environment after each build, so it does not leave the installed versions behind. Instead it prints them as it goes:
$ python -m build --wheel
...
* Getting build dependencies for wheel...
* Installed build dependency versions:
- hatchling==1.30.1
* Building wheel...
build prints each installed build dependency as name==version. Keep this handy when reporting a problem to a backend's issue tracker β which usually asks for the exact version you built with. See Troubleshooting <>.
π Building a wheel from your sdist
You can also point build at the source distribution you just produced. build checks the archive and runs a wheel build against its contents, which catches missing files in the sdist:
$ python -m build dist/mypackage-0.1.0.tar.gz
Successfully built mypackage-0.1.0-py3-none-any.whl
The wheel lands next to the sdist. Pass --outdir to write somewhere else.
If your package compiles C/C++ and you rebuild often, --sdist-extract-dir keeps the extracted sources at a fixed path so ccache/sccache can reuse earlier compilations; see Basic Usage <>. Big brain move honestly π₯
π Building without isolation
If you install the build dependencies yourself and build with --no-isolation, build first checks that every declared dependency is present in the interpreter you are running. When something is missing it stops with an Unmet dependencies error that names the interpreter it checked and, for each requirement, the version it wanted against the version it found β so you can tell "not installed" apart from "installed, but the wrong version". See Troubleshooting <> for how to read it.
π Recording what build produced
If a script or CI job needs the exact filenames build produced, add --report PATH to write them to a JSON file instead of parsing the output:
$ python -m build --report build-report.json
π Next steps
- Learn about Basic Usage
<>for common build workflows - Understand How it Works
<>for details on the build process - See Backend Configuration
<>to customize your build - When a build fails, follow Debug a failed build
<#debug-a-failed-build>to capture backend logs and keep the working directories
π Installation
π Installing build
The recommended way to install build is using uv (https://docs.astral.sh/uv/), but you can also install it via pip (https://pip.pypa.io/) or pipx (https://pipx.pypa.io/):
[uv]
$ uv tool install build
[pipx]
$ pipx install build
[pip]
$ pip install build
π Optional Dependencies
build supports optional extras for different use cases:
build[virtualenv]β Usevirtualenv(https://virtualenv.pypa.io/) for isolation instead ofvenv(https://docs.python.org/3/library/venv.html). This can be useful when using automation tools that rely onvirtualenv(such astoxβhttps://tox.wiki/), or when your operating system's Python package does not includevenvin the standard installation.
$ pip install 'build[virtualenv]'
build[uv]β bundlesuv(https://docs.astral.sh/uv/) so you can use it with--installer=uveven when you've got literally no other install of uv lying around π
$ pip install 'build[uv]'
π Verifying Attestations π
Attestations (https://github.com/pypa/build/attestations) exist for versions after 1.2.1, and you can verify them with the gh CLI tool, bestie:
$ python -m pip --no-cache-dir download --no-deps build
$ gh attestation verify build*.whl --repo pypa/build
PEP 740 (https://peps.python.org/pep-0740/) has been accepted, and support for PEP 740 attestations in build is being tracked in #987 (https://github.com/pypa/build/issues/987). We're watching that issue like a season finale π
π₯‘ Installing via conda π₯‘
Over on conda-forge this package goes by the name python-build (https://github.com/conda-forge/python-build-feedstock), so yes you can grab it through conda (https://github.com/conda/conda) or any equivalent:
$ conda install conda-forge::python-build
πͺ Bootstrapping πͺ
This package can build itself using only the tomli (https://github.com/hukkin/tomli) and pyproject-hooks (https://github.com/pypa/pyproject-hooks) dependencies β and ngl, tomli can be omitted entirely on Python 3.11+. In that case you should use the --skip-dependency-check flag β¨
π¦ Compatibility π¦
build is verified to be compatible with these Python versions, no cap:
- 3.10
- 3.11
- 3.12
- 3.13
- 3.14
- PyPy 3.10
- PyPy 3.11
π BASIC USAGE π
This guide covers the most common build commands and workflows. Buckle up, it's giving tutorial era π
π§ Building both source and wheel distributions (default) π§
By default, build creates both a source distribution (https://packaging.python.org/en/latest/specifications/source-distribution-format/) β often called an "sdist" β and a wheel (https://packaging.python.org/en/latest/specifications/binary-distribution-format/):
$ python -m build
The process, step by step:
- Builds the source distribution from your source code
- Extracts the source distribution to a temporary directory
- Builds the wheel from the extracted source distribution
This two-step process ensures your source distribution contains all necessary files to build your package. If files are missing, the wheel build will fail, alerting you to the problem. It's giving built-in bestie who tells you the truth π
This is different from python -m build --sdist --wheel, which builds both directly from source. The default behavior is preferable because it verifies your sdist is complete.
π Building only a wheel π
$ python -m build --wheel
Or the short form:
$ python -m build -w
This builds the wheel (https://packaging.python.org/en/latest/specifications/binary-distribution-format/) β the installable package β directly from your source directory, skipping the source distribution step. It's faster, but it doesn't verify your source distribution is complete. trade-offs, babe
π¦ Building only a source distribution π¦
$ python -m build --sdist
Or the short form:
$ python -m build -s
ποΈ Specifying the source directory ποΈ
Build defaults to the current directory. To build from a different location:
$ python -m build path/to/project
Or explicitly:
$ python -m build --srcdir path/to/project
π« Building from a source distribution π«
The positional argument also accepts a .tar.gz source distribution (https://packaging.python.org/en/latest/specifications/source-distribution-format/). Build checks the filename and the presence of PKG-INFO, extracts the archive into a temporary directory, and runs the wheel build against the extracted source. The wheel lands next to the input archive. Honestly? iconic behavior π
$ python -m build dist/mypackage-1.0.0.tar.gz
No flag means the same as --wheel. --metadata works the same way. --sdist errors out, since the archive already is an sdist π«
π Specifying the output directory π
By default, distributions are placed in dist/ within the source directory. To use a different location:
$ python -m build --outdir /path/to/output
Or the short form:
$ python -m build -o /path/to/output
β‘ Speeding up rebuilds of compiled packages β‘
When the default build extracts the intermediate sdist, it uses a random temporary directory. Compiler caches such as ccache (https://ccache.dev/) and sccache (https://github.com/mozilla/sccache) fold the absolute source path into their cache key, so a fresh path on every run means every C/C++ file is recompiled from scratch. Not the vibe. π
Pin the extraction path with --sdist-extract-dir so the cache can recognise unchanged files across rebuilds:
$ python -m build --sdist-extract-dir build/sdist
Build creates the directory if missing, clears and repopulates it each run so the contents stay fresh, and keeps it after the build. The path stays stable while the project version is unchanged, as it is during iteration on the extension sources. The same flag applies when building a wheel from a .tar.gz.
Build still runs in a fresh isolated environment whose path is not pinned, so include directories from that environment (Python headers, dependency headers) keep changing. Caching those compile units needs toolchain-side path remapping on top of this flag (e.g. -fdebug-prefix-map, or sccache's base_dir). Fr fr, that part is on your toolchain bestie π§
π Controlling verbosity π
Increase verbosity to see more details:
$ python -m build -v
Or even more verbose:
$ python -m build -vv
Decrease verbosity for quieter output:
$ python -m build -q
π Using a faster installer π
By default, build uses pip (https://pip.pypa.io/) to install build dependencies. For faster builds, use uv (https://docs.astral.sh/uv/) β a faster alternative to pip:
$ python -m build --installer=uv
This requires uv to be installed:
$ pip install uv
ποΈ Building without isolation ποΈ
By default, build creates an isolated environment (https://packaging.python.org/en/latest/glossary/#term-Isolated-Build) β a clean temporary virtual environment (https://docs.python.org/3/tutorial/venv.html) β to ensure reproducible builds. To skip this and use your current environment:
$ python -m build --no-isolation
Or the short form:
$ python -m build -n
Warning: When using --no-isolation, you must manually install all build dependencies. This is mainly useful for: - Offline or air-gapped environments (no internet access) - Debugging build issues - Linux distribution packaging where dependencies are provided externally
π Skipping dependency checks π
To skip checking if build dependencies are installed (requires --no-isolation):
$ python -m build --no-isolation --skip-dependency-check
Or:
$ python -m build -nx
π Common workflows π
π± Development build π±
Quick build during development:
$ python -m build --wheel --installer=uv
π€ Fast CI build π€
In CI where dependencies are pre-installed:
$ pip install build build-backend-here
$ python -m build --no-isolation
π Release build π
For uploading to PyPI (https://pypi.org/), build both sdist and wheel. Pick your uploader bestie:
[uv]
$ python -m build
$ uv publish
uv publish (https://docs.astral.sh/uv/guides/publish/) is a modern option that handles uploading to PyPI with built-in support for trusted publishing.
[twine]
$ python -m build
$ twine check dist/*
$ twine upload dist/*
See the twine documentation (https://twine.readthedocs.io/) for upload options.
Tip: Use the hynek/build-and-inspect-python-package GitHub Action which handles this workflow including verification. See CI/CD Integration <>.
π Testing the sdist π
Build from the sdist to ensure it's complete:
$ python -m build --sdist
$ python -m build dist/mypackage-1.0.0.tar.gz
Or test installation:
$ python -m build
$ python -m pip install dist/mypackage-1.0.0.tar.gz
$ python -c "import mypackage; print(mypackage.__version__)"
π§Ό Cleaning before build π§Ό
There's no built-in clean command. To ensure a fresh build, manually remove the dist directory:
$ rm -rf dist/
$ python -m build
Or to avoid stale artifacts, use a unique output directory:
$ python -m build --outdir dist/v1.0.0
π Getting metadata without building π
To extract package metadata without building the full package:
$ python -m build --metadata
This outputs the wheel metadata in JSON format to stdout. data? served. β¨
π·οΈ Checking the build version π·οΈ
To see which version of build you're using:
$ python -m build --version
π οΈ Installing build dependencies only π οΈ
For specialized workflows like static analysis or linting, you may want to install just the build dependencies without actually building:
from build import ProjectBuilder
builder = ProjectBuilder(".")
# Get all build dependencies
requires = builder.build_system_requires
for dist in ["sdist", "wheel"]:
requires.extend(builder.get_requires_for_build(dist))
# Install them
import subprocess
subprocess.run(["pip", "install", *requires])
This is useful when you need the same environment that build would create, but want to run other tools (like mypy, ruff, or custom linters) instead of building the package.
See API Documentation <> for more programmatic usage examples.
π See also π
- Backend Configuration <> for passing options to your build backend
- Corporate Environments <> for using build with private indexes
- Troubleshooting <> for common problems
- Command-Line Interface <> for all command-line options
π build vs Other Tools π
The Python packaging ecosystem has several tools that work together. This guide helps you understand when to use build versus other tools. It's giving the group project breakdown π
πββοΈ When to use build πββοΈ
Use build when you want to:
- Create distribution packages (source distributions (
https://packaging.python.org/en/latest/specifications/source-distribution-format/) and/or wheels (https://packaging.python.org/en/latest/specifications/binary-distribution-format/)) for your project - Build for your current Python version and platform only
- Create packages to upload to PyPI (
https://pypi.org/) - Test that your package builds correctly
- Build as part of a development workflow
Build is a build frontend β it knows how to invoke build backends (setuptools (https://setuptools.pypa.io/), hatchling (https://hatch.pypa.io/latest/), flit (https://flit.pypa.io/), etc.) but doesn't handle testing, multi-version builds, or task automation. She's focused, okay? π
$ python -m build
This builds your package for the currently active Python interpreter.
π‘ When to use cibuildwheel π‘
Use cibuildwheel (https://cibuildwheel.readthedocs.io/) when you need to build wheels (https://packaging.python.org/en/latest/specifications/binary-distribution-format/) for multiple Python versions and platforms:
- Building wheels for distribution on PyPI (
https://pypi.org/) - Supporting Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.14
- Supporting multiple operating systems β Linux, macOS, Windows, the whole squad π
- Building
manylinux/musllinuxwheels β those are the standardized Linux wheel formats, and yes bestie they have their own PEPs:https://peps.python.org/pep-0513/andhttps://peps.python.org/pep-0656/π - Building wheels with compiled extensions β C, C++, Rust, etc. all welcome β¨
So like, cibuildwheel literally uses build under the hood but it handles all the complexity for you, no cap:
- Running builds in manylinux containers β
https://github.com/pypa/manylinuxπ³ - Testing wheels after building
- Handling platform-specific quirks (the annoying ones π)
- Proper ABI tagging β
https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/π
Example .github/workflows/build.yml:
name: Build
on: [push, pull_request]
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: pypa/cibuildwheel@v2.17
- uses: actions/upload-artifact@v4
with:
name: wheels
path: ./wheelhouse/*.whl
Full deets are in the cibuildwheel documentation, https://cibuildwheel.readthedocs.io/ π
When to use tox π
Use tox β https://tox.wiki/ β when you need to:
- Test your package across multiple Python versions (like
3.10,3.11,3.12,3.13,3.14) π - Run tests in isolated environments β
https://packaging.python.org/en/latest/glossary/#term-Virtual-Environmentπ§ - Automate testing workflows
- Run linters (aka code checkers), formatters, type checkers
- Build documentation
- Orchestrate multiple development tasks
Example tox.toml:
[env_run_base]
description = "run test suite with {basepython}"
deps = [
"pytest",
"pytest-cov",
]
commands = [
["pytest", "tests"],
]
[env.build]
description = "build the package"
deps = ["build"]
commands = [["python", "-m", "build"]]
[env.lint]
description = "run linters and type checkers"
deps = [
"ruff",
"mypy",
]
commands = [
["ruff", "check", "."],
["mypy", "src"],
]
Tox can call build to create distributions, but its main purpose is test automation, just so you know π«Ά
See the tox documentation at https://tox.wiki/ for complete details.
When to use nox πΈ
Use nox β https://nox.thea.codes/ β when you want tox-like functionality but you'd rather write Python than INI, iykyk:
Example noxfile.py:
import nox
@nox.session(python=["3.10", "3.11", "3.12", "3.13", "3.14"])
def tests(session):
session.install("pytest", "pytest-cov")
session.run("pytest", "tests")
@nox.session
def build(session):
session.install("build")
session.run("python", "-m", "build")
See the nox documentation at https://nox.thea.codes/ for complete details β¨
When to use uv β
Use uv β https://docs.astral.sh/uv/ β when you want:
- Fast package installation (faster than
pip, fr fr) - Workspace management (monorepos with multiple packages)
- Combined dependency resolution and installation
- Modern Python packaging workflows
uv can also build packages:
$ uv build
This is equivalent to python -m build --installer=uv π
See the uv documentation at https://docs.astral.sh/uv/ for complete details.
When to use pip π
Use pip when you need to:
- Install packages (not build them, bestie, that's a different job)
- Install from PyPI or other indexes
- Install in editable mode for development
- Manage dependencies in your environment
pip can build packages as a side effect of installation, but for explicit building, use build. Periodt.
# Installing (use pip)
$ pip install mypackage
# Installing in editable mode (use pip)
$ pip install -e .
# Building distributions (use build)
$ python -m build
How they work together π€
A typical complete workflow might use all these tools:
Development workflow
# Install your package in editable mode
$ pip install -e .[dev]
# Run tests across Python versions
$ tox
# Build distributions
$ python -m build
# Upload to PyPI
$ twine upload dist/*
CI/CD workflow
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: pip install tox
- run: tox -e py
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hynek/build-and-inspect-python-package@v2
build_wheels:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: pypa/cibuildwheel@v2.17
This workflow:
- Uses
toxto test across Python versions - Uses
build(via hynek's action) to create sdist - Uses
cibuildwheelto create wheels for all platforms
Decision tree π³
[graph].SS Quick reference
ββββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββββββββββββββ
β Tool β Purpose β Use for β
ββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββββββββββββ€
β build β Build sdist/wheel β Creating β
β β for one Python β distributions β
ββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββββββββββββ€
β cibuildwheel β Build wheels for all β Distributing on PyPI β
β β platforms β β
ββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββββββββββββ€
β tox β Test across Python β CI/testing β
β β versions β automation β
ββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββββββββββββ€
β nox β Like tox, but β CI/testing β
β β Python-based β automation β
ββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββββββββββββ€
β uv β Fast package manager β Installing + β
β β β optional building β
ββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββββββββββββ€
β pip β Package installer β Installing packages β
ββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββββββββββββ€
β twine β Upload to PyPI β Publishing β
ββββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββββββββββββ
See also π
- Basic Usage <> for
buildcommand examples - CI/CD Integration <> for CI/CD integration
- Build Backends <> to understand
buildvs backend
CI/CD Integration π
This guide shows how to integrate build into your continuous integration and deployment workflows. Slay.
GitHub Actions π
Using hynek's build-and-inspect-python-package
The recommended way to build and verify packages in GitHub Actions β https://docs.github.com/en/actions β is using the hynek/build-and-inspect-python-package action, https://github.com/hynek/build-and-inspect-python-package. This action builds your package, inspects it for common issues, and provides the built artifacts. Iconic behaviour honestly.
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hynek/build-and-inspect-python-package@v2
This action automatically:
- Builds both sdist and wheel β¨
- Verifies the sdist contains all necessary files
- Checks for common packaging issues
- Uploads artifacts for download
- Works with any PEP 517 build backend
For more details, see the action documentation at https://github.com/hynek/build-and-inspect-python-package π
Manual build with isolation π§Ό
If you need more control, use build directly with isolated environments. pipx is pre-installed on all GitHub Actions runners:
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build package
run: pipx run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
Fast builds with pre-installed dependencies β‘
For faster CI builds, pre-install the build dependencies and use --no-isolation:
name: Fast Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install build hatchling # Replace hatchling with your backend
- name: Build package
run: python -m build --no-isolation
Using uv for faster builds π¦
Use uv as the installer for faster dependency installation:
name: Build with uv
β¦and the rest of that workflow file wraps up like this, bestie π
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv and build
run: pip install uv build
- name: Build package
run: python -m build --installer=uv
β¨ Matrix builds across Python versions β¨
Wanna test that your build survives on multiple Python versions? Say less:
name: Build Matrix
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install build
run: pip install build
- name: Build package
run: python -m build
π¦ Building wheels for multiple platforms π¦
Got C extensions or platform-specific code? Then it's cibuildwheel o'clock:
name: Build Wheels
on: [push, pull_request]
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
steps:
- uses: actions/checkout@v4
- uses: pypa/cibuildwheel@v2.17
- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl
build_sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hynek/build-and-inspect-python-package@v2
Peep build vs Other Tools to figure out when you want build and when you want cibuildwheel π
π Publishing to PyPI π
You can build and publish in one release workflow using the PyPA publish action (https://github.com/pypa/gh-action-pypi-publish):
name: Publish
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hynek/build-and-inspect-python-package@v2
publish:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- uses: pypa/gh-action-pypi-publish@release/v1
πΈ GitLab CI πΈ
Basic build
build:
image: python:3.12
script:
- pip install build
- python -m build
artifacts:
paths:
- dist/
With caching
Cache those pip packages so your builds stop taking a million years:
build:
image: python:3.12
cache:
paths:
- .cache/pip
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
script:
- pip install build
- python -m build
artifacts:
paths:
- dist/
Fast builds with pre-installed dependencies
build:
image: python:3.12
script:
- pip install build hatchling
- python -m build --no-isolation
artifacts:
paths:
- dist/
Matrix builds
.build_template:
script:
- pip install build
- python -m build
artifacts:
paths:
- dist/
build:py310:
extends: .build_template
image: python:3.10
build:py314:
extends: .build_template
image: python:3.14
π CircleCI π
Basic build
version: 2.1
jobs:
build:
docker:
- image: cimg/python:3.12
steps:
- checkout
- run:
name: Install build
command: pip install build
- run:
name: Build package
command: python -m build
- store_artifacts:
path: dist/
workflows:
build_workflow:
jobs:
- build
β Travis CI β
Basic build
language: python
python:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
install:
- pip install build
script:
- python -m build
π§ Azure Pipelines π§
Basic build
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
strategy:
matrix:
Python310:
python.version: '3.10'
Python314:
python.version: '3.14'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
- script: |
pip install build
python -m build
displayName: 'Build package'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: 'dist'
artifactName: 'dist'
π Jenkins π
Declarative pipeline
pipeline {
agent any
stages {
stage('Build') {
steps {
sh '''
python3 -m pip install build
python3 -m build
'''
}
}
stage('Archive') {
steps {
archiveArtifacts artifacts: 'dist/*', fingerprint: true
}
}
}
}
π Docker π
Multi-stage build
Build your packages inside a Docker container, iconic behaviour:
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml README.md ./
COPY src/ src/
RUN pip install build && python -m build
# Runtime image
FROM python:3.12-slim
COPY --from=builder /app/dist/*.whl /tmp/
RUN pip install /tmp/*.whl && rm /tmp/*.whl
CMD ["python"]
π tox Integration π
Running build inside tox environments, no cap:
[env_run_base]
description = "run test suite"
deps = ["pytest"]
commands = [["pytest", "tests"]]
[env.build]
description = "build the package"
deps = ["build"]
commands = [["python", "-m", "build"]]
[env."build-check"]
description = "build and check the package"
deps = [
"build",
"twine",
]
commands = [
["python", "-m", "build"],
["twine", "check", "dist/*"],
]
And to actually run that build environment:
$ tox -e build
π Discovering Built Artifacts π
Okay so this one is important, bestie. Artifact filenames are dynamic, which means pipelines that need to act on the exact files the build produced (uploading, signing, computing digests) should not guess them or glob them. Instead, pass --report PATH to capture a machine-readable list, then read it back with a JSON tool like jq. And pick a PATH that lives outside the output directory, so tools that slurp up dist/* wholesale don't trip over it:
- name: Build and upload
run: |
python -m build --report build-report.json
twine upload $(jq -r '.artifacts[].path' build-report.json)
The report also includes each artifact's size and SHA-256 hash, which is super useful for emitting provenance or checksums:
$ jq -r '.artifacts[] | "\(.hashes.sha256) \(.name)"' build-report.json
See Command-Line Interface for the full report schema π«Ά
β¨ Verifying Builds β¨
Always verify your built packages before you publish. Always. Not negotiable:
- name: Check package
run: |
pip install twine
twine check dist/*
Or use hynek's action, which has the verification baked in automatically π
π¦ Conditional Builds π¦
Only build on specific events? Gotchu:
name: Build
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hynek/build-and-inspect-python-package@v2
publish:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
- uses: pypa/gh-action-pypi-publish@release/v1
So this builds on every push and PR, but only publishes when you push a version tag. Main character energy π
π Troubleshooting CI Builds π
Enable verbose output
Throw in the -vv flag when you're debugging:
- name: Build package
run: python -m build -vv
Preserve build artifacts
Upload the dist directory even when things go wrong:
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
Cache build environments
If you're building over and over, cache pip's download cache:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
π See also π
- Troubleshooting β for build-specific issues
- Corporate Environments β for private indexes and proxies
- build vs Other Tools β for when to use
buildvs other tools - hynek/build-and-inspect-python-package (
https://github.com/hynek/build-and-inspect-python-package) β Recommended GitHub Action - cibuildwheel (
https://cibuildwheel.readthedocs.io/) β For multi-platform wheel builds
πΈ Corporate Environments πΈ
This guide covers the common scenarios you'll hit when using build behind corporate firewalls, with private package indexes (aka custom package repositories), or in restricted network environments.
π Private package indexes π
Using a private PyPI mirror
Set the PIP_INDEX_URL environment variable (https://pip.pypa.io/en/stable/topics/configuration/#environment-variables) to point at your private package index (https://packaging.python.org/en/latest/glossary/#term-Package-Index) β which is, y'know, an alternative to PyPI (https://pypi.org/):
$ export PIP_INDEX_URL=https://pypi.company.com/simple
$ python -m build
Or if you just want it for a single command:
$ PIP_INDEX_URL=https://pypi.company.com/simple python -m build
Additional indexes
Wanna use both PyPI and a private index? Periodt:
$ export PIP_INDEX_URL=https://pypi.org/simple
$ export PIP_EXTRA_INDEX_URL=https://pypi.company.com/simple
$ python -m build
π¨ Warning:PIP_EXTRA_INDEX_URLis lowkey a security situation, bestie. A malicious package chilling on any index can shadow packages from other indexes. UsePIP_INDEX_URLexclusively whenever you can. That's the move. π
π Authentication
πͺͺ Embedded credentials
You can yeet the credentials straight into the index URL:
$ PIP_INDEX_URL=https://username:password@pypi.company.com/simple python -m build
β οΈ Warning: embedded credentials are giving "leaked password in shell history and CI logs". Use environment variable substitution when you can:
$ PIP_INDEX_URL=https://${PYPI_USER}:${PYPI_TOKEN}@pypi.company.com/simple python -m build
π Using keyring
build passes --no-input to pip, which blocks those hidden credential prompts that make the process look like it's frozen forever π. And when the keyring CLI is available on PATH, build automatically sets PIP_KEYRING_PROVIDER=subprocess (or UV_KEYRING_PROVIDER=subprocess when you're using the uv installer), so the installer delegates credential lookups to the system keyring β no keyring needed inside the isolated build environment. Iconic. β¨
Install keyring system-wide so it's available on PATH:
$ pipx install keyring
Or install build with the keyring extra:
$ pip install build[keyring]
Then store your credentials:
$ keyring set https://pypi.company.com username
For specialized backends (e.g., Google Artifact Registry, Azure Artifacts), install the corresponding keyring plugin alongside keyring:
$ pipx install keyring
$ pipx inject keyring keyrings.google-artifactregistry-auth
You can also set the keyring provider explicitly via environment variable β super useful in CI, or when keyring lives in a non-standard location:
$ export PIP_KEYRING_PROVIDER=subprocess # for pip installer
$ export UV_KEYRING_PROVIDER=subprocess # for uv installer
$ python -m build
To disable keyring entirely:
$ export PIP_KEYRING_PROVIDER=disabled
$ export UV_KEYRING_PROVIDER=disabled
$ python -m build
π Using .netrc
Make a ~/.netrc file (~/_netrc on Windows) with your credentials in it:
machine pypi.company.com
login username
password your-password
Make sure that file has restricted permissions (we are NOT sharing our secrets with the whole group chat):
$ chmod 600 ~/.netrc
π SSL/TLS certificates
π’ Custom CA certificates
If your company runs a custom CA certificate:
$ export REQUESTS_CA_BUNDLE=/path/to/company-ca-bundle.crt
$ python -m build
Or use pip's certificate option:
$ export PIP_CERT=/path/to/company-ca-bundle.crt
$ python -m build
π Self-signed certificates
For development environments with self-signed certificates β and heads up, this is not recommended for production, bestie, this is a sandbox-only vibe:
$ export PIP_TRUSTED_HOST=pypi.company.com
$ python -m build
π§ Using virtualenv for better SSL support
If you keep catching SSL/TLS errors with Python's built-in venv, install virtualenv:
$ pip install build[virtualenv]
That hooks you up with a more recent version of pip that has better SSL support and truststore capabilities. Glow up π
π Note: Python 3.11.8+ and 3.12.2+ already have improved SSL support and may not need this workaround. Iykyk.
π Proxy servers
π HTTP/HTTPS proxies
Configure the proxy environment variables:
$ export HTTP_PROXY=http://proxy.company.com:8080
$ export HTTPS_PROXY=http://proxy.company.com:8080
$ python -m build
With authentication:
$ export HTTP_PROXY=http://user:pass@proxy.company.com:8080
$ export HTTPS_PROXY=http://user:pass@proxy.company.com:8080
π« No proxy for certain hosts
$ export NO_PROXY=localhost,127.0.0.1,.company.com
$ python -m build
βοΈ pip configuration files
π Global configuration
Instead of environment variables, you can create a pip configuration file.
On Linux/macOS (~/.config/pip/pip.conf):
[global]
index-url = https://pypi.company.com/simple
trusted-host = pypi.company.com
cert = /path/to/company-ca-bundle.crt
On Windows (%APPDATA%\\pip\\pip.ini):
[global]
index-url = https://pypi.company.com/simple
trusted-host = pypi.company.com
cert = C:\\path\\to\\company-ca-bundle.crt
π Per-project configuration
Create a pip.conf file in your project directory. This one's a whole serve for team settings β everybody gets the same config, no drama. π«Ά
βοΈ Air-gapped environments
For completely offline builds, you have to manually install dependencies first:
$ pip install setuptools wheel your-build-backend
$ python -m build --no-isolation
The --no-isolation flag tells build to use your current environment instead of creating an isolated one.
β οΈ Warning: when you're using --no-isolation, make sure all build dependencies are installed and compatible. No half-finished setups allowed. π
π€ CI/CD environments
π GitHub Actions
- name: Build package
run: python -m build
env:
PIP_INDEX_URL: ${{ secrets.PYPI_INDEX_URL }}
PIP_CERT: /path/to/cert.crt
π¦ GitLab CI
build:
script:
- export PIP_INDEX_URL=$PYPI_INDEX_URL
- python -m build
π¨ Catching backend warnings
To catch deprecation warnings from your build backend (absolute must in CI):
$ PYTHONWARNINGS=error::DeprecationWarning python -m build
Or for setuptools specifically:
$ PYTHONWARNINGS=error:::setuptools.config.setupcfg python -m build
See Troubleshooting <> for more details on warnings.
π Common issues
π Build fails with authentication errors
build passes --no-input to pip to prevent hidden credential prompts. If your private index requires authentication, pip will fail instead of hanging there looking stuck. Configure one of the authentication methods above (embedded credentials, keyring, or .netrc) to provide credentials non-interactively. Periodt.
π« 401/403 errors
Authentication failed, bestie. Verify your credentials and make sure they actually have access to the required packages.
π SSL verification failed
Your system doesn't trust the certificate. Use REQUESTS_CA_BUNDLE or PIP_CERT to provide the CA certificate, or install build[virtualenv] for better SSL support.
π Conda environment conflicts
If you're in a conda environment and you're getting venv creation issues:
$ conda deactivate
$ python -m build
Or use virtualenv:
$ pip install build[virtualenv]
$ python -m build
π See also
Troubleshooting <>for more problem-solving tipsEnvironment Variables <>for a complete list of environment variablespip documentation <https://pip.pypa.io/>for more pip configuration options
π οΈ Backend Configuration
The --config-setting (or -C) flag lets you pass options to your build backend. The syntax and the available options depend entirely on which backend you're using (e.g., setuptools <https://setuptools.pypa.io/>, hatchling, flit <https://flit.pypa.io/>). Choose your fighter. π₯
β οΈ Important: the--config-settingflag passes options to the build backend that actually builds your package, not tobuilditself. Different backends accept different options. Check your backend's documentation for available settings.
βοΈ Basic syntax
The basic syntax is:
$ python -m build -C KEY=VALUE
Or for options without values:
$ python -m build -C KEY=
π Note:buildalso accepts the bare-C KEYform (without=) and reads it as-C KEY=, but it warns you becausepiprejects it. Write-C KEY=to keep the command working in both tools.
Multiple settings can be provided:
$ python -m build -C KEY1=VALUE1 -C KEY2=VALUE2
π§± Setuptools
Setuptools requires a special --build-option wrapper for most settings.
π’ Build numbers
To add a build number to your wheel:
$ python -m build --wheel \
-C--build-option=bdist_wheel \
-C--build-option=--build-number \
-C--build-option=123
This creates a wheel named package-1.0.0-123-py3-none-any.whl.
π οΈ Compiler selection
To specify a compiler on Windows:
$ python -m build \
-C--build-option=build_ext \
-C--build-option=--compiler \
-C--build-option=mingw32
π Note: each argument must be a separate -C flag. Setuptools processes these as separate command-line arguments.
π¬ Getting help
To see available setuptools build options:
$ python -m build -C--build-option=--help
π§ scikit-build-core
scikit-build-core uses a more intuitive dotted syntax.
ποΈ CMake options
$ python -m build \
-Ccmake.define.BUILD_TESTING=OFF \
-Ccmake.define.CMAKE_BUILD_TYPE=Release
π Logging
$ python -m build -Clogging.level=INFO
π¦ Install target
$ python -m build -Cinstall.strip=false
Multiple options:
$ python -m build \
-Clogging.level=DEBUG \
-Ccmake.define.USE_CUDA=ON \
-Cinstall.components=runtime
π meson-python
Meson-python passes options directly to meson.
βοΈ Setup options
$ python -m build -Csetup-args=-Doption=value
π§ Compile options
$ python -m build \
-Csetup-args=-Dbuildtype=release \
-Csetup-args=-Db_ndebug=true
π£ hatchling
Hatchling supports various build options.
π Reproducible builds
$ python -m build -Creproducible=true
π Shared data
$ python -m build -Cshared-data=/custom/path
πͺΆ flit-core
Flit-core has minimal configuration options as it focuses on simplicity.
π poetry-core
Poetry-core currently does not support configuration settings via the command line.
π§© Using --config-json
For complex nested configuration, use --config-json instead:
$ python -m build --config-json='{"cmake": {"define": {"VAR1": "value1", "VAR2": "value2"}}}'
This is particularly useful in CI/CD scripts where configuration can be generated programmatically.
π Note:--config-settingand--config-jsoncannot be used together.
β οΈ Common Issues
β Options starting with hyphens
If your option starts with a hyphen, you must use the = syntax to prevent build from interpreting it as its own flag:
$ python -m build -C=--my-setting
Not -C --my-setting which would fail.
π€ Backend not recognizing options
If your backend doesn't recognize an option:
- Check your backend's documentation for the correct syntax
- Verify you're using the latest version of the backend
- Some backends only support certain options in newer versions
π Finding backend documentation
Setuptools <https://setuptools.pypa.io/>scikit-build-core <https://scikit-build-core.readthedocs.io/>meson-python <https://meson-python.readthedocs.io/>hatchling <https://hatch.pypa.io/latest/config/build/>flit-core <https://flit.pypa.io/>poetry-core <https://python-poetry.org/docs/>pdm-backend <https://pdm-backend.fming.dev/>
π See also
Basic Usage <>for general build commandsCommand-Line Interface <>for all command-line optionsBuild Backends <>to understand how backends work
π©Ή Troubleshooting
This guide helps you resolve common issues when using build.
β¨ Build fails with missing dependencies β¨
Symptom: your build dies with stuff like "ModuleNotFoundError" or "No module named 'X'". π
Cause: bestie, your build backend (or its lil dependency friends) just isn't installed in that isolated environment.
Solution 1 β let build handle it automatically (this is the default, so just vibe):
$ python -m build
build will read your pyproject.toml and install all the required dependencies for you. slay. π
Solution 2 β if you're using --no-isolation, you gotta install the deps yourself:
$ pip install setuptools your-build-backend
$ python -m build --no-isolation
π "Unmet dependencies" with --no-isolation π
Symptom: a --no-isolation build just stops and hits you with:
ERROR Unmet dependencies (checked against /usr/local/bin/python3.9):
anndata>=0.7.4
wanted: >=0.7.4
found: not installed
Cause: okay so this one's important β build checks the declared requirements against the interpreter named in the header (the one running build), not against a virtual environment or your system package manager. So read each entry like this:
found: not installedβ the package is legit invisible to that interpreter's metadata. If you're sitting there like "but I installed it tho" (say, via a Linux distribution or a FreeBSD port), it got installed for a different Python, sobuildliterally cannot see it. Fix: install it for the interpreter in the header, or run that interpreter'spython -m build.found: <version>β the package is installed, but its version doesn't satisfywanted. So upgrade or downgrade it to match. no cap.
Solution 1: install or fix the offending dependency for the interpreter shown in the header.
Solution 2: if you manage build dependencies externally (distribution packaging girlies, this is you), just skip the check entirely:
$ python -m build --no-isolation --skip-dependency-check
See Basic Usage <> for more on --skip-dependency-check. π«Ά
π Find the backend version for a bug report π
Symptom: a backend's issue tracker is asking for the exact version of the backend you used. But pyproject.toml only lists the requirement specifiers (setuptools >= 40.8.0), and on an isolated build the resolved version lives in the temporary environment that build deletes afterwards. rude. π
Solution: read it from build's output! After installing the build dependencies, build prints the resolved version of each one:
$ python -m build --wheel
...
* Getting build dependencies for wheel...
* Installed build dependency versions:
- setuptools==82.0.1
* Building wheel...
Copy that name==version line for the backend straight into your report. β¨ With --no-isolation build installs nothing, so read the version from the active interpreter instead (for example pip show setuptools).
π§ Build hangs or appears frozen π§
Symptom: the build command runs but produces no output for a long, long time. the silence is deafening fr.
Possible causes:
- Waiting for authentication: if you're using a private package index,
buildmay be waiting for credentials.
See Corporate Environments <> for authentication setup.
- Large download: the build backend dependencies are being downloaded.
Use -v or -vv for verbose output:
$ python -m build -vv
- Build backend is actually running: some backends (especially for C extensions) just take time, bestie.
Check your system monitor for CPU/disk activity to confirm work is actually happening.
π SSL certificate verification failed π
Symptom: errors like "SSL: CERTIFICATE_VERIFY_FAILED" or "certificate verify failed".
Cause: your system doesn't trust the SSL certificate of the package index. distrust era. π
Solution 1 β provide the CA certificate (recommended):
$ export PIP_CERT=/path/to/company-ca-bundle.crt
$ python -m build
Solution 2 β use build with virtualenv for better SSL support:
$ pip install build[virtualenv]
$ python -m build
Solution 3 β for development only, disable verification (not recommended):
$ export PIP_TRUSTED_HOST=pypi.company.com
$ python -m build
See Corporate Environments <> for more details.
π« Permission denied errors π«
Symptom: "PermissionError" or "Access is denied" when creating directories or files.
Common causes:
- Output directory is protected: the
dist/directory is owned by another user or process.
Solution: remove the directory first, or use a different output location:
$ rm -rf dist/
$ python -m build
Or:
$ python -m build --outdir /tmp/my-build
- Running inside a read-only directory: the source directory is on a read-only filesystem.
Solution: copy the source to a writable location first.
- Antivirus software: some antivirus tools block Python from creating virtual environments. π
Solution: add Python and your project directory to your antivirus exclusions.
π Wheel is not compatible with this platform π
Symptom: "is not a supported wheel on this platform" when you try to install the built wheel.
Cause: the wheel was built for a different Python version or platform. it's giving mismatched energy.
Solution: build on the same platform and Python version where you'll install it, or use cibuildwheel for multi-platform builds.
See build vs Other Tools <> for when to use cibuildwheel vs build.
π Build succeeds but package is missing files π
Symptom: after building, the package installs but is missing source files, data files, or modules. where did they go π
Cause: the files aren't included in the source distribution manifest.
Solution: the fix depends on your build backend, so let's go:
For setuptools (setuptools documentation <https://setuptools.pypa.io/>):
Add a MANIFEST.in file, or use package_data in pyproject.toml:
[tool.setuptools.package-data]
mypackage = ["data/*.json", "templates/*.html"]
For hatchling (hatchling documentation <https://hatch.pypa.io/latest/config/build/>):
Use the include and exclude options:
[tool.hatch.build.targets.wheel]
include = [
"src/**/*.py",
"src/**/*.json",
]
For flit (flit documentation <https://flit.pypa.io/>):
Flit includes all files tracked by git <https://git-scm.com/> by default. So make sure your files are committed, bestie.
Test your sdist: always verify the sdist contains all the necessary files:
$ python -m build --sdist
$ tar -tzf dist/mypackage-1.0.0.tar.gz
π΅ ModuleNotFoundError in the built wheel π΅
Symptom: the wheel installs successfully, but importing the package fails with "ModuleNotFoundError".
Cause: the package structure in your source doesn't match what the build backend expects.
Common issues:
- Missing
__init__.py: ensure every package directory has an__init__.pyfile (not needed for namespace packages). - Wrong directory structure: for src-layout, ensure you have
src/packagename/not justpackagename/. - Not configuring package discovery: tell your build backend where to find packages.
For setuptools with src-layout:
[tool.setuptools.packages.find]
where = ["src"]
βοΈ Version conflict between build dependencies βοΈ
Symptom: the build fails with errors about conflicting package versions. drama in the dependency tree, honestly.
Cause: build dependencies have incompatible version requirements.
Solution 1 β update your build backend and its dependencies:
$ pip install --upgrade pip setuptools
Then try building again:
$ python -m build
Solution 2 β if using --no-isolation, create a fresh virtual environment:
$ python -m venv fresh-env
$ source fresh-env/bin/activate # On Windows: fresh-env\\Scripts\\activate
$ pip install build
$ python -m build
The isolated build (without --no-isolation) avoids this issue by creating a clean environment each time. clean girl aesthetic π§
π Build works locally but fails in CI π
Common causes:
- Different Python versions: CI may use a different Python version than your local environment.
Solution: specify the Python version in your CI config, and test locally with that version.
- Missing system dependencies: C extensions may need system libraries.
Solution: install the required libraries in CI before building:
# GitHub Actions example
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libffi-dev
- File path issues: Windows vs Unix path separators. the eternal struggle.
Solution: use pathlib.Path in pyproject.toml configurations and build scripts.
- Git not available: some backends use git for version detection.
Solution: ensure git is installed in CI, or use explicit versioning in pyproject.toml.
See CI/CD Integration <> for CI-specific guidance.
π¨ Catching deprecation warnings π¨
To catch warnings from your build backend in CI:
$ PYTHONWARNINGS=error::DeprecationWarning python -m build
Or for setuptools specifically:
$ PYTHONWARNINGS=error:::setuptools.config.setupcfg python -m build
This fails the build if deprecated features are used, helping you catch issues early. iconic behavior honestly.
π» Build backend not found π»
Symptom: "Backend 'X' is not available" or "No module named 'X'".
Cause: the build backend specified in pyproject.toml isn't installed.
Solution: this shouldn't happen with isolated builds (the default). But if you do see it:
- Check your
pyproject.toml[build-system]section is correct:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
- If using
--no-isolation, install the backend:
$ pip install hatchling
$ python -m build --no-isolation
π No build-system section in pyproject.toml π
Symptom: the build succeeds but you're not sure which backend is being used. mystery backend era.
Cause: no [build-system] section is specified in pyproject.toml.
Solution: as recommended in PEP 517 <https://peps.python.org/pep-0517/>, if no backend is specified, build will fallback to setuptools.build_meta:__legacy__. This is for backward compatibility with older projects. For new projects, explicitly specify your build backend:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
This makes your build configuration explicit and avoids relying on the fallback behavior. periodt.
ποΈ Build installed in user site-packages ποΈ
Symptom: virtual environment creation fails with errors like "executable /usr/local/bin/python missing".
Cause: when build is installed in the user site-packages (pip install --user build), it can interfere with isolated environment creation.
Solution: install build in a virtual environment instead:
$ python -m venv build-env
$ source build-env/bin/activate # On Windows: build-env\\Scripts\\activate
$ pip install build
$ python -m build
Or use pipx to install build in isolation:
$ pipx install build
$ pipx run build
π Package name has dashes but wheel has underscores π
Symptom: your package is named my-package but the wheel is my_package-1.0.0-py3-none-any.whl.
This is expected behavior: According to PEP 427 (https://peps.python.org/pep-0427/), wheel filenames normalize package names by replacing dashes with underscores. This is not a bug. π
Your users can still install with either name:
$ pip install my-package # Works
$ pip install my_package # Also works
The package metadata preserves your original name, only the filename is normalized. π
πΈ Getting more information πΈ
π£οΈ Verbose output
Use -v or -vv for detailed output:
$ python -m build -v # Verbose
$ python -m build -vv # Very verbose
This shows what build is doing and can help identify where failures occur. Receipts, bestie. π§Ύ
π Check your configuration
Verify your pyproject.toml is valid:
$ python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb')))"
π§ͺ Test the build backend directly
You can test if your build backend works independently:
$ pip install your-build-backend
$ python -m your_build_backend
Vibe check the backend on its own so you know whether it's your project or your backend acting up. π
π¬ Inspect the build environment
build removes its isolated environment when the build ends. To keep it for inspection, point --env-dir at an empty location:
$ python -m build --env-dir .build-env
build removes that location after a successful build and keeps it after a failure, so a failed build leaves the environment in .build-env for you to examine. Setting TMPDIR (or TEMP on Windows) only moves the temporary directory; it does not keep the environment around. Important distinction, don't get it twisted.
build forwards the backend's output to your terminal as it happens, so compilation logs and error messages are already visible. Raise the verbosity to add build's own diagnostics and stream the environment setup live:
$ python -m build -vv
π΅οΈβοΈ Debug a failed build
When a backend fails, build prints a TIP line that links back here. Work through these steps to gather what you need.
πΊ Stream the backend output
build forwards everything the backend writes to standard output and error straight to your terminal, so compiler invocations and backend tracebacks already appear without any extra flag. Raise the verbosity to add build's own diagnostics and to stream the environment setup (installing the backend and its dependencies) live instead of only on failure:
$ python -m build -vv
No extra flag needed for the backend's own screaming β it's already reaching you. π
π¦ Keep the build environment and sources
A failed build deletes its temporary working directories before you can look at them. Pin both so they survive the run:
$ python -m build --env-dir .build-env --sdist-extract-dir .build-src
--env-dir keeps the isolated environment (the installed backend and its dependencies) on failure; see Inspect the build environment. --sdist-extract-dir keeps the extracted sdist, which is the directory the backend actually runs in, so any files the backend leaves next to your sources stay put. π«Ά
π Find the backend's own logs
build cannot capture log files the backend writes inside its own working directory, because PEP 517 gives the frontend no way to discover them. Each backend exposes its own setting for keeping them. For meson-python and scikit-build, point its build directory at a path you control:
$ python -m build -C build-dir=_build
The backend then usually writes its build tree and logs under _build. Check your backend's documentation for the equivalent setting. π₯
π΅π« Still having issues?
If your issue isn't covered here:
- Check the issue tracker (
https://github.com/pypa/build/issues) for similar problems - Enable verbose output (
-vv) and include the full output when reporting issues - Include your
pyproject.tomland Python version when asking for help
π See also
- Corporate Environments for proxy, SSL, and authentication issues
- CI/CD Integration for CI/CD-specific problems
- Environment Variables for environment variables that affect
build
π Command-Line Interface π
A simple, correct Python build frontend.
By default, a source distribution (sdist) (https://packaging.python.org/en/latest/specifications/source-distribution-format/) is built from the project root and a binary distribution (wheel) (https://packaging.python.org/en/latest/specifications/binary-distribution-format/) is built from the sdist. If this is undesirable, you can pass --sdist and/or --wheel to build distributions independently of each other.
The positional srcdir argument also accepts a .tar.gz source distribution. Build checks the filename and the presence of PKG-INFO, extracts the archive into a temporary directory, and runs the wheel build against the extracted source. The default --outdir is the directory containing the archive. --sdist errors against an archive, since the archive already is an sdist. π
π python -m build
A simple, correct Python build frontend.
By default, a source distribution (sdist) is built from the project root and a binary distribution (wheel) is built from the sdist. If this is undesirable, you can pass --sdist and/or --wheel to build distributions independently of each other.
python -m build [-h] [--version] [--quiet | --verbose] [--outdir PATH]
[--sdist-extract-dir PATH] [--sdist] [--wheel] [--metadata]
[--report PATH] [--config-setting KEY=[VALUE] | --config-json JSON_STRING]
[--installer {pip,uv} | --no-isolation] [--env-dir PATH]
[--dependency-constraints-txt PATH] [--skip-dependency-check]
[srcdir]
π python -m build positional arguments
srcdirβ source directory,.tar.gzsource distribution, or (with--metadata) a.whlto read metadata from (defaults to the current working directory)
βοΈ python -m build global options
-h,--helpβ show this help message and exit--version,-Vβ show program's version number and exit--quiet,-qβ reduce verbosity (default:0)--verbose,-vβ increase verbosity (default:0)
π¨ python -m build build options
--outdir PATH,-o PATHβ output directory (defaults to{srcdir}/dist). Cannot be used together with--metadata--sdist-extract-dir PATHβ extract the intermediate sdist toPATH(created if missing and kept afterwards) instead of a random temporary directory; reusing it across rebuilds gives compiler caches such asccache/sccachea stable source path. Only affects the default (via-sdist) build and building a wheel from an sdist--sdist,-sβ build a source distribution (disables the default behavior)--wheel,-wβ build a wheel (disables the default behavior)--metadataβ print out a wheel's metadata in JSON format, building it first unless the source argument is already a.whl. Cannot be used in conjunction with--sdistor--wheel--report PATHβ write a machine-readable JSON report of the built artifacts (name, path, kind, size and SHA-256 hash) to this path. Cannot be used together with--metadata--config-setting KEY=[VALUE],-C KEY=[VALUE]β settings to pass to the backend. Multiple settings can be provided. Settings beginning with a hyphen will erroneously be interpreted as options tobuildif separated by a space; use--config-setting=--my-setting-C--my-other-settinginstead. A setting passed without=VALUEis given an empty value, but this form is not supported bypip; writeKEY=instead for compatibility--config-json JSON_STRINGβ settings to pass to the backend as a JSON object. This is an alternative to--config-settingthat allows complex nested structures. Cannot be used together with--config-setting
π₯ python -m build installation options
--installer INSTALLERβ Python package installer to use (defaults topip)--no-isolation,-nβ disable building the project in an isolated virtual environment. Build dependencies must be installed separately when this option is used--env-dir PATHβ create the isolated build environment at this location instead of a temporary directory. The location must be empty; it is removed on success and kept on failure so it can be inspected--dependency-constraints-txt PATHβ constrain build dependencies using aconstraints.txtwhen installing dependencies--skip-dependency-check,-xβ do not check that build dependencies are installed
π«§ Isolation Behavior
By default build will build the package in an isolated environment, but this behavior can be disabled with --no-isolation. When using isolation, build creates a temporary virtual environment, installs the build dependencies specified in your pyproject.toml, runs the build, and then cleans up the environment. This ensures reproducible builds regardless of what packages are installed in your development environment. Chef's kiss reproducibility. π
Pass --env-dir PATH to put the environment at a fixed location instead of a temporary directory. The location must be empty. build removes it after a successful build and keeps it after a failure so you can inspect it. A fixed path helps compilation caches like ccache and sccache, which treat a changed build-environment path as a new file and miss the cache. You cannot combine --env-dir with --no-isolation. π«
When a build fails, --env-dir and --sdist-extract-dir keep the environment and the extracted sources for inspection; see Debug a failed build (#debug-a-failed-build).
π§· Dependency Check
With --no-isolation, build does not install anything; it checks that the build dependencies are already present in the interpreter running build. When a requirement is unmet it exits with an Unmet dependencies error that names the interpreter checked and, for each requirement, the version specifier that was wanted and the version that was found (not installed when absent):
ERROR Unmet dependencies (checked against /usr/local/bin/python3.9):
anndata>=0.7.4
wanted: >=0.7.4
found: not installed
matplotlib>=3.4 -> kiwisolver>=1.0.1
wanted: >=1.0.1
found: 1.0.0
Transitive requirements are shown as a parent -> child chain; the wanted/found lines describe the unmet leaf. Pass --skip-dependency-check to skip this check (see Basic Usage).
π Verbose Output
Repeating -v raises the verbosity level. Each level adds to the previous one:
-vstreams the output of the environment-creation and dependency-installation subprocesses.-vvadditionally passes-vthrough to the installer.
Regardless of verbosity, after installing the build dependencies of an isolated build, build prints a summary of the resolved versions, one name==version per line. build reads these from the isolated environment's installed metadata, so they reflect what was installed rather than the specifiers in pyproject.toml:
$ python -m build --wheel
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools >= 42.0.0
* Getting build dependencies for wheel...
* Installed build dependency versions:
- setuptools==82.0.1
* Building wheel...
Successfully built mypackage-1.0.0-py3-none-any.whl
okay bestie, important: build only tells you that stuff for isolated builds. if you went --no-isolation, it installs literally nothing, so the move is to go poke at whatever interpreter is actually active using your installer of choice instead β like, for example, pip list. π
π Build Report π
--report PATH is the girlboss that writes a JSON description of everything you built to PATH, and it only does it after a successful build (no receipts for a flop). the artifact names are dynamic β they literally encode version, Python, ABI and platform tags β so this gives your scripts and your CI a stable way to refer to the files that came out, instead of you sitting there globbing dist/ like it's 2010. π¦
also, she writes the report to a file she controls, never to standard output, because the build backend might be writing to stdout/stderr too and we are not doing that collision.
$ python -m build --report build-report.json
$ twine upload $(jq -r '.artifacts[].path' build-report.json)
Note: keep that report OUT of the output directory fr. tools very commonly uploaddist/*wholesale (hi,twine upload dist/*), and a stray non-distribution file chillin' in there can break the whole thing. the write is atomic, so a reader never catches a half-written file mid-vibe β but parallel builds still need their own distinctPATHeach, otherwise they'll overwrite each other's report. no cap.
the schema is:
{
"version": "1.0",
"artifacts": [
{
"name": "mypackage-1.0.0.tar.gz",
"path": "dist/mypackage-1.0.0.tar.gz",
"kind": "sdist",
"size": 852,
"hashes": {
"sha256": "dbd5486e6e893663263caf84a4b87d67cc28f6963b6650f69eaa54b78e42ece4"
}
},
{
"name": "mypackage-1.0.0-py3-none-any.whl",
"path": "dist/mypackage-1.0.0-py3-none-any.whl",
"kind": "wheel",
"size": 1121,
"hashes": {
"sha256": "ef460897fdce998634efc3385aa4261b4280a2fe2ab0e08ddcacc8496658465d"
}
}
]
}
so version is the schema version. each artifact lists:
- its
name(the basename π«) - its
path(relative to the current directory, mirroring--outdir) - its
kind(eithersdistorwheel) - its
sizein bytes - and
hasheskeyed by algorithm
and heads up: --report cannot be combined with --metadata. those two do not share a room. π
to inspect a wheel that's listed in the report, you just pass its path straight into --metadata β because when the source argument is a .whl file, build reads METADATA out of the archive and prints it as JSON instead of building anything at all. iconic behaviour honestly.
$ python -m build --report build-report.json
$ python -m build --metadata "$(jq -r '.artifacts[] | select(.kind=="wheel") | .path' build-report.json)"
β¨ Alternative CLI Script β¨
there's also a pyproject-build CLI script, and she is functionally identical to python -m build. same energy, different entrance. this is useful for tools like pipx (https://github.com/pipxproject/pipx) that prefer direct script entry points. π«Ά
$ pyproject-build
$ pyproject-build --help
both commands accept the same options and behave identically. periodt.
πΈ Common Patterns πΈ
for practical usage examples and workflows, see the Basic Usage page.
π See Also π
- Basic Usage page β for common workflows
- Backend Configuration page β for passing options to backends
- Environment Variables page β for environment variables that affect
build
π API Documentation π
build ships a Python API for programmatic use in build tools, CI systems, and automation scripts. she's not just a CLI girlie, she's a whole library. π
Note: when you're not using an isolated environment, the build backend has to already be installed in the current environment. and when you are usingDefaultIsolatedEnv, you must explicitly install the build dependencies into the isolated environment viainstall()before callingbuild()β because that environment is created empty. she's a blank canvas, bestie, she has nothing. π
π¦ Basic Usage π¦
building a package programmatically:
from build import ProjectBuilder
builder = ProjectBuilder("path/to/project")
builder.build("wheel", "dist/")
building both sdist and wheel:
from build import ProjectBuilder
builder = ProjectBuilder(".")
builder.build("sdist", "dist/")
builder.build("wheel", "dist/")
using isolated environments (the default):
DefaultIsolatedEnv creates a clean virtual environment β but it's initially empty. you must install the project's build dependencies before you invoke the backend:
from build import ProjectBuilder
from build.env import DefaultIsolatedEnv
with DefaultIsolatedEnv() as env:
builder = ProjectBuilder.from_isolated_env(env, ".")
# Install build-system.requires (e.g., flit-core, setuptools, ...)
env.install(builder.build_system_requires)
# Install additional backend dependencies for the target distribution
env.install(builder.get_requires_for_build("wheel"))
builder.build("wheel", "dist/")
pass path to create the environment at a fixed location instead of in a temporary directory. that location has to be empty β DefaultIsolatedEnv removes it on a clean exit, and keeps it when the block raises. (yes, so you can go look at the crime scene. π)
with DefaultIsolatedEnv(path=".build-env") as env:
builder = ProjectBuilder.from_isolated_env(env, ".")
env.install(builder.build_system_requires)
builder.build("wheel", "dist/")
disabling isolation:
from build import ProjectBuilder
builder = ProjectBuilder(".", runner=lambda cmd, **kwargs: None)
builder.build("wheel", "dist/")
getting package metadata without building:
from build import ProjectBuilder
import tempfile
builder = ProjectBuilder(".")
with tempfile.TemporaryDirectory() as tmpdir:
metadata_dir = builder.metadata_path(tmpdir)
# Read METADATA file from metadata_dir to extract package info
accessing build dependencies:
from build import ProjectBuilder
builder = ProjectBuilder(".")
requires = builder.build_system_requires
print(f"Build requires: {requires}")
# Get additional dependencies for building a wheel
wheel_requires = builder.get_requires_for_build("wheel")
print(f"Wheel build requires: {wheel_requires}")
handling errors:
from build import ProjectBuilder
from build import BuildException
try:
builder = ProjectBuilder(".")
builder.build("wheel", "dist/")
except BuildException as e:
print(f"Build failed: {e}")
π Reference π
β build module β
build β A simple, correct Python build frontend
exception build.BuildBackendException(exception, description=None, exc_info=(None, None, None))β bases:Exception. raised when a backend operation fails. πexception build.BuildExceptionβ bases:Exception. raised bybuildwhen a build cannot proceed. she's the general "nope, we're not doing this" energy.exception build.BuildSystemTableValidationErrorβ bases:BuildException. raised when the[build-system]table inpyproject.tomlis invalid. πexception build.FailedProcessError(exception, description)β bases:Exception. raised when a setup or preparation operation fails.class build.ProjectBuilder(source_dir, python_executable='/usr/bin/python3.14', runner=<function default_subprocess_runner>)β bases:object. this is the PEP 517 consumer API, the main character of the whole reference section. π
Parameters:
source_dir(str | PathLike[str]) β the source directorypython_executable(str) β the python executable where the backend livesrunner(Callable[[Sequence[str], str | None, Mapping[str, str] | None], None]) β runner for backend subprocesses
the runner, if you provide one, has to accept these arguments:
cmd: a list of strings representing the command and arguments to execute, as would be passed to e.g.'subprocess.check_call'.cwd: a string representing the working directory that must be used for the subprocess. corresponds to the providedsource_dir.extra_environ: a dict mapping environment variable names to values which must be set for the subprocess execution.
the default runner simply calls the backend hooks in a subprocess, writing backend output to stdout/stderr.
build(distribution, output_directory, config_settings=None, metadata_directory=None)β build a distribution.
Parameters:
distribution(Literal['sdist', 'wheel', 'editable']) β distribution to build (sdist or wheel)output_directory(str | PathLike[str]) β directory to put the built distribution inconfig_settings(Mapping[str, str | Sequence[str]] | None) β config settings for the build backendmetadata_directory(str | None) β if provided, should be the return value of a previouspreparecall on the same distribution kind
Return type: str
Returns: the full path to the built distribution β¨
property build_system_requires: set[str]β the dependencies defined inpyproject.toml'sbuild-system.requiresfield, or the default build dependencies ifpyproject.tomlis missing orbuild-systemis undefined.check_dependencies(distribution, config_settings=None)β return the dependencies which are not satisfied from the combined set ofbuild_system_requiresandget_requires_for_build()for a given distribution.
Parameters:
distribution(Literal['sdist', 'wheel', 'editable']) β distribution to check (sdist or wheel)config_settings(Mapping[str, str | Sequence[str]] | None) β config settings for the build backend
Return type: set[tuple[str, ...]]
Returns: set of variable-length unmet dependency tuples π
classmethod from_isolated_env(env, source_dir, runner=<function default_subprocess_runner>)
Return type: Self
get_requires_for_build(distribution, config_settings=None)β bestie this one hands you back the dependencies the backend declared, on top ofbuild_system_requires, for whatever distribution you point at β¨
Params, listen up:
- distribution (
Literal['sdist', 'wheel', 'editable']) β which distribution you want the deps of (sdistorwheel) - config_settings (
Mapping[str, str | Sequence[str]] | None) β config settings for the build backend
Return type: set[str]
metadata_path(output_directory)β makes the metadata directory for a distribution and tells you where it lives π
If the backend doesn't support the prepare_metadata_for_build_wheel hook, a wheel gets built and the metadata is yoinked out of it. Yes, really.
Params:
- output_directory (
str | PathLike[str]) β the directory where the metadata distribution goes
Return type: str
Returns: the path of the metadata directory
prepare(distribution, output_directory, config_settings=None)β preps metadata for a distribution, no cap π
Params:
- distribution (
Literal['sdist', 'wheel', 'editable']) β the distribution to build (must bewheel) - output_directory (
str | PathLike[str]) β directory to put the prepared metadata in - config_settings (
Mapping[str, str | Sequence[str]] | None) β config settings for the build backend
Return type: str | None
Returns: the full path to the prepared metadata directory
property python_executable: strβ the Python executable that was used to invoke the backend πproperty source_dir: strβ the project source directory, literally right there.
π exception build.TypoWarning
Bases: Warning
Raised when a possible typo is found. Your fave lil spell-check but for build stuff π
π«Ά build.check_dependency(req_string, ancestral_req_strings=(), parent_extras=frozenset({}))
Verify that a dependency and all of its dependencies are met. The whole tree, bestie, not just the top one.
Params:
- req_string (
str) β requirement string - parent_extras (
Set[str]) β extras (eg."test"inmyproject[test])
Yields unmet dependencies π
πΈ build.env module
β¨ class build.env.DefaultIsolatedEnv(*, installer='pip', path=None)
Bases: IsolatedEnv
An isolated environment that supports several different underlying implementations. It's giving versatile π
install(requirements, constraints=(), *, _fresh=False)
Install packages from PEP 508 requirements in the isolated build environment.
Params:
- requirements (
Collection[str]) β PEP 508 requirement specification to install
Note: passing non-PEP 508 strings will result in undefined behavior, you should not rely on it. It is merely an implementation detail, it may change any time without warning. So likeβ¦ don't π
Return type: None
installed_versions(requirements)
Maps the canonical name of each requirement to the version installed in the environment.
Requirements that resolved to nothing installed are omitted. It reads the distribution metadata directly from the environment's purelib so no subprocess is spawned in the isolated environment. Efficient queen π
Params:
- requirements (
Collection[str]) β PEP 508 requirement specifications to look up; non-PEP 508 entries (such as local paths or URLs accepted byinstall()) are skipped since their installed name cannot be derived
Return type: dict[str, str]
make_extra_environ()
Generate additional env vars specific to the isolated environment.
Return type: dict[str, str]
property path: str
The location of the isolated build environment π
property python_executable: str
The python executable of the isolated build environment.
π¦ class build.env.IsolatedEnv(*args, **kwargs)
Bases: Protocol
Isolated build environment ABC.
abstractmethod make_extra_environ()
Generate additional env vars specific to the isolated environment.
Return type: Mapping[str, str] | None
abstract property python_executable: str
The Python executable of the isolated environment.
π build.util module
build.util.project_wheel_metadata(source_dir, isolated=True, *, runner=<function quiet_subprocess_runner>)
Return the wheel metadata for a project.
Uses the prepare_metadata_for_build_wheel hook if available, otherwise build_wheel.
Deprecated since version 1.5.0: generate metadata with the python -m build --metadata command instead. Like, use the new thing, bestie π₯²
Params:
- source_dir (
str | PathLike[str]) β project source directory - isolated (
bool) β whether or not to run invoke the backend in the current environment or to create an isolated one and invoke it there - runner (
Callable[[Sequence[str], str | None, Mapping[str, str] | None], None]) β an alternative runner for backend subprocesses
Return type: PackageMetadata
π Environment Variables
This reference documents environment variables that affect build's behavior. You can use these to configure private package indexes, proxies, SSL certificates, and other settings. So much power in your lil hands π
β Variables Set by build
VIRTUAL_ENV
build sets this variable when creating [isolated environments](https://packaging.python.org/en/latest/glossary/#term-Isolated-Build). It contains the absolute path to the temporary [virtual environment](https://docs.python.org/3/tutorial/venv.html) that build creates for installing dependencies. Build backends can use this to detect they're running in an isolated environment. Sneaky but useful π§
π Package Index Configuration
By default, build uses [pip](https://pip.pypa.io/) to install build dependencies in isolated environments. Configure pip using these environment variables to work with private package indexes or mirrors. For detailed examples, see Corporate Environments.
Note: ThesePIP_*variables only apply when using the defaultpipinstaller. When using--installer=uv, configure uv through its own [environment variables](https://docs.astral.sh/uv/configuration/environment/) instead (e.g.,UV_INDEX_URL,UV_EXTRA_INDEX_URL). Different installer, different rules, iykyk.
PIP_INDEX_URL
Sets the primary [package index](https://packaging.python.org/en/latest/glossary/#term-Package-Index) URL where pip looks for packages. Use this to point to private package repositories or PyPI mirrors. Defaults to https://pypi.org/simple.
$ export PIP_INDEX_URL=https://pypi.company.com/simple
$ python -m build
PIP_EXTRA_INDEX_URL
Adds additional package indexes to search beyond the primary index. This allows using both [PyPI](https://pypi.org/) and a private index simultaneously.
$ export PIP_EXTRA_INDEX_URL=https://pypi.company.com/simple
$ python -m build
Warning: a malicious package on any extra index can shadow packages from other indexes. Use PIP_INDEX_URL exclusively when possible for better security. Stay safe out there π«Ά
PIP_CERT
Path to a custom CA certificate bundle for SSL/TLS verification. Use this when your organization uses self-signed certificates or a custom certificate authority. Defaults to system certificates.
$ export PIP_CERT=/path/to/company-ca-bundle.crt
$ python -m build
PIP_TRUSTED_HOST
Disables SSL verification for the specified host. Only use this in secure development environments where SSL certificates aren't available.
$ export PIP_TRUSTED_HOST=pypi.company.com
$ python -m build
Warning: disabling SSL verification is insecure. Prefer using PIP_CERT with proper certificates. No cap, don't do the risky thing π
π Network Configuration
PIP_TIMEOUT
Sets the timeout in seconds for network operations. Increase this on slow or unreliable network connections. Defaults to 15 seconds.
$ export PIP_TIMEOUT=60
$ python -m build
PIP_RETRIES
Number of times pip retries failed downloads. Increase this on unstable network connections. Defaults to 5 retries.
$ export PIP_RETRIES=10
$ python -m build
PIP_NO_CACHE_DIR
Disables pip's package cache directory, forcing fresh downloads every time. Useful in CI environments to ensure completely clean builds, though this makes builds slower.
$ export PIP_NO_CACHE_DIR=1
$ python -m build
π Proxy Configuration
If you're behind a corporate proxy, configure these variables to route pip's network requests through your proxy server. Both uppercase and lowercase versions are recognized π
HTTP_PROXY / http_proxy
Proxy server for HTTP requests. Specify the proxy URL including protocol and port.
$ export HTTP_PROXY=http://proxy.company.com:8080
$ python -m build
HTTPS_PROXY / https_proxy
Proxy server for HTTPS requests. Even though the proxy URL uses http://, this handles HTTPS traffic.
$ export HTTPS_PROXY=http://proxy.company.com:8080
$ python -m build
NO_PROXY / no_proxy
Comma-separated list of hosts that should bypass the proxy. Use this for internal servers that don't require proxy access.
$ export NO_PROXY=localhost,127.0.0.1,.company.com
$ python -m build
π SSL/TLS Certificate Configuration
These variables configure SSL/TLS certificates for secure connections. Use them when your organization uses self-signed certificates or custom certificate authorities.
REQUESTS_CA_BUNDLE
Path to custom CA certificate bundle. The [requests](https://requests.readthedocs.io/) library (which [pip](https://pip.pypa.io/) depends on) uses this variable. Alternative to PIP_CERT.
$ export SSL_CERT_FILE=/path/to/company-ca-bundle.crt
$ python -m build
πΈ SSL_CERT_FILE πΈ
Yet another way to point at a custom CA certificate bundle, bestie. Same energy as the one above, just a different name β and yeah, various Python libraries actually recognize this one. π
$ export SSL_CERT_FILE=/path/to/company-ca-bundle.crt
$ python -m build
π Python Runtime Configuration π
okay so these ones aren't about build itself β they're you talking directly to Python, and build just vibes along. π¦
β οΈ PYTHONWARNINGS β οΈ
This is the knob that controls Python's warning filters β the docs live at https://docs.python.org/3/library/warnings.html. Use it in CI to catch deprecated features or potential issues in your build configuration by turning warnings into errors. turning warnings into errors in CI is so iconic, it catches stuff before it bites you. β¨
Catch deprecation warnings:
$ PYTHONWARNINGS=error::DeprecationWarning python -m build
Catch setuptools-specific warnings:
$ PYTHONWARNINGS=error:::setuptools.config.setupcfg python -m build
π PYTHONUTF8 π
This one Enables UTF-8 mode on Windows (https://docs.python.org/3/library/os.html#utf-8-mode). Use this when building packages with non-ASCII characters in filenames or file content on Windows systems. literally just "let me have my Unicode, please." π«Ά
$ set PYTHONUTF8=1
$ python -m build
π TMPDIR / TEMP / TMP π
This trio Controls where build creates the temporary isolated build environment. Useful for debugging, or when you need the temporary directory on a specific filesystem (e.g., for ccache or compilation caching). π
On Unix-like systems, use TMPDIR:
$ TMPDIR=/path/to/custom/tmp python -m build
On Windows, use TEMP:
$ set TEMP=C:\path\to\custom\tmp
$ python -m build
heads up bestie β These variables only change where build creates the temporary directory. build still removes its environment when the build ends. To pin the environment to a stable path, or to keep it for inspection, use the --env-dir option instead. π
Common use cases:
- Disk space: Use a different filesystem with more available space
- Performance: Use faster storage (e.g., tmpfs on Linux)
- Restricted filesystems: Point at the writable part of a read-only container
IMPORTANT ONE, no cap: TMPDIR only relocates the base directory; build still appends a random suffix, so the extraction path differs on every run. For compiler caches such as ccache or sccache, which key on the source path, that random suffix is a guaranteed cache miss β use --sdist-extract-dir to pin the path instead. π
See Troubleshooting <> for debugging examples.
π§© Backend-Specific Variables π§©
Every build backend is a whole main character and may define its own environment variables for configuration. Check your backend's documentation, babes:
- Setuptools β
https://setuptools.pypa.io/en/latest/userguide/index.html - Hatchling β
https://hatch.pypa.io/latest/config/build/ - PDM-Backend β
https://pdm-backend.fming.dev/ - Flit β
https://flit.pypa.io/en/stable/ - Poetry-Core β
https://python-poetry.org/docs/pyproject/ - scikit-build-core β
https://scikit-build-core.readthedocs.io/en/latest/configuration.html - meson-python β
https://meson-python.readthedocs.io/en/latest/reference/config-settings.html
π« Non-Isolated Builds π«
When using --no-isolation, build skips creating an isolated environment (https://packaging.python.org/en/latest/glossary/#term-Isolated-Build). Which means every single environment variable from your current shell passes directly to the build backend, including:
PYTHONPATHβ Affects Python module discoveryPATHβ Determines which executables are found- Any custom variables your build backend uses
fr fr, listen: build does not setVIRTUAL_ENVin this mode. You're responsible for installing build dependencies yourself. See Basic Usage<>for appropriate use cases. π
π Alternative: Configuration Files π
Instead of environment variables, you can configure pip (https://pip.pypa.io/) using persistent configuration files. This is convenient if you always use the same settings β set it once and forget it, periodt. β¨
Linux/macOS: ~/.config/pip/pip.conf
Windows: %APPDATA%\pip\pip.ini
Example configuration:
[global]
index-url = https://pypi.company.com/simple
trusted-host = pypi.company.com
cert = /path/to/company-ca-bundle.crt
For more details and examples, see Corporate Environments <> and the pip configuration documentation (https://pip.pypa.io/en/stable/topics/configuration/). π«Ά
π See also π
- Corporate Environments
<>for proxy and SSL configuration - Troubleshooting
<>for common issues - Command-Line Interface
<>for command-line options - pip documentation β
https://pip.pypa.io/en/stable/topics/configuration/for pip configuration
π How It Works π
This document explains how build works internally and the build process flow. buckle up bestie, we're going behind the scenes. π
β¨ The Build Process β¨
When you run python -m build, the following happens: [graph]
The process in detail:
- Read
pyproject.toml
build reads your project's pyproject.toml (https://packaging.python.org/en/latest/specifications/pyproject-toml/) to determine:
- Which build backend to use (from
[build-system]section) - What dependencies the backend needs (
requireslist) - Build backend configuration (
backend-pathif specified)
- Create isolated environment (default behavior)
build creates a temporary virtual environment (https://docs.python.org/3/tutorial/venv.html) to ensure reproducible builds:
- Creates a fresh virtualenv in a temporary directory
- Sets
VIRTUAL_ENVenvironment variable (https://docs.python.org/3/library/venv.html#how-venvs-work) - Installs the build backend and its dependencies via pip (
https://pip.pypa.io/)
This isolation ensures your build doesn't depend on what's installed in your development environment, making builds reproducible across different machines. lowkey the whole point, honestly.
- Invoke build backend hooks
build calls standardized hooks (functions) in your build backend:
- For source distributions: calls
build_sdist(sdist_directory, config_settings=None) - For wheels: calls
build_wheel(wheel_directory, config_settings=None, metadata_directory=None)
These hooks are defined in PEP 517 (https://peps.python.org/pep-0517/) (the build system interface standard).
build runs each hook in a subprocess and forwards its standard output and error straight through, so a failing backend's messages reach your terminal regardless of verbosity (-vv adds build's own diagnostics on top). build cannot collect log files the backend writes inside its own working directory: PEP 517 (https://peps.python.org/pep-0517/) exposes no way for a frontend to learn where those live, so retrieving them depends on backend-specific settings. See Debug a failed build for how to keep them.
- Build artifacts
The backend creates distribution files (https://packaging.python.org/en/latest/glossary/#term-Distribution-Package):
- Source distribution (sdist): A tarball (
https://packaging.python.org/en/latest/specifications/source-distribution-format/) (package-version.tar.gz) containing your source code - Wheel: A zip file (
https://packaging.python.org/en/latest/specifications/binary-distribution-format/) (package-version-py3-none-any.whl) that can be installed directly
- Cleanup
build removes the temporary isolated environment, leaving only the distribution files in dist/. clean girl behavior. π§
π Default Build Strategy π
By default, python -m build creates both sdist and wheel following this strategy:
- Build sdist from source directory
- Extract sdist to temporary directory
- Build wheel from the extracted sdist
This "build wheel from sdist" strategy ensures your sdist is complete. If files are missing from the sdist, the wheel build will fail, alerting you to the problem. it's giving "quality control", and honestly? we love her for it.
Step 2 defaults to a random temporary directory that build removes after the run. Build randomises the path on purpose: it surfaces backends that hard-code absolute paths and keeps successive builds independent. That same randomness defeats compiler caches keyed on the source path, so --sdist-extract-dir lets you opt into a stable, persistent location when cache hits matter more than isolation. π₯
If the positional argument is a .tar.gz rather than a directory, the default is wheel-only: build checks the archive, extracts it into a temporary directory, and runs the wheel build against the extracted source. There is no sdist step. The archive already is an sdist, and PEP 517 has no hook for turning one sdist into another, so --sdist against an sdist errors out. π
π¦ Build Isolation Explained π¦
πΈ Why isolation matters πΈ
Without isolation, builds can be non-reproducible. Different developers have different packages installed, and CI environments may have different packages than local development. Upgrading a package in your dev environment could break builds, and you might accidentally depend on a package that's not declared. Isolation solves this by creating a clean environment for every build. a fresh start every single time, we stan. β¨
πΈ How isolation works πΈ
- build creates a temporary virtual environment using Python's
venvmodule - Installs only the packages listed in
[build-system] requires - Runs the build backend in this isolated environment
- Deletes the environment after building
The isolated environment has access to the Python standard library, the build backend and its dependencies, and nothing from your development environment except environment variables. π
build picks a temporary directory for this environment by default and deletes it after the build. Pass --env-dir to choose the location yourself. You might do this to keep a failed environment for inspection, to place the environment on a writable path inside a read-only container, or to give a fixed path to a compilation cache such as ccache or sccache. Those caches treat a changed build-environment path as a new file, so a path that changes every run never hits the cache. and that's a guaranteed cache miss, bestie β no cap.
πΈ When to disable isolation πΈ
Use --no-isolation only when:
- You're in an offline/air-gapped environment and have pre-installed dependencies
- You're debugging build issues and need to inspect the environment
- You're a Linux distribution packager providing dependencies externally
- You understand the reproducibility implications
π that last bullet is doing a lot of heavy lifting but okay, we said it, it's a vibe check on your own understanding, periodt.
okay so under --no-isolation? bestie, build installs NOTHING. It doesn't touch your environment at all β instead it validates that the dependencies you declared are already sitting there (and if you'd rather it hush, --skip-dependency-check is the opt-out). Which is so much better than silently handing you a broken build and letting you find out at 3am π
The check is honestly so simple: it compares every declared requirement against the installed metadata of the single interpreter that's running build, discovered through importlib.metadata. And that explains two outcomes that look like bugs but are absolutely not bugs β the error message spells both out per requirement (wanted versus found):
- A package can be present and still get reported missing, because its installed version doesn't satisfy the specifier. (
foundshows you that version. π) - A package you know is installed can read as not installed when it was installed for a different Python. A system or distribution package manager often targets an interpreter other than the one invoking build, and metadata is not shared across interpreters. Periodt.
Good news: the error names the interpreter it checked, so the mismatch is right there in your face instead of being left to guesswork π«Ά
See Basic Usage <> for usage.
π§© Build Frontends vs Build Backends π§©
build is a build frontend. It reads pyproject.toml, creates isolated environments, invokes build backends, and handles the command-line interface. That's the whole job description. It does NOT know how to build your specific package. Not its business, not its vibe π
Your build backend (setuptools, hatchling, flit, etc.) is the one who knows how, by implementing PEP 517 hooks. The backend handles package discovery, file inclusion, metadata generation, and creates the actual sdist and wheel files.
This separation of concerns is genuinely iconic because it:
- allows different backends for different project needs
- ensures frontend improvements benefit all backends
- allows backend improvements to work with all frontends
See Build Backends <> for more details.
π PEP 517 Integration π
build implements PEP 517 <https://peps.python.org/pep-0517/> β that's the standardized build system interface that defines how build tools communicate with backends. The key concepts, let's go:
π Build system table
Your pyproject.toml <https://packaging.python.org/en/latest/specifications/pyproject-toml/> must have a [build-system] section:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
requiresβ the packages needed to build (they get installed in the isolated environment)build-backendβ the Python import path to the backend object
πͺ Backend hooks
build calls these standardized hooks in your backend:
get_requires_for_build_sdist(config_settings)β returns additional dependencies needed for sdistbuild_sdist(sdist_directory, config_settings)β creates sdist, returns filenameget_requires_for_build_wheel(config_settings)β returns additional dependencies needed for wheelbuild_wheel(wheel_directory, config_settings, metadata_directory)β creates wheel, returns filename
Optional hooks:
prepare_metadata_for_build_wheel(metadata_directory, config_settings)β generates wheel metadata without a full build
βοΈ Config settings
The config_settings parameter in hooks receives settings from the --config-setting / -C flags.
build converts:
$ python -m build -C key=value
Into:
config_settings = {"key": "value"}
The backend interprets these settings. See Backend Configuration <> for examples.
π Metadata Extraction π
build can pull package metadata without doing a full build:
$ python -m build --metadata
This:
- Creates isolated environment
- Calls
prepare_metadata_for_build_wheel()if available - Otherwise, calls
build_wheel()and extracts metadata - Outputs metadata in JSON format to stdout
Super useful for tools that need package metadata without building the whole full package.
π’ Diagnostics and Verbosity π’
pyproject.toml declares build dependencies as specifiers (setuptools >= 40.8.0), not exact versions. The installer resolves each specifier to a concrete version at build time, and on an isolated build that version lives only in the temporary environment β which build deletes the moment it's finished π The installer's own output does report the versions, but only at -vv, and interleaved with the rest of its log. Chaos. Truly.
So once build has installed the dependencies, it prints a concise summary of the resolved version of each, as name==version β literally the single piece a backend bug report usually asks for. Ate that.
How does it get them? build reads the installed distribution metadata from the environment's site-packages through importlib.metadata, rather than spawning a process inside the environment. Which means the summary is cheap and works exactly the same for the venv/virtualenv and uv backends. And because it reads installed metadata, it reflects what build actually used β including any transitive resolution the specifier allowed.
Careful though, this is an isolated-build concern. Under --no-isolation build installs nothing β it builds against the interpreter that's already running it β so the versions are whatever that interpreter has, discoverable with your installer.
π¨ Error Handling π¨
build does minimal error handling. On purpose. It's a whole vibe:
- Validates
pyproject.tomlexists and is valid TOML - Checks the
[build-system]section exists - Verifies the backend is importable
- Checks hook return values are valid
Most error handling is delegated to the backend. If the backend fails, build reports the error and exits.
This keeps build simple and focused on its role as a frontend π
π¦ Installer Selection π¦
By default, build uses pip to install dependencies in isolated environments. You can absolutely use a different installer:
$ python -m build --installer=uv
Requirements for custom installers:
- Must accept
installcommand - Must accept package specifications as arguments
- Must support
--no-depsflag
Currently, pip and uv are the primary supported installers.
π§ Virtual Environment Backend π§
build can use different virtual environment implementations:
venv(default) β Python's built-invenvmodulevirtualenvβ third-partyvirtualenvpackage
To use virtualenv:
$ pip install build[virtualenv]
$ python -m build
virtualenv provides better SSL support on older Python versions, faster environment creation, and more features (though build uses only basic functionality β it's not greedy like that).
See Corporate Environments <> for when this matters.
ποΈ Output Directory Structure ποΈ
By default, build places files in dist/ within your source directory:
myproject/
βββ dist/
β βββ myproject-1.0.0.tar.gz # sdist
β βββ myproject-1.0.0-py3-none-any.whl # wheel
βββ src/
β βββ myproject/
β βββ __init__.py
βββ pyproject.toml
Use --outdir to change the output location:
$ python -m build --outdir /tmp/build-output
π Environment Variable Handling π
build passes most environment variables to the build backend.
Always passed:
PATHβ for finding executablesPYTHONPATHβ for Python module discovery- HTTP/HTTPS proxy variables
- SSL certificate variables
- Custom pip variables (
PIP_*)
Set by build:
VIRTUAL_ENVβ points to the isolated environment (when using isolation)
When using --no-isolation:
- All environment variables from your shell are passed through unchanged
This behavior allows corporate environments to configure pip via environment variables, build backends to use custom environment variables, and proxy and SSL configuration to work transparently β¨
See Environment Variables <> for details.
π Reproducible Builds π
build aims for reproducible builds by:
- Isolated environments β same dependencies every time
- Declared dependencies β
[build-system]requiresis explicit - Standardized interface β PEP 517 hooks are well-defined
- Minimal intervention β build doesn't modify your code or backend behavior
However β and this is the important part, bestie β full reproducibility also requires pinned backend versions in requires, reproducible builds in your backend (some backends support SOURCE_DATE_EPOCH), a consistent Python version, and a consistent platform for platform-specific wheels. build can't do all of that for you π«
π Build Reports π
The names of the distribution files build produces are not known ahead of time. They encode the version and, for wheels, the Python, ABI and platform tags. Downstream steps such as uploading, signing, or recording checksums need a way to learn what was built.
--report writes this information to a JSON file. It uses a file rather than stdout because the build backend runs as a subprocess that inherits build's streams and may print to them β so a consumer could not separate the report from backend chatter on stdout. A file build controls keeps the machine-readable output apart from the human- and backend-facing log. So clean. So thoughtful.
Build writes the report only after the build succeeds, and replaces it atomically, so a consumer never reads a half-written file π₯
See Command-Line Interface <> for the schema.
β‘ Performance Considerations β‘
Build time is dominated by:
- Environment creation β creating virtualenv (1-2 seconds)
- Dependency installation β installing backend and dependencies (varies)
- Backend build β actual package building (varies greatly)
Ways to speed up builds include:
- using
--installer=uvfor faster dependency installation - using
--no-isolationif dependencies are pre-installed (this loses reproducibility, so know what you're doing π) - installing
build[virtualenv]for faster environment creation - using
--wheelto skip sdist when it's not needed
The default (isolation with pip) prioritizes reproducibility over speed. That's the trade, and it's deliberate.
π See also π
- Build Backends <> for choosing and configuring backends
- PEP 517 <https://peps.python.org/pep-0517/> for the build system specification
- PEP 518 <https://peps.python.org/pep-0518/> for the
pyproject.tomlspecification
ποΈ Build Backends ποΈ
This document explains what build backends are, how they work with build, and how to choose the right one for your project.
π What is a Build Backend?
A build backend is a Python package that knows how to build your project into distributable formats <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> (source distributions <https://packaging.python.org/en/latest/specifications/source-distribution-format/> and wheels <https://packaging.python.org/en/latest/specifications/binary-distribution-format/>). The backend implements a standardized interface defined in PEP 517 <https://peps.python.org/pep-0517/>, which build (the frontend) uses to create your package distributions. Teamwork, honestly π«Ά
When you run python -m build, the frontend (that's build, hi, that's me) reads your pyproject.toml β see https://packaging.python.org/en/latest/specifications/pyproject-toml/ β to figure out which backend to use, then it goes and pokes that backend into actually doing the building. Bestie, this frontend/backend split is the whole moment: it lets different projects use totally different build systems (think setuptools https://setuptools.pypa.io/, hatchling https://hatch.pypa.io/latest/, or flit https://flit.pypa.io/) while literally all of them work with the same frontend tool. Obsessed. π
How Backends Work with build β¨
The relationship between build and backends follows a clear contract:
[graph]
Your pyproject.toml says which backend to use in the [build-system] section. Like, using hatchling as your backend looks like this:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
The requires field lists the packages needed to perform a build β and guess what, build installs those in an isolated environment, no mess, no cross-contamination. π₯ The build-backend field is the Python import path to the backend object that implements the PEP 517 interface. That's the whole vibe.
When building, build creates an isolated environment, installs the required packages, imports the backend module, and calls standardized hook functions like build_wheel() or build_sdist(). Then the backend does its thing β creating the distribution files β and hands back their filenames. Iconic teamwork fr fr. π€
Available Build Backends π
Several build backends are out here, each with different features and philosophies. Pick your fighter.
setuptools π
setuptools is THE traditional, most widely used build backend. It's got extensive features and backwards compatibility with older packaging standards, which makes it perfect for complex projects and anything that needs to keep playing nice with older tooling.
To use setuptools, configure your pyproject.toml like this:
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
setuptools ate at handling complex build requirements β C extensions, namespace packages, entry points, all of it. But it can be complex to configure correctly and it's carrying a lot of legacy features that may leave you confused. Lowkey a lot, ngl.
See the setuptools documentation at https://setuptools.pypa.io/ for details.
hatchling π¦
hatchling is the build backend from the Hatch project. It gives you a modern, user-friendly interface with sensible defaults and good performance. It's giving main character energy honestly.
To use hatchling:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
hatchling works well for pure Python packages and has excellent plugin support for extending functionality. It's all about convention over configuration, and it has built-in support for common patterns like src-layout and editable installs. The main limitation? It's newer, so it may have less community documentation than setuptools. Trade-offs, bestie.
See the hatchling documentation at https://hatch.pypa.io/latest/config/build/ for details.
flit-core πΈ
flit-core is a minimalist build backend that focuses on simplicity for pure Python packages. Simple. Clean. No drama.
To use flit-core:
[build-system]
requires = ["flit-core>=3.2"]
build-backend = "flit_core.buildapi"
flit-core works best for simple, pure Python packages without complex build requirements. It has minimal configuration and fast build times β and by default, it includes all files tracked by git in the source distribution. The trade-off is limited support for complex scenarios like C extensions or non-standard package layouts. You win some, you lose some. π
See the flit documentation at https://flit.pypa.io/ for details.
pdm-backend π
pdm-backend is the build backend from the PDM project, offering modern features and good integration with PEP 621 metadata.
To use pdm-backend:
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
pdm-backend provides strong PEP 621 support, good handling of dynamic metadata, and support for both pure Python and extension modules. It's actively developed and includes features like SCM versioning and custom build hooks. She's busy and thriving. β¨
See the pdm-backend documentation at https://pdm-backend.fming.dev/ for details.
poetry-core π
poetry-core is the build backend extracted from Poetry. It's designed primarily for use with the Poetry tool but can be used standalone β she's independent now.
To use poetry-core:
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
poetry-core handles pure Python packages well and integrates tightly with Poetry's ecosystem. However, configuration is done through Poetry's custom [tool.poetry] section rather than standard [project] metadata, which can reduce interoperability with other tools. So like⦠know what you're signing up for.
See the poetry documentation at https://python-poetry.org/ for details.
scikit-build-core π₯
scikit-build-core is a build backend specifically designed for projects with CMake-based C/C++ extensions. It was built for this exact situation.
To use scikit-build-core:
[build-system]
requires = ["scikit-build-core"]
build-backend = "scikit_build_core.build"
scikit-build-core provides excellent CMake integration, cross-platform compilation support, and modern PEP 621 metadata support. It's the recommended backend for projects using CMake for extension modules, replacing the older scikit-build. Glow-up complete. π
See the scikit-build-core documentation at https://scikit-build-core.readthedocs.io/ for details.
meson-python π
meson-python is a build backend for projects using the Meson build system, particularly useful for compiled extensions.
To use meson-python:
[build-system]
requires = ["meson-python"]
build-backend = "mesonpy"
meson-python offers fast builds, excellent cross-platform support, and good integration with Meson's dependency management. It's especially strong for projects with complex compiled components and is increasingly popular in the scientific Python ecosystem. STEM girlies stay winning. π§
See the meson-python documentation at https://meson-python.readthedocs.io/ for details.
Choosing a Backend π€
The right backend depends on your project's needs, bestie β there's no one-size-fits-all and that's okay. For simple pure Python packages, flit-core or hatchling provide straightforward configuration and fast builds. Projects that need extensive customization or have complex requirements often benefit from setuptools' mature feature set and extensive documentation.
If your project includes C or C++ extensions built with CMake, scikit-build-core is the natural choice. For projects using Meson for compilation, meson-python provides the best integration. Projects already using Poetry might prefer poetry-core for consistency, though this limits interoperability with other tools. It's giving committed relationship with Poetry and honestly, valid.
New projects without special requirements should consider hatchling or flit-core for their modern approach and simplicity. Existing projects using setuptools can continue doing so, as it remains well-maintained and feature-complete. No need to panic-migrate, periodt. π
Configuring Backends βοΈ
Each backend has its own configuration approach. Modern backends use the [project] section of pyproject.toml for metadata following PEP 621. Backend-specific settings go in a [tool.backend-name] section.
For example, with hatchling you might have:
[project]
name = "mypackage"
version = "1.0.0"
[tool.hatch.build]
include = ["src/**/*.py"]
With setuptools:
[project]
name = "mypackage"
version = "1.0.0"
[tool.setuptools.packages.find]
where = ["src"]
Always consult your backend's documentation for configuration options β like, always, I'm not joking. Common configuration needs include specifying which files to include, where to find packages, how to handle version numbers, and how to process extension modules. π«Ά
Passing Options to Backends ποΈ
build lets you pass configuration settings to backends via the --config-setting or -C flag. The syntax and available options depend entirely on the backend. Your backend, your rules.
For example, with setuptools you might pass build options:
$ python -m build -C--build-option=--build-number=123
With scikit-build-core, you can configure CMake:
$ python -m build -Ccmake.define.BUILD_TESTING=OFF
Each backend interprets these settings differently. See Backend Configuration <> for backend-specific examples.
Okay so here's the tea: config settings map keys to values, so every key needs a value. Drop the=VALUEand the key gets an empty value, which suits backends that act on a flag's presence alone.buildaccepts this shorthand but warns about it, becausepiponly takes the explicitKEY=VALUEform. WriteKEY=for an empty value and your command runs under both tools. Learn from my lil warning, bestie. π
Dynamic Metadata π
Some backends support generating metadata dynamically at build time. Common use cases include reading version numbers from git tags, generating file lists from directory contents, or computing dependencies based on environment. Very cute, very powerful.
Most backends support version from SCM (source control management). For example, with hatchling using hatch-vcs:
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[tool.hatch.version]
source = "vcs"
The backend reads version information from git tags during the build. This keeps version numbers in sync between git and your package without manual updates. Never hand-editing a version number again, we love to see it. β¨
Dynamic metadata is powerful but has trade-offs. It can make builds less reproducible if not carefully managed, and it may prevent installation from sdists if the dynamic information source (like git) isn't available. Real talk β know the cost before you commit.
Backend Extensions and Plugins π
Many backends support plugins to extend functionality. setuptools has an extensive plugin ecosystem including plugins for namespace packages, entry points, and custom build steps. hatchling has a plugin system for custom build hooks, version sources, and metadata providers.
When choosing a backend, consider whether you need extensibility through plugins. If your project has unique build requirements, a backend with a strong plugin system may be beneficial even if it's more complex to configure initially. Invest in the glow-up early, iykyk. π
Switching Backends π
Switching from one backend to another is possible but requires care. The main steps involve updating your [build-system] section in pyproject.toml, migrating configuration from the old backend's format to the new one, and testing that the built distributions are equivalent. Do not just yolo this, bestie.
When migrating, always build and inspect both sdist and wheel to verify all files are included correctly. Test installation from both distribution types. Check that metadata (version, dependencies, entry points) is correctly preserved. All of it, no skipping. π«Ά
Most migrations involve moving from setuptools to a modern backend. The new backend's documentation typically includes migration guides. Common challenges include differences in how package discovery works, how data files are included, and how version numbers are specified. Those three? They're always the drama. π
Backend Development π
Okay so real talk: for like 99% of you, just use a backend that already exists, bestie. It's the right call!! But if you have unique requirements that literally no existing backend covers? Then yes, you can absolutely implement your own PEP 517 backend. Go off. π
The bare minimum a backend has to do is implement these two functions inside a Python module:
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
# Build wheel, return filename
pass
def build_sdist(sdist_directory, config_settings=None):
# Build sdist, return filename
pass
That's the floor, not the ceiling though. A full-featured backend is a whole other beast β you're handling metadata extraction, dependency specification, editable installs, and error handling. So ngl, unless your needs are wildly specialized, contributing to an existing backend or just using its plugin system is way more practical than birthing a brand new one. Lowkey the move. π«Ά
Why setuptools commands don't work with build π«
If you're migrating from setup.py, you're probably used to running stuff like:
$ python setup.py clean
$ python setup.py test
$ python setup.py develop
And the tea is: those commands do not work with build. Not being dramatic, they just don't. build uses the PEP 517 interface, and that interface only defines hooks for building distributions β build_wheel and build_sdist. It does not support arbitrary setuptools commands. Periodt.
So here's your modern alternative list, iconic behavior only:
clean: remove thedist/directory manually, or just let your CI handle it πtest: usepytestdirectly or viatoxdevelop: usepip install --editable .for editable installssdist/bdist_wheel: usepython -m build
If you genuinely need custom build steps, implement them in your build backend's hooks or use a plugin system. For example, setuptools supports setup.py with custom build steps that run during the PEP 517 build process. See? There's a path. β¨
See also πΈ
How it Works <>for howbuildinvokes backendsBackend Configuration <>for backend-specific configuration examplesPEP 517 <https://peps.python.org/pep-0517/>for the backend interface specificationPEP 621 <https://peps.python.org/pep-0621/>for standard project metadata
Contributing π
build welcomes contributions from the community, and this guide is gonna explain exactly how to contribute to the project. Come through!!
Development Setup π§
To contribute to build, you need a development environment set up on your local machine. Start by forking the repository on GitHub <https://github.com/pypa/build> and cloning your fork to your local machine. Once it's cloned, navigate to the project directory and install build in development mode along with all development dependencies. That's the vibe check. β
The project uses tox <https://tox.wiki/> for managing development environments and running tests. Install tox globally or in a virtual environment, then use it to run tests and other development tasks. The tox configuration includes environments for testing across multiple Python versions, running linters, building documentation, and more. So much range, honestly. π
Development Workflow π₯
The typical development workflow begins with creating a new branch for your changes. Branch names should be descriptive of the feature or fix you're implementing β yes, fix-stuff is not it, bestie. Make your changes in this branch, and make sure you add or update tests to cover your modifications.
Before committing, run the test suite locally to verify your changes don't break existing functionality. The project uses pytest <https://docs.pytest.org/> for testing, and you can run tests using tox. After ensuring tests pass, commit your changes with a clear, descriptive commit message following the project's commit conventions. π
Each pull request should include a changelog fragment. Create a file in docs/changelog/ named <pr_number>.<type>.rst where type is feature, bugfix, doc, removal, or misc. The file should contain a single line describing the change. For example, 123.feature.rst might contain:
Add support for custom backends - by :user:`contributor`
See docs/changelog/README.rst for complete details. No skipping that file, it's got the full lore. π
When your changes are ready, push your branch to your fork on GitHub and open a pull request against the main build repository. The pull request description should clearly explain what changes you've made and why. Reference any related issues using GitHub's issue linking syntax. β¨
Running Tests π
Due to its nature, build has a somewhat complex test suite with two sets of tests: unit tests and integration tests. Unit tests verify the actual code implementation, while integration tests run build on real world projects as a sanity check. To run tests we use tox.
Some example commands for this project include running type checking with tox -e type, running only unit tests against Python 3.14 with tox run -e 3.14, running both unit and integration tests with tox run -- --run-integration, running only integration tests with tox run -- --only-integration, or running only integration tests with parallel tasks using tox run -- -n auto --only-integration.
Heads up!! Tests run in parallel by default, but if you pass any arguments, you need to include -n auto if you want to keep parallel runs. Don't forget it and then wonder why everything is slow. π
Integration tests take a long time to run, so they are disabled by default. Passing either --run-integration or --only-integration arguments through tox to pytest will run them, where the latter will disable unit tests and only run integration tests. CI still runs both test suites, so don't think you're getting away with anything.
tox -- -n auto --only-integration
The project has a fairly large environment matrix, running tests for all supported Python versions and implementations, and with the module being invoked directly from path, sdist install, or wheel install. To run tests only for Python 3.14:
tox -e 3.14
and with the module being invoked directly from path, sdist install, or wheel install.
Additionally, there are environments for type checking and documentation building, plus extras like checking code with minimum versions of dependencies. For type checking:
tox -e type
Code coverage is tracked to ensure all code paths are tested. Aim for complete coverage of any new code you add β yes, all of it, main character energy only. π The CI system will report coverage metrics on your pull request and runs the test suite across all supported operating systems.
Code Style and Linting β
build follows modern Python code style conventions enforced by ruff <https://docs.astral.sh/ruff/>. Before submitting a pull request, run the linter to ensure your code meets the project's style guidelines. The ruff configuration is defined in the pyproject.toml file and includes both formatting and linting rules.
The project also uses type annotations throughout the codebase. All new code should include appropriate type hints, and changes to existing code should preserve or improve type annotations. Use pyright <https://microsoft.github.io/pyright/> for type checking to verify your type annotations are correct. Typed code is so slay, genuinely. π
Documentation π¦
Good documentation is essential for build's users and contributors. If your contribution adds new features or changes existing behavior, update the relevant documentation files. The documentation is written in reStructuredText <https://docutils.sourceforge.io/rst.html> and built using Sphinx <https://www.sphinx-doc.org/>.
The documentation follows the Diataxis framework <https://diataxis.fr/>, organizing content into tutorials, how-to guides, technical reference, and explanations. Place your documentation in the appropriate section based on its purpose. Tutorial content teaches beginners, how-to guides solve specific problems, reference sections document details, and explanations provide context and design rationale. Know which one you're writing, bestie β the framework does not play. π
Build the documentation locally to verify your changes render correctly. The tox configuration includes an environment for building docs. Review the rendered output to ensure formatting, links, and code examples appear as intended. Always check your homework before turning it in.
Pull Request Process π
When you open a pull request, maintainers will review your changes and may request modifications. Be responsive to feedback and make requested changes in additional commits on your branch. The pull request will be merged once approved by a maintainer and all CI checks pass. Simple as that. β¨
The CI system runs the full test suite across all supported Python versions, verifies code style compliance, builds the documentation, and performs other checks. All CI checks must pass before a pull request can be merged. If CI fails, review the output to understand what went wrong and push fixes to your branch. Not me getting a red X on my PR... π fix it and move on.
Workflow Security Practices π
If your contribution involves modifying GitHub Actions workflows in the .github/workflows/ directory, additional security considerations apply. The project follows modern security best practices to protect the release process and prevent supply chain attacks. All workflow modifications must maintain these security properties. This is the serious section, so lock in. π«‘
Actions must be pinned to specific commit SHAs rather than tags or branch names. This prevents attacks where an attacker gains control of an action repository and moves a tag to point to malicious code. When adding or updating an action reference, use the full commit SHA as a comment alongside the action version for maintainability. For example, uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 pins to a specific commit while documenting the version for future reference.
Workflows must follow the principle of least privilege by setting permissions: {} at the workflow level and granting only the minimal necessary permissions to individual jobs. Each job should explicitly declare the permissions it requires, such as contents: read for jobs that only need to read repository contents or id-token: write for jobs that need to authenticate with external services using OIDC. Never grant broader permissions than necessary, as excessive permissions increase the impact of potential vulnerabilities. Giving everything write access is giving red flag energy, fr fr. π©
Checkout actions must include persist-credentials: false to prevent GitHub tokens from being persisted in the working directory where they could be leaked through artifacts or caches. This protection is particularly important for workflows that upload artifacts or create caches that might be accessible to others. The only exception is the pre-release workflow which needs persistent credentials to push commits and tags, and this exception is acceptable because the workflow runs in a protected environment requiring manual approval.
When proposing workflow changes, consider the security implications of each modification. Adding new permissions, introducing new actions, or changing environment configurations all affect the project's security posture. If you're unsure whether a workflow change has security implications, ask a maintainer for guidance. The project uses automated security scanning with tools like zizmor to catch common workflow vulnerabilities, but manual review remains essential for maintaining security.
π Issue Tracking
ngl bestie, before you go dump a whole new feature into the codebase, check the issue tracker first π€. Like, pls, see if somebody already talked about it. If nobody has, maybe open an issue and chat with the maintainers about what you're planning β this keeps your contribution aligned with where the project is actually headed and stops two of y'all from doing the same work twice π. No duplicate effort in this house.
When you're reporting bugs, give us the full tea: the complete error message and traceback, what you expected to happen vs. what actually happened π, the steps to reproduce, and all your environment deets β like your Python version and operating system π«Ά.
For feature requests, explain the use case β like why would this help build's users? And drop some examples of how the feature would be used if it got implemented β¨.
π Community Guidelines
build is part of the PyPA (Python Packaging Authority) ecosystem (https://www.pypa.io/) and follows the PyPA Code of Conduct. Be respectful and constructive in all your interactions with other contributors, periodt β. Keep the feedback on the code and the ideas β not on the humans. We don't do that here π
.
The project values clear communication, thorough testing, and well-documented code. Take the time to explain your changes clearly in commit messages and pull requests. Write tests that future contributors can actually understand π§. And document not just what your code does, but why you made those particular design choices β iykyk.
π Getting Help
Got questions about contributing? Several resources have you covered, bestie π«Ά. The issue tracker can be used for questions, though the more complex discussions might be better suited to other venues. The project's documentation has extensive info about build's design and implementation π.
For real-time chatting, consider joining the PyPA Discord server (https://discord.gg/pypa) where lots of packaging tool maintainers and contributors hang out π. You can also ask questions on the Python Packaging Discourse forum (https://discuss.python.org/c/packaging/14).
π See also
- PyPA Code of Conduct β
https://www.pypa.io/en/latest/code-of-conduct/ - Issue Tracker β
https://github.com/pypa/build/issues - tox documentation β
https://tox.wiki/ - pytest documentation β
https://docs.pytest.org/ - ruff documentation β
https://docs.astral.sh/ruff/ - Sphinx documentation β
https://www.sphinx-doc.org/ - PyPA Discord β
https://discord.gg/pypa - Python Packaging Discourse β
https://discuss.python.org/c/packaging/14
π Release
Releases follow semantic versioning (https://semver.org/) and use automated workflows to handle the version bumping, changelog building, commit creation, and publishing π
. Maintainers trigger releases through GitHub Actions, or they run the release script locally for testing β¨.
π Changelog Fragments
Contributors add changelog fragments that get collected during releases π. You create a file in docs/changelog/ named <pr_number>.<type>.rst where type is:
featureβ for new functionalitybugfixβ for fixesdocβ for documentation changesremovalβ for deprecationsmiscβ for internal changes
The file should contain a single line describing the change, optionally crediting the author with :user:username syntax π. For example, `123.feature.rst` might contain `Add support for custom backends - by :user:`contributor. These fragments get collected during releases to build the changelog automatically.
π€ Automated Release
Navigate to the pre-release workflow (https://github.com/pypa/build/actions/workflows/pre-release.yml) and click Run workflow π±οΈ. Then select a version bump type:
autoβ for patch bumpsmajorβ for X+1.0.0minorβ for X.Y+1.0patchβ for X.Y.Z+1
The workflow updates the version in src/build/__init__.py, runs towncrier build to collect changelog fragments from docs/changelog/ and incorporate them into CHANGELOG.rst, creates a release commit with message chore: prepare for X.Y.Z, creates an annotated tag, and pushes both to trigger the CD workflow π₯.
The tag push triggers the continuous deployment pipeline which builds the source distribution and wheel, creates a GitHub release with auto-generated notes, generates build attestations for supply chain security, and publishes to PyPI via trusted publishing π. The whole process takes approximately five minutes from workflow trigger to published package β±οΈ.
π§ͺ Local Release
Run the release script locally for testing or manual control. Make sure tox is installed and your git identity is configured:
$ pip install tox
$ git config user.name "Your Name"
$ git config user.email "your@email.com"
The script creates annotated git tags without GPG signing π«£. Package authenticity is guaranteed by PyPI attestations, not git tag signatures.
π§Ό Dry run without pushing
Test the release process locally:
$ tox -e release -- --version auto --no-push
This updates the version file, builds the changelog from fragments, creates the commit and tag locally, but does not push to the remote repository π.
π’ Full local release
Specify an explicit version or bump type:
$ tox -e release -- --version 1.5.0
$ tox -e release -- --version patch # 1.4.0 β 1.4.1
$ tox -e release -- --version minor # 1.4.0 β 1.5.0
$ tox -e release -- --version major # 1.4.0 β 2.0.0
The script updates src/build/__init__.py, runs towncrier build to collect changelog fragments, runs pre-commit hooks, creates a commit with message chore: prepare for X.Y.Z, creates an annotated tag, and pushes both to origin π
. The GitHub Actions CD workflow takes over to build and publish.
π See also
PyPI removed PGP signature support in May 2023 π. Package authenticity is guaranteed through PyPI attestations per PEP 740, which cryptographically link packages to their source repository and build process π.
π οΈ Troubleshooting
β³ Workflow timeout
If the pre-release workflow times out, run the release script locally with --no-push, then manually push with git push && git push --tags. The CD workflow triggers automatically on tag push β¨.
π₯ Publishing conflicts
If a version is already published to PyPI, the publish job fails safely π. The CD workflow is idempotent except for PyPI publishing. For corrections, create a post-release like X.Y.Z.post1.
π§© Implementation
The automation consists of:
- the pre-release workflow at
.github/workflows/pre-release.ymlfor manual triggering - the CD workflow at
.github/workflows/cd.ymlthat triggers on tag pushes - the release script at
tasks/release.pyfor version bumping and tag creation - the
[env.release]environment intox.tomlproviding dependencies
π 1.5.1 (2026-07-09)
β¨ Features
- Add
--report=PATHto write a machine-readable JSON report of built artifacts;--metadatanow also accepts.whlfiles β by @gaborbernat (https://github.com/sponsors/gaborbernat) (#198 βhttps://github.com/pypa/build/issues/198) - The srcdir argument now accepts
.tar.gzsource distributions, extracting and building from them β by @gaborbernat (https://github.com/sponsors/gaborbernat) (#311 βhttps://github.com/pypa/build/issues/311) - The "Unmet dependencies" error from
--no-isolationbuilds now shows the wanted version, found version, and interpreter β by @gaborbernat (https://github.com/sponsors/gaborbernat) (#504 βhttps://github.com/pypa/build/issues/504) - Add
--sdist-extract-dirto extract the intermediate sdist into a persistent directory, enabling compiler cache reuse across rebuilds β by @gaborbernat (https://github.com/sponsors/gaborbernat) (#614 βhttps://github.com/pypa/build/issues/614) - Add
--env-dirto place the isolated build environment at a fixed path, enabling compiler cache reuse across builds β by @gaborbernat (https://github.com/sponsors/gaborbernat) (#655 βhttps://github.com/pypa/build/issues/655) - Print a summary of resolved dependency versions (
name==version) after installing them in isolated builds β by @gaborbernat (https://github.com/sponsors/gaborbernat) (#959 βhttps://github.com/pypa/build/issues/959) - On build failure, print a tip pointing to
--env-dirand--sdist-extract-dirfor debugging and link to the "Debug a failed build" how-to β reported by @dimpase (https://github.com/sponsors/dimpase), implemented by @gaborbernat (https://github.com/sponsors/gaborbernat) (#966 βhttps://github.com/pypa/build/issues/966)
π Bugfixes
- Drain verbose subprocess output inline instead of using a
ThreadPoolExecutor, which silently swallowed logging errors β by @henryiii (https://github.com/sponsors/henryiii) (#1098 βhttps://github.com/pypa/build/issues/1098) - Reject a file passed as
--env-dirwith a clear error instead of a rawFileExistsErrorβ by @henryiii (https://github.com/sponsors/henryiii) (#1100 βhttps://github.com/pypa/build/issues/1100) - Emit CLI warnings to stderr instead of stdout, so they no longer corrupt
--metadataJSON output on stdout β by @ymyzk (https://github.com/sponsors/ymyzk) (#1111 βhttps://github.com/pypa/build/issues/1111) - Fix the Windows symlink support probe always returning
Falsedue to a stale object interpolated into the destination path β by @henryiii (https://github.com/sponsors/henryiii) (#1118 βhttps://github.com/pypa/build/issues/1118) - Fix
metadata_path's build-backend fallback returning a nonexistent dist-info path for wheels with a build tag β by @henryiii (https://github.com/sponsors/henryiii) (#1119 βhttps://github.com/pypa/build/issues/1119) - Write pip/uv requirements and constraints files with
\ninstead ofos.linesep, avoiding doubled\r\r\nline endings on Windows β by @henryiii (https://github.com/sponsors/henryiii) (#1120 βhttps://github.com/pypa/build/issues/1120) - Batch of small robustness fixes: correct macOS release parsing for the minimum pip version, avoid sharing the mutable default build-system table between builders, keep the original error when isolated-environment setup fails early, and raise
BuildExceptionfor an invalid wheel β by @henryiii (https://github.com/sponsors/henryiii) (#1121 βhttps://github.com/pypa/build/issues/1121) - Decide color support independently for stdout and stderr instead of only checking
stdout.isatty(), so redirecting one stream no longer disables or leaks ANSI colors on the other β by @henryiii (https://github.com/sponsors/henryiii) (#1123 βhttps://github.com/pypa/build/issues/1123)
ποΈ Deprecations and Removals
- Deprecate
build.util.project_wheel_metadata()(#build.util.project_wheel_metadata); usepython -m build --metadatainstead β by @gaborbernat (https://github.com/sponsors/gaborbernat) (#557 βhttps://github.com/pypa/build/issues/557) - Warn on config settings that have no value (like
-C--my-flagπ) β bestie, that's giving unreadable chaos. Use-C--my-flag=instead so you stay compatible with pip π β by@gaborbernat(https://github.com/sponsors/gaborbernat) (#640https://github.com/pypa/build/issues/640)
Miscellaneous π§
#1058β https://github.com/pypa/build/issues/1058#1060β https://github.com/pypa/build/issues/1060#1062β https://github.com/pypa/build/issues/1062#1093β https://github.com/pypa/build/issues/1093#1099β https://github.com/pypa/build/issues/1099#1107β https://github.com/pypa/build/issues/1107#1113β https://github.com/pypa/build/issues/1113#1117β https://github.com/pypa/build/issues/1117#1122β https://github.com/pypa/build/issues/1122#1124β https://github.com/pypa/build/issues/1124#1128β https://github.com/pypa/build/issues/1128
1.5.0 (2026-04-30)
Features β¨
- Python 3.9 support? Gone. Dropped. Not invited to the party π β by
@henryiii(https://github.com/sponsors/henryiii) (#1036https://github.com/pypa/build/issues/1036)
Bugfixes π§
- Make
--ignore-installedopt-in from the API viafresh=Trueβ by@henryiii(https://github.com/sponsors/henryiii) (#1056https://github.com/pypa/build/issues/1056)
Miscellaneous π§
1.4.4 (2026-04-22)
Bugfixes π§
- Fix the release pipeline generating
CHANGELOG.rstentries with inconsistent heading levels, which brokesphinx -Wand pinned Read the Docs stable at1.4.0β by@gaborbernat(https://github.com/sponsors/gaborbernat). (#1031https://github.com/pypa/build/issues/1031) - Revert
#1039(https://github.com/pypa/build/pull/1039) from build1.4.3, no longer checkdirect_url(for now) β by@henryiii(https://github.com/sponsors/henryiii) (#1039https://github.com/pypa/build/issues/1039) - Add
--ignore-installedto thepip installcommand so packages that are already sitting in the isolated build environment stop causing trouble β by@henryiii(https://github.com/sponsors/henryiii) (#1037https://github.com/pypa/build/issues/1037) (#1040https://github.com/pypa/build/issues/1040) - Partial revert of
#973(https://github.com/pypa/build/pull/973), keeping log messages in one entry, multiple lines. (#1044https://github.com/pypa/build/issues/1044)
Miscellaneous π§
#1048β https://github.com/pypa/build/issues/1048#1049β https://github.com/pypa/build/issues/1049
1.4.3 (2026-04-10)
Features β¨
- Add
kindparameter to log messages to separate semantic and representation β by@abitrolly(https://github.com/sponsors/abitrolly) (#973https://github.com/pypa/build/issues/973)
Bugfixes π§
- Strip
PYTHONPATHfrom the environment during isolated builds so host packages can't leak into your build, no cap π« β by@gaborbernat(https://github.com/sponsors/gaborbernat) (#405https://github.com/pypa/build/issues/405) - Pass
--no-inputto pip to prevent hidden credential prompts that cause hangs, and automatically setPIP_KEYRING_PROVIDER=subprocess(orUV_KEYRING_PROVIDER=subprocessfor the uv installer) when the keyring CLI is onPATHβ by@gaborbernat(https://github.com/sponsors/gaborbernat) (#409https://github.com/pypa/build/issues/409) check_dependencynow reports URL requirements as unmet instead of silently accepting them when a package with the same name is installed β by@gaborbernat(https://github.com/sponsors/gaborbernat) (#860https://github.com/pypa/build/issues/860)- Fix the misleading missing dependency error display where transitive dependency chains showed the top-level package on a separate line, making it look like the top-level package itself was missing β by
@gaborbernat(https://github.com/sponsors/gaborbernat) (#875https://github.com/pypa/build/issues/875) - Fix the towncrier template to generate changelog categories in definition order β by
@gaborbernat(https://github.com/sponsors/gaborbernat) (#1007https://github.com/pypa/build/issues/1007) - Resolve thread-safety races in the build API πββοΈπ¨ β by
@gaborbernat(https://github.com/sponsors/gaborbernat) (#1015https://github.com/pypa/build/issues/1015) - Validate
backend-pathentries exist on disk with a clear error β by@gaborbernat(https://github.com/sponsors/gaborbernat) (#1016https://github.com/pypa/build/issues/1016)
Miscellaneous π§
#1020β https://github.com/pypa/build/issues/1020#1021β https://github.com/pypa/build/issues/1021
1.4.2 (2026-03-25)
Bugfixes π§
- Ensure the uv installer uses the current version of Python, avoiding an issue if
UV_PYTHONis set, for example. (#977https://github.com/pypa/build/issues/977) - Fix
_has_valid_outer_pipreturningTruewhen pip is missing, which made build try to use a non-existent pip instead of falling back to virtualenv π. (#1003https://github.com/pypa/build/issues/1003)
1.4.1 (2026-03-24)
Features β¨
- Allow setting build constraints β by
@gaborbernat(https://github.com/sponsors/gaborbernat) (#963https://github.com/pypa/build/issues/963) - Automate releases with pre-release workflow and trusted publishing β by
@gaborbernat(https://github.com/sponsors/gaborbernat) (#991https://github.com/pypa/build/issues/991)
Bugfixes π§
- Fix the pip hack workaround β by
@gaborbernat(https://github.com/sponsors/gaborbernat) (#980https://github.com/pypa/build/issues/980)
Documentation π
- Fix documentation grammar and typos (
#979https://github.com/pypa/build/issues/979) - Reorganize documentation using the Diataxis framework β by
@gaborbernat(https://github.com/sponsors/gaborbernat) (#988https://github.com/pypa/build/issues/988) - Document the release process and workflow security practices in the contributing guide (
#991https://github.com/pypa/build/issues/991)
Miscellaneous π§
1.4.0 (2026-01-08)
- Add
--quietflag π€« (#947https://github.com/pypa/build/pull/947) - Add option to dump PEP 517 metadata with
--metadata(#940https://github.com/pypa/build/pull/940,#943https://github.com/pypa/build/pull/943) - Support
UVenvironment variable (#971https://github.com/pypa/build/pull/971) - Remove a workaround for
3.14b1(#960https://github.com/pypa/build/pull/960) - In
3.14final release, color defaults toTruealready (#962https://github.com/pypa/build/pull/962) - Pass
sp-repo-review(#942https://github.com/pypa/build/pull/942) - In pytest configuration,
log_levelis better thanlog_cli_level(#950https://github.com/pypa/build/pull/950) - Split up typing and mypy (
#944https://github.com/pypa/build/pull/944) - Use
types-colorama(#945https://github.com/pypa/build/pull/945) - In docs, first argument for
_has_dependencyis a name (PR#970https://github.com/pypa/build/pull/970) - Fix test failure when
flit-coreis installed (PR#921https://github.com/pypa/build/pull/921)
1.3.0 (2025-08-01)
- Add
--config-json(PR#916https://github.com/pypa/build/pull/916, fixes issue#900https://github.com/pypa/build/issues/900) - Drop Python 3.8 (PR
#891https://github.com/pypa/build/pull/891) - Test on Python
3.14, colorful help on3.14+π (PR#895https://github.com/pypa/build/pull/895) - Fix
ModuleNotFoundErrorwhen pip is not installed (PR#898https://github.com/pypa/build/pull/898) - Disable use of
pip install --pythonfor debundled pip (PR#861https://github.com/pypa/build/pull/861) - Don't pass
no-wheelto virtualenv if it would warn (PR#892https://github.com/pypa/build/pull/892) - Optimize our tests to run faster β‘ (PR
#871https://github.com/pypa/build/pull/871,#872https://github.com/pypa/build/pull/872,#738https://github.com/pypa/build/pull/738) - Allow running our tests without virtualenv (PR
#911https://github.com/pypa/build/pull/911) - Fix issues in our tests (PR
#824https://github.com/pypa/build/pull/824,#918https://github.com/pypa/build/pull/918,#870https://github.com/pypa/build/pull/870,#915https://github.com/pypa/build/pull/915,#862https://github.com/pypa/build/pull/862,#863https://github.com/pypa/build/pull/863,#899https://github.com/pypa/build/pull/899,#896https://github.com/pypa/build/pull/896,#854https://github.com/pypa/build/pull/854) - Use SPDX identifiers for our license metadata (PR
#914https://github.com/pypa/build/pull/914) - Use dependency-groups for our development (PR
#880https://github.com/pypa/build/pull/880) - Mention conda and update the uv mention in README/docs (PR
#842https://github.com/pypa/build/pull/842,#816https://github.com/pypa/build/pull/816,#917https://github.com/pypa/build/pull/917)
1.2.2 (2024-09-06)
- Add
editabletobuilder.get_requires_for_build's static types (PR#764https://github.com/pypa/build/pull/764, fixes issue#763https://github.com/pypa/build/issues/763) - Include artifact attestations in our release (PR
#782https://github.com/pypa/build/pull/782) - Fix typing compatibility with typed
pyproject-hooks(PR#788https://github.com/pypa/build/pull/788) - Mark more tests with
network(PR#808https://github.com/pypa/build/pull/808) - Add more intersphinx links to docs (PR
#804https://github.com/pypa/build/pull/804) - Make uv optional for tests (PR
#807https://github.com/pypa/build/pull/807 and#813https://github.com/pypa/build/pull/813)
1.2.1 (2024-03-28)
- Avoid an error when the terminal width is undetectable on Python
< 3.11(PR#761https://github.com/pypa/build/pull/761)
1.2.0 (2024-03-27)
- Add
--installeroption, supporting pip and uv. Addeduvextra. (PR#751https://github.com/pypa/build/pull/751) - Improve console output and provide
-vfor dependency installation (PR#749https://github.com/pypa/build/pull/749) - Avoid compiling unused bytecode when using pip (PR
#752https://github.com/pypa/build/pull/752) - Dropped support for Python 3.7 (PR
#743https://github.com/pypa/build/pull/743)
1.1.1 (2024-02-29)
- Fixed invoking outer pip from user site packages (PR
#746https://github.com/pypa/build/pull/746, fixes issue#745https://github.com/pypa/build/issues/745) - Corrected the minimum pip version required to use an outer pip (PR
#746https://github.com/pypa/build/pull/746, fixes issue#745https://github.com/pypa/build/issues/745)
β¨ 1.1.0 (2024-02-29) β¨
- If an external
pipis already there, we use that instead of installing one, which speeds up environment setup a lil bit with virtualenv and a whole lot with venv. (PR #736βhttps://github.com/pypa/build/pull/736) - We stopped auto-injecting
wheelas a build dependency whenpyproject.tomlis missing β by @webknjaz. (PR #716βhttps://github.com/pypa/build/pull/716) importlib_metadatais now used on Python <3.10.2, for bugfixes that aren't in those CPython standard libraries yet (not required when bootstrapping) β by @GianlucaFicarelli. (PR #693βhttps://github.com/pypa/build/pull/693, fixes issue #692 βhttps://github.com/pypa/build/issues/692)
π 1.0.3 (2023-09-06) π
- We dodge the CPython 3.8.17, 3.9.17, 3.10.12 and 3.11.4
tarfilesymlink bug that got triggered by addingdata_filterback in 1.0.0. (PR #675βhttps://github.com/pypa/build/pull/675, fixes issue #674 βhttps://github.com/pypa/build/issues/674)
π 1.0.0 (2023-09-01) π
- The
tomllibrary fallback is gone βtomlcan no longer be used as a substitute fortomli. (PR #567βhttps://github.com/pypa/build/pull/567) - Added a
runnerparameter toutil.project_wheel_metadata. (PR #566βhttps://github.com/pypa/build/pull/566, fixes issue #553 βhttps://github.com/pypa/build/issues/553) - Big glow-up for the API: the
ProjectBuilderconstructor signature got modified, an alternativeProjectBuilder.from_envconstructor got added, theenv.IsolatedEnvinterface got redefined, andenv.DefaultIsolatedEnvgot exposed, replacingenv.IsolatedEnvBuilder. The whole point was to shift responsibility for modifying the environment off the project builder and onto theIsolatedEnventirely, and to make sure the builder always gets initialised from anIsolatedEnvin a consistent way. Mutating the project builder is no longer supported. (PR #537βhttps://github.com/pypa/build/pull/537) virtualenvis no longer imported when you're using-n, because faster builds are the vibe. (PR #636βhttps://github.com/pypa/build/pull/636, fixes issue #510 βhttps://github.com/pypa/build/issues/510)- The SDist now contains the repository contents, tests included. Flit-core 3.8+ required. (
PR #657βhttps://github.com/pypa/build/pull/657,#661βhttps://github.com/pypa/build/pull/661, fixes issue #656 βhttps://github.com/pypa/build/issues/656) - The minimum version of
importlib-metadatawent up to 4.6, and Python 3.10, because of a bug in the standard library version with URL requirements in extras. Still not required for 3.8 when bootstrapping (as long as you don't have URL requirements in extras). (PR #631βhttps://github.com/pypa/build/pull/631, fixes issue #630 βhttps://github.com/pypa/build/issues/630) - Docs are now built with Sphinx 7. (
PR #660βhttps://github.com/pypa/build/pull/660) - Tests now contain a network marker. (
PR #649βhttps://github.com/pypa/build/pull/649, fixes issue #648 βhttps://github.com/pypa/build/issues/648) - Config-settings are now passed to
get_requires*hooks, which fixes a long standing bug. If this affects your setuptools build, you can use-C--build-option=<cmd> -C--build-option=<option>to workaround an issue with Setuptools not allowing unrecognised build options when running this hook. (PR #627βhttps://github.com/pypa/build/pull/627, fixes issue #264 βhttps://github.com/pypa/build/issues/#264) - Testing on Python 3.12 betas/RCs now. (
PR #624βhttps://github.com/pypa/build/pull/624) - Malicious files get filtered out when extracting tar archives, whenever Python supports it. (
PR #609βhttps://github.com/pypa/build/pull/609) - Encoding is now specified, which fixes issues when
PYTHONWARNDEFAULTENCODINGis set. (PR #587βhttps://github.com/pypa/build/pull/587, fixes issue #577 βhttps://github.com/pypa/build/issues/577) - Ruff is now used for linting. πΆ
πΈ 0.10.0 (2023-01-11) πΈ
- The
pep517dependency got replaced withpyproject_hooks, which is whatpep517got renamed into. (PR #539βhttps://github.com/pypa/build/pull/539, Fixes #529 βhttps://github.com/pypa/build/issues/529) - The build backend changed from setuptools to flit. (
PR #470βhttps://github.com/pypa/build/pull/470, Fixes #394 βhttps://github.com/pypa/build/issues/394) - Support for Python 3.6: dropped. π (
PR #532βhttps://github.com/pypa/build/pull/532)
π¦ 0.9.0 (2022-10-27) π¦
- A Python 3.11.0 unavoidable warning with
venvgot hidden. (PR #527βhttps://github.com/pypa/build/pull/527) - Fixed an infinite recursion error in
check_dependencywhen dependencies are circular. (PR #512βhttps://github.com/pypa/build/pull/512, Fixes #511 βhttps://github.com/pypa/build/issues/511) coloramais now only imported on Windows. (PR #494βhttps://github.com/pypa/build/pull/494, Fixes #493 βhttps://github.com/pypa/build/issues/493)- Output gets flushed more often, so it interleaves less. (
PR #494βhttps://github.com/pypa/build/pull/494) - Small API cleanup β better
__all__, andsrcdiris read only now. (PR #477βhttps://github.com/pypa/build/pull/477) importlib_metadatais only used when it's actually needed. (PR #401βhttps://github.com/pypa/build/pull/401)- The printout now clarifies when build dependencies are being installed. (
PR #514βhttps://github.com/pypa/build/pull/514)
π 0.8.0 (2022-05-22) π
os.PathLike[str]is accepted in addition tostrfor paths in the public API. (PR #392βhttps://github.com/pypa/build/pull/392, Fixes #372 βhttps://github.com/pypa/build/issues/372)- Schema validation for the
build-systemtable got added, to check conformity with PEP 517 and PEP 518. (PR #365βhttps://github.com/pypa/build/pull/365, Fixes #364 βhttps://github.com/pypa/build/issues/364) - Better support for Python 3.11 (sysconfig schemes
PR #434βhttps://github.com/pypa/build/pull/434,PR #463βhttps://github.com/pypa/build/pull/463,tomllibPR #443βhttps://github.com/pypa/build/pull/443, warningsPR #420βhttps://github.com/pypa/build/pull/420) - Improved error printouts. (
PR #442βhttps://github.com/pypa/build/pull/442) packagingis avoided unless it's needed. (PR #395βhttps://github.com/pypa/build/pull/395, Fixes #393 βhttps://github.com/pypa/build/issues/393)
Breaking Changes π
- If creating a virtual environment in the
build.envmodule fails, it now raisesbuild.FailedProcessError. (PR #442βhttps://github.com/pypa/build/pull/442)
π 0.7.0 (2021-09-16) π
- Added the
build.utilmodule with a high-level utility API. (PR #340βhttps://github.com/pypa/build/pull/340)
π 0.6.0.post1 (2021-08-05) π
- Fixed compatibility with Python 3.6 and 3.7. (
PR #339βhttps://github.com/pypa/build/pull/339, Fixes #338 βhttps://github.com/pypa/build/issues/338)
β 0.6.0 (2021-08-02) β
- Improved output. (
PR #333βhttps://github.com/pypa/build/pull/333, Fixes #142 βhttps://github.com/pypa/build/issues/142) - The CLI now honors
NO_COLOR. (PR #333βhttps://github.com/pypa/build/pull/333) - The CLI can now be forced to colorize its output by setting the
FORCE_COLORenvironment variable. (PR #335βhttps://github.com/pypa/build/pull/335) - Logging got added to
buildandbuild.env. (PR #333βhttps://github.com/pypa/build/pull/333) - Switched to a TOML v1 compliant parser. (
PR #336βhttps://github.com/pypa/build/pull/336, Fixes #308 βhttps://github.com/pypa/build/issues/308)
Breaking Changes π
- Support for Python 2 and 3.5: dropped.
π§ 0.5.1 (2021-06-22) π§
- Fixed invoking the backend on an inexistent output directory with multiple levels. (
PR #318βhttps://github.com/pypa/build/pull/318, Fixes #316 βhttps://github.com/pypa/build/issues/316) - When building wheels via sdists, an isolated temporary directory is used. (
PR #321βhttps://github.com/pypa/build/pull/321, Fixes #320 βhttps://github.com/pypa/build/issues/320)
π 0.5.0 (2021-06-19) π
- Added the
ProjectBuilder.metadata_pathhelper. (PR #303βhttps://github.com/pypa/build/pull/303, Fixes #301 βhttps://github.com/pypa/build/issues/301) - Added a
build.__main__.build_package_via_sdistmethod. (PR #304βhttps://github.com/pypa/build/pull/304) - The appropriate installation scheme is used for Apple Python venvs. (
PR #314βhttps://github.com/pypa/build/pull/314, Fixes #310 βhttps://github.com/pypa/build/issues/310)
Breaking Changes π
- Binary distributions are now built via the sdist by default in the CLI (
PR #304βhttps://github.com/pypa/build/pull/304, Fixes #257 βhttps://github.com/pypa/build/issues/257) βpython -m buildwill now build a sdist, extract it, and build a wheel from the source. - As a side-effect of
PR #304(https://github.com/pypa/build/pull/304),build.__main__.build_packageno longer does CLI error handling (printing a nice message and exiting the program). - Importing
build.__main__no longer has any side-effects β it no longer overrideswarnings.showwarningor runscolorama.initon import. (PR #312βhttps://github.com/pypa/build/pull/312)
π«Ά 0.4.0 (2021-05-23) π«Ά
- It's now validated that the supplied source directory is valid. (
PR #260βhttps://github.com/pypa/build/pull/260, Fixes #259 βhttps://github.com/pypa/build/issues/259) - Minimum versions of build's runtime dependencies are now set and tested. (
PR #267βhttps://github.com/pypa/build/pull/267, Fixes #263 βhttps://github.com/pypa/build/issues/263) - Symlinks get used when creating venv's, when they're available. (
PR #274βhttps://github.com/pypa/build/pull/274, Fixes #271 βhttps://github.com/pypa/build/issues/271) - You get an error sooner if a pip upgrade is required and it fails. (
PR #288βhttps://github.com/pypa/build/pull/288, Fixes #256 βhttps://github.com/pypa/build/issues/256) - A
runnerargument got added toProjectBuilder. (PR #290βhttps://github.com/pypa/build/pull/290, Fixes #289 βhttps://github.com/pypa/build/issues/289) - Irrelevant
pep517error tracebacks are hidden, and error messages got better. (PR #296βhttps://github.com/pypa/build/pull/296) - We try to use
coloramato fix colors on Windows. (PR #300βhttps://github.com/pypa/build/pull/300)
Breaking Changes π
- As a side-effect of
PR #260(https://github.com/pypa/build/pull/260), projects that contain neither apyproject.tomlnor asetup.pyget reported as invalid. This affects projects that specify only asetup.cfg, and such projects are recommended to add apyproject.toml. The new behavior is on par with what pip currently does, so if you're affected by this, your project should not be pip installable. - The
--skip-dependenciesoption has been renamed to--skip-dependency-check. (PR #297βhttps://github.com/pypa/build/pull/297) - The
skip_dependenciesargument onbuild.__main__.build_packagegot a whole rebrand bestie, it'sskip_dependency_checknow (PR #297 <https://github.com/pypa/build/pull/297>) build.ConfigSettingsis nowbuild.ConfigSettingsType, that's it that's the glow up (PR #298 <https://github.com/pypa/build/pull/298>)build.ProjectBuilder.build_dependenciesβ‘οΈbuild.ProjectBuilder.build_system_requires(PR #284 <https://github.com/pypa/build/pull/284>, Fixes #182 <https://github.com/pypa/build/issues/182>)build.ProjectBuilder.get_dependenciesβ‘οΈbuild.ProjectBuilder.get_requires_for_build(PR #284 <https://github.com/pypa/build/pull/284>, Fixes #182 <https://github.com/pypa/build/issues/182>)
πΈ 0.3.1 (2021-03-09) πΈ
- Supports being used directly from
pipx runin 0.16.1.0+ which is so convenient honestly (PR #247 <https://github.com/pypa/build/pull/247>) - Uses UTF-8 encoding when reading
pyproject.toml, no more cursed mojibake moments (PR #251 <https://github.com/pypa/build/pull/251>, Fixes #250 <https://github.com/pypa/build/issues/250>)
π¦ 0.3.0 (2021-02-19) π¦
- Upgrades pip based on the venv pip version, which dodges the error on Debian Python 3.6.5-3.8 and those issues installing wheels on Big Sur (PR #229 <https://github.com/pypa/build/pull/229>, PR #230 <https://github.com/pypa/build/pull/230>, Fixes #228 <https://github.com/pypa/build/issues/228>)
- Builds dependencies in isolation instead of inside the build environment, cleaner fr (PR #232 <https://github.com/pypa/build/pull/232>, Fixes #231 <https://github.com/pypa/build/issues/231>)
- Falls back on
venvifvirtualenvis too old (PR #241 <https://github.com/pypa/build/pull/241>) - Adds the metadata preparation hook (PR #217 <https://github.com/pypa/build/pull/217>, Fixes #130 <https://github.com/pypa/build/issues/130>)
π 0.2.1 (2021-02-09) π
- Fixes the error coming from an unrecognised pip flag on Python 3.6.0 to 3.6.5 (PR #227 <https://github.com/pypa/build/pull/227>, Fixes #226 <https://github.com/pypa/build/issues/226>)
π 0.2.0 (2021-02-07) π
- Checks dependencies recursively (PR #183 <https://github.com/pypa/build/pull/183>, Fixes #25 <https://github.com/pypa/build/issues/25>)
- Builds wheel and sdist distributions in separate environments, because they may have different dependencies, the way that makes so much sense (PR #195 <https://github.com/pypa/build/pull/195>, Fixes #194 <https://github.com/pypa/build/issues/194>)
- Adds support for pre-releases in
check_dependency(PR #204 <https://github.com/pypa/build/pull/204>, Fixes #191 <https://github.com/pypa/build/issues/191>) - Fixes console scripts not being available during build (PR #221 <https://github.com/pypa/build/pull/221>, Fixes #214 <https://github.com/pypa/build/issues/214>)
- Does not add the default backend requirements to
requireswhen no backend is specified (PR #177 <https://github.com/pypa/build/pull/177>, Fixes #107 <https://github.com/pypa/build/issues/107>) - Returns the sdist name in
ProjectBuild.build(PR #197 <https://github.com/pypa/build/pull/197>) - Improves documentation (PR #178 <https://github.com/pypa/build/pull/178>, PR #203 <https://github.com/pypa/build/pull/203>)
- Adds changelog (PR #219 <https://github.com/pypa/build/pull/219>, Fixes #169 <https://github.com/pypa/build/issues/169>)
π Breaking changes π
- Moves the
config_settingsargument to the hook calls (PR #218 <https://github.com/pypa/build/pull/218>, Fixes #216 <https://github.com/pypa/build/issues/216>)
β¨ 0.1.0 (2020-10-29) β¨
- Moved the upstream to PyPA
- Fixed building with isolation in a virtual environment
- Added
env.IsolatedEnvabstract class - Added
env.IsolatedEnvBuilder(replacesenv.IsolatedEnvironmentusages) - Added
python_executableargument to theProjectBuilderconstructor - Added
--version/-Voption to the CLI - Added support for Python 3.9
- Added
py.typedmarker - Various miscellaneous fixes in the virtual environment creation
- Many general improvements in the documentation
- Documentation moved to the furo theme
- Updated the CoC to the PSF CoC, which PyPA has adopted
π Breaking changes π
- Renamed the entrypoint script to
pyproject-build - Removed default arguments from all paths in
ProjectBuilder - Removed
ProjectBuilder.hook - Renamed
__main__.buildto__main__.build_package - Changed the default
outdirvalue to{srcdir}/dest - Removed
env.IsolatedEnvironment
π 0.0.4 (2020-09-08) π
- Packages are now built in isolation by default, iconic behaviour honestly
- Added
--no-isolation/-nflag to build in the current environment - Add
--config-setting/-Coption to pass options to the backend - Add
IsolatedEnvironmentclass - Fix creating the output directory if it doesn't exist
- Fix building with in-tree backends
- Fix broken entrypoint script (
python-build) - Add warning about incomplete verification when verifying extras
- Automatically detect typos in the build system table
- Minor documentation improvements
π 0.0.3.1 (2020-06-10) π
- Fix bug preventing the CLI from being invoked, thank god
- Improved documentation
π§ 0.0.3 (2020-06-09) π§
- Misc improvements
- Added documentation
β 0.0.2 (2020-05-29) β
- Add
setuptoolsas a default fallback backend - Fix extras handling in requirement strings
π«Ά 0.0.1 (2020-05-17) π«Ά
- Initial release, the birth of a legend
βοΈ AUTHOR βοΈ
Filipe LaΓns β yes, one singular icon wrote all of this π
Β©οΈ COPYRIGHT Β©οΈ
2020, Filipe LaΓns
And that's the whole lore bestie, we made it to the end of the page π This treasure of a manual is 1.5.1, dated Jul 20, 2026, and it lives as BUILD(1) β you now know literally everything about it, go build something and slay β¨π¦π
π¦ where this page comes from
- ubuntu stonking: python3-build 1.5.1-1 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms