Files
uv/docs/concepts/build-backend.md
T
konsti db4dfb4cf9 Add logging to the uv build backend (#15533)
Add support for `RUST_LOG` to the uv build backend. While we were
previously using logging statements in the uv build backend, they could
only be shown when when using the direct build fast path through uv, as
there was no tracing subscriber to write log messages out. This means no
debug logging when using the build backend through pip, `python -m
build`, an incompatible version of uv, or any other build frontend; No
option to figure why includes and excludes behave the way they do.

This PR closes this gap by adding a tracing subscriber. The only option
to enable it is `RUST_LOG`, as we don't have a CLI. The formatting style
is the same as for uv, and color is also support in the same way, albeit
only through anstream's support for TTYs and environment variables. We
recommend only `RUST_LOG=uv=debug` and `RUST_LOG=uv=verbose` in the
docs, but this can be used to debug into crates such as `glob`, too.

<img width="1008" height="325" alt="image"
src="https://github.com/user-attachments/assets/d33df219-750b-46a2-b3b4-8895aa137ab9"
/>

**Before**

```
$ pip wheel . -v [...]
Looking in links: /home/konsti/projects/uv/target/wheels/
Processing /home/konsti/projects/uv/scripts/packages/built-by-uv
  Running command pip subprocess to install build dependencies
  Looking in links: /home/konsti/projects/uv/target/wheels/
  Processing /home/konsti/projects/uv/target/wheels/uv_build-0.8.13-py3-none-manylinux_2_39_x86_64.whl
  Installing collected packages: uv_build
  Successfully installed uv_build-0.8.13
  Installing build dependencies ... done
  Running command Getting requirements to build wheel
  Getting requirements to build wheel ... done
  Running command Preparing metadata (pyproject.toml)
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: built-by-uv
  Running command Building wheel for built-by-uv (pyproject.toml)
  Error: Unsupported glob expression in: `tool.uv.build-backend.*-exclude`

  Caused by:
      Invalid character `!` at position 10 in glob: `**/build-*!$§%!½¼²¼³¬!§%$§%.h`. hint: Characters can be escaped with a backslash
  error: subprocess-exited-with-error

  × Building wheel for built-by-uv (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> See above for output.

  note: This error originates from a subprocess, and is likely not a problem with pip.
  full command: /usr/bin/python3 /usr/lib/python3/dist-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py build_wheel /tmp/tmpow1illc9
  cwd: /home/konsti/projects/uv/scripts/packages/built-by-uv
  Building wheel for built-by-uv (pyproject.toml) ... error
  ERROR: Failed building wheel for built-by-uv
Failed to build built-by-uv
ERROR: Failed to build one or more wheels
```

**After**

```
$ RUST_LOG=uv=debug pip wheel . -v [...]
Looking in links: /home/konsti/projects/uv/target/wheels/
Processing /home/konsti/projects/uv/scripts/packages/built-by-uv
  Running command pip subprocess to install build dependencies
  Looking in links: /home/konsti/projects/uv/target/wheels/
  Processing /home/konsti/projects/uv/target/wheels/uv_build-0.8.13-py3-none-manylinux_2_39_x86_64.whl
  Installing collected packages: uv_build
  Successfully installed uv_build-0.8.13
  Installing build dependencies ... done
  Running command Getting requirements to build wheel
  Getting requirements to build wheel ... done
  Running command Preparing metadata (pyproject.toml)
  DEBUG Writing metadata files to /tmp/pip-modern-metadata-l_kh78cj
  DEBUG Found PEP 639 license declarations, using METADATA 2.4
  DEBUG License files match: `LICENSE-APACHE`
  DEBUG License files match: `LICENSE-MIT`
  DEBUG License files match: `third-party-licenses/PEP-401.txt`
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: built-by-uv
  Running command Building wheel for built-by-uv (pyproject.toml)
  DEBUG Checking metadata directory /tmp/pip-modern-metadata-l_kh78cj/built_by_uv-0.1.0.dist-info
  DEBUG Found PEP 639 license declarations, using METADATA 2.4
  DEBUG License files match: `LICENSE-APACHE`
  DEBUG License files match: `LICENSE-MIT`
  DEBUG License files match: `third-party-licenses/PEP-401.txt`
  DEBUG Writing wheel at /tmp/pip-wheel-bu6to9i7/built_by_uv-0.1.0-py3-none-any.whl
  DEBUG Wheel excludes: ["__pycache__", "*.pyc", "*.pyo", "build-*!$§%!½¼²¼³¬!§%$§%.h", "/src/built_by_uv/not-packaged.txt"]
  Error: Unsupported glob expression in: `tool.uv.build-backend.*-exclude`

  Caused by:
      Invalid character `!` at position 10 in glob: `**/build-*!$§%!½¼²¼³¬!§%$§%.h`. hint: Characters can be escaped with a backslash
  error: subprocess-exited-with-error

  × Building wheel for built-by-uv (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> See above for output.

  note: This error originates from a subprocess, and is likely not a problem with pip.
  full command: /usr/bin/python3 /usr/lib/python3/dist-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py build_wheel /tmp/tmpjrxou13a
  cwd: /home/konsti/projects/uv/scripts/packages/built-by-uv
  Building wheel for built-by-uv (pyproject.toml) ... error
  ERROR: Failed building wheel for built-by-uv
Failed to build built-by-uv
ERROR: Failed to build one or more wheels
```

(There is no color in the above uv log statements, as pip doesn't
register as a TTY)

Fixes #12723
2025-08-27 09:14:00 +02:00

9.7 KiB

The uv build backend

A build backend transforms a source tree (i.e., a directory) into a source distribution or a wheel.

uv supports all build backends (as specified by PEP 517), but also provides a native build backend (uv_build) that integrates tightly with uv to improve performance and user experience.

Choosing a build backend

The uv build backend is a great choice for most Python projects. It has reasonable defaults, with the goal of requiring zero configuration for most users, but provides flexible configuration to accommodate most Python project structures. It integrates tightly with uv, to improve messaging and user experience. It validates project metadata and structures, preventing common mistakes. And, finally, it's very fast.

The uv build backend currently only supports pure Python code. An alternative backend is required to build a library with extension modules.

!!! tip

While the backend supports a number of options for configuring your project structure, when build scripts or
a more flexible project layout are required, consider using the
[hatchling](https://hatch.pypa.io/latest/config/build/#build-system) build backend instead.

Using the uv build backend

To use uv as a build backend in an existing project, add uv_build to the [build-system] section in your pyproject.toml:

[build-system]
requires = ["uv_build>=0.8.13,<0.9.0"]
build-backend = "uv_build"

!!! note

The uv build backend follows the same [versioning policy](../reference/policies/versioning.md)
as uv. Including an upper bound on the `uv_build` version ensures that your package continues to
build correctly as new versions are released.

To create a new project that uses the uv build backend, use uv init:

$ uv init

When the project is built, e.g., with uv build, the uv build backend will be used to create the source distribution and wheel.

Bundled build backend

The build backend is published as a separate package (uv_build) that is optimized for portability and small binary size. However, the uv executable also includes a copy of the build backend, which will be used during builds performed by uv, e.g., during uv build, if its version is compatible with the uv_build requirement. If it's not compatible, a compatible version of the uv_build package will be used. Other build frontends, such as python -m build, will always use the uv_build package, typically choosing the latest compatible version.

Modules

Python packages are expected to contain one or more Python modules, which are directories containing an __init__.py. By default, a single root module is expected at src/<package_name>/__init__.py.

For example, the structure for a project named foo would be:

pyproject.toml
src
└── foo
    └── __init__.py

uv normalizes the package name to determine the default module name: the package name is lowercased and dots and dashes are replaced with underscores, e.g., Foo-Bar would be converted to foo_bar.

The src/ directory is the default directory for module discovery.

These defaults can be changed with the module-name and module-root settings. For example, to use a FOO module in the root directory, as in the project structure:

pyproject.toml
FOO
└── __init__.py

The correct build configuration would be:

[tool.uv.build-backend]
module-name = "FOO"
module-root = ""

Namespace packages

Namespace packages are intended for use-cases where multiple packages write modules into a shared namespace.

Namespace package modules are identified by a . in the module-name. For example, to package the module bar in the shared namespace foo, the project structure would be:

pyproject.toml
src
└── foo
    └── bar
        └── __init__.py

And the module-name configuration would be:

[tool.uv.build-backend]
module-name = "foo.bar"

!!! important

The `__init__.py` file is not included in `foo`, since it's the shared namespace module.

It's also possible to have a complex namespace package with more than one root module, e.g., with the project structure:

pyproject.toml
src
├── foo
│   └── __init__.py
└── bar
    └── __init__.py

While we do not recommend this structure (i.e., you should use a workspace with multiple packages instead), it is supported by setting module-name to a list of names:

[tool.uv.build-backend]
module-name = ["foo", "bar"]

For packages with many modules or complex namespaces, the namespace = true option can be used to avoid explicitly declaring each module name, e.g.:

[tool.uv.build-backend]
namespace = true

!!! warning

Using `namespace = true` disables safety checks. Using an explicit list of module names is
strongly recommended outside of legacy projects.

The namespace option can also be used with module-name to explicitly declare the root, e.g., for the project structure:

pyproject.toml
src
└── foo
    ├── bar
    │   └── __init__.py
    └── baz
        └── __init__.py

The recommended configuration would be:

[tool.uv.build-backend]
module-name = "foo"
namespace = true

Stub packages

The build backend also supports building type stub packages, which are identified by the -stubs suffix on the package or module name, e.g., foo-stubs. The module name for type stub packages must end in -stubs, so uv will not normalize the - to an underscore. Additionally, uv will search for a __init__.pyi file. For example, the project structure would be:

pyproject.toml
src
└── foo-stubs
    └── __init__.pyi

Type stub modules are also supported for namespace packages.

File inclusion and exclusion

The build backend is responsible for determining which files in a source tree should be packaged into the distributions.

To determine which files to include in a source distribution, uv first adds the included files and directories, then removes the excluded files and directories. This means that exclusions always take precedence over inclusions.

By default, uv excludes __pycache__, *.pyc, and *.pyo.

When building a source distribution, the following files and directories are included:

From these, items matching tool.uv.build-backend.source-exclude and the default excludes are removed.

When building a wheel, the following files and directories are included:

From these, tool.uv.build-backend.source-exclude, tool.uv.build-backend.wheel-exclude and the default excludes are removed. The source dist excludes are applied to avoid source tree to wheel source builds including more files than source tree to source distribution to wheel build.

There are no specific wheel includes. There must only be one top level module, and all data files must either be under the module root or in the appropriate data directory. Most packages store small data in the module root alongside the source code.

!!! tip

When using the uv build backend through a frontend that is not uv, such as pip or
`pythom -m build`, debug logging can be enabled through environment variables with
`RUST_LOG=uv=debug` or `RUST_LOG=uv=verbose`. When used through uv, the uv build backend shares
the verbosity level of uv.

Include and exclude syntax

Includes are anchored, which means that pyproject.toml includes only <root>/pyproject.toml and not <root>/bar/pyproject.toml. To recursively include all files under a directory, use a /** suffix, e.g. src/**. Recursive inclusions are also anchored, e.g., assets/**/sample.csv includes all sample.csv files in <root>/assets or any of its children.

!!! note

For performance and reproducibility, avoid patterns without an anchor such as `**/sample.csv`.

Excludes are not anchored, which means that __pycache__ excludes all directories named __pycache__ regardless of its parent directory. All children of an exclusion are excluded as well. To anchor a directory, use a / prefix, e.g., /dist will exclude only <root>/dist.

All fields accepting patterns use the reduced portable glob syntax from PEP 639, with the addition that characters can be escaped with a backslash.