mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 05:03:46 +00:00
Use +- install output for Python versions (#5201)
## Summary Follow-up to https://github.com/astral-sh/uv/pull/4939. Uses a format that's closer to `uv pip install`, with some special-casing for single Pythons. ## Test Plan A few examples:    
This commit is contained in:
parent
360079fd05
commit
d54ae4e381
4 changed files with 123 additions and 64 deletions
|
|
@ -350,7 +350,7 @@ impl Ord for PythonInstallationKey {
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
self.implementation
|
self.implementation
|
||||||
.cmp(&other.implementation)
|
.cmp(&other.implementation)
|
||||||
.then_with(|| other.version().cmp(&self.version()))
|
.then_with(|| self.version().cmp(&other.version()))
|
||||||
.then_with(|| self.os.to_string().cmp(&other.os.to_string()))
|
.then_with(|| self.os.to_string().cmp(&other.os.to_string()))
|
||||||
.then_with(|| self.arch.to_string().cmp(&other.arch.to_string()))
|
.then_with(|| self.arch.to_string().cmp(&other.arch.to_string()))
|
||||||
.then_with(|| self.libc.to_string().cmp(&other.libc.to_string()))
|
.then_with(|| self.libc.to_string().cmp(&other.libc.to_string()))
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ use tracing::debug;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::Connectivity;
|
use uv_client::Connectivity;
|
||||||
use uv_configuration::PreviewMode;
|
use uv_configuration::PreviewMode;
|
||||||
use uv_fs::Simplified;
|
|
||||||
use uv_python::downloads::{DownloadResult, ManagedPythonDownload, PythonDownloadRequest};
|
use uv_python::downloads::{DownloadResult, ManagedPythonDownload, PythonDownloadRequest};
|
||||||
use uv_python::managed::{ManagedPythonInstallation, ManagedPythonInstallations};
|
use uv_python::managed::{ManagedPythonInstallation, ManagedPythonInstallations};
|
||||||
use uv_python::{
|
use uv_python::{
|
||||||
|
|
@ -19,6 +18,7 @@ use uv_python::{
|
||||||
};
|
};
|
||||||
use uv_warnings::warn_user_once;
|
use uv_warnings::warn_user_once;
|
||||||
|
|
||||||
|
use crate::commands::python::{ChangeEvent, ChangeEventKind};
|
||||||
use crate::commands::reporters::PythonDownloadReporter;
|
use crate::commands::reporters::PythonDownloadReporter;
|
||||||
use crate::commands::{elapsed, ExitStatus};
|
use crate::commands::{elapsed, ExitStatus};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
@ -76,6 +76,7 @@ pub(crate) async fn install(
|
||||||
|
|
||||||
let installed_installations: Vec<_> = installations.find_all()?.collect();
|
let installed_installations: Vec<_> = installations.find_all()?.collect();
|
||||||
let mut unfilled_requests = Vec::new();
|
let mut unfilled_requests = Vec::new();
|
||||||
|
let mut uninstalled = Vec::new();
|
||||||
for (request, download_request) in requests.iter().zip(download_requests) {
|
for (request, download_request) in requests.iter().zip(download_requests) {
|
||||||
if matches!(requests.as_slice(), [PythonRequest::Any]) {
|
if matches!(requests.as_slice(), [PythonRequest::Any]) {
|
||||||
writeln!(printer.stderr(), "Searching for Python installations")?;
|
writeln!(printer.stderr(), "Searching for Python installations")?;
|
||||||
|
|
@ -101,12 +102,8 @@ pub(crate) async fn install(
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if reinstall {
|
if reinstall {
|
||||||
writeln!(
|
|
||||||
printer.stderr(),
|
|
||||||
"Uninstalling {}",
|
|
||||||
installation.key().green()
|
|
||||||
)?;
|
|
||||||
fs::remove_dir_all(installation.path())?;
|
fs::remove_dir_all(installation.path())?;
|
||||||
|
uninstalled.push(installation.key().clone());
|
||||||
unfilled_requests.push(download_request);
|
unfilled_requests.push(download_request);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -152,41 +149,32 @@ pub(crate) async fn install(
|
||||||
let result = download
|
let result = download
|
||||||
.fetch(&client, installations_dir, Some(&reporter))
|
.fetch(&client, installations_dir, Some(&reporter))
|
||||||
.await;
|
.await;
|
||||||
(download.python_version(), result)
|
(download.key(), result)
|
||||||
})
|
})
|
||||||
.buffered(4)
|
.buffered(4)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let mut installed = vec![];
|
||||||
let mut failed = false;
|
let mut failed = false;
|
||||||
for (version, result) in results {
|
for (key, result) in results {
|
||||||
match result {
|
match result {
|
||||||
Ok(download) => {
|
Ok(download) => {
|
||||||
let path = match download {
|
let path = match download {
|
||||||
// We should only encounter already-available during concurrent installs
|
// We should only encounter already-available during concurrent installs
|
||||||
DownloadResult::AlreadyAvailable(path) => path,
|
DownloadResult::AlreadyAvailable(path) => path,
|
||||||
DownloadResult::Fetched(path) => {
|
DownloadResult::Fetched(path) => path,
|
||||||
writeln!(
|
|
||||||
printer.stderr(),
|
|
||||||
"Installed {} to: {}",
|
|
||||||
format!("Python {version}").cyan(),
|
|
||||||
path.user_display().cyan()
|
|
||||||
)?;
|
|
||||||
path
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
installed.push(key.clone());
|
||||||
|
|
||||||
// Ensure the installations have externally managed markers
|
// Ensure the installations have externally managed markers
|
||||||
let installed = ManagedPythonInstallation::new(path.clone())?;
|
let managed = ManagedPythonInstallation::new(path.clone())?;
|
||||||
installed.ensure_externally_managed()?;
|
managed.ensure_externally_managed()?;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
failed = true;
|
failed = true;
|
||||||
writeln!(
|
writeln!(printer.stderr(), "Failed to install {}: {err}", key.green())?;
|
||||||
printer.stderr(),
|
|
||||||
"Failed to install {}: {err}",
|
|
||||||
version.green()
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,17 +186,54 @@ pub(crate) async fn install(
|
||||||
return Ok(ExitStatus::Failure);
|
return Ok(ExitStatus::Failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = if downloads.len() == 1 { "" } else { "s" };
|
if let [installed] = installed.as_slice() {
|
||||||
writeln!(
|
// Ex) "Installed Python 3.9.7 in 1.68s"
|
||||||
printer.stderr(),
|
writeln!(
|
||||||
"{}",
|
printer.stderr(),
|
||||||
format!(
|
"{}",
|
||||||
"Installed {} {}",
|
format!(
|
||||||
format!("{} version{s}", downloads.len()).bold(),
|
"Installed {} {}",
|
||||||
format!("in {}", elapsed(start.elapsed())).dimmed()
|
format!("Python {}", installed.version()).bold(),
|
||||||
)
|
format!("in {}", elapsed(start.elapsed())).dimmed()
|
||||||
.dimmed()
|
)
|
||||||
)?;
|
.dimmed()
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// Ex) "Installed 2 versions in 1.68s"
|
||||||
|
let s = if installed.len() == 1 { "" } else { "s" };
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"Installed {} {}",
|
||||||
|
format!("{} version{s}", installed.len()).bold(),
|
||||||
|
format!("in {}", elapsed(start.elapsed())).dimmed()
|
||||||
|
)
|
||||||
|
.dimmed()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in uninstalled
|
||||||
|
.into_iter()
|
||||||
|
.map(|key| ChangeEvent {
|
||||||
|
key,
|
||||||
|
kind: ChangeEventKind::Removed,
|
||||||
|
})
|
||||||
|
.chain(installed.into_iter().map(|key| ChangeEvent {
|
||||||
|
key,
|
||||||
|
kind: ChangeEventKind::Added,
|
||||||
|
}))
|
||||||
|
.sorted_unstable_by(|a, b| a.key.cmp(&b.key).then_with(|| a.kind.cmp(&b.kind)))
|
||||||
|
{
|
||||||
|
match event.kind {
|
||||||
|
ChangeEventKind::Added => {
|
||||||
|
writeln!(printer.stderr(), " {} {}", "+".green(), event.key.bold(),)?;
|
||||||
|
}
|
||||||
|
ChangeEventKind::Removed => {
|
||||||
|
writeln!(printer.stderr(), " {} {}", "-".red(), event.key.bold(),)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,17 @@ pub(crate) mod install;
|
||||||
pub(crate) mod list;
|
pub(crate) mod list;
|
||||||
pub(crate) mod pin;
|
pub(crate) mod pin;
|
||||||
pub(crate) mod uninstall;
|
pub(crate) mod uninstall;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub(super) enum ChangeEventKind {
|
||||||
|
/// The Python version was uninstalled.
|
||||||
|
Removed,
|
||||||
|
/// The Python version was installed.
|
||||||
|
Added,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct ChangeEvent {
|
||||||
|
key: uv_python::PythonInstallationKey,
|
||||||
|
kind: ChangeEventKind,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use uv_python::managed::ManagedPythonInstallations;
|
||||||
use uv_python::PythonRequest;
|
use uv_python::PythonRequest;
|
||||||
use uv_warnings::warn_user_once;
|
use uv_warnings::warn_user_once;
|
||||||
|
|
||||||
|
use crate::commands::python::{ChangeEvent, ChangeEventKind};
|
||||||
use crate::commands::{elapsed, ExitStatus};
|
use crate::commands::{elapsed, ExitStatus};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
|
|
@ -68,18 +69,7 @@ pub(crate) async fn uninstall(
|
||||||
.filter(|installation| download_request.satisfied_by_key(installation.key()))
|
.filter(|installation| download_request.satisfied_by_key(installation.key()))
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
if matching_installations.insert(installation.clone()) {
|
matching_installations.insert(installation.clone());
|
||||||
if matches!(requests.as_slice(), [PythonRequest::Any]) {
|
|
||||||
writeln!(printer.stderr(), "Found: {}", installation.key().green(),)?;
|
|
||||||
} else {
|
|
||||||
writeln!(
|
|
||||||
printer.stderr(),
|
|
||||||
"Found existing installation for {}: {}",
|
|
||||||
request.cyan(),
|
|
||||||
installation.key().green(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
if matches!(requests.as_slice(), [PythonRequest::Any]) {
|
if matches!(requests.as_slice(), [PythonRequest::Any]) {
|
||||||
|
|
@ -114,8 +104,9 @@ pub(crate) async fn uninstall(
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let mut uninstalled = vec![];
|
||||||
let mut failed = false;
|
let mut failed = false;
|
||||||
for (key, result) in results.iter().sorted_by_key(|(key, _)| key) {
|
for (key, result) in results {
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
failed = true;
|
failed = true;
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
@ -124,7 +115,7 @@ pub(crate) async fn uninstall(
|
||||||
key.green()
|
key.green()
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(printer.stderr(), "Uninstalled: {}", key.green())?;
|
uninstalled.push(key.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,21 +126,50 @@ pub(crate) async fn uninstall(
|
||||||
return Ok(ExitStatus::Failure);
|
return Ok(ExitStatus::Failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = if matching_installations.len() == 1 {
|
if let [uninstalled] = uninstalled.as_slice() {
|
||||||
""
|
// Ex) "Uninstalled Python 3.9.7 in 1.68s"
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"Uninstalled {} {}",
|
||||||
|
format!("Python {}", uninstalled.version()).bold(),
|
||||||
|
format!("in {}", elapsed(start.elapsed())).dimmed()
|
||||||
|
)
|
||||||
|
.dimmed()
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
"s"
|
// Ex) "Uninstalled 2 versions in 1.68s"
|
||||||
};
|
let s = if uninstalled.len() == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{}",
|
"{}",
|
||||||
format!(
|
format!(
|
||||||
"Uninstalled {} {}",
|
"Uninstalled {} {}",
|
||||||
format!("{} version{s}", matching_installations.len()).bold(),
|
format!("{} version{s}", uninstalled.len()).bold(),
|
||||||
format!("in {}", elapsed(start.elapsed())).dimmed()
|
format!("in {}", elapsed(start.elapsed())).dimmed()
|
||||||
)
|
)
|
||||||
.dimmed()
|
.dimmed()
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in uninstalled
|
||||||
|
.into_iter()
|
||||||
|
.map(|key| ChangeEvent {
|
||||||
|
key,
|
||||||
|
kind: ChangeEventKind::Removed,
|
||||||
|
})
|
||||||
|
.sorted_unstable_by(|a, b| a.key.cmp(&b.key).then_with(|| a.kind.cmp(&b.kind)))
|
||||||
|
{
|
||||||
|
match event.kind {
|
||||||
|
ChangeEventKind::Added => {
|
||||||
|
writeln!(printer.stderr(), " {} {}", "+".green(), event.key.bold(),)?;
|
||||||
|
}
|
||||||
|
ChangeEventKind::Removed => {
|
||||||
|
writeln!(printer.stderr(), " {} {}", "-".red(), event.key.bold(),)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue