diff --git a/crates/uv-distribution-filename/src/expanded_tags.rs b/crates/uv-distribution-filename/src/expanded_tags.rs index 3b5022fd4..b04898089 100644 --- a/crates/uv-distribution-filename/src/expanded_tags.rs +++ b/crates/uv-distribution-filename/src/expanded_tags.rs @@ -5,7 +5,7 @@ use thiserror::Error; use uv_platform_tags::{ AbiTag, LanguageTag, ParseAbiTagError, ParseLanguageTagError, ParsePlatformTagError, - PlatformTag, Tags, + PlatformTag, TagCompatibility, Tags, }; use crate::splitter::MemchrSplitter; @@ -39,6 +39,30 @@ impl ExpandedTags { compatible_tags.is_compatible(tag.python_tags(), tag.abi_tags(), tag.platform_tags()) }) } + + /// Return the Python tags in this expanded tag set. + pub fn python_tags(&self) -> impl Iterator { + self.0.iter().flat_map(WheelTag::python_tags) + } + + /// Return the ABI tags in this expanded tag set. + pub fn abi_tags(&self) -> impl Iterator { + self.0.iter().flat_map(WheelTag::abi_tags) + } + + /// Return the platform tags in this expanded tag set. + pub fn platform_tags(&self) -> impl Iterator { + self.0.iter().flat_map(WheelTag::platform_tags) + } + + /// Return the [`TagCompatibility`] of the wheel with the given tags + pub fn compatibility(&self, compatible_tags: &Tags) -> TagCompatibility { + compatible_tags.compatibility( + self.python_tags().copied().collect::>().as_slice(), + self.abi_tags().copied().collect::>().as_slice(), + self.platform_tags().cloned().collect::>().as_slice(), + ) + } } #[derive(Error, Debug)] diff --git a/crates/uv-installer/src/satisfies.rs b/crates/uv-installer/src/satisfies.rs index ca8f75b35..1fb177f9c 100644 --- a/crates/uv-installer/src/satisfies.rs +++ b/crates/uv-installer/src/satisfies.rs @@ -1,12 +1,14 @@ use std::borrow::Cow; use std::fmt::Debug; +use owo_colors::OwoColorize; use same_file::is_same_file; use tracing::{debug, trace}; use url::Url; use uv_cache_info::CacheInfo; use uv_cache_key::{CanonicalUrl, RepositoryUrl}; +use uv_distribution_filename::ExpandedTags; use uv_distribution_types::{ BuildInfo, BuildVariables, ConfigSettings, ExtraBuildRequirement, ExtraBuildRequires, ExtraBuildVariables, InstalledDirectUrlDist, InstalledDist, InstalledDistKind, @@ -14,7 +16,7 @@ use uv_distribution_types::{ }; use uv_git_types::GitOid; use uv_normalize::PackageName; -use uv_platform_tags::Tags; +use uv_platform_tags::{IncompatibleTag, TagCompatibility, Tags}; use uv_pypi_types::{DirInfo, DirectUrl, VcsInfo, VcsKind}; #[derive(Debug, Copy, Clone)] @@ -314,7 +316,11 @@ impl RequirementSatisfaction { // If the distribution isn't compatible with the current platform, it is a mismatch. if let Ok(Some(wheel_tags)) = distribution.read_tags() { if !wheel_tags.is_compatible(tags) { - debug!("Platform tags mismatch for {name}: {distribution}"); + if let Some(hint) = generate_dist_compatibility_hint(wheel_tags, tags) { + debug!("Platform tags mismatch for {distribution}: {hint}"); + } else { + debug!("Platform tags mismatch for {distribution}"); + } return Self::Mismatch; } } @@ -355,3 +361,124 @@ fn extra_build_variables_for<'settings>( ) -> Option<&'settings BuildVariables> { extra_build_variables.get(name) } + +/// Generate a hint for explaining tag compatibility issues. +// TODO(zanieb): We should refactor this to share logic with `generate_wheel_compatibility_hint` +fn generate_dist_compatibility_hint(wheel_tags: &ExpandedTags, tags: &Tags) -> Option { + let TagCompatibility::Incompatible(incompatible_tag) = wheel_tags.compatibility(tags) else { + return None; + }; + + match incompatible_tag { + IncompatibleTag::Python => { + let wheel_tags = wheel_tags.python_tags(); + let current_tag = tags.python_tag(); + + if let Some(current) = current_tag { + let message = if let Some(pretty) = current.pretty() { + format!("{} (`{}`)", pretty.cyan(), current.cyan()) + } else { + format!("`{}`", current.cyan()) + }; + + Some(format!( + "The distribution is compatible with {}, but you're using {}", + wheel_tags + .map(|tag| if let Some(pretty) = tag.pretty() { + format!("{} (`{}`)", pretty.cyan(), tag.cyan()) + } else { + format!("`{}`", tag.cyan()) + }) + .collect::>() + .join(", "), + message + )) + } else { + Some(format!( + "The distribution requires {}", + wheel_tags + .map(|tag| if let Some(pretty) = tag.pretty() { + format!("{} (`{}`)", pretty.cyan(), tag.cyan()) + } else { + format!("`{}`", tag.cyan()) + }) + .collect::>() + .join(", ") + )) + } + } + IncompatibleTag::Abi => { + let wheel_tags = wheel_tags.abi_tags(); + let current_tag = tags.abi_tag(); + + if let Some(current) = current_tag { + let message = if let Some(pretty) = current.pretty() { + format!("{} (`{}`)", pretty.cyan(), current.cyan()) + } else { + format!("`{}`", current.cyan()) + }; + Some(format!( + "The distribution is compatible with {}, but you're using {}", + wheel_tags + .map(|tag| if let Some(pretty) = tag.pretty() { + format!("{} (`{}`)", pretty.cyan(), tag.cyan()) + } else { + format!("`{}`", tag.cyan()) + }) + .collect::>() + .join(", "), + message + )) + } else { + Some(format!( + "The distribution requires {}", + wheel_tags + .map(|tag| if let Some(pretty) = tag.pretty() { + format!("{} (`{}`)", pretty.cyan(), tag.cyan()) + } else { + format!("`{}`", tag.cyan()) + }) + .collect::>() + .join(", ") + )) + } + } + IncompatibleTag::Platform => { + let wheel_tags = wheel_tags.platform_tags(); + let current_tag = tags.platform_tag(); + + if let Some(current) = current_tag { + let message = if let Some(pretty) = current.pretty() { + format!("{} (`{}`)", pretty.cyan(), current.cyan()) + } else { + format!("`{}`", current.cyan()) + }; + Some(format!( + "The distribution is compatible with {}, but you're on {}", + wheel_tags + .map(|tag| if let Some(pretty) = tag.pretty() { + format!("{} (`{}`)", pretty.cyan(), tag.cyan()) + } else { + format!("`{}`", tag.cyan()) + }) + .collect::>() + .join(", "), + message + )) + } else { + Some(format!( + "The distribution requires {}", + wheel_tags + .map(|tag| if let Some(pretty) = tag.pretty() { + format!("{} (`{}`)", pretty.cyan(), tag.cyan()) + } else { + format!("`{}`", tag.cyan()) + }) + .collect::>() + .join(", ") + )) + } + } + _ => None, + } +}