BUILD(1)

NAME
build - build 1.5.1

A simple, correct Python packaging <https://packaging.python.org/> build frontend.

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. It focuses solely on building packages and does not manage dependencies or virtual environments.

MISSION STATEMENT

Many Python tools combine multiple capabilities into one project. For example, pip <https://github.com/pypa/pip> both installs packages and can build them. While convenient, this tight coupling isn't always desirable. Some users need standalone build tools for custom environments (outside PyPI <https://pypi.org/>), or they manage packages themselves (like Linux distributions do).

This project fills that gap by providing a standalone build tool following modern Python packaging standards for how build tools communicate with backends and how pyproject.toml defines build requirements.

We keep dependencies minimal to make build easy to install and use in restricted environments.

DIFFERENCES FROM OTHER TOOLS

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.

  uv build

uv build <https://docs.astral.sh/uv/> is essentially equivalent to python -m build --installer=uv. Both follow packaging standards. 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.

  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/>.

  hatch build

hatch build <https://hatch.pypa.io/> is the build command from the Hatch project management tool. It provides a convenient wrapper around the build process as part of the larger Hatch ecosystem for managing Python projects, while build is a standalone tool focused solely on building.

  flit build

flit build <https://flit.pypa.io/> is the build command from the Flit project. One important difference: 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.

  cibuildwheel

cibuildwheel <https://cibuildwheel.pypa.io/> is a different kind of tool. 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. 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.

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.

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 a pyproject.toml file.

  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.

    1.  Create a project directory:

            $ mkdir mypackage
            $ cd mypackage

    2.  Create a pyproject.toml file:

            [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"

    3.  Create a README.md file:

            # My Package

            This is a simple example package.

    4.  Create your Python package:

            $ mkdir src/mypackage
            $ touch src/mypackage/__init__.py

    5.  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):

    1.  Source distribution: mypackage-0.1.0.tar.gz

        This tarball <https://en.wikipedia.org/wiki/Tar_(computing)> (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/>.

    2.  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.

Build used an isolated environment to ensure your package builds consistently regardless of what you have installed on your computer. It:

    1.  Created a temporary virtual environment <https://docs.python.org/3/tutorial/venv.html>

    2.  Installed only your build dependencies (hatchling <https://hatch.pypa.io/latest/>)

    3.  Invoked the build backend to create the distribution files

    4.  Cleaned up the temporary environment when done

  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 <>.

  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] - Use virtualenv <https://virtualenv.pypa.io/> for isolation instead of venv <https://docs.python.org/3/library/venv.html>. This can be useful when using automation tools that rely on virtualenv (such as tox <https://tox.wiki/>), or when your operating system's Python package does not include venv in the standard installation.

                $ pip install 'build[virtualenv]'

    •   build[uv] - Bundle uv <https://docs.astral.sh/uv/> for use with --installer=uv if you don't have another install of uv available.

                $ pip install 'build[uv]'

  Verifying Attestations

Attestations <https://github.com/pypa/build/attestations> are available after 1.2.1 and can be verified with the gh CLI tool:

            $ 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. Support for PEP 740 attestations in build is being tracked in #987 <https://github.com/pypa/build/issues/987>.

  Installing via conda

On conda-forge, this package is called python-build <https://github.com/conda-forge/python-build-feedstock>, therefore it can be installed via conda <https://github.com/conda/conda> or an equivalent:

            $ conda install conda-forge::python-build

  Bootstrapping

This package can build itself only with the tomli <https://github.com/hukkin/tomli> (can be omitted in Python 3.11+) and pyproject-hooks <https://github.com/pypa/pyproject-hooks> dependencies. The --skip-dependency-check flag should be used in this case.

  Compatibility

build is verified to be compatible with the following Python versions:

    •   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.

  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 "sdist") and a wheel <https://packaging.python.org/en/latest/specifications/binary-distribution-format/>:

            $ python -m build

The process:

    1.  Builds the source distribution from your source code

    2.  Extracts the source distribution to a temporary directory

    3.  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.

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/> (installable package) directly from your source directory, skipping the source distribution step. This is faster but doesn't verify your source distribution is complete.

  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.

            $ 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.

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).

  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: [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.

  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.

  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.

            $ 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)

    •   Building manylinux <https://peps.python.org/pep-0513/>/musllinux <https://peps.python.org/pep-0656/> wheels (standardized Linux wheel formats)

    •   Building wheels with compiled extensions (C, C++, Rust, etc.)

cibuildwheel uses build under the hood but handles all the complexity of:

    •   Running builds in manylinux containers <https://github.com/pypa/manylinux>

    •   Testing wheels after building

    •   Handling platform-specific quirks

    •   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

See cibuildwheel documentation <https://cibuildwheel.readthedocs.io/> for complete details.

  When to use tox

