diff --git a/crates/uv-configuration/src/lib.rs b/crates/uv-configuration/src/lib.rs index 96a539218..da3966701 100644 --- a/crates/uv-configuration/src/lib.rs +++ b/crates/uv-configuration/src/lib.rs @@ -5,6 +5,7 @@ pub use constraints::*; pub use name_specifiers::*; pub use overrides::*; pub use package_options::*; +pub use preview::*; pub use target_triple::*; mod authentication; @@ -14,4 +15,5 @@ mod constraints; mod name_specifiers; mod overrides; mod package_options; +mod preview; mod target_triple; diff --git a/crates/uv-configuration/src/preview.rs b/crates/uv-configuration/src/preview.rs new file mode 100644 index 000000000..38572589b --- /dev/null +++ b/crates/uv-configuration/src/preview.rs @@ -0,0 +1,37 @@ +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum PreviewMode { + #[default] + Disabled, + Enabled, +} + +impl PreviewMode { + pub fn is_enabled(&self) -> bool { + matches!(self, Self::Enabled) + } + + pub fn is_disabled(&self) -> bool { + matches!(self, Self::Disabled) + } +} + +impl From for PreviewMode { + fn from(version: bool) -> Self { + if version { + PreviewMode::Enabled + } else { + PreviewMode::Disabled + } + } +} + +impl Display for PreviewMode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Disabled => write!(f, "disabled"), + Self::Enabled => write!(f, "enabled"), + } + } +} diff --git a/crates/uv-workspace/src/settings.rs b/crates/uv-workspace/src/settings.rs index 0d6351eba..5534d859f 100644 --- a/crates/uv-workspace/src/settings.rs +++ b/crates/uv-workspace/src/settings.rs @@ -33,6 +33,7 @@ pub(crate) struct Tools { pub struct Options { pub native_tls: Option, pub no_cache: Option, + pub preview: Option, pub cache_dir: Option, pub pip: Option, } diff --git a/crates/uv/src/cli.rs b/crates/uv/src/cli.rs index 069e38836..840433720 100644 --- a/crates/uv/src/cli.rs +++ b/crates/uv/src/cli.rs @@ -79,6 +79,13 @@ pub(crate) struct GlobalArgs { #[arg(global = true, long, overrides_with("native_tls"), hide = true)] pub(crate) no_native_tls: bool, + + /// Whether to enable experimental, preview features. + #[arg(global = true, long, hide = true, env = "UV_PREVIEW", value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_preview"))] + pub(crate) preview: bool, + + #[arg(global = true, long, overrides_with("preview"), hide = true)] + pub(crate) no_preview: bool, } #[derive(Debug, Clone, clap::ValueEnum)] diff --git a/crates/uv/src/commands/run.rs b/crates/uv/src/commands/run.rs index f2e1ea075..876dc1a93 100644 --- a/crates/uv/src/commands/run.rs +++ b/crates/uv/src/commands/run.rs @@ -17,11 +17,13 @@ use std::{env, iter}; use tempfile::{tempdir_in, TempDir}; use tokio::process::Command; use tracing::debug; +use uv_warnings::warn_user; use uv_cache::Cache; use uv_client::{BaseClientBuilder, RegistryClient, RegistryClientBuilder}; use uv_configuration::{ - ConfigSettings, Constraints, NoBinary, NoBuild, Overrides, Reinstall, SetupPyStrategy, + ConfigSettings, Constraints, NoBinary, NoBuild, Overrides, PreviewMode, Reinstall, + SetupPyStrategy, }; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; @@ -45,9 +47,14 @@ pub(crate) async fn run( mut requirements: Vec, isolated: bool, no_workspace: bool, + preview: PreviewMode, cache: &Cache, printer: Printer, ) -> Result { + if preview.is_disabled() { + warn_user!("`uv run` is experimental and may change without warning."); + } + let command = if let Some(target) = target { let target_path = PathBuf::from(&target); if target_path diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 900bf38fc..149026b86 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -476,6 +476,9 @@ async fn run() -> Result { .await } Commands::Run(args) => { + // Resolve the settings from the command-line arguments and workspace configuration. + let args = settings::RunSettings::resolve(args, workspace); + let requirements = args .with .into_iter() @@ -502,6 +505,7 @@ async fn run() -> Result { requirements, args.isolated, args.no_workspace, + globals.preview, &cache, printer, ) diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 5a302fada..89c60e97b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1,3 +1,4 @@ +use std::ffi::OsString; use std::path::PathBuf; use distribution_types::IndexLocations; @@ -5,7 +6,7 @@ use install_wheel_rs::linker::LinkMode; use uv_cache::{CacheArgs, Refresh}; use uv_client::Connectivity; use uv_configuration::{ - ConfigSettings, IndexStrategy, KeyringProviderType, NoBinary, NoBuild, Reinstall, + ConfigSettings, IndexStrategy, KeyringProviderType, NoBinary, NoBuild, PreviewMode, Reinstall, SetupPyStrategy, TargetTriple, Upgrade, }; use uv_normalize::PackageName; @@ -16,7 +17,7 @@ use uv_workspace::{PipOptions, Workspace}; use crate::cli::{ ColorChoice, GlobalArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, - PipListArgs, PipShowArgs, PipSyncArgs, PipUninstallArgs, VenvArgs, + PipListArgs, PipShowArgs, PipSyncArgs, PipUninstallArgs, RunArgs, VenvArgs, }; use crate::commands::ListFormat; @@ -28,6 +29,7 @@ pub(crate) struct GlobalSettings { pub(crate) verbose: u8, pub(crate) color: ColorChoice, pub(crate) native_tls: bool, + pub(crate) preview: PreviewMode, } impl GlobalSettings { @@ -44,6 +46,11 @@ impl GlobalSettings { native_tls: flag(args.native_tls, args.no_native_tls) .or(workspace.and_then(|workspace| workspace.options.native_tls)) .unwrap_or(false), + preview: PreviewMode::from( + flag(args.preview, args.no_preview) + .or(workspace.and_then(|workspace| workspace.options.preview)) + .unwrap_or(false), + ), } } } @@ -71,6 +78,41 @@ impl CacheSettings { } } +/// The resolved settings to use for a `run` invocation. +#[allow(clippy::struct_excessive_bools)] +#[derive(Debug, Clone)] +pub(crate) struct RunSettings { + // CLI-only settings. + pub(crate) target: Option, + pub(crate) args: Vec, + pub(crate) isolated: bool, + pub(crate) with: Vec, + pub(crate) no_workspace: bool, +} + +impl RunSettings { + /// Resolve the [`RunSettings`] from the CLI and workspace configuration. + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn resolve(args: RunArgs, _workspace: Option) -> Self { + let RunArgs { + target, + args, + isolated, + with, + no_workspace, + } = args; + + Self { + // CLI-only settings. + target, + args, + isolated, + with, + no_workspace, + } + } +} + /// The resolved settings to use for a `pip compile` invocation. #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] diff --git a/uv.schema.json b/uv.schema.json index 47ff009ec..d832ea07d 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -31,6 +31,12 @@ "type": "null" } ] + }, + "preview": { + "type": [ + "boolean", + "null" + ] } }, "additionalProperties": false,