diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 480acca82b89..abb6cc57174d 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1946,6 +1946,12 @@ pub struct RunArgs { /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. #[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)] pub python: Option, + + /// Whether to show resolver and installer output from any environment modifications. + /// + /// By default, environment modifications are omitted, but enabled under `--verbose`. + #[arg(long, env = "UV_SHOW_RESOLUTION", value_parser = clap::builder::BoolishValueParser::new(), hide = true)] + pub show_resolution: bool, } #[derive(Args)] @@ -2307,6 +2313,12 @@ pub struct ToolRunArgs { /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. #[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)] pub python: Option, + + /// Whether to show resolver and installer output from any environment modifications. + /// + /// By default, environment modifications are omitted, but enabled under `--verbose`. + #[arg(long, env = "UV_SHOW_RESOLUTION", value_parser = clap::builder::BoolishValueParser::new(), hide = true)] + pub show_resolution: bool, } #[derive(Args)] diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index c6b1ead0530c..37d8f318ea77 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -38,6 +38,7 @@ use crate::settings::ResolverInstallerSettings; pub(crate) async fn run( command: ExternalCommand, requirements: Vec, + show_resolution: bool, locked: bool, frozen: bool, package: Option, @@ -87,7 +88,7 @@ pub(crate) async fn run( // Initialize any shared state. let state = SharedState::default(); - let reporter = PythonDownloadReporter::single(printer); + let reporter = PythonDownloadReporter::single(printer.filter(show_resolution)); // Determine whether the command to execute is a PEP 723 script. let script_interpreter = if let RunCommand::Python(target, _) = &command { @@ -144,7 +145,7 @@ pub(crate) async fn run( concurrency, native_tls, cache, - printer, + printer.filter(show_resolution), ) .await?; @@ -202,7 +203,7 @@ pub(crate) async fn run( connectivity, native_tls, cache, - printer, + printer.filter(show_resolution), ) .await?; @@ -218,7 +219,7 @@ pub(crate) async fn run( concurrency, native_tls, cache, - printer, + printer.filter(show_resolution), ) .await { @@ -247,7 +248,7 @@ pub(crate) async fn run( concurrency, native_tls, cache, - printer, + printer.filter(show_resolution), ) .await?; @@ -403,7 +404,7 @@ pub(crate) async fn run( concurrency, native_tls, cache, - printer, + printer.filter(show_resolution), ) .await?, ) diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index efc4a7eae590..2ff7dd321d76 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -31,7 +31,7 @@ use crate::commands::reporters::PythonDownloadReporter; use crate::commands::project::resolve_names; use crate::commands::{ - project, project::environment::CachedEnvironment, tool::common::matching_packages, tool_list, + project::environment::CachedEnvironment, tool::common::matching_packages, tool_list, }; use crate::commands::{ExitStatus, SharedState}; use crate::printer::Printer; @@ -59,6 +59,7 @@ pub(crate) async fn run( command: Option, from: Option, with: &[RequirementsSource], + show_resolution: bool, python: Option, settings: ResolverInstallerSettings, invocation_source: ToolRunCommand, @@ -106,7 +107,7 @@ pub(crate) async fn run( concurrency, native_tls, cache, - printer, + printer.filter(show_resolution), ) .await?; @@ -315,7 +316,7 @@ async fn get_or_create_environment( // Resolve the `from` requirement. let from = { - project::resolve_names( + resolve_names( vec![RequirementsSpecification::parse_package(from)?], &interpreter, settings, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 1bf03f713cca..686213390cc9 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -650,6 +650,7 @@ async fn run(cli: Cli) -> Result { args.command, args.from, &requirements, + args.show_resolution || globals.verbose > 0, args.python, args.settings, invocation_source, @@ -910,6 +911,7 @@ async fn run_project( commands::run( args.command, requirements, + args.show_resolution || globals.verbose > 0, args.locked, args.frozen, args.package, diff --git a/crates/uv/src/printer.rs b/crates/uv/src/printer.rs index 3cd038515395..cf559eb0b6ce 100644 --- a/crates/uv/src/printer.rs +++ b/crates/uv/src/printer.rs @@ -45,6 +45,16 @@ impl Printer { Self::NoProgress => Stderr::Enabled, } } + + /// Filter the [`Printer`], casting to [`Printer::Quiet`] if the condition is false. + #[must_use] + pub(crate) fn filter(self, condition: bool) -> Self { + if condition { + self + } else { + Self::Quiet + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 117b6df85d0e..e242377c75cc 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -194,6 +194,7 @@ pub(crate) struct RunSettings { pub(crate) command: ExternalCommand, pub(crate) with: Vec, pub(crate) with_requirements: Vec, + pub(crate) show_resolution: bool, pub(crate) package: Option, pub(crate) no_project: bool, pub(crate) python: Option, @@ -206,8 +207,6 @@ impl RunSettings { #[allow(clippy::needless_pass_by_value)] pub(crate) fn resolve(args: RunArgs, filesystem: Option) -> Self { let RunArgs { - locked, - frozen, extra, all_extras, no_all_extras, @@ -216,6 +215,9 @@ impl RunSettings { command, with, with_requirements, + show_resolution, + locked, + frozen, installer, build, refresh, @@ -238,6 +240,7 @@ impl RunSettings { .into_iter() .filter_map(Maybe::into_option) .collect(), + show_resolution, package, no_project, python, @@ -258,6 +261,7 @@ pub(crate) struct ToolRunSettings { pub(crate) from: Option, pub(crate) with: Vec, pub(crate) with_requirements: Vec, + pub(crate) show_resolution: bool, pub(crate) python: Option, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, @@ -272,6 +276,7 @@ impl ToolRunSettings { from, with, with_requirements, + show_resolution, installer, build, refresh, @@ -286,6 +291,7 @@ impl ToolRunSettings { .into_iter() .filter_map(Maybe::into_option) .collect(), + show_resolution, python, refresh: Refresh::from(refresh), settings: ResolverInstallerSettings::combine( diff --git a/crates/uv/tests/common/mod.rs b/crates/uv/tests/common/mod.rs index 767692505a88..a9844e894a98 100644 --- a/crates/uv/tests/common/mod.rs +++ b/crates/uv/tests/common/mod.rs @@ -481,7 +481,7 @@ impl TestContext { /// Create a `uv run` command with options shared across scenarios. pub fn run(&self) -> Command { let mut command = Command::new(get_bin()); - command.arg("run"); + command.arg("run").env("UV_SHOW_RESOLUTION", "1"); self.add_shared_args(&mut command); command } @@ -489,13 +489,16 @@ impl TestContext { /// Create a `uv tool run` command with options shared across scenarios. pub fn tool_run(&self) -> Command { let mut command = Command::new(get_bin()); - command.arg("tool").arg("run"); + command + .arg("tool") + .arg("run") + .env("UV_SHOW_RESOLUTION", "1"); self.add_shared_args(&mut command); command } /// Create a `uv tool install` command with options shared across scenarios. - pub fn tool_install(&self) -> std::process::Command { + pub fn tool_install(&self) -> Command { let mut command = self.tool_install_without_exclude_newer(); command.arg("--exclude-newer").arg(EXCLUDE_NEWER); command @@ -507,16 +510,16 @@ impl TestContext { /// it can result in tests failing when the index state changes. Therefore, /// if you use this, there should be some other kind of mitigation in place. /// For example, pinning package versions. - pub fn tool_install_without_exclude_newer(&self) -> std::process::Command { - let mut command = std::process::Command::new(get_bin()); + pub fn tool_install_without_exclude_newer(&self) -> Command { + let mut command = Command::new(get_bin()); command.arg("tool").arg("install"); self.add_shared_args(&mut command); command } /// Create a `uv tool list` command with options shared across scenarios. - pub fn tool_list(&self) -> std::process::Command { - let mut command = std::process::Command::new(get_bin()); + pub fn tool_list(&self) -> Command { + let mut command = Command::new(get_bin()); command.arg("tool").arg("list"); self.add_shared_args(&mut command); command @@ -531,8 +534,8 @@ impl TestContext { } /// Create a `uv tool uninstall` command with options shared across scenarios. - pub fn tool_uninstall(&self) -> std::process::Command { - let mut command = std::process::Command::new(get_bin()); + pub fn tool_uninstall(&self) -> Command { + let mut command = Command::new(get_bin()); command.arg("tool").arg("uninstall"); self.add_shared_args(&mut command); command diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index ebea994e4a5a..0405c5f45322 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -882,3 +882,36 @@ fn run_from_directory() -> Result<()> { Ok(()) } + +/// By default, omit resolver and installer output. +#[test] +fn run_without_output() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { r#" + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.8" + dependencies = ["anyio", "sniffio==1.3.1"] + "# + })?; + + let test_script = context.temp_dir.child("main.py"); + test_script.write_str(indoc! { r" + import sniffio + " + })?; + + uv_snapshot!(context.filters(), context.run().env_remove("UV_SHOW_RESOLUTION").arg("--with").arg("iniconfig").arg("main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv run` is experimental and may change without warning + "###); + + Ok(()) +} diff --git a/crates/uv/tests/tool_run.rs b/crates/uv/tests/tool_run.rs index 8bb97fbcfcbb..35f52dd11b0a 100644 --- a/crates/uv/tests/tool_run.rs +++ b/crates/uv/tests/tool_run.rs @@ -139,12 +139,12 @@ fn tool_run_at_version() { // When `--from` is used, `@` is not treated as a version request uv_snapshot!(filters, context.tool_run() - .arg("--from") - .arg("pytest") - .arg("pytest@8.0.0") - .arg("--version") - .env("UV_TOOL_DIR", tool_dir.as_os_str()) - .env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###" + .arg("--from") + .arg("pytest") + .arg("pytest@8.0.0") + .arg("--version") + .env("UV_TOOL_DIR", tool_dir.as_os_str()) + .env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###" success: false exit_code: 1 ----- stdout ----- @@ -262,13 +262,13 @@ fn tool_run_warn_executable_not_in_from() { filters.push(("(?s)fastapi` instead.*", "fastapi` instead.")); uv_snapshot!(filters, context.tool_run() - .arg("--from") - .arg("fastapi") - .arg("fastapi") - .env("UV_EXCLUDE_NEWER", "2024-05-04T00:00:00Z") // TODO: Remove this once EXCLUDE_NEWER is bumped past 2024-05-04 - // (FastAPI 0.111 is only available from this date onwards) - .env("UV_TOOL_DIR", tool_dir.as_os_str()) - .env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###" + .arg("--from") + .arg("fastapi") + .arg("fastapi") + .env("UV_EXCLUDE_NEWER", "2024-05-04T00:00:00Z") // TODO: Remove this once EXCLUDE_NEWER is bumped past 2024-05-04 + // (FastAPI 0.111 is only available from this date onwards) + .env("UV_TOOL_DIR", tool_dir.as_os_str()) + .env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###" success: false exit_code: 1 ----- stdout ----- @@ -811,3 +811,27 @@ fn tool_run_list_installed() { warning: `uv tool run` is experimental and may change without warning "###); } + +/// By default, omit resolver and installer output. +#[test] +fn tool_run_without_output() { + let context = TestContext::new("3.12").with_filtered_counts(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + uv_snapshot!(context.filters(), context.tool_run() + .env_remove("UV_SHOW_RESOLUTION") + .arg("--") + .arg("pytest") + .arg("--version") + .env("UV_TOOL_DIR", tool_dir.as_os_str()) + .env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + pytest 8.1.1 + + ----- stderr ----- + warning: `uv tool run` is experimental and may change without warning + "###); +}