From dc82a848419f4ce9fd4b4b55a0c589af8be2fa26 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 5 Dec 2024 15:30:48 +0100 Subject: [PATCH] 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 --- .../src/project_build_backend.rs | 4 + crates/uv-dev/src/generate_cli_reference.rs | 1 + crates/uv/src/commands/project/init.rs | 27 ++++++- crates/uv/src/lib.rs | 1 + crates/uv/tests/it/init.rs | 78 +++++++++++++++++++ 5 files changed, 107 insertions(+), 4 deletions(-) diff --git a/crates/uv-configuration/src/project_build_backend.rs b/crates/uv-configuration/src/project_build_backend.rs index b852d23e9..fb1a171e9 100644 --- a/crates/uv-configuration/src/project_build_backend.rs +++ b/crates/uv-configuration/src/project_build_backend.rs @@ -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"))] diff --git a/crates/uv-dev/src/generate_cli_reference.rs b/crates/uv-dev/src/generate_cli_reference.rs index 997b2d720..1c621c320 100644 --- a/crates/uv-dev/src/generate_cli_reference.rs +++ b/crates/uv-dev/src/generate_cli_reference.rs @@ -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( diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 57fe0832f..8974dcc52 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -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 { + 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] diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ff7f60465..03fa5b823 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1320,6 +1320,7 @@ async fn run_project( no_config, &cache, printer, + globals.preview, ) .await } diff --git a/crates/uv/tests/it/init.rs b/crates/uv/tests/it/init.rs index cf4f40970..1daef7afc 100644 --- a/crates/uv/tests/it/init.rs +++ b/crates/uv/tests/it/init.rs @@ -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(()) +}