Skip to content

Commit

Permalink
Add uv toolchain uninstall (astral-sh#4646)
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Jul 2, 2024
1 parent ad5151c commit 8dabc29
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 6 deletions.
14 changes: 12 additions & 2 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2022,15 +2022,18 @@ pub enum ToolchainCommand {
/// List the available toolchains.
List(ToolchainListArgs),

/// Download and install a specific toolchain.
/// Download and install toolchains.
Install(ToolchainInstallArgs),

/// Search for a toolchain
/// Search for a toolchain.
#[command(disable_version_flag = true)]
Find(ToolchainFindArgs),

/// Show the toolchains directory.
Dir,

/// Uninstall toolchains.
Uninstall(ToolchainUninstallArgs),
}

#[derive(Args)]
Expand Down Expand Up @@ -2064,6 +2067,13 @@ pub struct ToolchainInstallArgs {
pub force: bool,
}

#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct ToolchainUninstallArgs {
/// The toolchains to uninstall.
pub targets: Vec<String>,
}

#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct ToolchainFindArgs {
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-toolchain/src/implementation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub enum ImplementationName {
PyPy,
}

#[derive(Debug, Eq, PartialEq, Clone)]
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd)]
pub enum LenientImplementationName {
Known(ImplementationName),
Unknown(String),
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-toolchain/src/managed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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..
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct InstalledToolchain {
/// The path to the top-level directory of the installed toolchain.
path: PathBuf,
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::install::install as toolchain_install;
pub(crate) use toolchain::list::list as toolchain_list;
pub(crate) use toolchain::uninstall::uninstall as toolchain_uninstall;
use uv_cache::Cache;
use uv_fs::Simplified;
use uv_installer::compile_tree;
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/toolchain/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub(crate) async fn install(
{
writeln!(
printer.stderr(),
"Found installed toolchain '{}' that satisfies {request}",
"Found installed toolchain `{}` that satisfies {request}",
toolchain.key()
)?;
if force {
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/commands/toolchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub(crate) mod dir;
pub(crate) mod find;
pub(crate) mod install;
pub(crate) mod list;
pub(crate) mod uninstall;
120 changes: 120 additions & 0 deletions crates/uv/src/commands/toolchain/uninstall.rs
Original file line number Diff line number Diff line change
@@ -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)
}
9 changes: 9 additions & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,15 @@ async fn run() -> Result<ExitStatus> {
)
.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 {
command: ToolchainCommand::Find(args),
}) => {
Expand Down
22 changes: 21 additions & 1 deletion crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use uv_cli::{
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
PipSyncArgs, PipTreeArgs, PipUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolInstallArgs,
ToolListArgs, ToolRunArgs, ToolUninstallArgs, ToolchainFindArgs, ToolchainInstallArgs,
ToolchainListArgs, VenvArgs,
ToolchainListArgs, ToolchainUninstallArgs, VenvArgs,
};
use uv_client::Connectivity;
use uv_configuration::{
Expand Down Expand Up @@ -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.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
Expand Down

0 comments on commit 8dabc29

Please sign in to comment.