Use tox <https://tox.wiki/> when you need to:

    •   Test your package across multiple Python versions (e.g., 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 (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.

See tox documentation <https://tox.wiki/> for complete details.

  When to use nox

Use nox <https://nox.thea.codes/> when you want tox-like functionality but prefer Python over INI:

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 nox documentation <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)

    •   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 uv documentation <https://docs.astral.sh/uv/> for complete details.

  When to use pip

Use pip when you need to:

    •   Install packages (not build them)

    •   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.

            # 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:

    1.  Uses tox to test across Python versions

    2.  Uses build (via hynek's action) to create sdist

    3.  Uses cibuildwheel to create wheels for all platforms

  Decision tree
[graph].SS Quick reference
Tool | Purpose | Use for
build | Build sdist/wheel for one Python | Creating distributions
cibuildwheel | Build wheels for all platforms | Distributing on PyPI
tox | Test across Python versions | CI/testing automation
nox | Like tox, but Python-based | CI/testing automation
uv | Fast package manager | Installing + optional building
pip | Package installer | Installing packages
twine | Upload to PyPI | Publishing

  See also

    •   Basic Usage <> for build command examples

    •   CI/CD Integration <> for CI/CD integration

    •   Build Backends <> to understand build vs backend

  CI/CD Integration

This guide shows how to integrate build into your continuous integration and deployment workflows.

  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 <https://github.com/hynek/build-and-inspect-python-package> action. This action builds your package, inspects it for common issues, and provides the built artifacts.

            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 <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 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

            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

Test building with multiple Python versions:

            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

For packages with C extensions or platform-specific code, use cibuildwheel:

            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

See build vs Other Tools <> for when to use build vs cibuildwheel.

  Publishing to PyPI

Combine building and publishing in a 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 pip packages to speed up builds:

            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 packages in a Docker container:

            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

Using build within tox environments:

            [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/*"],
            ]

Run the build environment:

            $ tox -e build

  Discovering Built Artifacts

Artifact filenames are dynamic, so pipelines that need to act on the exact files build produced (upload, sign, compute digests) should not guess or glob them. Pass --report PATH to capture a machine-readable list and read it back with a JSON tool such as jq. Pick a PATH outside the output directory so tools that upload dist/* wholesale do not 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, 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 publishing:

            - name: Check package
              run: |
                pip install twine
                twine check dist/*

Or use hynek's action which includes verification automatically.

  Conditional Builds

Only build on specific events:

            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

This builds on every push and PR, but only publishes when pushing a version tag.

  Troubleshooting CI Builds

  Enable verbose output

Add -vv flag for debugging:

            - name: Build package
              run: python -m build -vv

  Preserve build artifacts

Upload the dist directory even on failure:

            - name: Upload artifacts
              if: always()
              uses: actions/upload-artifact@v4
              with:
                name: dist
                path: dist/

  Cache build environments

For repeated builds, 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 build vs 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 common scenarios when using build behind corporate firewalls, with private package indexes (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 to your private package index <https://packaging.python.org/en/latest/glossary/#term-Package-Index> (an alternative to PyPI <https://pypi.org/>):

            $ export PIP_INDEX_URL=https://pypi.company.com/simple
            $ python -m build

Or for a single command:

            $ PIP_INDEX_URL=https://pypi.company.com/simple python -m build

  Additional indexes

To use both PyPI and a private index:

            $ export PIP_INDEX_URL=https://pypi.org/simple
            $ export PIP_EXTRA_INDEX_URL=https://pypi.company.com/simple
            $ python -m build

Warning:
        Using PIP_EXTRA_INDEX_URL has security implications. A malicious package on any index can shadow packages from other indexes. Use PIP_INDEX_URL exclusively when possible.

  Authentication

  Embedded credentials

You can embed credentials directly in the index URL:

            $ PIP_INDEX_URL=https://username:password@pypi.company.com/simple python -m build

Warning:
        Be careful with embedded credentials in shell history and CI logs. Use environment variable substitution when possible:

            $ PIP_INDEX_URL=https://${PYPI_USER}:${PYPI_TOKEN}@pypi.company.com/simple python -m build

  Using keyring

Build passes --no-input to pip, preventing hidden credential prompts that cause the process to appear stuck. When the keyring CLI is available on PATH, build automatically sets PIP_KEYRING_PROVIDER=subprocess (or UV_KEYRING_PROVIDER=subprocess when using the uv installer) so the installer delegates credential lookups to the system keyring without needing keyring installed inside the isolated build environment.

Install keyring system-wide so it is 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, which is useful in CI or when keyring is installed 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

Create a ~/.netrc file (~/_netrc on Windows) with your credentials:

            machine pypi.company.com
            login username
            password your-password

Ensure the file has restricted permissions:

            $ chmod 600 ~/.netrc

  SSL/TLS certificates

  Custom CA certificates

If your company uses 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 (not recommended for production):

            $ export PIP_TRUSTED_HOST=pypi.company.com
            $ python -m build

  Using virtualenv for better SSL support

If you encounter SSL/TLS errors with Python's built-in venv, install virtualenv:

            $ pip install build[virtualenv]

This provides a more recent version of pip with better SSL support and truststore capabilities.

Note:
        Python 3.11.8+ and 3.12.2+ have improved SSL support and may not need this workaround.

  Proxy servers

  HTTP/HTTPS proxies

Configure 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 is useful for team settings.

  Air-gapped environments

For completely offline builds, you must 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 using --no-isolation, ensure all build dependencies are installed and compatible.

  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 (useful 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. Configure one of the authentication methods above (embedded credentials, keyring, or .netrc) to provide credentials non-interactively.

  401/403 errors

Authentication failed. Verify your credentials and ensure they 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 experiencing 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 tips

    •   Environment Variables <> for a complete list of environment variables

    •   pip documentation <https://pip.pypa.io/> for more pip configuration options

  Backend Configuration

The --config-setting (or -C) flag allows you to pass options to your build backend. The syntax and available options depend entirely on which backend you're using (e.g., setuptools <https://setuptools.pypa.io/>, hatchling, flit <https://flit.pypa.io/>).

Important:
        The --config-setting flag passes options to the build backend that actually builds your package, not to build itself. 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:
        build also accepts the bare -C KEY form (without =) and reads it as -C KEY=, but warns you because pip rejects 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-setting and --config-json cannot 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:

    1.  Check your backend's documentation for the correct syntax

    2.  Verify you're using the latest version of the backend

    3.  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 commands

    •   Command-Line Interface <> for all command-line options

    •   Build Backends <> to understand how backends work

  Troubleshooting

This guide helps you resolve common issues when using build.

  Build fails with missing dependencies

Symptom: Build fails with errors like "ModuleNotFoundError" or "No module named 'X'".

Cause: Your build backend or its dependencies aren't installed in the isolated environment.

Solution 1: Let build handle it automatically (default):

            $ python -m build

Build will read your pyproject.toml and install all required dependencies.

Solution 2: If using --no-isolation, manually install dependencies:

            $ pip install setuptools your-build-backend
            $ python -m build --no-isolation

  "Unmet dependencies" with --no-isolation

Symptom: A --no-isolation build stops with:

            ERROR Unmet dependencies (checked against /usr/local/bin/python3.9):
                anndata>=0.7.4
                    wanted: >=0.7.4
                    found: not installed

Cause: 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. Read each entry as:

    •   found: not installed - the package is invisible to that interpreter's metadata. If you believe it is installed (for example via a Linux distribution or FreeBSD port), it was installed for a different Python, so build cannot see it. Install it for the interpreter in the header, or run that interpreter's python -m build.

    •   found: <version> - the package is installed but its version does not satisfy wanted. Upgrade or downgrade it to match.

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), 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 asks for the exact version of the backend you used. pyproject.toml lists only the requirement specifiers (setuptools >= 40.8.0), and on an isolated build the resolved version lives in the temporary environment that build deletes afterwards.

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 the name==version line for the backend 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: Build command runs but produces no output for a long time.

Possible causes:

    1.  Waiting for authentication: If you're using a private package index, build may be waiting for credentials.

        See Corporate Environments <> for authentication setup.

    2.  Large download: Build backend dependencies are being downloaded.

        Use -v or -vv for verbose output:

                $ python -m build -vv

    3.  Build backend is actually running: Some backends (especially for C extensions) can take time.

        Check system monitor for CPU/disk activity to confirm work is 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.

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:

    1.  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

    2.  Running inside a read-only directory: The source directory is on a read-only filesystem.

        Solution: Copy the source to a writable location first.

    3.  Antivirus software: Some antivirus tools block Python from creating virtual environments.

        Solution: Add Python and your project directory to antivirus exclusions.

  Wheel is not compatible with this platform

Symptom: "is not a supported wheel on this platform" when trying to install the built wheel.

Cause: The wheel was built for a different Python version or platform.

Solution: Build on the same platform and Python version where you'll install, 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.

Cause: Files aren't included in the source distribution manifest.

Solution: The fix depends on your build backend:

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. Ensure files are committed.

Test your sdist: Always verify the sdist contains all 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:

    1.  Missing ``__init__.py``: Ensure every package directory has an __init__.py file (not needed for namespace packages).

    2.  Wrong directory structure: For src-layout, ensure you have src/packagename/ not just packagename/.

    3.  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: Build fails with errors about conflicting package versions.

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.

  Build works locally but fails in CI

Common causes:

    1.  Different Python versions: CI may use a different Python version than your local environment.

        Solution: Specify Python version in CI config, test locally with that version.

    2.  Missing system dependencies: C extensions may need system libraries.

        Solution: Install 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

    3.  File path issues: Windows vs Unix path separators.

        Solution: Use pathlib.Path in pyproject.toml configurations and build scripts.

    4.  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.

  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). If you see this:

    1.  Check your pyproject.toml [build-system] section is correct:

                [build-system]
                requires = ["hatchling"]
                build-backend = "hatchling.build"

    2.  If using --no-isolation, install the backend:

                $ pip install hatchling
                $ python -m build --no-isolation

  No build-system section in pyproject.toml

Symptom: Build succeeds but you're not sure which backend is being used.

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.

  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.

  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

  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.

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

  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:

    1.  Check the issue tracker <https://github.com/pypa/build/issues> for similar problems

    2.  Enable verbose output (-vv) and include the full output when reporting issues

    3.  Include your pyproject.toml and 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.gz source distribution, or (with --metadata) a .whl to 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 to PATH (created if missing and kept afterwards) instead of a random temporary directory; reusing it across rebuilds gives compiler caches such as ccache/sccache a 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 --sdist or --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 to build if separated by a space; use --config-setting=--my-setting -C--my-other-setting instead. A setting passed without =VALUE is given an empty value, but this form is not supported by pip; write KEY= instead for compatibility

    •   --config-json JSON_STRING - settings to pass to the backend as a JSON object. This is an alternative to --config-setting that allows complex nested structures. Cannot be used together with --config-setting

  python -m build installation options

    •   --installer INSTALLER - Python package installer to use (defaults to pip)

    •   --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 a constraints.txt when 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.

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:

    •   -v streams the output of the environment-creation and dependency-installation subprocesses.

    •   -vv additionally passes -v through 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

build reports this for isolated builds only; with --no-isolation build installs nothing, so inspect the active interpreter with your installer instead (for example pip list).

  Build Report

--report PATH writes a JSON description of the built artifacts to PATH after a successful build. The artifact names are dynamic (they encode version, Python, ABI and platform tags), so this gives scripts and CI a stable way to refer to the produced files instead of globbing dist/. Build writes the report to a file it controls, not to standard output, because the build backend may write to stdout/stderr too.

            $ python -m build --report build-report.json
            $ twine upload $(jq -r '.artifacts[].path' build-report.json)

Note:
        Keep the report out of the output directory: tools commonly upload dist/* wholesale (twine upload dist/*), and a stray non-distribution file there can break that. The write is atomic, so a reader never sees a partial file; builds running in parallel still need a distinct PATH each to avoid overwriting one another's report.

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"
                  }
                }
              ]
            }

version is the schema version. Each artifact lists its name (basename), path (relative to the current directory, mirroring --outdir), kind (sdist or wheel), size in bytes, and hashes keyed by algorithm. --report cannot be combined with --metadata.

To inspect a wheel listed in the report, pass its path straight to --metadata: when the source argument is a .whl file, build reads METADATA from the archive and prints it as JSON instead of building anything.

            $ python -m build --report build-report.json
            $ python -m build --metadata "$(jq -r '.artifacts[] | select(.kind=="wheel") | .path' build-report.json)"

  Alternative CLI Script

A pyproject-build CLI script is also available, which is functionally identical to python -m build. 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.

  Common Patterns

For practical usage examples and workflows, see Basic Usage <>.

  See Also

    •   Basic Usage <> for common workflows

    •   Backend Configuration <> for passing options to backends

    •   Environment Variables <> for environment variables that affect build

  API Documentation

build provides a Python API for programmatic use in build tools, CI systems, and automation scripts.

Note:
        When not using an isolated environment, the build backend must be installed in the current environment. When using DefaultIsolatedEnv, you must explicitly install build dependencies into the isolated environment via install() before calling build() — the environment is created empty.

  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 (default):

DefaultIsolatedEnv creates a clean virtual environment, but it is initially empty. You must install the project's build dependencies before invoking 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 a temporary directory. The location must be empty; DefaultIsolatedEnv removes it on a clean exit and keeps it when the block raises:

            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

        Exception raised when a backend operation fails.

    exception build.BuildException
        Bases: Exception

        Exception raised by build when a build cannot proceed.

    exception build.BuildSystemTableValidationError
        Bases: BuildException

        Exception raised when the [build-system] table in pyproject.toml is invalid.

    exception build.FailedProcessError(exception, description)
        Bases: Exception

        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

        The PEP 517 consumer API.

        Parameters

            •   source_dir (str | PathLike[str]) -- The source directory

            •   python_executable (str) -- The python executable where the backend lives

            •   runner (Callable[[Sequence[str], str | None, Mapping[str, str] | None], None]) -- Runner for backend subprocesses

    The runner, if provided, must accept the following 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 provided source_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 in

                •   config_settings (Mapping[str, str | Sequence[str]] | None) -- Config settings for the build backend

                •   metadata_directory (str | None) -- If provided, should be the return value of a previous prepare call 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 in the pyproject.toml's build-system.requires field or the default build dependencies if pyproject.toml is missing or build-system is undefined.

        check_dependencies(distribution, config_settings=None)
            Return the dependencies which are not satisfied from the combined set of build_system_requires and get_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)
            Return the dependencies defined by the backend in addition to build_system_requires for a given distribution.

            Parameters

                •   distribution (Literal['sdist', 'wheel', 'editable']) -- Distribution to get the dependencies of (sdist or wheel)

                •   config_settings (Mapping[str, str | Sequence[str]] | None) -- Config settings for the build backend

            Return type
                set[str]

        metadata_path(output_directory)
            Generate the metadata directory of a distribution and return its path.

            If the backend does not support the prepare_metadata_for_build_wheel hook, a wheel will be built and the metadata will be extracted from it.

            Parameters
                output_directory (str | PathLike[str]) -- Directory to put the metadata distribution in

            Return type
                str

            Returns
                The path of the metadata directory

        prepare(distribution, output_directory, config_settings=None)
            Prepare metadata for a distribution.

            Parameters

                •   distribution (Literal['sdist', 'wheel', 'editable']) -- Distribution to build (must be wheel)

                •   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 used to invoke the backend.

        property source_dir: str
            Project source directory.

    exception build.TypoWarning
        Bases: Warning

        Warning raised when a possible typo is found.

    build.check_dependency(req_string, ancestral_req_strings=(), parent_extras=frozenset({}))
        Verify that a dependency and all of its dependencies are met.

        Parameters

            •   req_string (str) -- Requirement string

            •   parent_extras (Set[str]) -- Extras (eg. "test" in myproject[test])

        Yields
            Unmet dependencies

  build.env module

    class build.env.DefaultIsolatedEnv(*, installer='pip', path=None)
        Bases: IsolatedEnv

        Isolated environment which supports several different underlying implementations.

        install(requirements, constraints=(), *, _fresh=False)
            Install packages from PEP 508 requirements in the isolated build environment.

            Parameters
                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.

            Return type
                None

        installed_versions(requirements)
            Map the canonical name of each requirement to the version installed in the environment.

            Requirements that resolved to nothing installed are omitted. Reads the distribution metadata directly from the environment's purelib so no subprocess is spawned in the isolated environment.

            Parameters
                requirements (Collection[str]) -- PEP 508 requirement specifications to look up; non-PEP 508 entries (such as local paths or URLs accepted by install()) 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.

        Parameters

            •   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.

  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.

  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:
        These PIP_* variables only apply when using the default pip installer. 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).

  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.

  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.

  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 REQUESTS_CA_BUNDLE=/path/to/company-ca-bundle.crt
            $ python -m build

  SSL_CERT_FILE

Another variable for specifying a custom CA certificate bundle. Recognized by various Python libraries.

            $ export SSL_CERT_FILE=/path/to/company-ca-bundle.crt
            $ python -m build

  Python Runtime Configuration

  PYTHONWARNINGS

Controls Python's warning filters <https://docs.python.org/3/library/warnings.html>. Use this in CI to catch deprecated features or potential issues in your build configuration by turning warnings into errors.

Catch deprecation warnings:

            $ PYTHONWARNINGS=error::DeprecationWarning python -m build

Catch setuptools-specific warnings:

            $ PYTHONWARNINGS=error:::setuptools.config.setupcfg python -m build

  PYTHONUTF8

Enables UTF-8 mode <https://docs.python.org/3/library/os.html#utf-8-mode> on Windows. Use this when building packages with non-ASCII characters in filenames or file content on Windows systems.

            $ set PYTHONUTF8=1
            $ python -m build

  TMPDIR / TEMP / TMP

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

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

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

Each build backend may define its own environment variables for configuration. Check your backend's documentation:

    •   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>. All environment variables from your current shell pass directly to the build backend, including:

    •   PYTHONPATH - Affects Python module discovery

    •   PATH - Determines which executables are found

    •   Any custom variables your build backend uses

build does not set VIRTUAL_ENV in 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.

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.

  The Build Process

When you run python -m build, the following happens: [graph]

The process in detail:

    1.  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 (requires list)

        •   Build backend configuration (backend-path if specified)

    2.  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_ENV environment 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.

    3.  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 <#debug-a-failed-build> for how to keep them.

    4.  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

    5.  Cleanup

        build removes the temporary isolated environment, leaving only the distribution files in dist/.

  Default Build Strategy

By default, python -m build creates both sdist and wheel following this strategy:

    1.  Build sdist from source directory

    2.  Extract sdist to temporary directory

    3.  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.

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.

  How isolation works

    1.  build creates a temporary virtual environment using Python's venv module

    2.  Installs only the packages listed in [build-system] requires

    3.  Runs the build backend in this isolated environment

    4.  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.

  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

Under --no-isolation build installs nothing; it instead validates that the declared dependencies are already present (use --skip-dependency-check to opt out), so a missing dependency is reported rather than silently producing a broken build. That check compares each declared requirement against the installed metadata of the single interpreter running build, discovered through importlib.metadata. This explains two otherwise confusing outcomes, which the error message spells out per requirement (wanted versus found). A package can be present but reported missing because its installed version does not satisfy the specifier (found shows that version). And 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. The error names the interpreter checked so the mismatch is visible rather than left to guesswork.

See Basic Usage <> for usage.

  Build Frontends vs Build Backends

build is a build frontend that reads pyproject.toml, creates isolated environments, invokes build backends, and handles the command-line interface. It does NOT know how to build your specific package.

Your build backend (setuptools, hatchling, flit, etc.) knows how to build your package 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 allows different backends for different project needs, ensures frontend improvements benefit all backends, and 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/> (the standardized build system interface that defines how build tools communicate with backends). The key concepts:

  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: Packages needed to build (installed in isolated environment)

    •   build-backend: Python import path to backend object

  Backend hooks

build calls these standardized hooks in your backend:

    •   get_requires_for_build_sdist(config_settings) - Returns additional dependencies needed for sdist

    •   build_sdist(sdist_directory, config_settings) - Creates sdist, returns filename

    •   get_requires_for_build_wheel(config_settings) - Returns additional dependencies needed for wheel

    •   build_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 full build

  Config settings

The config_settings parameter in hooks receives settings from --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 extract package metadata without a full build:

            $ python -m build --metadata

This:

    1.  Creates isolated environment

    2.  Calls prepare_metadata_for_build_wheel() if available

    3.  Otherwise, calls build_wheel() and extracts metadata

    4.  Outputs metadata in JSON format to stdout

Useful for tools that need package metadata without building the 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 once it finishes. The installer's own output reports the versions, but only at -vv, interleaved with the rest of its log.

So once build has installed the dependencies it prints a concise summary of the resolved version of each, as name==version, the single piece a backend bug report usually asks for. build obtains these by reading the installed distribution metadata from the environment's site-packages through importlib.metadata rather than by spawning a process inside the environment, so the summary is cheap and works the same for the venv/virtualenv and uv backends. Because it reads installed metadata, it reflects what build used, including any transitive resolution the specifier allowed.

This is an isolated-build concern. Under --no-isolation build installs nothing - it builds against the interpreter that is already running it - so the versions are whatever that interpreter has, discoverable with your installer.

  Error Handling

build performs minimal error handling:

    •   Validates pyproject.toml exists and is valid TOML

    •   Checks [build-system] section exists

    •   Verifies 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 use a different installer:

            $ python -m build --installer=uv

Requirements for custom installers:

    •   Must accept install command

    •   Must accept package specifications as arguments

    •   Must support --no-deps flag

Currently, pip and uv are the primary supported installers.

  Virtual Environment Backend

build can use different virtual environment implementations:

    •   venv (default): Python's built-in venv module

    •   virtualenv: Third-party virtualenv package

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).

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 executables

    •   PYTHONPATH - For Python module discovery

    •   HTTP/HTTPS proxy variables

    •   SSL certificate variables

    •   Custom pip variables (PIP_*)

Set by build:

    •   VIRTUAL_ENV - Points to 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:

    1.  Isolated environments: Same dependencies every time

    2.  Declared dependencies: [build-system] requires is explicit

    3.  Standardized interface: PEP 517 hooks are well-defined

    4.  Minimal intervention: build doesn't modify your code or backend behavior

However, full reproducibility also requires pinned backend versions in requires, reproducible builds in your backend (some backends support SOURCE_DATE_EPOCH), consistent Python version, and consistent platform for platform-specific wheels.

  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. 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:

    1.  Environment creation: Creating virtualenv (1-2 seconds)

    2.  Dependency installation: Installing backend and dependencies (varies)

    3.  Backend build: Actual package building (varies greatly)

Ways to speed up builds include using --installer=uv for faster dependency installation, using --no-isolation if dependencies are pre-installed (loses reproducibility), installing build[virtualenv] for faster environment creation, and using --wheel to skip sdist when not needed.

The default (isolation with pip) prioritizes reproducibility over speed.

  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.toml specification

  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.

When you run python -m build, the frontend (build) reads your pyproject.toml <https://packaging.python.org/en/latest/specifications/pyproject-toml/> to determine which backend to use, then invokes that backend to perform the actual building. The separation between frontend and backend allows different projects to use different build systems (like setuptools <https://setuptools.pypa.io/>, hatchling <https://hatch.pypa.io/latest/>, or flit <https://flit.pypa.io/>) while all working with the same frontend tool.

  How Backends Work with build

The relationship between build and backends follows a clear contract: [graph]

Your pyproject.toml specifies which backend to use in the [build-system] section. For example, using hatchling as the backend looks like this:

            [build-system]
            requires = ["hatchling"]
            build-backend = "hatchling.build"

The requires field lists the packages needed to perform a build, which build installs in an isolated environment. The build-backend field specifies the Python import path to the backend object that implements the PEP 517 interface.

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(). The backend then creates the distribution files and returns their filenames.

  Available Build Backends

Several build backends are available, each with different features and philosophies.

  setuptools

setuptools is the traditional and most widely used build backend. It has extensive features and backwards compatibility with older packaging standards, making it suitable for complex projects and projects that need to maintain compatibility 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 excels at handling complex build requirements including C extensions, namespace packages, and entry points. However, it can be complex to configure correctly and has many legacy features that may be confusing.

See the setuptools documentation <https://setuptools.pypa.io/> for details.

  hatchling

hatchling is the build backend from the Hatch project. It provides a modern, user-friendly interface with sensible defaults and good performance.

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 emphasizes convention over configuration and includes built-in support for common patterns like src-layout and editable installs. The main limitation is that it's newer and may have less community documentation than setuptools.

See the hatchling documentation <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.

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. 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.

See the flit documentation <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.

See the pdm-backend documentation <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.

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.

See the poetry documentation <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.

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.

See the scikit-build-core documentation <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.

See the meson-python documentation <https://meson-python.readthedocs.io/> for details.

  Choosing a Backend

The right backend depends on your project's needs. 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.

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.

  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. 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 allows passing configuration settings to backends via the --config-setting or -C flag. The syntax and available options depend entirely on the backend.

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.

Config settings map keys to values, so every key needs a value. Drop the =VALUE and the key gets an empty value, which suits backends that act on a flag's presence alone. build accepts this shorthand but warns about it, because pip only takes the explicit KEY=VALUE form. Write KEY= for an empty value and your command runs under both tools.

  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.

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.

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.

  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.

  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.

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.

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.

  Backend Development

For most users, using an existing backend is the right choice. However, if you have unique requirements not met by any existing backend, you can implement your own PEP 517 backend.

A minimal backend must implement these functions in 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

Implementing a full-featured backend requires handling metadata extraction, dependency specification, editable installs, and error handling. Unless you have very specialized needs, contributing to an existing backend or using its plugin system is usually more practical than creating a new one.

  Why setuptools commands don't work with build

If you're migrating from setup.py, you may be used to running commands like:

            $ python setup.py clean
            $ python setup.py test
            $ python setup.py develop

These commands do not work with build: build uses the PEP 517 interface, which only defines hooks for building distributions (build_wheel and build_sdist). It does not support arbitrary setuptools commands.

Modern alternatives:

    •   clean: Remove the dist/ directory manually or let your CI do it

    •   test: Use pytest directly or via tox

    •   develop: Use pip install --editable . for editable installs

    •   sdist/bdist_wheel: Use python -m build

If you 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 also

    •   How it Works <> for how build invokes backends

    •   Backend Configuration <> for backend-specific configuration examples

    •   PEP 517 <https://peps.python.org/pep-0517/> for the backend interface specification

    •   PEP 621 <https://peps.python.org/pep-0621/> for standard project metadata

  Contributing

build welcomes contributions from the community. This guide explains how to contribute to the project.

  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 cloned, navigate to the project directory and install build in development mode along with all development dependencies.

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.

  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. Make your changes in this branch, ensuring 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.

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.

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.

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.

            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. 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.

  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.

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.

  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.

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.

  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.

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.

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

Before starting work on a significant feature or change, check the issue tracker to see if it's already been discussed. If not, consider opening an issue to discuss your proposed changes with maintainers. This helps ensure your contribution aligns with the project's direction and avoids duplicate effort.

When reporting bugs, include detailed information about the issue. Provide the full error message and traceback, describe what you expected to happen versus what actually happened, list the steps to reproduce the problem, and include information about your environment such as Python version and operating system.

For feature requests, explain the use case and why the feature would benefit build's users. Include examples of how the feature would be used if implemented.

  Community Guidelines

build is part of the PyPA (Python Packaging Authority) <https://www.pypa.io/> ecosystem and follows the PyPA Code of Conduct. Be respectful and constructive in all interactions with other contributors. Focus feedback on the code and ideas, not on individuals.

The project values clear communication, thorough testing, and well-documented code. Take time to explain your changes clearly in commit messages and pull requests. Write tests that future contributors can understand. Document not just what your code does, but why you made particular design choices.

  Getting Help

If you have questions about contributing, several resources are available. The issue tracker can be used for questions, though complex discussions may be better suited to other venues. The project's documentation includes extensive information about build's design and implementation.

For real-time discussion, consider joining the PyPA Discord server <https://discord.gg/pypa> where many packaging tool maintainers and contributors participate. 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 version bumping, changelog building, commit creation, and publishing. Maintainers trigger releases through GitHub Actions or run the release script locally for testing.

  Changelog Fragments

Contributors add changelog fragments that get collected during releases. Create a file in docs/changelog/ named <pr_number>.<type>.rst where type is feature for new functionality, bugfix for fixes, doc for documentation changes, removal for deprecations, or misc 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 are 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. Select a version bump type: auto for patch bumps, major for X+1.0.0, minor for X.Y+1.0, or patch 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 process takes approximately five minutes from workflow trigger to published package.

  Local Release

Run the release script locally for testing or manual control. Ensure tox is installed and 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.yml for manual triggering, the CD workflow at .github/workflows/cd.yml that triggers on tag pushes, the release script at tasks/release.py for version bumping and tag creation, and the [env.release] environment in tox.toml providing dependencies.

  1.5.1 (2026-07-09)

  Features

    •   Add --report=PATH to write a machine-readable JSON report of built artifacts; --metadata now also accepts .whl files - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#198 <https://github.com/pypa/build/issues/198>)

    •   The srcdir argument now accepts .tar.gz source 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-isolation builds 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-dir to 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-dir to 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-dir and --sdist-extract-dir for 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-dir with a clear error instead of a raw FileExistsError - 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 --metadata JSON 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 False due 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 \n instead of os.linesep, avoiding doubled \r\r\n line 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 BuildException for 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>; use python -m build --metadata instead - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#557 <https://github.com/pypa/build/issues/557>)

    •   Warn on config settings without a value (e.g. -C--my-flag); use -C--my-flag= instead for compatibility with pip - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#640 <https://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

    •   Drop Python 3.9 support - by @henryiii <https://github.com/sponsors/henryiii> (#1036 <https://github.com/pypa/build/issues/1036>)

  Bugfixes

    •   Make --ignore-installed opt-in from the API via fresh=True - by @henryiii <https://github.com/sponsors/henryiii> (#1056 <https://github.com/pypa/build/issues/1056>)

  Miscellaneous

    •   #1033 <https://github.com/pypa/build/issues/1033>

  1.4.4 (2026-04-22)

  Bugfixes

    •   Fix release pipeline generating CHANGELOG.rst entries with inconsistent heading levels, which broke sphinx -W and pinned Read the Docs stable at 1.4.0 - by @gaborbernat <https://github.com/sponsors/gaborbernat>. (#1031 <https://github.com/pypa/build/issues/1031>)

    •   Revert #1039 <https://github.com/pypa/build/pull/1039> from build 1.4.3, no longer check direct_url (for now) - by @henryiii <https://github.com/sponsors/henryiii> (#1039 <https://github.com/pypa/build/issues/1039>)

    •   Add --ignore-installed to pip install command to prevent issues with packages already present in the isolated build environment - by @henryiii <https://github.com/sponsors/henryiii> (#1037 <https://github.com/pypa/build/issues/1037>) (#1040 <https://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. (#1044 <https://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 kind parameter to log messages to separate semantic and representation - by @abitrolly <https://github.com/sponsors/abitrolly> (#973 <https://github.com/pypa/build/issues/973>)

  Bugfixes

    •   Strip PYTHONPATH from the environment during isolated builds to prevent host packages from leaking into the build - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#405 <https://github.com/pypa/build/issues/405>)

    •   Pass --no-input to pip to prevent hidden credential prompts that cause hangs, and automatically set PIP_KEYRING_PROVIDER=subprocess (or UV_KEYRING_PROVIDER=subprocess for the uv installer) when the keyring CLI is on PATH -- by @gaborbernat <https://github.com/sponsors/gaborbernat> (#409 <https://github.com/pypa/build/issues/409>)

    •   check_dependency now 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> (#860 <https://github.com/pypa/build/issues/860>)

    •   Fix misleading missing dependency error display where transitive dependency chains showed the top-level package on a separate line, making it appear as if the top-level package itself was missing - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#875 <https://github.com/pypa/build/issues/875>)

    •   Fix towncrier template to generate changelog categories in definition order - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#1007 <https://github.com/pypa/build/issues/1007>)

    •   Resolve thread-safety races in the build API - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#1015 <https://github.com/pypa/build/issues/1015>)

    •   Validate backend-path entries exist on disk with a clear error - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#1016 <https://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_PYTHON is set, for example. (#977 <https://github.com/pypa/build/issues/977>)

    •   Fix _has_valid_outer_pip returning True when pip is missing, causing build to try using a non-existent pip instead of falling back to virtualenv. (#1003 <https://github.com/pypa/build/issues/1003>)

  1.4.1 (2026-03-24)

  Features

    •   Allow setting build constraints - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#963 <https://github.com/pypa/build/issues/963>)

    •   Automate releases with pre-release workflow and trusted publishing - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#991 <https://github.com/pypa/build/issues/991>)

  Bugfixes

    •   Fix pip hack workaround - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#980 <https://github.com/pypa/build/issues/980>)

  Documentation

    •   Fix documentation grammar and typos (#979 <https://github.com/pypa/build/issues/979>)

    •   Reorganize documentation using Diataxis framework - by @gaborbernat <https://github.com/sponsors/gaborbernat> (#988 <https://github.com/pypa/build/issues/988>)

    •   Document release process and workflow security practices in contributing guide (#991 <https://github.com/pypa/build/issues/991>)

  Miscellaneous

    •   #991 <https://github.com/pypa/build/issues/991>

  1.4.0 (2026-01-08)

    •   Add --quiet flag (#947 <https://github.com/pypa/build/pull/947>)

    •   Add option to dump PEP 517 metadata with --metadata (#940 <https://github.com/pypa/build/pull/940>, #943 <https://github.com/pypa/build/pull/943>)

    •   Support UV environment variable (#971 <https://github.com/pypa/build/pull/971>)

    •   Remove a workaround for 3.14b1 (#960 <https://github.com/pypa/build/pull/960>)

    •   In 3.14 final release, color defaults to True already (#962 <https://github.com/pypa/build/pull/962>)

    •   Pass sp-repo-review (#942 <https://github.com/pypa/build/pull/942>)

    •   In pytest configuration, log_level is better than log_cli_level (#950 <https://github.com/pypa/build/pull/950>)

    •   Split up typing and mypy (#944 <https://github.com/pypa/build/pull/944>)

    •   Use types-colorama (#945 <https://github.com/pypa/build/pull/945>)

    •   In docs, first argument for _has_dependency is a name (PR #970 <https://github.com/pypa/build/pull/970>)

    •   Fix test failure when flit-core is installed (PR #921 <https://github.com/pypa/build/pull/921>)

  1.3.0 (2025-08-01)

    •   Add --config-json (PR #916 <https://github.com/pypa/build/pull/916>, fixes issue #900 <https://github.com/pypa/build/issues/900>)

    •   Drop Python 3.8 (PR #891 <https://github.com/pypa/build/pull/891>)

    •   Test on Python 3.14, colorful help on 3.14+ (PR #895 <https://github.com/pypa/build/pull/895>)

    •   Fix ModuleNotFoundError when pip is not installed (PR #898 <https://github.com/pypa/build/pull/898>)

    •   Disable use of pip install --python for debundled pip (PR #861 <https://github.com/pypa/build/pull/861>)

    •   Don't pass no-wheel to virtualenv if it would warn (PR #892 <https://github.com/pypa/build/pull/892>)

    •   Optimize our tests to run faster (PR #871 <https://github.com/pypa/build/pull/871>, #872 <https://github.com/pypa/build/pull/872>, #738 <https://github.com/pypa/build/pull/738>)

    •   Allow running our tests without virtualenv (PR #911 <https://github.com/pypa/build/pull/911>)

    •   Fix issues in our tests (PR #824 <https://github.com/pypa/build/pull/824>, #918 <https://github.com/pypa/build/pull/918>, #870 <https://github.com/pypa/build/pull/870>, #915 <https://github.com/pypa/build/pull/915>, #862 <https://github.com/pypa/build/pull/862>, #863 <https://github.com/pypa/build/pull/863>, #899 <https://github.com/pypa/build/pull/899>, #896 <https://github.com/pypa/build/pull/896>, #854 <https://github.com/pypa/build/pull/854>)

    •   Use SPDX identifiers for our license metadata (PR #914 <https://github.com/pypa/build/pull/914>)

    •   Use dependency-groups for our development (PR #880 <https://github.com/pypa/build/pull/880>)

    •   Mention conda and update uv mention in README/docs (PR #842 <https://github.com/pypa/build/pull/842>, #816 <https://github.com/pypa/build/pull/816>, #917 <https://github.com/pypa/build/pull/917>)

  1.2.2 (2024-09-06)

    •   Add editable to builder.get_requires_for_build's static types (PR #764 <https://github.com/pypa/build/pull/764>, fixes issue #763 <https://github.com/pypa/build/issues/763>)

    •   Include artifact attestations in our release (PR #782 <https://github.com/pypa/build/pull/782>)

    •   Fix typing compatibility with typed pyproject-hooks (PR #788 <https://github.com/pypa/build/pull/788>)

    •   Mark more tests with network (PR #808 <https://github.com/pypa/build/pull/808>)

    •   Add more intersphinx links to docs (PR #804 <https://github.com/pypa/build/pull/804>)

    •   Make uv optional for tests (PR #807 <https://github.com/pypa/build/pull/807> and #813 <https://github.com/pypa/build/pull/813>)

  1.2.1 (2024-03-28)

    •   Avoid error when terminal width is undetectable on Python < 3.11 (PR #761 <https://github.com/pypa/build/pull/761>)

  1.2.0 (2024-03-27)

    •   Add --installer option, supporting pip and uv. Added uv extra. (PR #751 <https://github.com/pypa/build/pull/751>)

    •   Improve console output and provide -v for dependency installation (PR #749 <https://github.com/pypa/build/pull/749>)

    •   Avoid compiling unused bytecode when using pip (PR #752 <https://github.com/pypa/build/pull/752>)

    •   Dropped support for Python 3.7 (PR #743 <https://github.com/pypa/build/pull/743>)

  1.1.1 (2024-02-29)

    •   Fixed invoking outer pip from user site packages (PR #746 <https://github.com/pypa/build/pull/746>, fixes issue #745 <https://github.com/pypa/build/issues/745>)

    •   Corrected the minimum pip version required to use an outer pip (PR #746 <https://github.com/pypa/build/pull/746>, fixes issue #745 <https://github.com/pypa/build/issues/745>)

  1.1.0 (2024-02-29)

    •   Use external pip if available instead of installing, speeds up environment setup with virtualenv slightly and venv significantly. (PR #736 <https://github.com/pypa/build/pull/736>)

    •   Stopped injecting wheel as a build dependency automatically, in the case of missing pyproject.toml -- by @webknjaz <https://github.com/sponsors/webknjaz>. (PR #716 <https://github.com/pypa/build/pull/716>)

    •   Use importlib_metadata on Python <3.10.2 for bugfixes not present in those CPython standard libraries (not required when bootstrapping) -- by @GianlucaFicarelli <https://github.com/sponsors/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)

    •   Avoid CPython 3.8.17, 3.9.17, 3.10.12, and 3.11.4 tarfile symlink bug triggered by adding data_filter 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)

    •   Removed the toml library fallback; toml can no longer be used as a substitute for tomli (PR #567 <https://github.com/pypa/build/pull/567>)

    •   Added runner parameter to util.project_wheel_metadata (PR #566 <https://github.com/pypa/build/pull/566>, fixes issue #553 <https://github.com/pypa/build/issues/553>)

    •   Modified ProjectBuilder constructor signature, added alternative ProjectBuilder.from_env constructor, redefined env.IsolatedEnv interface, and exposed env.DefaultIsolatedEnv, replacing env.IsolatedEnvBuilder. The aim has been to shift responsibility for modifying the environment from the project builder to the IsolatedEnv entirely and to ensure that the builder will be initialised from an IsolatedEnv in a consistent manner. Mutating the project builder is no longer supported. (PR #537 <https://github.com/pypa/build/pull/537>)

    •   virtualenv is no longer imported when using -n, for faster builds (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, including tests. 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-metadata has been increased to 4.6 and Python 3.10 due to a bug in the standard library version with URL requirements in extras. This is 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 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, fixing 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>)

    •   Test on Python 3.12 betas/RCs (PR #624 <https://github.com/pypa/build/pull/624>)

    •   Filter out malicious files when extracting tar archives when Python supports it (PR #609 <https://github.com/pypa/build/pull/609>)

    •   Specify encoding, fixing issues when PYTHONWARNDEFAULTENCODING is 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)

    •   Replace pep517 dependency with pyproject_hooks, into which pep517 has been renamed (PR #539 <https://github.com/pypa/build/pull/539>, Fixes #529 <https://github.com/pypa/build/issues/529>)

    •   Change build backend from setuptools to flit (PR #470 <https://github.com/pypa/build/pull/470>, Fixes #394 <https://github.com/pypa/build/issues/394>)

    •   Dropped support for Python 3.6 (PR #532 <https://github.com/pypa/build/pull/532>)

  0.9.0 (2022-10-27)

    •   Hide a Python 3.11.0 unavoidable warning with venv (PR #527 <https://github.com/pypa/build/pull/527>)

    •   Fix infinite recursion error in check_dependency with circular dependencies (PR #512 <https://github.com/pypa/build/pull/512>, Fixes #511 <https://github.com/pypa/build/issues/511>)

    •   Only import colorama on Windows (PR #494 <https://github.com/pypa/build/pull/494>, Fixes #493 <https://github.com/pypa/build/issues/493>)

    •   Flush output more often to reduce interleaved output (PR #494 <https://github.com/pypa/build/pull/494>)

    •   Small API cleanup, like better __all__ and srcdir being read only. (PR #477 <https://github.com/pypa/build/pull/477>)

    •   Only use importlib_metadata when needed (PR #401 <https://github.com/pypa/build/pull/401>)

    •   Clarify in printout when build dependencies are being installed (PR #514 <https://github.com/pypa/build/pull/514>)

  0.8.0 (2022-05-22)

    •   Accept os.PathLike[str] in addition to str for paths in public API (PR #392 <https://github.com/pypa/build/pull/392>, Fixes #372 <https://github.com/pypa/build/issues/372>)

    •   Add schema validation for build-system table 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>, tomllib PR #443 <https://github.com/pypa/build/pull/443>, warnings PR #420 <https://github.com/pypa/build/pull/420>)

    •   Improved error printouts (PR #442 <https://github.com/pypa/build/pull/442>)

    •   Avoid importing packaging unless needed (PR #395 <https://github.com/pypa/build/pull/395>, Fixes #393 <https://github.com/pypa/build/issues/393>)

  Breaking Changes

    •   Failure to create a virtual environment in the build.env module now raises build.FailedProcessError (PR #442 <https://github.com/pypa/build/pull/442>)

  0.7.0 (2021-09-16)

    •   Add build.util module with a high-level utility API (PR #340 <https://github.com/pypa/build/pull/340>)

  0.6.0.post1 (2021-08-05)

    •   Fix 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 the output by setting the FORCE_COLOR environment variable (PR #335 <https://github.com/pypa/build/pull/335>)

    •   Added logging to build and build.env (PR #333 <https://github.com/pypa/build/pull/333>)

    •   Switch 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

    •   Dropped support for Python 2 and 3.5.

  0.5.1 (2021-06-22)

    •   Fix 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, use an isolated temporary directory (PR #321 <https://github.com/pypa/build/pull/321>, Fixes #320 <https://github.com/pypa/build/issues/320>)

  0.5.0 (2021-06-19)

    •   Add ProjectBuilder.metadata_path helper (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_sdist method (PR #304 <https://github.com/pypa/build/pull/304>)

    •   Use appropriate installation scheme 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 build will 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_package no longer does CLI error handling (print nice message and exit the program)

    •   Importing build.__main__ no longer has any side-effects, it no longer overrides warnings.showwarning or runs colorama.init on import (PR #312 <https://github.com/pypa/build/pull/312>)

  0.4.0 (2021-05-23)

    •   Validate 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>)

    •   Set and test minimum versions of build's runtime dependencies (PR #267 <https://github.com/pypa/build/pull/267>, Fixes #263 <https://github.com/pypa/build/issues/263>)

    •   Use symlinks on creating venv's when available (PR #274 <https://github.com/pypa/build/pull/274>, Fixes #271 <https://github.com/pypa/build/issues/271>)

    •   Error sooner if pip upgrade is required and fails (PR #288 <https://github.com/pypa/build/pull/288>, Fixes #256 <https://github.com/pypa/build/issues/256>)

    •   Add a runner argument to ProjectBuilder (PR #290 <https://github.com/pypa/build/pull/290>, Fixes #289 <https://github.com/pypa/build/issues/289>)

    •   Hide irrelevant pep517 error traceback and improve error messages (PR #296 <https://github.com/pypa/build/pull/296>)

    •   Try to use colorama to 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 not containing either a pyproject.toml or setup.py will be reported as invalid. This affects projects specifying only a setup.cfg, such projects are recommended to add a pyproject.toml. The new behavior is on par with what pip currently does, so if you are affected by this, your project should not be pip installable.

    •   The --skip-dependencies option has been renamed to --skip-dependency-check (PR #297 <https://github.com/pypa/build/pull/297>)

    •   The skip_dependencies argument of build.__main__.build_package has been renamed to skip_dependency_check (PR #297 <https://github.com/pypa/build/pull/297>)

    •   build.ConfigSettings has been renamed to build.ConfigSettingsType (PR #298 <https://github.com/pypa/build/pull/298>)

    •   build.ProjectBuilder.build_dependencies to 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 to 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)

    •   Support direct usage from pipx run in 0.16.1.0+ (PR #247 <https://github.com/pypa/build/pull/247>)

    •   Use UTF-8 encoding when reading pyproject.toml (PR #251 <https://github.com/pypa/build/pull/251>, Fixes #250 <https://github.com/pypa/build/issues/250>)

  0.3.0 (2021-02-19)

    •   Upgrade pip based on venv pip version, avoids error on Debian Python 3.6.5-3.8 or 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>)

    •   Build dependencies in isolation, instead of in the build environment (PR #232 <https://github.com/pypa/build/pull/232>, Fixes #231 <https://github.com/pypa/build/issues/231>)

    •   Fallback on venv if virtualenv is too old (PR #241 <https://github.com/pypa/build/pull/241>)

    •   Add 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)

    •   Fix error from 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)

    •   Check dependencies recursively (PR #183 <https://github.com/pypa/build/pull/183>, Fixes #25 <https://github.com/pypa/build/issues/25>)

    •   Build wheel and sdist distributions in separate environments, as they may have different dependencies (PR #195 <https://github.com/pypa/build/pull/195>, Fixes #194 <https://github.com/pypa/build/issues/194>)

    •   Add 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>)

    •   Do not add the default backend requirements to requires when no backend is specified (PR #177 <https://github.com/pypa/build/pull/177>, Fixes #107 <https://github.com/pypa/build/issues/107>)

    •   Return the sdist name in ProjectBuild.build (PR #197 <https://github.com/pypa/build/pull/197>)

    •   Improve documentation (PR #178 <https://github.com/pypa/build/pull/178>, PR #203 <https://github.com/pypa/build/pull/203>)

    •   Add changelog (PR #219 <https://github.com/pypa/build/pull/219>, Fixes #169 <https://github.com/pypa/build/issues/169>)

  Breaking changes

    •   Move config_settings argument 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.IsolatedEnv abstract class

    •   Added env.IsolatedEnvBuilder (replaces env.IsolatedEnvironment usages)

    •   Added python_executable argument to the ProjectBuilder constructor

    •   Added --version/-V option to the CLI

    •   Added support for Python 3.9

    •   Added py.typed marker

    •   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__.build to __main__.build_package

    •   Changed the default outdir value to {srcdir}/dest

    •   Removed env.IsolatedEnvironment

  0.0.4 (2020-09-08)

    •   Packages are now built in isolation by default

    •   Added --no-isolation/-n flag to build in the current environment

    •   Add --config-setting/-C option to pass options to the backend

    •   Add IsolatedEnvironment class

    •   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

    •   Improved documentation

  0.0.3 (2020-06-09)

    •   Misc improvements

    •   Added documentation

  0.0.2 (2020-05-29)

    •   Add setuptools as a default fallback backend

    •   Fix extras handling in requirement strings

  0.0.1 (2020-05-17)

    •   Initial release

Author
Filipe Laíns

Copyright
2020, Filipe Laíns