mirror of https://github.com/astral-sh/uv
157 lines
4.6 KiB
Rust
157 lines
4.6 KiB
Rust
use std::collections::{BTreeSet, HashSet};
|
|
use std::fmt::Write;
|
|
|
|
use anyhow::Result;
|
|
use owo_colors::OwoColorize;
|
|
|
|
use uv_cache::Cache;
|
|
use uv_configuration::PreviewMode;
|
|
use uv_fs::Simplified;
|
|
use uv_python::downloads::PythonDownloadRequest;
|
|
use uv_python::{
|
|
find_python_installations, DiscoveryError, EnvironmentPreference, PythonFetch,
|
|
PythonInstallation, PythonNotFound, PythonPreference, PythonRequest, PythonSource,
|
|
};
|
|
use uv_warnings::warn_user_once;
|
|
|
|
use crate::commands::ExitStatus;
|
|
use crate::printer::Printer;
|
|
use crate::settings::PythonListKinds;
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
|
|
enum Kind {
|
|
Download,
|
|
Managed,
|
|
System,
|
|
}
|
|
|
|
/// List available Python installations.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(crate) async fn list(
|
|
kinds: PythonListKinds,
|
|
all_versions: bool,
|
|
all_platforms: bool,
|
|
python_preference: PythonPreference,
|
|
python_fetch: PythonFetch,
|
|
preview: PreviewMode,
|
|
cache: &Cache,
|
|
printer: Printer,
|
|
) -> Result<ExitStatus> {
|
|
if preview.is_disabled() {
|
|
warn_user_once!("`uv python list` is experimental and may change without warning.");
|
|
}
|
|
|
|
let download_request = match kinds {
|
|
PythonListKinds::Installed => None,
|
|
PythonListKinds::Default => {
|
|
if python_fetch.is_automatic() {
|
|
Some(if all_platforms {
|
|
PythonDownloadRequest::default()
|
|
} else {
|
|
PythonDownloadRequest::from_env()?
|
|
})
|
|
} else {
|
|
// If fetching is not automatic, then don't show downloads as available by default
|
|
None
|
|
}
|
|
}
|
|
};
|
|
|
|
let downloads = download_request
|
|
.as_ref()
|
|
.map(uv_python::downloads::PythonDownloadRequest::iter_downloads)
|
|
.into_iter()
|
|
.flatten();
|
|
|
|
let installed = find_python_installations(
|
|
&PythonRequest::Any,
|
|
EnvironmentPreference::OnlySystem,
|
|
python_preference,
|
|
cache,
|
|
)
|
|
// Raise discovery errors if critical
|
|
.filter(|result| {
|
|
result
|
|
.as_ref()
|
|
.err()
|
|
.map_or(true, DiscoveryError::is_critical)
|
|
})
|
|
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
|
|
.into_iter()
|
|
// Drop any "missing" installations
|
|
.filter_map(std::result::Result::ok);
|
|
|
|
let mut output = BTreeSet::new();
|
|
for installation in installed {
|
|
let kind = if matches!(installation.source(), PythonSource::Managed) {
|
|
Kind::Managed
|
|
} else {
|
|
Kind::System
|
|
};
|
|
output.insert((
|
|
installation.python_version().clone(),
|
|
installation.os().to_string(),
|
|
installation.key().clone(),
|
|
kind,
|
|
Some(installation.interpreter().sys_executable().to_path_buf()),
|
|
));
|
|
}
|
|
for download in downloads {
|
|
output.insert((
|
|
download.python_version().version().clone(),
|
|
download.os().to_string(),
|
|
download.key().clone(),
|
|
Kind::Download,
|
|
None,
|
|
));
|
|
}
|
|
|
|
let mut seen_minor = HashSet::new();
|
|
let mut seen_patch = HashSet::new();
|
|
let mut include = Vec::new();
|
|
for (version, os, key, kind, path) in output.iter().rev() {
|
|
// Only show the latest patch version for each download unless all were requested
|
|
if !matches!(kind, Kind::System) {
|
|
if let [major, minor, ..] = version.release() {
|
|
if !seen_minor.insert((os.clone(), *major, *minor)) {
|
|
if matches!(kind, Kind::Download) && !all_versions {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if let [major, minor, patch] = version.release() {
|
|
if !seen_patch.insert((os.clone(), *major, *minor, *patch)) {
|
|
if matches!(kind, Kind::Download) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
include.push((key, path));
|
|
}
|
|
|
|
// Compute the width of the first column.
|
|
let width = include
|
|
.iter()
|
|
.fold(0usize, |acc, (key, _)| acc.max(key.to_string().len()));
|
|
|
|
for (key, path) in include {
|
|
let key = key.to_string();
|
|
if let Some(path) = path {
|
|
writeln!(
|
|
printer.stdout(),
|
|
"{key:width$} {}",
|
|
path.user_display().cyan()
|
|
)?;
|
|
} else {
|
|
writeln!(
|
|
printer.stdout(),
|
|
"{key:width$} {}",
|
|
"<download available>".dimmed()
|
|
)?;
|
|
}
|
|
}
|
|
|
|
Ok(ExitStatus::Success)
|
|
}
|