Skip to content

Commit e6ec3d4

Browse files
committed
Respect verbatim executable name in uvx
1 parent 172305a commit e6ec3d4

File tree

2 files changed

+81
-3
lines changed

2 files changed

+81
-3
lines changed

crates/uv/src/commands/tool/run.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,21 @@ async fn get_or_create_environment(
529529
// Ex) `ruff>=0.6.0`
530530
Target::Unspecified(requirement) => {
531531
let spec = RequirementsSpecification::parse_package(requirement)?;
532+
533+
// Extract the verbatim executable name, if possible.
534+
let name = match &spec.requirement {
535+
UnresolvedRequirement::Named(..) => {
536+
// Identify the package name from the PEP 508 specifier.
537+
//
538+
// For example, given `ruff>=0.6.0`, extract `ruff`, to use as the executable name.
539+
let index = requirement
540+
.find(|c| !matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.'))
541+
.unwrap_or(requirement.len());
542+
Some(&requirement[..index])
543+
}
544+
UnresolvedRequirement::Unnamed(..) => None,
545+
};
546+
532547
if let UnresolvedRequirement::Named(requirement) = &spec.requirement {
533548
if requirement.name.as_str() == "python" {
534549
return Err(anyhow::anyhow!(
@@ -539,6 +554,7 @@ async fn get_or_create_environment(
539554
.into());
540555
}
541556
}
557+
542558
let requirement = resolve_names(
543559
vec![spec],
544560
&interpreter,
@@ -556,12 +572,14 @@ async fn get_or_create_environment(
556572
.pop()
557573
.unwrap();
558574

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

567585
(executable, requirement)

crates/uv/tests/it/tool_run.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,3 +1810,63 @@ fn tool_run_from_at() {
18101810
+ executable-application==0.2.0
18111811
"###);
18121812
}
1813+
1814+
#[test]
1815+
fn tool_run_verbatim_name() {
1816+
let context = TestContext::new("3.12").with_filtered_counts();
1817+
let tool_dir = context.temp_dir.child("tools");
1818+
let bin_dir = context.temp_dir.child("bin");
1819+
1820+
// The normalized package name is `change-wheel-version`, but the executable is `change_wheel_version`.
1821+
// As such, this should fail.
1822+
uv_snapshot!(context.filters(), context.tool_run()
1823+
.arg("change-wheel-version")
1824+
.arg("--help")
1825+
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
1826+
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
1827+
success: false
1828+
exit_code: 1
1829+
----- stdout -----
1830+
The executable `change-wheel-version` was not found.
1831+
The following executables are provided by `change-wheel-version`:
1832+
- change_wheel_version
1833+
Consider using `uv tool run --from change-wheel-version <EXECUTABLE_NAME>` instead.
1834+
1835+
----- stderr -----
1836+
Resolved [N] packages in [TIME]
1837+
Prepared [N] packages in [TIME]
1838+
Installed [N] packages in [TIME]
1839+
+ change-wheel-version==0.5.0
1840+
+ installer==0.7.0
1841+
+ packaging==24.0
1842+
+ wheel==0.43.0
1843+
warning: An executable named `change-wheel-version` is not provided by package `change-wheel-version`.
1844+
"###);
1845+
1846+
uv_snapshot!(context.filters(), context.tool_run()
1847+
.arg("change_wheel_version")
1848+
.arg("--help")
1849+
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
1850+
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
1851+
success: true
1852+
exit_code: 0
1853+
----- stdout -----
1854+
usage: change_wheel_version [-h] [--local-version LOCAL_VERSION]
1855+
[--version VERSION] [--delete-old-wheel]
1856+
[--allow-same-version]
1857+
wheel
1858+
1859+
positional arguments:
1860+
wheel
1861+
1862+
options:
1863+
-h, --help show this help message and exit
1864+
--local-version LOCAL_VERSION
1865+
--version VERSION
1866+
--delete-old-wheel
1867+
--allow-same-version
1868+
1869+
----- stderr -----
1870+
Resolved [N] packages in [TIME]
1871+
"###);
1872+
}

0 commit comments

Comments
 (0)