Skip to content

Commit

Permalink
Respect verbatim executable name in uvx
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Feb 14, 2025
1 parent 172305a commit 64b9b1d
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 3 deletions.
24 changes: 21 additions & 3 deletions crates/uv/src/commands/tool/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,21 @@ async fn get_or_create_environment(
// Ex) `ruff>=0.6.0`
Target::Unspecified(requirement) => {
let spec = RequirementsSpecification::parse_package(requirement)?;

// Extract the verbatim executable name, if possible.
let name = match &spec.requirement {
UnresolvedRequirement::Named(..) => {
// Identify the package name from the PEP 508 specifier.
//
// For example, given `ruff>=0.6.0`, extract `ruff`, to use as the executable name.
let index = requirement
.find(|c| !matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.'))
.unwrap_or(requirement.len());
Some(&requirement[..index])
}
UnresolvedRequirement::Unnamed(..) => None,
};

if let UnresolvedRequirement::Named(requirement) = &spec.requirement {
if requirement.name.as_str() == "python" {
return Err(anyhow::anyhow!(
Expand All @@ -539,6 +554,7 @@ async fn get_or_create_environment(
.into());
}
}

let requirement = resolve_names(
vec![spec],
&interpreter,
Expand All @@ -556,12 +572,14 @@ async fn get_or_create_environment(
.pop()
.unwrap();

// Use the executable provided by the user, if possible (as in: `uvx --from package executable`).
//
// If no such executable was provided, rely on the package name (as in: `uvx git+https://github.com/pallets/flask`).
// Prefer, in order:
// 1. The verbatim executable provided by the user, independent of the requirement (as in: `uvx --from package executable`).
// 2. The verbatim executable provided by the user as a named requirement (as in: `uvx change_wheel_version`).
// 3. The resolved package name (as in: `uvx git+https://github.com/pallets/flask`).
let executable = request
.executable
.map(ToString::to_string)
.or_else(|| name.map(ToString::to_string))
.unwrap_or_else(|| requirement.name.to_string());

(executable, requirement)
Expand Down
88 changes: 88 additions & 0 deletions crates/uv/tests/it/tool_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1810,3 +1810,91 @@ fn tool_run_from_at() {
+ executable-application==0.2.0
"###);
}

#[test]
fn tool_run_verbatim_name() {
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");

// The normalized package name is `change-wheel-version`, but the executable is `change_wheel_version`.
uv_snapshot!(context.filters(), context.tool_run()
.arg("change_wheel_version")
.arg("--help")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
usage: change_wheel_version [-h] [--local-version LOCAL_VERSION]
[--version VERSION] [--delete-old-wheel]
[--allow-same-version]
wheel
positional arguments:
wheel
options:
-h, --help show this help message and exit
--local-version LOCAL_VERSION
--version VERSION
--delete-old-wheel
--allow-same-version
----- stderr -----
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
+ change-wheel-version==0.5.0
+ installer==0.7.0
+ packaging==24.0
+ wheel==0.43.0
"###);

uv_snapshot!(context.filters(), context.tool_run()
.arg("change-wheel-version")
.arg("--help")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: false
exit_code: 1
----- stdout -----
The executable `change-wheel-version` was not found.
The following executables are provided by `change-wheel-version`:
- change_wheel_version
Consider using `uv tool run --from change-wheel-version <EXECUTABLE_NAME>` instead.
----- stderr -----
Resolved [N] packages in [TIME]
warning: An executable named `change-wheel-version` is not provided by package `change-wheel-version`.
"###);

uv_snapshot!(context.filters(), context.tool_run()
.arg("--from")
.arg("change-wheel-version")
.arg("change_wheel_version")
.arg("--help")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
usage: change_wheel_version [-h] [--local-version LOCAL_VERSION]
[--version VERSION] [--delete-old-wheel]
[--allow-same-version]
wheel
positional arguments:
wheel
options:
-h, --help show this help message and exit
--local-version LOCAL_VERSION
--version VERSION
--delete-old-wheel
--allow-same-version
----- stderr -----
Resolved [N] packages in [TIME]
"###);
}

0 comments on commit 64b9b1d

Please sign in to comment.