diff --git a/crates/uv-python/src/implementation.rs b/crates/uv-python/src/implementation.rs index 4cfd1ef9e..869ddbc6a 100644 --- a/crates/uv-python/src/implementation.rs +++ b/crates/uv-python/src/implementation.rs @@ -10,14 +10,14 @@ pub enum Error { UnknownImplementation(String), } -#[derive(Debug, Eq, PartialEq, Clone, Copy, Default, PartialOrd, Ord)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, Default, PartialOrd, Ord, Hash)] pub enum ImplementationName { #[default] CPython, PyPy, } -#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd)] +#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] pub enum LenientImplementationName { Known(ImplementationName), Unknown(String), diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index 7a17ed4f0..003fddd17 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -208,7 +208,7 @@ pub enum PythonInstallationKeyError { ParseError(String, String), } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PythonInstallationKey { pub(crate) implementation: LenientImplementationName, pub(crate) major: u8, diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index d2d35fb04..07b33035d 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -229,7 +229,7 @@ Error=This Python installation is managed by uv and should not be modified. "; /// A uv-managed Python installation on the current system.. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct ManagedPythonInstallation { /// The path to the top-level directory of the installed Python. path: PathBuf, diff --git a/crates/uv-python/src/platform.rs b/crates/uv-python/src/platform.rs index d81d09939..e6dc3e72b 100644 --- a/crates/uv-python/src/platform.rs +++ b/crates/uv-python/src/platform.rs @@ -13,13 +13,13 @@ pub enum Error { UnknownLibc(String), } -#[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] pub struct Arch(pub(crate) target_lexicon::Architecture); -#[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] pub struct Os(pub(crate) target_lexicon::OperatingSystem); -#[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] pub enum Libc { Some(target_lexicon::Environment), None, diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 8cb51132b..1a912703a 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -1,9 +1,10 @@ +use std::collections::BTreeSet; use std::fmt::Write; use anyhow::Result; use fs_err as fs; use futures::StreamExt; - +use itertools::Itertools; use uv_cache::Cache; use uv_client::Connectivity; use uv_configuration::PreviewMode; @@ -36,6 +37,7 @@ pub(crate) async fn install( let installations_dir = installations.root(); let _lock = installations.acquire_lock()?; + let targets = targets.into_iter().collect::>(); let requests: Vec<_> = if targets.is_empty() { if let Some(requests) = requests_from_version_file().await? { requests @@ -101,15 +103,6 @@ pub(crate) async fn install( return Ok(ExitStatus::Success); } - if unfilled_requests.len() > 1 { - writeln!( - printer.stderr(), - "Found {}/{} versions requiring installation", - unfilled_requests.len(), - requests.len() - )?; - } - let downloads = unfilled_requests .into_iter() // Populate the download requests with defaults @@ -117,6 +110,18 @@ pub(crate) async fn install( .map(|request| ManagedPythonDownload::from_request(&request)) .collect::, uv_python::downloads::Error>>()?; + // Ensure we only download each version once + let downloads = downloads + .into_iter() + .unique_by(|download| download.key()) + .collect::>(); + + writeln!( + printer.stderr(), + "Found {} versions requiring installation", + downloads.len() + )?; + // Construct a client let client = uv_client::BaseClientBuilder::new() .connectivity(connectivity) diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index 8615eeb35..e8de59fc3 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -27,6 +27,7 @@ pub(crate) async fn uninstall( let installations = ManagedPythonInstallations::from_settings()?.init()?; let _lock = installations.acquire_lock()?; + let targets = targets.into_iter().collect::>(); let requests = targets .iter() .map(|target| PythonRequest::parse(target.as_str())) @@ -83,7 +84,7 @@ pub(crate) async fn uninstall( return Ok(ExitStatus::Failure); } - let tasks = futures::stream::iter(matching_installations.iter()) + let tasks = futures::stream::iter(matching_installations.iter().unique()) .map(|installation| async { ( installation.key(),