Suppress resolver output by default in `uv run` and `uv tool run` (#5580)

## Summary

The idea here is that we hide all resolver output (the grayed out
resolver messages, plus the list of environment modifications) by
default in `uv run` and `uv tool run`. You can pass `--show-resolution`
to re-enable them.

Closes https://github.com/astral-sh/uv/issues/5458.
This commit is contained in:
Charlie Marsh 2024-07-30 14:11:52 -04:00 committed by GitHub
parent f7494f24cf
commit d6c319a368
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 125 additions and 33 deletions

View File

@ -1946,6 +1946,12 @@ pub struct RunArgs {
/// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.
#[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)] #[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)]
pub python: Option<String>, pub python: Option<String>,
/// 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)] #[derive(Args)]
@ -2307,6 +2313,12 @@ pub struct ToolRunArgs {
/// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.
#[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)] #[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)]
pub python: Option<String>, pub python: Option<String>,
/// 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)] #[derive(Args)]

View File

@ -38,6 +38,7 @@ use crate::settings::ResolverInstallerSettings;
pub(crate) async fn run( pub(crate) async fn run(
command: ExternalCommand, command: ExternalCommand,
requirements: Vec<RequirementsSource>, requirements: Vec<RequirementsSource>,
show_resolution: bool,
locked: bool, locked: bool,
frozen: bool, frozen: bool,
package: Option<PackageName>, package: Option<PackageName>,
@ -87,7 +88,7 @@ pub(crate) async fn run(
// Initialize any shared state. // Initialize any shared state.
let state = SharedState::default(); 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. // Determine whether the command to execute is a PEP 723 script.
let script_interpreter = if let RunCommand::Python(target, _) = &command { let script_interpreter = if let RunCommand::Python(target, _) = &command {
@ -144,7 +145,7 @@ pub(crate) async fn run(
concurrency, concurrency,
native_tls, native_tls,
cache, cache,
printer, printer.filter(show_resolution),
) )
.await?; .await?;
@ -202,7 +203,7 @@ pub(crate) async fn run(
connectivity, connectivity,
native_tls, native_tls,
cache, cache,
printer, printer.filter(show_resolution),
) )
.await?; .await?;
@ -218,7 +219,7 @@ pub(crate) async fn run(
concurrency, concurrency,
native_tls, native_tls,
cache, cache,
printer, printer.filter(show_resolution),
) )
.await .await
{ {
@ -247,7 +248,7 @@ pub(crate) async fn run(
concurrency, concurrency,
native_tls, native_tls,
cache, cache,
printer, printer.filter(show_resolution),
) )
.await?; .await?;
@ -403,7 +404,7 @@ pub(crate) async fn run(
concurrency, concurrency,
native_tls, native_tls,
cache, cache,
printer, printer.filter(show_resolution),
) )
.await?, .await?,
) )

View File

@ -31,7 +31,7 @@ use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::project::resolve_names; use crate::commands::project::resolve_names;
use crate::commands::{ 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::commands::{ExitStatus, SharedState};
use crate::printer::Printer; use crate::printer::Printer;
@ -59,6 +59,7 @@ pub(crate) async fn run(
command: Option<ExternalCommand>, command: Option<ExternalCommand>,
from: Option<String>, from: Option<String>,
with: &[RequirementsSource], with: &[RequirementsSource],
show_resolution: bool,
python: Option<String>, python: Option<String>,
settings: ResolverInstallerSettings, settings: ResolverInstallerSettings,
invocation_source: ToolRunCommand, invocation_source: ToolRunCommand,
@ -106,7 +107,7 @@ pub(crate) async fn run(
concurrency, concurrency,
native_tls, native_tls,
cache, cache,
printer, printer.filter(show_resolution),
) )
.await?; .await?;
@ -315,7 +316,7 @@ async fn get_or_create_environment(
// Resolve the `from` requirement. // Resolve the `from` requirement.
let from = { let from = {
project::resolve_names( resolve_names(
vec![RequirementsSpecification::parse_package(from)?], vec![RequirementsSpecification::parse_package(from)?],
&interpreter, &interpreter,
settings, settings,

View File

@ -650,6 +650,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.command, args.command,
args.from, args.from,
&requirements, &requirements,
args.show_resolution || globals.verbose > 0,
args.python, args.python,
args.settings, args.settings,
invocation_source, invocation_source,
@ -910,6 +911,7 @@ async fn run_project(
commands::run( commands::run(
args.command, args.command,
requirements, requirements,
args.show_resolution || globals.verbose > 0,
args.locked, args.locked,
args.frozen, args.frozen,
args.package, args.package,

View File

@ -45,6 +45,16 @@ impl Printer {
Self::NoProgress => Stderr::Enabled, 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]

View File

@ -194,6 +194,7 @@ pub(crate) struct RunSettings {
pub(crate) command: ExternalCommand, pub(crate) command: ExternalCommand,
pub(crate) with: Vec<String>, pub(crate) with: Vec<String>,
pub(crate) with_requirements: Vec<PathBuf>, pub(crate) with_requirements: Vec<PathBuf>,
pub(crate) show_resolution: bool,
pub(crate) package: Option<PackageName>, pub(crate) package: Option<PackageName>,
pub(crate) no_project: bool, pub(crate) no_project: bool,
pub(crate) python: Option<String>, pub(crate) python: Option<String>,
@ -206,8 +207,6 @@ impl RunSettings {
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(args: RunArgs, filesystem: Option<FilesystemOptions>) -> Self { pub(crate) fn resolve(args: RunArgs, filesystem: Option<FilesystemOptions>) -> Self {
let RunArgs { let RunArgs {
locked,
frozen,
extra, extra,
all_extras, all_extras,
no_all_extras, no_all_extras,
@ -216,6 +215,9 @@ impl RunSettings {
command, command,
with, with,
with_requirements, with_requirements,
show_resolution,
locked,
frozen,
installer, installer,
build, build,
refresh, refresh,
@ -238,6 +240,7 @@ impl RunSettings {
.into_iter() .into_iter()
.filter_map(Maybe::into_option) .filter_map(Maybe::into_option)
.collect(), .collect(),
show_resolution,
package, package,
no_project, no_project,
python, python,
@ -258,6 +261,7 @@ pub(crate) struct ToolRunSettings {
pub(crate) from: Option<String>, pub(crate) from: Option<String>,
pub(crate) with: Vec<String>, pub(crate) with: Vec<String>,
pub(crate) with_requirements: Vec<PathBuf>, pub(crate) with_requirements: Vec<PathBuf>,
pub(crate) show_resolution: bool,
pub(crate) python: Option<String>, pub(crate) python: Option<String>,
pub(crate) refresh: Refresh, pub(crate) refresh: Refresh,
pub(crate) settings: ResolverInstallerSettings, pub(crate) settings: ResolverInstallerSettings,
@ -272,6 +276,7 @@ impl ToolRunSettings {
from, from,
with, with,
with_requirements, with_requirements,
show_resolution,
installer, installer,
build, build,
refresh, refresh,
@ -286,6 +291,7 @@ impl ToolRunSettings {
.into_iter() .into_iter()
.filter_map(Maybe::into_option) .filter_map(Maybe::into_option)
.collect(), .collect(),
show_resolution,
python, python,
refresh: Refresh::from(refresh), refresh: Refresh::from(refresh),
settings: ResolverInstallerSettings::combine( settings: ResolverInstallerSettings::combine(

View File

@ -481,7 +481,7 @@ impl TestContext {
/// Create a `uv run` command with options shared across scenarios. /// Create a `uv run` command with options shared across scenarios.
pub fn run(&self) -> Command { pub fn run(&self) -> Command {
let mut command = Command::new(get_bin()); let mut command = Command::new(get_bin());
command.arg("run"); command.arg("run").env("UV_SHOW_RESOLUTION", "1");
self.add_shared_args(&mut command); self.add_shared_args(&mut command);
command command
} }
@ -489,13 +489,16 @@ impl TestContext {
/// Create a `uv tool run` command with options shared across scenarios. /// Create a `uv tool run` command with options shared across scenarios.
pub fn tool_run(&self) -> Command { pub fn tool_run(&self) -> Command {
let mut command = Command::new(get_bin()); 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); self.add_shared_args(&mut command);
command command
} }
/// Create a `uv tool install` command with options shared across scenarios. /// 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(); let mut command = self.tool_install_without_exclude_newer();
command.arg("--exclude-newer").arg(EXCLUDE_NEWER); command.arg("--exclude-newer").arg(EXCLUDE_NEWER);
command command
@ -507,16 +510,16 @@ impl TestContext {
/// it can result in tests failing when the index state changes. Therefore, /// 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. /// if you use this, there should be some other kind of mitigation in place.
/// For example, pinning package versions. /// For example, pinning package versions.
pub fn tool_install_without_exclude_newer(&self) -> std::process::Command { pub fn tool_install_without_exclude_newer(&self) -> Command {
let mut command = std::process::Command::new(get_bin()); let mut command = Command::new(get_bin());
command.arg("tool").arg("install"); command.arg("tool").arg("install");
self.add_shared_args(&mut command); self.add_shared_args(&mut command);
command command
} }
/// Create a `uv tool list` command with options shared across scenarios. /// Create a `uv tool list` command with options shared across scenarios.
pub fn tool_list(&self) -> std::process::Command { pub fn tool_list(&self) -> Command {
let mut command = std::process::Command::new(get_bin()); let mut command = Command::new(get_bin());
command.arg("tool").arg("list"); command.arg("tool").arg("list");
self.add_shared_args(&mut command); self.add_shared_args(&mut command);
command command
@ -531,8 +534,8 @@ impl TestContext {
} }
/// Create a `uv tool uninstall` command with options shared across scenarios. /// Create a `uv tool uninstall` command with options shared across scenarios.
pub fn tool_uninstall(&self) -> std::process::Command { pub fn tool_uninstall(&self) -> Command {
let mut command = std::process::Command::new(get_bin()); let mut command = Command::new(get_bin());
command.arg("tool").arg("uninstall"); command.arg("tool").arg("uninstall");
self.add_shared_args(&mut command); self.add_shared_args(&mut command);
command command

View File

@ -882,3 +882,36 @@ fn run_from_directory() -> Result<()> {
Ok(()) 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(())
}

View File

@ -811,3 +811,27 @@ fn tool_run_list_installed() {
warning: `uv tool run` is experimental and may change without warning 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
"###);
}