diff --git a/crates/uv-configuration/src/project_build_backend.rs b/crates/uv-configuration/src/project_build_backend.rs index b852d23e9637..fb1a171e9d83 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 997b2d720923..1c621c320921 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 57fe0832faa0..8974dcc5214e 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 ff7f60465f41..03fa5b823d86 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 cf4f40970428..1daef7afc3cd 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(()) +}