Skip to content

Commit 5386701

Browse files
konstinzanieb
andauthored
Build backend: Make preview default and add configuration docs (#12804)
Add configuration documentation for the build backend and make it the preview default. The build backend should generally work with default configuration unless you want specific features such as flat layout or module renaming, there is only a dedicated configuration, but no concept or guide page for the build backend. Once the build backend is stable, we can update the guide documentation to explain that uv defaults to its own build backend, but other build backends are also supported. The uv build backend becomes the default in preview, giving it more exposure from users and preparing it to make it the default proper. The current documentation retains warnings that the build backend is in preview. To see current uses of `uv_build` on GitHub: https://github.com/search?q=path%3A**%2Fpyproject.toml+uv_build%3E%3D0&type=code --------- Co-authored-by: Zanie Blue <[email protected]>
1 parent 1cfc67d commit 5386701

File tree

8 files changed

+248
-6
lines changed

8 files changed

+248
-6
lines changed

crates/uv-configuration/src/project_build_backend.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// Available project build backends for use in `pyproject.toml`.
2-
#[derive(Clone, Copy, Debug, PartialEq, Default, serde::Deserialize)]
2+
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize)]
33
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
44
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
55
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@@ -11,7 +11,6 @@ pub enum ProjectBuildBackend {
1111
#[cfg_attr(feature = "schemars", schemars(skip))]
1212
/// Use uv as the project build backend.
1313
Uv,
14-
#[default]
1514
#[serde(alias = "hatchling")]
1615
#[cfg_attr(feature = "clap", value(alias = "hatchling"))]
1716
/// Use [hatchling](https://pypi.org/project/hatchling) as the project build backend.

crates/uv/src/commands/project/init.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ pub(crate) async fn init(
149149
no_config,
150150
cache,
151151
printer,
152+
preview,
152153
)
153154
.await?;
154155

@@ -289,6 +290,7 @@ async fn init_project(
289290
no_config: bool,
290291
cache: &Cache,
291292
printer: Printer,
293+
preview: PreviewMode,
292294
) -> Result<()> {
293295
// Discover the current workspace, if it exists.
294296
let workspace_cache = WorkspaceCache::default();
@@ -579,6 +581,7 @@ async fn init_project(
579581
author_from,
580582
no_readme,
581583
package,
584+
preview,
582585
)?;
583586

584587
if let Some(workspace) = workspace {
@@ -706,6 +709,7 @@ impl InitProjectKind {
706709
author_from: Option<AuthorFrom>,
707710
no_readme: bool,
708711
package: bool,
712+
preview: PreviewMode,
709713
) -> Result<()> {
710714
match self {
711715
InitProjectKind::Application => InitProjectKind::init_application(
@@ -720,6 +724,7 @@ impl InitProjectKind {
720724
author_from,
721725
no_readme,
722726
package,
727+
preview,
723728
),
724729
InitProjectKind::Library => InitProjectKind::init_library(
725730
name,
@@ -733,6 +738,7 @@ impl InitProjectKind {
733738
author_from,
734739
no_readme,
735740
package,
741+
preview,
736742
),
737743
}
738744
}
@@ -751,6 +757,7 @@ impl InitProjectKind {
751757
author_from: Option<AuthorFrom>,
752758
no_readme: bool,
753759
package: bool,
760+
preview: PreviewMode,
754761
) -> Result<()> {
755762
fs_err::create_dir_all(path)?;
756763

@@ -783,7 +790,11 @@ impl InitProjectKind {
783790
}
784791

785792
// Add a build system
786-
let build_backend = build_backend.unwrap_or_default();
793+
let build_backend = match build_backend {
794+
Some(build_backend) => build_backend,
795+
None if preview.is_enabled() => ProjectBuildBackend::Uv,
796+
None => ProjectBuildBackend::Hatch,
797+
};
787798
pyproject.push('\n');
788799
pyproject.push_str(&pyproject_build_system(name, build_backend));
789800
pyproject_build_backend_prerequisites(name, path, build_backend)?;
@@ -833,6 +844,7 @@ impl InitProjectKind {
833844
author_from: Option<AuthorFrom>,
834845
no_readme: bool,
835846
package: bool,
847+
preview: PreviewMode,
836848
) -> Result<()> {
837849
if !package {
838850
return Err(anyhow!("Library projects must be packaged"));
@@ -853,7 +865,11 @@ impl InitProjectKind {
853865
);
854866

855867
// Always include a build system if the project is packaged.
856-
let build_backend = build_backend.unwrap_or_default();
868+
let build_backend = match build_backend {
869+
Some(build_backend) => build_backend,
870+
None if preview.is_enabled() => ProjectBuildBackend::Uv,
871+
None => ProjectBuildBackend::Hatch,
872+
};
857873
pyproject.push('\n');
858874
pyproject.push_str(&pyproject_build_system(name, build_backend));
859875
pyproject_build_backend_prerequisites(name, path, build_backend)?;

crates/uv/tests/it/init.rs

+132
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,138 @@ fn init_library() -> Result<()> {
446446
Ok(())
447447
}
448448

449+
/// Test the uv build backend with using `uv init --lib --preview`. To be merged with the regular
450+
/// init lib test once the uv build backend becomes the stable default.
451+
#[test]
452+
fn init_library_preview() -> Result<()> {
453+
let context = TestContext::new("3.12");
454+
455+
let child = context.temp_dir.child("foo");
456+
child.create_dir_all()?;
457+
458+
let pyproject_toml = child.join("pyproject.toml");
459+
let init_py = child.join("src").join("foo").join("__init__.py");
460+
let py_typed = child.join("src").join("foo").join("py.typed");
461+
462+
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--lib").arg("--preview"), @r###"
463+
success: true
464+
exit_code: 0
465+
----- stdout -----
466+
467+
----- stderr -----
468+
Initialized project `foo`
469+
"###);
470+
471+
let pyproject = fs_err::read_to_string(&pyproject_toml)?;
472+
let mut filters = context.filters();
473+
filters.push((r#"\["uv_build>=.*,<.*"\]"#, r#"["uv_build[SPECIFIERS]"]"#));
474+
insta::with_settings!({
475+
filters => filters,
476+
}, {
477+
assert_snapshot!(
478+
pyproject, @r#"
479+
[project]
480+
name = "foo"
481+
version = "0.1.0"
482+
description = "Add your description here"
483+
readme = "README.md"
484+
requires-python = ">=3.12"
485+
dependencies = []
486+
487+
[build-system]
488+
requires = ["uv_build[SPECIFIERS]"]
489+
build-backend = "uv_build"
490+
"#
491+
);
492+
});
493+
494+
let init = fs_err::read_to_string(init_py)?;
495+
insta::with_settings!({
496+
filters => context.filters(),
497+
}, {
498+
assert_snapshot!(
499+
init, @r###"
500+
def hello() -> str:
501+
return "Hello from foo!"
502+
"###
503+
);
504+
});
505+
506+
let py_typed = fs_err::read_to_string(py_typed)?;
507+
insta::with_settings!({
508+
filters => context.filters(),
509+
}, {
510+
assert_snapshot!(
511+
py_typed, @""
512+
);
513+
});
514+
515+
uv_snapshot!(context.filters(), context.run().arg("--preview").current_dir(&child).arg("python").arg("-c").arg("import foo; print(foo.hello())"), @r###"
516+
success: true
517+
exit_code: 0
518+
----- stdout -----
519+
Hello from foo!
520+
521+
----- stderr -----
522+
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead
523+
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
524+
Creating virtual environment at: .venv
525+
Resolved 1 package in [TIME]
526+
Prepared 1 package in [TIME]
527+
Installed 1 package in [TIME]
528+
+ foo==0.1.0 (from file://[TEMP_DIR]/foo)
529+
"###);
530+
531+
Ok(())
532+
}
533+
534+
/// Test the uv build backend with using `uv init --package --preview`. To be merged with the regular
535+
/// init lib test once the uv build backend becomes the stable default.
536+
#[test]
537+
fn init_package_preview() -> Result<()> {
538+
let context = TestContext::new("3.12");
539+
540+
let child = context.temp_dir.child("foo");
541+
child.create_dir_all()?;
542+
543+
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--package").arg("--preview"), @r###"
544+
success: true
545+
exit_code: 0
546+
----- stdout -----
547+
548+
----- stderr -----
549+
Initialized project `foo`
550+
"###);
551+
552+
let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?;
553+
let mut filters = context.filters();
554+
filters.push((r#"\["uv_build>=.*,<.*"\]"#, r#"["uv_build[SPECIFIERS]"]"#));
555+
insta::with_settings!({
556+
filters => filters,
557+
}, {
558+
assert_snapshot!(
559+
pyproject, @r#"
560+
[project]
561+
name = "foo"
562+
version = "0.1.0"
563+
description = "Add your description here"
564+
readme = "README.md"
565+
requires-python = ">=3.12"
566+
dependencies = []
567+
568+
[project.scripts]
569+
foo = "foo:main"
570+
571+
[build-system]
572+
requires = ["uv_build[SPECIFIERS]"]
573+
build-backend = "uv_build"
574+
"#
575+
);
576+
});
577+
578+
Ok(())
579+
}
580+
449581
#[test]
450582
fn init_bare_lib() {
451583
let context = TestContext::new("3.12");

docs/concepts/projects/init.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ build-backend = "hatchling.build"
202202
!!! tip
203203

204204
You can select a different build backend template by using `--build-backend` with `hatchling`,
205-
`flit-core`, `pdm-backend`, `setuptools`, `maturin`, or `scikit-build-core`. An alternative
206-
backend is required if you want to create a [library with extension modules](#projects-with-extension-modules).
205+
`uv_build`, `flit-core`, `pdm-backend`, `setuptools`, `maturin`, or `scikit-build-core`. An
206+
alternative backend is required if you want to create a [library with extension modules](#projects-with-extension-modules).
207207

208208
The created module defines a simple API function:
209209

docs/configuration/build-backend.md

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# The uv build backend
2+
3+
!!! note
4+
5+
The uv build backend is currently in preview and may change without warning.
6+
7+
When preview mode is not enabled, uv uses [hatchling](https://pypi.org/project/hatchling/) as the default build backend.
8+
9+
A build backend transforms a source tree (i.e., a directory) into a source distribution or a wheel.
10+
While uv supports all build backends (as specified by PEP 517), it includes a `uv_build` backend
11+
that integrates tightly with uv to improve performance and user experience.
12+
13+
The uv build backend currently only supports Python code. An alternative backend is required if you
14+
want to create a
15+
[library with extension modules](../concepts/projects/init.md#projects-with-extension-modules).
16+
17+
To use the uv build backend as [build system](../concepts/projects/config.md#build-systems) in an
18+
existing project, add it to the `[build-system]` section in your `pyproject.toml`:
19+
20+
```toml
21+
[build-system]
22+
requires = ["uv_build>=0.6.13,<0.7"]
23+
build-backend = "uv_build"
24+
```
25+
26+
!!! important
27+
28+
The uv build backend follows the same [versioning policy](../reference/policies/versioning.md),
29+
setting an upper bound on the `uv_build` version ensures that the package continues to build in
30+
the future.
31+
32+
You can also create a new project that uses the uv build backend with `uv init`:
33+
34+
```shell
35+
uv init --build-backend uv
36+
```
37+
38+
`uv_build` is a separate package from uv, optimized for portability and small binary size. The `uv`
39+
command includes a copy of the build backend, so when running `uv build`, the same version will be
40+
used for the build backend as for the uv process. Other build frontends, such as `python -m build`,
41+
will choose the latest compatible `uv_build` version.
42+
43+
## Include and exclude configuration
44+
45+
To select which files to include in the source distribution, uv first adds the included files and
46+
directories, then removes the excluded files and directories. This means that exclusions always take
47+
precedence over inclusions.
48+
49+
When building the source distribution, the following files and directories are included:
50+
51+
- `pyproject.toml`
52+
- The module under `tool.uv.build-backend.module-root`, by default
53+
`src/<module-name or project_name_with_underscores>/**`.
54+
- `project.license-files` and `project.readme`.
55+
- All directories under `tool.uv.build-backend.data`.
56+
- All patterns from `tool.uv.build-backend.source-include`.
57+
58+
From these, `tool.uv.build-backend.source-exclude` and the default excludes are removed.
59+
60+
When building the wheel, the following files and directories are included:
61+
62+
- The module under `tool.uv.build-backend.module-root`, by default
63+
`src/<module-name or project_name_with_underscores>/**`.
64+
- `project.license-files` and `project.readme`, as part of the project metadata.
65+
- Each directory under `tool.uv.build-backend.data`, as data directories.
66+
67+
From these, `tool.uv.build-backend.source-exclude`, `tool.uv.build-backend.wheel-exclude` and the
68+
default excludes are removed. The source dist excludes are applied to avoid source tree to wheel
69+
source builds including more files than source tree to source distribution to wheel build.
70+
71+
There are no specific wheel includes. There must only be one top level module, and all data files
72+
must either be under the module root or in the appropriate
73+
[data directory](../reference/settings.md#build-backend_data). Most packages store small data in the
74+
module root alongside the source code.
75+
76+
## Include and exclude syntax
77+
78+
Includes are anchored, which means that `pyproject.toml` includes only
79+
`<project root>/pyproject.toml`. For example, `assets/**/sample.csv` includes all `sample.csv` files
80+
in `<project root>/assets` or any child directory. To recursively include all files under a
81+
directory, use a `/**` suffix, e.g. `src/**`.
82+
83+
!!! note
84+
85+
For performance and reproducibility, avoid patterns without an anchor such as `**/sample.csv`.
86+
87+
Excludes are not anchored, which means that `__pycache__` excludes all directories named
88+
`__pycache__` and its children anywhere. To anchor a directory, use a `/` prefix, e.g., `/dist` will
89+
exclude only `<project root>/dist`.
90+
91+
All fields accepting patterns use the reduced portable glob syntax from
92+
[PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key).

docs/configuration/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Read about the various ways to configure uv:
66
- [Using environment variables](./environment.md)
77
- [Configuring authentication](./authentication.md)
88
- [Configuring package indexes](./indexes.md)
9+
- [The uv build backend](build-backend.md)
910

1011
Or, jump to the [settings reference](../reference/settings.md) which enumerates the available
1112
configuration options.

mkdocs.template.yml

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ nav:
140140
- Authentication: configuration/authentication.md
141141
- Package indexes: configuration/indexes.md
142142
- Installer: configuration/installer.md
143+
- Build backend: configuration/build-backend.md
143144
- The pip interface:
144145
- pip/index.md
145146
- Using environments: pip/environments.md

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ version_files = [
8686
"docs/guides/integration/pre-commit.md",
8787
"docs/guides/integration/github.md",
8888
"docs/guides/integration/aws-lambda.md",
89+
"docs/configuration/build-backend.md",
8990
]
9091

9192
[tool.mypy]

0 commit comments

Comments
 (0)