uv/crates/uv/src/commands/pip_uninstall.rs

204 lines
6.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 uv_cache::Cache;
use uv_client::{Connectivity, RegistryClientBuilder};
use uv_fs::Simplified;
use uv_interpreter::PythonEnvironment;
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],
python: Option<String>,
system: bool,
break_system_packages: bool,
cache: Cache,
connectivity: Connectivity,
printer: Printer,
) -> Result<ExitStatus> {
let start = std::time::Instant::now();
// Initialize the registry client.
let client: uv_client::RegistryClient = RegistryClientBuilder::new(cache.clone())
.connectivity(connectivity)
.build();
// Read all requirements from the provided sources.
let RequirementsSpecification {
project: _project,
requirements,
constraints: _constraints,
overrides: _overrides,
editables,
index_url: _index_url,
extra_index_urls: _extra_index_urls,
no_index: _no_index,
find_links: _find_links,
extras: _extras,
} = RequirementsSpecification::from_simple_sources(sources, &client).await?;
// Detect the current Python interpreter.
let platform = Platform::current()?;
let venv = if let Some(python) = python.as_ref() {
PythonEnvironment::from_requested_python(python, &platform, &cache)?
} else if system {
PythonEnvironment::from_default_python(&platform, &cache)?
} else {
PythonEnvironment::from_virtualenv(platform, &cache)?
};
debug!(
"Using Python {} environment at {}",
venv.interpreter().python_version(),
venv.python_executable().simplified_display().cyan(),
);
// If the environment is externally managed, abort.
if let Some(externally_managed) = venv.interpreter().is_externally_managed() {
if break_system_packages {
debug!("Ignoring externally managed environment due to `--break-system-packages`");
} else {
return if let Some(error) = externally_managed.into_error() {
Err(anyhow::anyhow!(
"The interpreter at {} is externally managed, and indicates the following:\n\n{}\n\nConsider creating a virtual environment with `uv venv`.",
venv.root().simplified_display().cyan(),
textwrap::indent(&error, " ").green(),
))
} else {
Err(anyhow::anyhow!(
"The interpreter at {} is externally managed. Instead, create a virtual environment with `uv venv`.",
venv.root().simplified_display().cyan()
))
};
}
}
let _lock = venv.lock()?;
// Index the current `site-packages` directory.
let site_packages = uv_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 {
let installed = site_packages.get_packages(package);
if installed.is_empty() {
writeln!(
printer.stderr(),
"{}{} Skipping {} as it is not installed.",
"warning".yellow().bold(),
":".bold(),
package.as_ref().bold()
)?;
} else {
distributions.extend(installed);
}
}
// Identify all editables that are installed.
for editable in &editables {
let installed = site_packages.get_editables(editable);
if installed.is_empty() {
writeln!(
printer.stderr(),
"{}{} Skipping {} as it is not installed.",
"warning".yellow().bold(),
":".bold(),
editable.as_ref().bold()
)?;
} else {
distributions.extend(installed);
}
}
// 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.stderr(),
"{}{} No packages to uninstall.",
"warning".yellow().bold(),
":".bold(),
)?;
return Ok(ExitStatus::Success);
}
// Uninstall each package.
for distribution in &distributions {
let summary = uv_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.stderr(),
"{}",
format!(
"Uninstalled {} in {}",
format!(
"{} package{}",
distributions.len(),
if distributions.len() == 1 { "" } else { "s" }
)
.bold(),
elapsed(start.elapsed())
)
.dimmed()
)?;
for distribution in distributions {
writeln!(
printer.stderr(),
" {} {}{}",
"-".red(),
distribution.name().as_ref().bold(),
distribution.installed_version().to_string().dimmed()
)?;
}
Ok(ExitStatus::Success)
}