`uv init`: Make `uv_build` the default build backend (from `hatchling`) (#14661)

Closes https://github.com/astral-sh/uv/issues/14298

Switch the default build backend for `uv init` from `hatchling` to
`uv_build`.

This change affects the following two commands:

* `uv init --lib`
* `uv init [--app] --package`

It does not affect `uv init` or `uv init --app` without `--package`. `uv
init --build-backend <...>` also works as before.

**Before**

```
$ uv init --lib project
$ cat project/pyproject.toml
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
    { name = "konstin", email = "konstin@mailbox.org" }
]
requires-python = ">=3.13.2"
dependencies = []

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

**After**

```
$ uv init --lib project
$ cat project/pyproject.toml
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
    { name = "konstin", email = "konstin@mailbox.org" }
]
requires-python = ">=3.13.2"
dependencies = []

[build-system]
requires = ["uv_build>=0.7.20,<0.8"]
build-backend = "uv_build"
```

I cleaned up some tests for consistency in the second commit.
This commit is contained in:
konsti 2025-07-16 19:07:08 +01:00 committed by Zanie Blue
parent 95c0b71f77
commit 3c9aea87b4
7 changed files with 99 additions and 223 deletions

View File

@ -171,7 +171,7 @@ impl PyProjectToml {
/// ///
/// ```toml /// ```toml
/// [build-system] /// [build-system]
/// requires = ["uv_build>=0.4.15,<5"] /// requires = ["uv_build>=0.4.15,<0.5"]
/// build-backend = "uv_build" /// build-backend = "uv_build"
/// ``` /// ```
pub fn check_build_system(&self, uv_version: &str) -> Vec<String> { pub fn check_build_system(&self, uv_version: &str) -> Vec<String> {
@ -826,7 +826,7 @@ mod tests {
{payload} {payload}
[build-system] [build-system]
requires = ["uv_build>=0.4.15,<5"] requires = ["uv_build>=0.4.15,<0.5"]
build-backend = "uv_build" build-backend = "uv_build"
"# "#
} }
@ -909,7 +909,7 @@ mod tests {
foo-bar = "foo:bar" foo-bar = "foo:bar"
[build-system] [build-system]
requires = ["uv_build>=0.4.15,<5"] requires = ["uv_build>=0.4.15,<0.5"]
build-backend = "uv_build" build-backend = "uv_build"
"# "#
}; };
@ -1036,7 +1036,7 @@ mod tests {
foo-bar = "foo:bar" foo-bar = "foo:bar"
[build-system] [build-system]
requires = ["uv_build>=0.4.15,<5"] requires = ["uv_build>=0.4.15,<0.5"]
build-backend = "uv_build" build-backend = "uv_build"
"# "#
}; };
@ -1104,7 +1104,7 @@ mod tests {
let contents = extend_project(""); let contents = extend_project("");
let pyproject_toml = PyProjectToml::parse(&contents).unwrap(); let pyproject_toml = PyProjectToml::parse(&contents).unwrap();
assert_snapshot!( assert_snapshot!(
pyproject_toml.check_build_system("1.0.0+test").join("\n"), pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@"" @""
); );
} }
@ -1135,7 +1135,7 @@ mod tests {
version = "0.1.0" version = "0.1.0"
[build-system] [build-system]
requires = ["uv_build>=0.4.15,<5", "wheel"] requires = ["uv_build>=0.4.15,<0.5", "wheel"]
build-backend = "uv_build" build-backend = "uv_build"
"#}; "#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap(); let pyproject_toml = PyProjectToml::parse(contents).unwrap();
@ -1171,7 +1171,7 @@ mod tests {
version = "0.1.0" version = "0.1.0"
[build-system] [build-system]
requires = ["uv_build>=0.4.15,<5"] requires = ["uv_build>=0.4.15,<0.5"]
build-backend = "setuptools" build-backend = "setuptools"
"#}; "#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap(); let pyproject_toml = PyProjectToml::parse(contents).unwrap();

View File

@ -63,9 +63,6 @@ pub(crate) async fn init(
printer: Printer, printer: Printer,
preview: PreviewMode, preview: PreviewMode,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
if build_backend == Some(ProjectBuildBackend::Uv) && preview.is_disabled() {
warn_user_once!("The uv build backend is experimental and may change without warning");
}
match init_kind { match init_kind {
InitKind::Script => { InitKind::Script => {
let Some(path) = explicit_path.as_deref() else { let Some(path) = explicit_path.as_deref() else {
@ -596,7 +593,6 @@ async fn init_project(
author_from, author_from,
no_readme, no_readme,
package, package,
preview,
)?; )?;
if let Some(workspace) = workspace { if let Some(workspace) = workspace {
@ -724,7 +720,6 @@ impl InitProjectKind {
author_from: Option<AuthorFrom>, author_from: Option<AuthorFrom>,
no_readme: bool, no_readme: bool,
package: bool, package: bool,
preview: PreviewMode,
) -> Result<()> { ) -> Result<()> {
match self { match self {
InitProjectKind::Application => InitProjectKind::init_application( InitProjectKind::Application => InitProjectKind::init_application(
@ -739,7 +734,6 @@ impl InitProjectKind {
author_from, author_from,
no_readme, no_readme,
package, package,
preview,
), ),
InitProjectKind::Library => InitProjectKind::init_library( InitProjectKind::Library => InitProjectKind::init_library(
name, name,
@ -753,7 +747,6 @@ impl InitProjectKind {
author_from, author_from,
no_readme, no_readme,
package, package,
preview,
), ),
} }
} }
@ -772,7 +765,6 @@ impl InitProjectKind {
author_from: Option<AuthorFrom>, author_from: Option<AuthorFrom>,
no_readme: bool, no_readme: bool,
package: bool, package: bool,
preview: PreviewMode,
) -> Result<()> { ) -> Result<()> {
fs_err::create_dir_all(path)?; fs_err::create_dir_all(path)?;
@ -805,11 +797,7 @@ impl InitProjectKind {
} }
// Add a build system // Add a build system
let build_backend = match build_backend { let build_backend = build_backend.unwrap_or(ProjectBuildBackend::Uv);
Some(build_backend) => build_backend,
None if preview.is_enabled() => ProjectBuildBackend::Uv,
None => ProjectBuildBackend::Hatch,
};
pyproject.push('\n'); pyproject.push('\n');
pyproject.push_str(&pyproject_build_system(name, build_backend)); pyproject.push_str(&pyproject_build_system(name, build_backend));
pyproject_build_backend_prerequisites(name, path, build_backend)?; pyproject_build_backend_prerequisites(name, path, build_backend)?;
@ -859,7 +847,6 @@ impl InitProjectKind {
author_from: Option<AuthorFrom>, author_from: Option<AuthorFrom>,
no_readme: bool, no_readme: bool,
package: bool, package: bool,
preview: PreviewMode,
) -> Result<()> { ) -> Result<()> {
if !package { if !package {
return Err(anyhow!("Library projects must be packaged")); return Err(anyhow!("Library projects must be packaged"));
@ -880,11 +867,7 @@ impl InitProjectKind {
); );
// Always include a build system if the project is packaged. // Always include a build system if the project is packaged.
let build_backend = match build_backend { let build_backend = build_backend.unwrap_or(ProjectBuildBackend::Uv);
Some(build_backend) => build_backend,
None if preview.is_enabled() => ProjectBuildBackend::Uv,
None => ProjectBuildBackend::Hatch,
};
pyproject.push('\n'); pyproject.push('\n');
pyproject.push_str(&pyproject_build_system(name, build_backend)); pyproject.push_str(&pyproject_build_system(name, build_backend));
pyproject_build_backend_prerequisites(name, path, build_backend)?; pyproject_build_backend_prerequisites(name, path, build_backend)?;

View File

@ -1439,7 +1439,6 @@ fn build_fast_path() -> Result<()> {
let built_by_uv = current_dir()?.join("../../scripts/packages/built-by-uv"); let built_by_uv = current_dir()?.join("../../scripts/packages/built-by-uv");
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output1")), @r###" .arg(context.temp_dir.join("output1")), @r###"
@ -1465,7 +1464,6 @@ fn build_fast_path() -> Result<()> {
.assert(predicate::path::is_file()); .assert(predicate::path::is_file());
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output2")) .arg(context.temp_dir.join("output2"))
@ -1485,7 +1483,6 @@ fn build_fast_path() -> Result<()> {
.assert(predicate::path::is_file()); .assert(predicate::path::is_file());
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output3")) .arg(context.temp_dir.join("output3"))
@ -1505,7 +1502,6 @@ fn build_fast_path() -> Result<()> {
.assert(predicate::path::is_file()); .assert(predicate::path::is_file());
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output4")) .arg(context.temp_dir.join("output4"))
@ -1545,7 +1541,6 @@ fn build_list_files() -> Result<()> {
// By default, we build the wheel from the source dist, which we need to do even for the list // By default, we build the wheel from the source dist, which we need to do even for the list
// task. // task.
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output1")) .arg(context.temp_dir.join("output1"))
@ -1601,7 +1596,6 @@ fn build_list_files() -> Result<()> {
.assert(predicate::path::missing()); .assert(predicate::path::missing());
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output2")) .arg(context.temp_dir.join("output2"))
@ -1670,7 +1664,6 @@ fn build_list_files_errors() -> Result<()> {
// In CI, we run with link mode settings. // In CI, we run with link mode settings.
filters.push(("--link-mode <LINK_MODE> ", "")); filters.push(("--link-mode <LINK_MODE> ", ""));
uv_snapshot!(filters, context.build() uv_snapshot!(filters, context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output1")) .arg(context.temp_dir.join("output1"))
@ -1694,7 +1687,6 @@ fn build_list_files_errors() -> Result<()> {
// Windows normalization // Windows normalization
filters.push(("/crates/uv/../../", "/")); filters.push(("/crates/uv/../../", "/"));
uv_snapshot!(filters, context.build() uv_snapshot!(filters, context.build()
.arg("--preview")
.arg(&anyio_local) .arg(&anyio_local)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output2")) .arg(context.temp_dir.join("output2"))
@ -1987,12 +1979,7 @@ fn force_pep517() -> Result<()> {
// We need to use a real `uv_build` package. // We need to use a real `uv_build` package.
let context = TestContext::new("3.12").with_exclude_newer("2025-05-27T00:00:00Z"); let context = TestContext::new("3.12").with_exclude_newer("2025-05-27T00:00:00Z");
context context.init().assert().success();
.init()
.arg("--build-backend")
.arg("uv")
.assert()
.success();
let pyproject_toml = context.temp_dir.child("pyproject.toml"); let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#" pyproject_toml.write_str(indoc! {r#"
@ -2026,7 +2013,7 @@ fn force_pep517() -> Result<()> {
----- stderr ----- ----- stderr -----
Building source distribution... Building source distribution...
Error: Missing module directory for `does_not_exist` in `src`. Found: `temp` Error: Missing source directory at: `src`
× Failed to build `[TEMP_DIR]/` × Failed to build `[TEMP_DIR]/`
The build backend returned an error The build backend returned an error
Call to `uv_build.build_sdist` failed (exit status: 1) Call to `uv_build.build_sdist` failed (exit status: 1)

View File

@ -222,8 +222,7 @@ fn preserve_executable_bit() -> Result<()> {
let project_dir = context.temp_dir.path().join("preserve_executable_bit"); let project_dir = context.temp_dir.path().join("preserve_executable_bit");
context context
.init() .init()
.arg("--build-backend") .arg("--lib")
.arg("uv")
.arg(&project_dir) .arg(&project_dir)
.assert() .assert()
.success(); .success();
@ -296,7 +295,7 @@ fn rename_module() -> Result<()> {
module-name = "bar" module-name = "bar"
[build-system] [build-system]
requires = ["uv_build>=0.5,<0.8"] requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build" build-backend = "uv_build"
"#})?; "#})?;
@ -377,7 +376,7 @@ fn rename_module_editable_build() -> Result<()> {
module-name = "bar" module-name = "bar"
[build-system] [build-system]
requires = ["uv_build>=0.5,<0.8"] requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build" build-backend = "uv_build"
"#})?; "#})?;
@ -436,7 +435,7 @@ fn build_module_name_normalization() -> Result<()> {
version = "1.0.0" version = "1.0.0"
[build-system] [build-system]
requires = ["uv_build>=0.5,<0.8"] requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build" build-backend = "uv_build"
[tool.uv.build-backend] [tool.uv.build-backend]
@ -548,7 +547,7 @@ fn build_sdist_with_long_path() -> Result<()> {
version = "1.0.0" version = "1.0.0"
[build-system] [build-system]
requires = ["uv_build>=0.7,<0.8"] requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build" build-backend = "uv_build"
"#})?; "#})?;
context context
@ -591,7 +590,7 @@ fn sdist_error_without_module() -> Result<()> {
version = "1.0.0" version = "1.0.0"
[build-system] [build-system]
requires = ["uv_build>=0.7,<0.8"] requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build" build-backend = "uv_build"
"#})?; "#})?;
@ -661,7 +660,7 @@ fn complex_namespace_packages() -> Result<()> {
module-name = "{project_name_dist_info}.{part_name}" module-name = "{project_name_dist_info}.{part_name}"
[build-system] [build-system]
requires = ["uv_build>=0.5.15,<10000"] requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build" build-backend = "uv_build"
"# "#
}; };
@ -770,8 +769,7 @@ fn symlinked_file() -> Result<()> {
let project = context.temp_dir.child("project"); let project = context.temp_dir.child("project");
context context
.init() .init()
.arg("--build-backend") .arg("--lib")
.arg("uv")
.arg(project.path()) .arg(project.path())
.assert() .assert()
.success(); .success();
@ -783,7 +781,7 @@ fn symlinked_file() -> Result<()> {
license-files = ["LICENSE"] license-files = ["LICENSE"]
[build-system] [build-system]
requires = ["uv_build>=0.5.15,<10000"] requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build" build-backend = "uv_build"
"# "#
})?; })?;

View File

@ -664,6 +664,14 @@ impl TestContext {
)); ));
// For wiremock tests // For wiremock tests
filters.push((r"127\.0\.0\.1:\d*".to_string(), "[LOCALHOST]".to_string())); filters.push((r"127\.0\.0\.1:\d*".to_string(), "[LOCALHOST]".to_string()));
// Avoid breaking the tests when bumping the uv version
filters.push((
format!(
r#"requires = \["uv_build>={},<[0-9.]+"\]"#,
uv_version::version()
),
r#"requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]"#.to_string(),
));
Self { Self {
root: ChildPath::new(root.path()), root: ChildPath::new(root.path()),

View File

@ -314,7 +314,7 @@ fn init_application_package() -> Result<()> {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -327,9 +327,9 @@ fn init_application_package() -> Result<()> {
foo = "foo:main" foo = "foo:main"
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
@ -390,7 +390,7 @@ fn init_library() -> Result<()> {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -400,9 +400,9 @@ fn init_library() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
@ -446,91 +446,6 @@ fn init_library() -> Result<()> {
Ok(()) Ok(())
} }
/// Test the uv build backend with using `uv init --lib --preview`. To be merged with the regular
/// init lib test once the uv build backend becomes the stable default.
#[test]
fn init_library_preview() -> Result<()> {
let context = TestContext::new("3.12");
let child = context.temp_dir.child("foo");
child.create_dir_all()?;
let pyproject_toml = child.join("pyproject.toml");
let init_py = child.join("src").join("foo").join("__init__.py");
let py_typed = child.join("src").join("foo").join("py.typed");
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--lib").arg("--preview"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Initialized project `foo`
"###);
let pyproject = fs_err::read_to_string(&pyproject_toml)?;
let mut filters = context.filters();
filters.push((r#"\["uv_build>=.*,<.*"\]"#, r#"["uv_build[SPECIFIERS]"]"#));
insta::with_settings!({
filters => filters,
}, {
assert_snapshot!(
pyproject, @r#"
[project]
name = "foo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["uv_build[SPECIFIERS]"]
build-backend = "uv_build"
"#
);
});
let init = fs_err::read_to_string(init_py)?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
init, @r###"
def hello() -> str:
return "Hello from foo!"
"###
);
});
let py_typed = fs_err::read_to_string(py_typed)?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
py_typed, @""
);
});
uv_snapshot!(context.filters(), context.run().arg("--preview").current_dir(&child).arg("python").arg("-c").arg("import foo; print(foo.hello())"), @r###"
success: true
exit_code: 0
----- stdout -----
Hello from foo!
----- stderr -----
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ foo==0.1.0 (from file://[TEMP_DIR]/foo)
"###);
Ok(())
}
/// Test the uv build backend with using `uv init --package --preview`. To be merged with the regular /// Test the uv build backend with using `uv init --package --preview`. To be merged with the regular
/// init lib test once the uv build backend becomes the stable default. /// init lib test once the uv build backend becomes the stable default.
#[test] #[test]
@ -550,10 +465,8 @@ fn init_package_preview() -> Result<()> {
"###); "###);
let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?; let pyproject = fs_err::read_to_string(child.join("pyproject.toml"))?;
let mut filters = context.filters();
filters.push((r#"\["uv_build>=.*,<.*"\]"#, r#"["uv_build[SPECIFIERS]"]"#));
insta::with_settings!({ insta::with_settings!({
filters => filters, filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r#" pyproject, @r#"
@ -569,7 +482,7 @@ fn init_package_preview() -> Result<()> {
foo = "foo:main" foo = "foo:main"
[build-system] [build-system]
requires = ["uv_build[SPECIFIERS]"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "uv_build" build-backend = "uv_build"
"# "#
); );
@ -615,7 +528,7 @@ fn init_bare_lib() {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -623,9 +536,9 @@ fn init_bare_lib() {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
} }
@ -667,7 +580,7 @@ fn init_bare_package() {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -675,9 +588,9 @@ fn init_bare_package() {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
} }
@ -1154,7 +1067,7 @@ fn init_library_current_dir() -> Result<()> {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -1164,9 +1077,9 @@ fn init_library_current_dir() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
@ -1283,7 +1196,7 @@ fn init_dot_args() -> Result<()> {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -1293,9 +1206,9 @@ fn init_dot_args() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
@ -1361,7 +1274,7 @@ fn init_workspace() -> Result<()> {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -1371,9 +1284,9 @@ fn init_workspace() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
@ -1546,7 +1459,7 @@ fn init_workspace_relative_sub_package() -> Result<()> {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -1556,9 +1469,9 @@ fn init_workspace_relative_sub_package() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
@ -1643,7 +1556,7 @@ fn init_workspace_outside() -> Result<()> {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -1653,9 +1566,9 @@ fn init_workspace_outside() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
@ -1725,7 +1638,7 @@ fn init_normalized_names() -> Result<()> {
filters => context.filters(), filters => context.filters(),
}, { }, {
assert_snapshot!( assert_snapshot!(
pyproject, @r###" pyproject, @r#"
[project] [project]
name = "foo-bar" name = "foo-bar"
version = "0.1.0" version = "0.1.0"
@ -1735,9 +1648,9 @@ fn init_normalized_names() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"### "#
); );
}); });
@ -3008,8 +2921,8 @@ fn init_with_author() {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"# "#
); );
}); });
@ -3038,8 +2951,8 @@ fn init_with_author() {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"# "#
); );
}); });
@ -3822,9 +3735,9 @@ fn init_lib_build_backend_scikit() -> Result<()> {
Ok(()) Ok(())
} }
/// Run `uv init --app --package --build-backend uv` to create a packaged application project /// Run `uv init --app --package --build-backend hatchling` to create a packaged application project
#[test] #[test]
fn init_application_package_uv() -> Result<()> { fn init_application_package_hatchling() -> Result<()> {
let context = TestContext::new("3.12"); let context = TestContext::new("3.12");
let child = context.temp_dir.child("foo"); let child = context.temp_dir.child("foo");
@ -3833,41 +3746,34 @@ fn init_application_package_uv() -> Result<()> {
let pyproject_toml = child.join("pyproject.toml"); let pyproject_toml = child.join("pyproject.toml");
let init_py = child.join("src").join("foo").join("__init__.py"); let init_py = child.join("src").join("foo").join("__init__.py");
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app").arg("--package").arg("--build-backend").arg("uv"), @r###" uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app").arg("--package").arg("--build-backend").arg("hatchling"), @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
warning: The uv build backend is experimental and may change without warning
Initialized project `foo` Initialized project `foo`
"###); "###);
let pyproject = fs_err::read_to_string(&pyproject_toml)?; let pyproject = fs_err::read_to_string(&pyproject_toml)?;
let mut filters = context.filters(); assert_snapshot!(
filters.push((r#"\["uv_build>=.*,<.*"\]"#, r#"["uv_build[SPECIFIERS]"]"#)); pyproject, @r#"
insta::with_settings!({ [project]
filters => filters, name = "foo"
}, { version = "0.1.0"
assert_snapshot!( description = "Add your description here"
pyproject, @r###" readme = "README.md"
[project] requires-python = ">=3.12"
name = "foo" dependencies = []
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[project.scripts] [project.scripts]
foo = "foo:main" foo = "foo:main"
[build-system] [build-system]
requires = ["uv_build[SPECIFIERS]"] requires = ["hatchling"]
build-backend = "uv_build" build-backend = "hatchling.build"
"### "#
); );
});
let init = fs_err::read_to_string(init_py)?; let init = fs_err::read_to_string(init_py)?;
insta::with_settings!({ insta::with_settings!({
@ -3881,8 +3787,7 @@ fn init_application_package_uv() -> Result<()> {
); );
}); });
// Use preview to go through the fast path. uv_snapshot!(context.filters(), context.run().arg("foo").current_dir(&child).env_remove(EnvVars::VIRTUAL_ENV), @r###"
uv_snapshot!(context.filters(), context.run().arg("--preview").arg("foo").current_dir(&child).env_remove(EnvVars::VIRTUAL_ENV), @r###"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
@ -3935,8 +3840,8 @@ fn init_with_description() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"# "#
); );
}); });
@ -3977,8 +3882,8 @@ fn init_without_description() -> Result<()> {
dependencies = [] dependencies = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]
build-backend = "hatchling.build" build-backend = "uv_build"
"# "#
); );
}); });

View File

@ -1,10 +1,5 @@
# The uv build backend # The uv build backend
!!! note
Currently, the default build backend for `uv init` is
[hatchling](https://pypi.org/project/hatchling/). This will change to `uv` in a future version.
A build backend transforms a source tree (i.e., a directory) into a source distribution or a wheel. 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](https://peps.python.org/pep-0517/)), but uv supports all build backends (as specified by [PEP 517](https://peps.python.org/pep-0517/)), but
@ -49,7 +44,7 @@ build-backend = "uv_build"
To create a new project that uses the uv build backend, use `uv init`: To create a new project that uses the uv build backend, use `uv init`:
```console ```console
$ uv init --build-backend uv $ uv init
``` ```
When the project is built, e.g., with [`uv build`](../guides/package.md), the uv build backend will When the project is built, e.g., with [`uv build`](../guides/package.md), the uv build backend will