Skip to content

Commit

Permalink
uvx/uv tool run: Add context message before listing available tools w…
Browse files Browse the repository at this point in the history
…hen no arguments are provided (#7641)

## Summary

Adds a helpful context message when `uvx` is run without arguments
To clarify, it is displaying the installed tools.

This addresses confusion, such as the one highlighted in issue #7348,
by making the output more user-friendly and informative.

Related #4024 

## Test Plan

Updated the test snapshots to include the new output.
Running the tests locally with `cargo nextest run` confirms that the
tests pass.
The CI pipeline should also pass.

### Manuel Testing

**uvx**
```shell
# Make sure you have the updated version of uv installed on your path.
# cargo install --path ./crates/uv --force
❯ uvx
Provide a command to invoke with `uvx <command>` or `uvx --from <package> <command>`.

The following tools are already installed:

black v24.8.0
- black
- blackd
ruff v0.6.7
- ruff

See `uvx --help` for more information.
```

**uv tool list**
```shell
# Make sure you have the updated version of uv installed on your path.
# cargo install --path ./crates/uv --force
❯ uv tool list
black v24.8.0
- black
- blackd
ruff v0.6.7
- ruff
```

**uv tool run**
```shell
# Make sure you have the updated version of uv installed on your path.
# cargo install --path ./crates/uv --force
❯ uv tool run
Provide a command to invoke with `uv tool run <command>` or `uv tool run --from <package> <command>`.

The following tools are already installed:

black v24.8.0
- black
- blackd
ruff v0.6.7
- ruff

See `uv tool run --help` for more information.
```
---

Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com>

---------

Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com>
  • Loading branch information
kakkoyun authored Sep 26, 2024
1 parent 5e64dac commit 3ce3403
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 13 deletions.
83 changes: 78 additions & 5 deletions crates/uv/src/commands/tool/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ use crate::commands::pip::operations;
use crate::commands::project::{resolve_names, EnvironmentSpecification, ProjectError};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::tool::Target;
use crate::commands::{
project::environment::CachedEnvironment, tool::common::matching_packages, tool_list,
};
use crate::commands::{project::environment::CachedEnvironment, tool::common::matching_packages};
use crate::commands::{ExitStatus, SharedState};
use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings;
Expand Down Expand Up @@ -79,9 +77,13 @@ pub(crate) async fn run(
cache: Cache,
printer: Printer,
) -> anyhow::Result<ExitStatus> {
// treat empty command as `uv tool list`
// Treat empty command similar to `uv tool list`, list available tools.
let Some(command) = command else {
return tool_list(false, false, &cache, printer).await;
match list_available_tools(invocation_source, &cache, printer).await {
// It is a failure because user misses a required tool name.
Ok(()) => return Ok(ExitStatus::Error),
Err(err) => return Err(err),
};
};

let (target, args) = command.split();
Expand Down Expand Up @@ -262,6 +264,77 @@ fn get_entrypoints(
)?)
}

/// Display a list of tools that provide the executable.
///
/// If there is no package providing the executable, we will display a message to how to install a package.
async fn list_available_tools(
invocation_source: ToolRunCommand,
cache: &Cache,
printer: Printer,
) -> anyhow::Result<()> {
writeln!(
printer.stdout(),
"Provide a command to invoke with `{invocation_source} <command>` \
or `{invocation_source} --from <package> <command>`.\n"
)?;

let installed_tools = InstalledTools::from_settings()?;
let no_tools_installed_msg =
"No tools installed. See `uv tool install --help` for more information.";
let _lock = match installed_tools.lock().await {
Ok(lock) => lock,
Err(uv_tool::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
writeln!(printer.stdout(), "{no_tools_installed_msg}")?;
return Ok(());
}
Err(err) => return Err(err.into()),
};

let mut tools = installed_tools.tools()?.into_iter().collect::<Vec<_>>();
tools.sort_by_key(|(name, _)| name.clone());

if tools.is_empty() {
writeln!(printer.stdout(), "{no_tools_installed_msg}")?;
return Ok(());
}

let mut buf = String::new();
for (name, tool) in tools {
// Skip invalid tools.
let Ok(tool) = tool else {
continue;
};

// Output tool name and version.
let Ok(version) = installed_tools.version(&name, cache) else {
continue;
};
writeln!(buf, "{}", format!("{name} v{version}").bold())?;

// Output tool entrypoints.
for entrypoint in tool.entrypoints() {
writeln!(buf, "- {}", entrypoint.name)?;
}
}

// Installed tools were malformed or failed fetching versions.
if buf.is_empty() {
writeln!(printer.stderr(), "{no_tools_installed_msg}")?;
return Ok(());
}

writeln!(
printer.stdout(),
"The following tools are already installed:\n"
)?;
writeln!(printer.stdout(), "{buf}")?;
writeln!(
printer.stdout(),
"See `{invocation_source} --help` for more information."
)?;
Ok(())
}

/// Display a warning if an executable is not provided by package.
///
/// If found in a dependency of the requested package instead of the requested package itself, we will hint to use that instead.
Expand Down
23 changes: 15 additions & 8 deletions crates/uv/tests/tool_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -750,12 +750,14 @@ fn tool_run_list_installed() {
uv_snapshot!(context.filters(), context.tool_run()
.env("UV_TOOL_DIR", tool_dir.as_os_str())
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
success: false
exit_code: 2
----- stdout -----
Provide a command to invoke with `uv tool run <command>` or `uv tool run --from <package> <command>`.
No tools installed. See `uv tool install --help` for more information.
----- stderr -----
No tools installed
"###);

// Install `black`.
Expand All @@ -771,13 +773,19 @@ fn tool_run_list_installed() {
uv_snapshot!(context.filters(), context.tool_run()
.env("UV_TOOL_DIR", tool_dir.as_os_str())
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
success: false
exit_code: 2
----- stdout -----
Provide a command to invoke with `uv tool run <command>` or `uv tool run --from <package> <command>`.
The following tools are already installed:
black v24.2.0
- black
- blackd
See `uv tool run --help` for more information.
----- stderr -----
"###);
}
Expand Down Expand Up @@ -891,9 +899,8 @@ fn tool_run_with_editable() -> anyhow::Result<()> {
"###);

// Requesting an editable requirement should install it in a layer, even if it satisfied
uv_snapshot!(context.filters(), context.tool_run().arg("--with-editable").arg("./src/anyio_local").arg("flask").arg("--version").env("UV_TOOL_DIR", tool_dir.as_os_str()).env("XDG_BIN_HOME", bin_dir.as_os_str())

, @r###"
uv_snapshot!(context.filters(), context.tool_run().arg("--with-editable").arg("./src/anyio_local").arg("flask").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 -----
Expand Down

0 comments on commit 3ce3403

Please sign in to comment.