uv/crates/puffin-cli/src/commands/pip_uninstall.rs

152 lines
4.6 KiB
Rust

use std::fmt::Write;
use anyhow::Result;
use owo_colors::OwoColorize;
use tracing::debug;
use distribution_types::{InstalledMetadata, Name};
use platform_host::Platform;
use puffin_cache::Cache;
use puffin_interpreter::Virtualenv;
use crate::commands::{elapsed, ExitStatus};
use crate::printer::Printer;
use crate::requirements::{RequirementsSource, RequirementsSpecification};
/// Uninstall packages from the current environment.
pub(crate) async fn pip_uninstall(
sources: &[RequirementsSource],
cache: Cache,
mut printer: Printer,
) -> Result<ExitStatus> {
let start = std::time::Instant::now();
// Read all requirements from the provided sources.
let (requirements, editables) = RequirementsSpecification::requirements_and_editables(sources)?;
// Detect the current Python interpreter.
let platform = Platform::current()?;
let venv = Virtualenv::from_env(platform, &cache)?;
debug!(
"Using Python interpreter: {}",
venv.python_executable().display()
);
let _lock = venv.lock()?;
// Index the current `site-packages` directory.
let site_packages = puffin_installer::SitePackages::from_executable(&venv)?;
// Sort and deduplicate the packages, which are keyed by name.
let packages = {
let mut packages = requirements
.into_iter()
.map(|requirement| requirement.name)
.collect::<Vec<_>>();
packages.sort_unstable();
packages.dedup();
packages
};
// Sort and deduplicate the editable packages, which are keyed by URL rather than package name.
let editables = {
let mut editables = editables
.iter()
.map(requirements_txt::EditableRequirement::raw)
.collect::<Vec<_>>();
editables.sort_unstable();
editables.dedup();
editables
};
// Map to the local distributions.
let distributions = {
let mut distributions = Vec::with_capacity(packages.len() + editables.len());
// Identify all packages that are installed.
for package in &packages {
if let Some(distribution) = site_packages.get(package) {
distributions.push(distribution);
} else {
writeln!(
printer,
"{}{} Skipping {} as it is not installed.",
"warning".yellow().bold(),
":".bold(),
package.as_ref().bold()
)?;
};
}
// Identify all editables that are installed.
for editable in &editables {
if let Some(distribution) = site_packages.get_editable(editable) {
distributions.push(distribution);
} else {
writeln!(
printer,
"{}{} Skipping {} as it is not installed.",
"warning".yellow().bold(),
":".bold(),
editable.as_ref().bold()
)?;
};
}
// Deduplicate, since a package could be listed both by name and editable URL.
distributions.sort_unstable_by_key(|dist| dist.path());
distributions.dedup_by_key(|dist| dist.path());
distributions
};
if distributions.is_empty() {
writeln!(
printer,
"{}{} No packages to uninstall.",
"warning".yellow().bold(),
":".bold(),
)?;
return Ok(ExitStatus::Success);
}
// Uninstall each package.
for distribution in &distributions {
let summary = puffin_installer::uninstall(distribution).await?;
debug!(
"Uninstalled {} ({} file{}, {} director{})",
distribution.name(),
summary.file_count,
if summary.file_count == 1 { "" } else { "s" },
summary.dir_count,
if summary.dir_count == 1 { "y" } else { "ies" },
);
}
writeln!(
printer,
"{}",
format!(
"Uninstalled {} in {}",
format!(
"{} package{}",
distributions.len(),
if distributions.len() == 1 { "" } else { "s" }
)
.bold(),
elapsed(start.elapsed())
)
.dimmed()
)?;
for distribution in distributions {
writeln!(
printer,
" {} {}{}",
"-".red(),
distribution.name().as_ref().white().bold(),
distribution.installed_version().to_string().dimmed()
)?;
}
Ok(ExitStatus::Success)
}