Add uv toolchain uninstall (#4646)

This commit is contained in:
Zanie Blue 2024-07-01 22:37:53 -04:00 committed by GitHub
parent ad5151cda8
commit 8dabc29d80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 167 additions and 6 deletions

View file

@ -2022,15 +2022,18 @@ pub enum ToolchainCommand {
/// List the available toolchains. /// List the available toolchains.
List(ToolchainListArgs), List(ToolchainListArgs),
/// Download and install a specific toolchain. /// Download and install toolchains.
Install(ToolchainInstallArgs), Install(ToolchainInstallArgs),
/// Search for a toolchain /// Search for a toolchain.
#[command(disable_version_flag = true)] #[command(disable_version_flag = true)]
Find(ToolchainFindArgs), Find(ToolchainFindArgs),
/// Show the toolchains directory. /// Show the toolchains directory.
Dir, Dir,
/// Uninstall toolchains.
Uninstall(ToolchainUninstallArgs),
} }
#[derive(Args)] #[derive(Args)]
@ -2064,6 +2067,13 @@ pub struct ToolchainInstallArgs {
pub force: bool, pub force: bool,
} }
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct ToolchainUninstallArgs {
/// The toolchains to uninstall.
pub targets: Vec<String>,
}
#[derive(Args)] #[derive(Args)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct ToolchainFindArgs { pub struct ToolchainFindArgs {

View file

