Build backend: Add template to uv init (#9661)

Add a preview option `uv init --build-backend uv --preview` that uses
the uv build backend when generating the project. The uv build backend
is in preview, so the option is also guarded by preview and hidden from
the help message and docs.

For https://github.com/astral-sh/uv/issues/3957#issuecomment-2518757563
This commit is contained in:
konsti 2024-12-05 15:30:48 +01:00 committed by GitHub
parent 77df01f4bf
commit dc82a84841
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 107 additions and 4 deletions

View File

@ -4,6 +4,10 @@
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ProjectBuildBackend {
#[cfg_attr(feature = "clap", value(hide = true))]
#[cfg_attr(feature = "schemars", value(hide = true))]
/// Use uv as the project build backend.
Uv,
#[default]
#[serde(alias = "hatchling")]
#[cfg_attr(feature = "clap", value(alias = "hatchling"))]

View File

@ -315,6 +315,7 @@ fn emit_possible_options(opt: &clap::Arg, output: &mut String) {
"\nPossible values:\n{}",
values
.into_iter()
.filter(|value| !value.is_hide_set())
.map(|value| {
let name = value.get_name();
value.get_help().map_or_else(

View File

@ -1,16 +1,16 @@
use anyhow::{anyhow, Context, Result};
use owo_colors::OwoColorize;
use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use anyhow::{anyhow, Context, Result};
use owo_colors::OwoColorize;
use std::str::FromStr;
use tracing::{debug, warn};
use uv_cache::Cache;
use uv_cli::AuthorFrom;
use uv_client::{BaseClientBuilder, Connectivity};
use uv_configuration::{
ProjectBuildBackend, TrustedHost, VersionControlError, VersionControlSystem,
PreviewMode, ProjectBuildBackend, TrustedHost, VersionControlError, VersionControlSystem,
};
use uv_fs::{Simplified, CWD};
use uv_git::GIT;
@ -57,7 +57,11 @@ pub(crate) async fn init(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
) -> 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 {
InitKind::Script => {
let Some(path) = explicit_path.as_deref() else {
@ -864,6 +868,21 @@ fn pyproject_project(
fn pyproject_build_system(package: &PackageName, build_backend: ProjectBuildBackend) -> String {
let module_name = package.as_dist_info_name();
match build_backend {
ProjectBuildBackend::Uv => {
// Limit to the stable version range.
let min_version = Version::from_str(uv_version::version()).unwrap();
debug_assert!(
min_version.release()[0] == 0,
"migrate to major version bumps"
);
let max_version = Version::new([0, min_version.release()[1] + 1]);
indoc::formatdoc! {r#"
[build-system]
requires = ["uv>={min_version},<{max_version}"]
build-backend = "uv"
"#}
}
.to_string(),
// Pure-python backends
ProjectBuildBackend::Hatch => indoc::indoc! {r#"
[build-system]

View File

@ -1320,6 +1320,7 @@ async fn run_project(
no_config,
&cache,
printer,
globals.preview,
)
.await
}

View File

@ -3193,3 +3193,81 @@ fn init_lib_build_backend_scikit() -> Result<()> {
Ok(())
}
/// Run `uv init --app --package --build-backend uv` to create a packaged application project
#[test]
fn init_application_package_uv() -> 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");
uv_snapshot!(context.filters(), context.init().current_dir(&child).arg("--app").arg("--package").arg("--build-backend").arg("uv"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: The uv build backend is experimental and may change without warning
Initialized project `foo`
"###);
let pyproject = fs_err::read_to_string(&pyproject_toml)?;
let mut filters = context.filters();
filters.push((r#"\["uv>=.*,<.*"\]"#, r#"["uv[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 = []
[project.scripts]
foo = "foo:main"
[build-system]
requires = ["uv[SPECIFIERS]"]
build-backend = "uv"
"###
);
});
let init = fs_err::read_to_string(init_py)?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
init, @r###"
def main() -> None:
print("Hello from foo!")
"###
);
});
// Use preview to go through the fast path.
uv_snapshot!(context.filters(), context.run().arg("--preview").arg("foo").current_dir(&child).env_remove(EnvVars::VIRTUAL_ENV), @r###"
success: true
exit_code: 0
----- stdout -----
Hello from foo!
----- stderr -----
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(())
}