diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 321f952cf..adb6f884e 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use anyhow::{bail, Result}; use owo_colors::OwoColorize; -use tracing::trace; +use tracing::{debug, trace}; use uv_cache::{Cache, Refresh}; use uv_cache_info::Timestamp; use uv_client::{BaseClientBuilder, Connectivity}; @@ -403,7 +403,12 @@ pub(crate) async fn install( &cache, printer, ) - .await? + .await + .inspect_err(|_| { + // If we failed to sync, remove the newly created environment. + debug!("Failed to sync environment; removing `{}`", from.name); + let _ = installed_tools.remove_environment(&from.name); + })? }; install_executables( diff --git a/crates/uv/tests/tool_install.rs b/crates/uv/tests/tool_install.rs index d045e6516..1f5792f5c 100644 --- a/crates/uv/tests/tool_install.rs +++ b/crates/uv/tests/tool_install.rs @@ -1385,6 +1385,69 @@ fn tool_install_no_entrypoints() { Installed 1 package in [TIME] + iniconfig==2.0.0 "###); + + // Ensure the tool environment is not created. + tool_dir + .child("iniconfig") + .assert(predicate::path::missing()); + bin_dir + .child("iniconfig") + .assert(predicate::path::missing()); +} + +/// Test installing a package that can't be installed. +#[test] +fn tool_install_uninstallable() { + let context = TestContext::new("3.12").with_filtered_exe_suffix(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + (r"#+", "#"), + ]) + .collect::>(); + uv_snapshot!(filters, context.tool_install() + .arg("pyenv") + .env("UV_TOOL_DIR", tool_dir.as_os_str()) + .env("XDG_BIN_HOME", bin_dir.as_os_str()) + .env("PATH", bin_dir.as_os_str()), @r##" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to prepare distributions + Caused by: Failed to fetch wheel: pyenv==0.0.1 + Caused by: Build backend failed to build wheel through `build_wheel` (exit status: 1) + + [stdout] + running bdist_wheel + running build + installing to build/bdist.linux-x86_64/wheel + running install + + [stderr] + # NOTE # + We are sorry, but this package is not installable with pip. + + Please read the installation instructions at: + + https://github.com/pyenv/pyenv#installation + # + + + "##); + + // Ensure the tool environment is not created. + tool_dir.child("pyenv").assert(predicate::path::missing()); + bin_dir.child("pyenv").assert(predicate::path::missing()); } /// Test installing a tool with a bare URL requirement.