@ -17,7 +17,7 @@ pub enum ImplementationName {
PyPy, PyPy,
} }
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd)]
pub enum LenientImplementationName { pub enum LenientImplementationName {
Known(ImplementationName), Known(ImplementationName),
Unknown(String), Unknown(String),

View file

@ -204,7 +204,7 @@ Error=This toolchain is managed by uv and should not be modified.
"; ";
/// A uv-managed Python toolchain installed on the current system.. /// A uv-managed Python toolchain installed on the current system..
#[derive(Debug, Clone)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct InstalledToolchain { pub struct InstalledToolchain {
/// The path to the top-level directory of the installed toolchain. /// The path to the top-level directory of the installed toolchain.
path: PathBuf, path: PathBuf,

View file

@ -33,6 +33,7 @@ pub(crate) use toolchain::dir::dir as toolchain_dir;
pub(crate) use toolchain::find::find as toolchain_find; pub(crate) use toolchain::find::find as toolchain_find;
pub(crate) use toolchain::install::install as toolchain_install; pub(crate) use toolchain::install::install as toolchain_install;
pub(crate) use toolchain::list::list as toolchain_list; pub(crate) use toolchain::list::list as toolchain_list;
pub(crate) use toolchain::uninstall::uninstall as toolchain_uninstall;
use uv_cache::Cache; use uv_cache::Cache;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_installer::compile_tree; use uv_installer::compile_tree;

View file

@ -63,7 +63,7 @@ pub(crate) async fn install(
{ {
writeln!( writeln!(
printer.stderr(), printer.stderr(),
"Found installed toolchain '{}' that satisfies {request}", "Found installed toolchain `{}` that satisfies {request}",
toolchain.key() toolchain.key()
)?; )?;
if force { if force {

View file

@ -2,3 +2,4 @@ pub(crate) mod dir;
pub(crate) mod find; pub(crate) mod find;
pub(crate) mod install; pub(crate) mod install;
pub(crate) mod list; pub(crate) mod list;
pub(crate) mod uninstall;

View file

@ -0,0 +1,120 @@
use anyhow::Result;
use futures::StreamExt;
use itertools::Itertools;
use std::collections::BTreeSet;
use std::fmt::Write;
use uv_configuration::PreviewMode;
use uv_toolchain::downloads::{self, PythonDownloadRequest};
use uv_toolchain::managed::InstalledToolchains;
use uv_toolchain::ToolchainRequest;
use uv_warnings::warn_user_once;
use crate::commands::ExitStatus;
use crate::printer::Printer;
/// Uninstall Python toolchains.
pub(crate) async fn uninstall(
targets: Vec<String>,
preview: PreviewMode,
printer: Printer,
) -> Result<ExitStatus> {
if preview.is_disabled() {
warn_user_once!("`uv toolchain uninstall` is experimental and may change without warning.");
}
let toolchains = InstalledToolchains::from_settings()?.init()?;
let requests = targets
.iter()
.map(|target| ToolchainRequest::parse(target.as_str()))
.collect::<Vec<_>>();
let download_requests = requests
.iter()
.map(PythonDownloadRequest::from_request)
.collect::<Result<Vec<_>, downloads::Error>>()?;
let installed_toolchains: Vec<_> = toolchains.find_all()?.collect();
let mut matching_toolchains = BTreeSet::default();
for (request, download_request) in requests.iter().zip(download_requests) {
writeln!(
printer.stderr(),
"Looking for installed toolchains matching {request} ({download_request})"
)?;
let mut found = false;
for toolchain in installed_toolchains
.iter()
.filter(|toolchain| download_request.satisfied_by_key(toolchain.key()))
{
found = true;
if matching_toolchains.insert(toolchain.clone()) {
writeln!(
printer.stderr(),
"Found toolchain `{}` that matches {request}",
toolchain.key()
)?;
}
}
if !found {
writeln!(printer.stderr(), "No toolchains found matching {request}")?;
}
}
if matching_toolchains.is_empty() {
if matches!(requests.as_slice(), [ToolchainRequest::Any]) {
writeln!(printer.stderr(), "No installed toolchains found")?;
} else if requests.len() > 1 {
writeln!(
printer.stderr(),
"No toolchains found matching the requests"
)?;
} else {
writeln!(printer.stderr(), "No toolchains found matching the request")?;
}
return Ok(ExitStatus::Failure);
}
let tasks = futures::stream::iter(matching_toolchains.iter())
.map(|toolchain| async {
(
toolchain.key(),
fs_err::tokio::remove_dir_all(toolchain.path()).await,
)
})
.buffered(4);
let results = tasks.collect::<Vec<_>>().await;
let mut failed = false;
for (key, result) in results.iter().sorted_by_key(|(key, _)| key) {
if let Err(err) = result {
failed = true;
writeln!(
printer.stderr(),
"Failed to uninstall toolchain `{key}`: {err}"
)?;
} else {
writeln!(printer.stderr(), "Uninstalled toolchain `{key}`")?;
}
}
if failed {
if matching_toolchains.len() > 1 {
writeln!(printer.stderr(), "Uninstall of some toolchains failed")?;
}
return Ok(ExitStatus::Failure);
}
let s = if matching_toolchains.len() == 1 {
""
} else {
"s"
};
writeln!(
printer.stderr(),
"Uninstalled {} toolchain{s}",
matching_toolchains.len()
)?;
Ok(ExitStatus::Success)
}

View file

@ -906,6 +906,15 @@ async fn run() -> Result<ExitStatus> {
) )
.await .await
} }
Commands::Toolchain(ToolchainNamespace {
command: ToolchainCommand::Uninstall(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolchainUninstallSettings::resolve(args, filesystem);
show_settings!(args);
commands::toolchain_uninstall(args.targets, globals.preview, printer).await
}
Commands::Toolchain(ToolchainNamespace { Commands::Toolchain(ToolchainNamespace {
command: ToolchainCommand::Find(args), command: ToolchainCommand::Find(args),
}) => { }) => {

View file

@ -15,7 +15,7 @@ use uv_cli::{
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
PipSyncArgs, PipTreeArgs, PipUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolInstallArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolInstallArgs,
ToolListArgs, ToolRunArgs, ToolUninstallArgs, ToolchainFindArgs, ToolchainInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, ToolchainFindArgs, ToolchainInstallArgs,
ToolchainListArgs, VenvArgs, ToolchainListArgs, ToolchainUninstallArgs, VenvArgs,
}; };
use uv_client::Connectivity; use uv_client::Connectivity;
use uv_configuration::{ use uv_configuration::{
@ -374,6 +374,26 @@ impl ToolchainInstallSettings {
} }
} }
/// The resolved settings to use for a `toolchain uninstall` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct ToolchainUninstallSettings {
pub(crate) targets: Vec<String>,
}
impl ToolchainUninstallSettings {
/// Resolve the [`ToolchainUninstallSettings`] from the CLI and filesystem configuration.
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(
args: ToolchainUninstallArgs,
_filesystem: Option<FilesystemOptions>,
) -> Self {
let ToolchainUninstallArgs { targets } = args;
Self { targets }
}
}
/// The resolved settings to use for a `toolchain find` invocation. /// The resolved settings to use for a `toolchain find` invocation.
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]