mirror of https://github.com/astral-sh/uv
Show expected and available ABI tags in resolver errors (#10527)
## Summary The idea here is to show both (1) an example of a compatible tag and (2) the tags that were available, whenever we fail to resolve due to an abscence of matching wheels. Closes https://github.com/astral-sh/uv/issues/2777.
This commit is contained in:
parent
e0e8ba582a
commit
2ffa31946d
|
|
@ -5019,6 +5019,7 @@ dependencies = [
|
||||||
"fs-err 3.0.0",
|
"fs-err 3.0.0",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"jiff",
|
"jiff",
|
||||||
|
"owo-colors",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"rkyv",
|
"rkyv",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ bitflags = { workspace = true }
|
||||||
fs-err = { workspace = true }
|
fs-err = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
jiff = { workspace = true }
|
jiff = { workspace = true }
|
||||||
|
owo-colors = { workspace = true }
|
||||||
petgraph = { workspace = true }
|
petgraph = { workspace = true }
|
||||||
rkyv = { workspace = true }
|
rkyv = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use arcstr::ArcStr;
|
use arcstr::ArcStr;
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use uv_distribution_filename::{BuildTag, WheelFilename};
|
use uv_distribution_filename::{BuildTag, WheelFilename};
|
||||||
use uv_pep440::VersionSpecifiers;
|
use uv_pep440::VersionSpecifiers;
|
||||||
use uv_pep508::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString};
|
use uv_pep508::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString};
|
||||||
use uv_platform_tags::{IncompatibleTag, TagPriority};
|
use uv_platform_tags::{AbiTag, IncompatibleTag, TagPriority, Tags};
|
||||||
use uv_pypi_types::{HashDigest, Yanked};
|
use uv_pypi_types::{HashDigest, Yanked};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -164,6 +166,40 @@ impl IncompatibleDist {
|
||||||
Self::Unavailable => format!("have {self}"),
|
Self::Unavailable => format!("have {self}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn context_message(
|
||||||
|
&self,
|
||||||
|
tags: Option<&Tags>,
|
||||||
|
requires_python: Option<AbiTag>,
|
||||||
|
) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Self::Wheel(incompatibility) => match incompatibility {
|
||||||
|
IncompatibleWheel::Tag(IncompatibleTag::Python) => {
|
||||||
|
let tag = tags?.python_tag().map(ToString::to_string)?;
|
||||||
|
Some(format!("(e.g., `{tag}`)", tag = tag.cyan()))
|
||||||
|
}
|
||||||
|
IncompatibleWheel::Tag(IncompatibleTag::Abi) => {
|
||||||
|
let tag = tags?.abi_tag().map(ToString::to_string)?;
|
||||||
|
Some(format!("(e.g., `{tag}`)", tag = tag.cyan()))
|
||||||
|
}
|
||||||
|
IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion) => {
|
||||||
|
let tag = requires_python?;
|
||||||
|
Some(format!("(e.g., `{tag}`)", tag = tag.cyan()))
|
||||||
|
}
|
||||||
|
IncompatibleWheel::Tag(IncompatibleTag::Platform) => {
|
||||||
|
let tag = tags?.platform_tag().map(ToString::to_string)?;
|
||||||
|
Some(format!("(e.g., `{tag}`)", tag = tag.cyan()))
|
||||||
|
}
|
||||||
|
IncompatibleWheel::Tag(IncompatibleTag::Invalid) => None,
|
||||||
|
IncompatibleWheel::NoBinary => None,
|
||||||
|
IncompatibleWheel::Yanked(..) => None,
|
||||||
|
IncompatibleWheel::ExcludeNewer(..) => None,
|
||||||
|
IncompatibleWheel::RequiresPython(..) => None,
|
||||||
|
},
|
||||||
|
Self::Source(..) => None,
|
||||||
|
Self::Unavailable => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for IncompatibleDist {
|
impl Display for IncompatibleDist {
|
||||||
|
|
@ -246,6 +282,8 @@ pub enum IncompatibleWheel {
|
||||||
/// The wheel tags do not match those of the target Python platform.
|
/// The wheel tags do not match those of the target Python platform.
|
||||||
Tag(IncompatibleTag),
|
Tag(IncompatibleTag),
|
||||||
/// The required Python version is not a superset of the target Python version range.
|
/// The required Python version is not a superset of the target Python version range.
|
||||||
|
///
|
||||||
|
/// TODO(charlie): Consider making this two variants to reduce enum size.
|
||||||
RequiresPython(VersionSpecifiers, PythonRequirementKind),
|
RequiresPython(VersionSpecifiers, PythonRequirementKind),
|
||||||
/// The wheel was yanked.
|
/// The wheel was yanked.
|
||||||
Yanked(Yanked),
|
Yanked(Yanked),
|
||||||
|
|
@ -483,6 +521,40 @@ impl PrioritizedDist {
|
||||||
pub fn best_wheel(&self) -> Option<&(RegistryBuiltWheel, WheelCompatibility)> {
|
pub fn best_wheel(&self) -> Option<&(RegistryBuiltWheel, WheelCompatibility)> {
|
||||||
self.0.best_wheel_index.map(|i| &self.0.wheels[i])
|
self.0.best_wheel_index.map(|i| &self.0.wheels[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the set of all Python tags for the distribution.
|
||||||
|
pub fn python_tags(&self) -> BTreeSet<&str> {
|
||||||
|
self.0
|
||||||
|
.wheels
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(wheel, _)| wheel.filename.python_tag.iter().map(String::as_str))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the set of all ABI tags for the distribution.
|
||||||
|
pub fn abi_tags(&self) -> BTreeSet<&str> {
|
||||||
|
self.0
|
||||||
|
.wheels
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(wheel, _)| wheel.filename.abi_tag.iter().map(String::as_str))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the set of platform tags for the distribution that are ABI-compatible with the given
|
||||||
|
/// tags.
|
||||||
|
pub fn platform_tags<'a>(&'a self, tags: &'a Tags) -> BTreeSet<&'a str> {
|
||||||
|
let mut candidates = BTreeSet::new();
|
||||||
|
for (wheel, _) in &self.0.wheels {
|
||||||
|
for wheel_py in &wheel.filename.python_tag {
|
||||||
|
for wheel_abi in &wheel.filename.abi_tag {
|
||||||
|
if tags.is_compatible_abi(wheel_py.as_str(), wheel_abi.as_str()) {
|
||||||
|
candidates.extend(wheel.filename.platform_tag.iter().map(String::as_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidates
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompatibleDist<'a> {
|
impl<'a> CompatibleDist<'a> {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ use std::{cmp, num::NonZeroU32};
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::abi_tag::AbiTag;
|
use crate::{AbiTag, Arch, LanguageTag, Os, Platform, PlatformError};
|
||||||
use crate::{Arch, LanguageTag, Os, Platform, PlatformError};
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum TagsError {
|
pub enum TagsError {
|
||||||
|
|
@ -75,6 +74,8 @@ pub struct Tags {
|
||||||
/// `python_tag` |--> `abi_tag` |--> `platform_tag` |--> priority
|
/// `python_tag` |--> `abi_tag` |--> `platform_tag` |--> priority
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
map: Arc<FxHashMap<String, FxHashMap<String, FxHashMap<String, TagPriority>>>>,
|
map: Arc<FxHashMap<String, FxHashMap<String, FxHashMap<String, TagPriority>>>>,
|
||||||
|
/// The highest-priority tag for the Python version and platform.
|
||||||
|
best: Option<(String, String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tags {
|
impl Tags {
|
||||||
|
|
@ -83,6 +84,9 @@ impl Tags {
|
||||||
/// Tags are prioritized based on their position in the given vector. Specifically, tags that
|
/// Tags are prioritized based on their position in the given vector. Specifically, tags that
|
||||||
/// appear earlier in the vector are given higher priority than tags that appear later.
|
/// appear earlier in the vector are given higher priority than tags that appear later.
|
||||||
pub fn new(tags: Vec<(String, String, String)>) -> Self {
|
pub fn new(tags: Vec<(String, String, String)>) -> Self {
|
||||||
|
// Store the highest-priority tag for each component.
|
||||||
|
let best = tags.first().cloned();
|
||||||
|
|
||||||
// Index the tags by Python version, ABI, and platform.
|
// Index the tags by Python version, ABI, and platform.
|
||||||
let mut map = FxHashMap::default();
|
let mut map = FxHashMap::default();
|
||||||
for (index, (py, abi, platform)) in tags.into_iter().rev().enumerate() {
|
for (index, (py, abi, platform)) in tags.into_iter().rev().enumerate() {
|
||||||
|
|
@ -93,7 +97,11 @@ impl Tags {
|
||||||
.entry(platform)
|
.entry(platform)
|
||||||
.or_insert(TagPriority::try_from(index).expect("valid tag priority"));
|
.or_insert(TagPriority::try_from(index).expect("valid tag priority"));
|
||||||
}
|
}
|
||||||
Self { map: Arc::new(map) }
|
|
||||||
|
Self {
|
||||||
|
map: Arc::new(map),
|
||||||
|
best,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the compatible tags for the given Python implementation (e.g., `cpython`), version,
|
/// Returns the compatible tags for the given Python implementation (e.g., `cpython`), version,
|
||||||
|
|
@ -291,6 +299,30 @@ impl Tags {
|
||||||
}
|
}
|
||||||
max_compatibility
|
max_compatibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the highest-priority Python tag for the [`Tags`].
|
||||||
|
pub fn python_tag(&self) -> Option<&str> {
|
||||||
|
self.best.as_ref().map(|(py, _, _)| py.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the highest-priority ABI tag for the [`Tags`].
|
||||||
|
pub fn abi_tag(&self) -> Option<&str> {
|
||||||
|
self.best.as_ref().map(|(_, abi, _)| abi.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the highest-priority platform tag for the [`Tags`].
|
||||||
|
pub fn platform_tag(&self) -> Option<&str> {
|
||||||
|
self.best.as_ref().map(|(_, _, platform)| platform.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the given language and ABI tags are compatible with the current
|
||||||
|
/// environment.
|
||||||
|
pub fn is_compatible_abi<'a>(&'a self, python_tag: &'a str, abi_tag: &'a str) -> bool {
|
||||||
|
self.map
|
||||||
|
.get(python_tag)
|
||||||
|
.map(|abis| abis.contains_key(abi_tag))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The priority of a platform tag.
|
/// The priority of a platform tag.
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ impl CandidateSelector {
|
||||||
return Some(Candidate {
|
return Some(Candidate {
|
||||||
name: package_name,
|
name: package_name,
|
||||||
version,
|
version,
|
||||||
|
prioritized: None,
|
||||||
dist: CandidateDist::Compatible(CompatibleDist::InstalledDist(
|
dist: CandidateDist::Compatible(CompatibleDist::InstalledDist(
|
||||||
dist,
|
dist,
|
||||||
)),
|
)),
|
||||||
|
|
@ -302,6 +303,7 @@ impl CandidateSelector {
|
||||||
return Some(Candidate {
|
return Some(Candidate {
|
||||||
name: package_name,
|
name: package_name,
|
||||||
version,
|
version,
|
||||||
|
prioritized: None,
|
||||||
dist: CandidateDist::Compatible(CompatibleDist::InstalledDist(dist)),
|
dist: CandidateDist::Compatible(CompatibleDist::InstalledDist(dist)),
|
||||||
choice_kind: VersionChoiceKind::Installed,
|
choice_kind: VersionChoiceKind::Installed,
|
||||||
});
|
});
|
||||||
|
|
@ -583,6 +585,8 @@ pub(crate) struct Candidate<'a> {
|
||||||
name: &'a PackageName,
|
name: &'a PackageName,
|
||||||
/// The version of the package.
|
/// The version of the package.
|
||||||
version: &'a Version,
|
version: &'a Version,
|
||||||
|
/// The prioritized distribution for the package.
|
||||||
|
prioritized: Option<&'a PrioritizedDist>,
|
||||||
/// The distributions to use for resolving and installing the package.
|
/// The distributions to use for resolving and installing the package.
|
||||||
dist: CandidateDist<'a>,
|
dist: CandidateDist<'a>,
|
||||||
/// Whether this candidate was selected from a preference.
|
/// Whether this candidate was selected from a preference.
|
||||||
|
|
@ -599,6 +603,7 @@ impl<'a> Candidate<'a> {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
|
prioritized: Some(dist),
|
||||||
dist: CandidateDist::from(dist),
|
dist: CandidateDist::from(dist),
|
||||||
choice_kind,
|
choice_kind,
|
||||||
}
|
}
|
||||||
|
|
@ -632,6 +637,11 @@ impl<'a> Candidate<'a> {
|
||||||
pub(crate) fn dist(&self) -> &CandidateDist<'a> {
|
pub(crate) fn dist(&self) -> &CandidateDist<'a> {
|
||||||
&self.dist
|
&self.dist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the prioritized distribution for the candidate.
|
||||||
|
pub(crate) fn prioritized(&self) -> Option<&PrioritizedDist> {
|
||||||
|
self.prioritized
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Name for Candidate<'_> {
|
impl Name for Candidate<'_> {
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,12 @@ use uv_distribution_types::{
|
||||||
};
|
};
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
use uv_pep440::{LocalVersionSlice, Version};
|
use uv_pep440::{LocalVersionSlice, Version};
|
||||||
|
use uv_platform_tags::Tags;
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
use crate::candidate_selector::CandidateSelector;
|
use crate::candidate_selector::CandidateSelector;
|
||||||
use crate::dependency_provider::UvDependencyProvider;
|
use crate::dependency_provider::UvDependencyProvider;
|
||||||
|
use crate::fork_indexes::ForkIndexes;
|
||||||
use crate::fork_urls::ForkUrls;
|
use crate::fork_urls::ForkUrls;
|
||||||
use crate::prerelease::AllowPrerelease;
|
use crate::prerelease::AllowPrerelease;
|
||||||
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter};
|
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter};
|
||||||
|
|
@ -27,7 +29,7 @@ use crate::resolution::ConflictingDistributionError;
|
||||||
use crate::resolver::{
|
use crate::resolver::{
|
||||||
MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
|
MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
|
||||||
};
|
};
|
||||||
use crate::Options;
|
use crate::{InMemoryIndex, Options};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ResolveError {
|
pub enum ResolveError {
|
||||||
|
|
@ -130,9 +132,9 @@ impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
|
||||||
pub(crate) type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
|
pub(crate) type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
|
||||||
|
|
||||||
/// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report.
|
/// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NoSolutionError {
|
pub struct NoSolutionError {
|
||||||
error: pubgrub::NoSolutionError<UvDependencyProvider>,
|
error: pubgrub::NoSolutionError<UvDependencyProvider>,
|
||||||
|
index: InMemoryIndex,
|
||||||
available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
|
available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
|
||||||
available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
|
available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
|
||||||
selector: CandidateSelector,
|
selector: CandidateSelector,
|
||||||
|
|
@ -142,7 +144,9 @@ pub struct NoSolutionError {
|
||||||
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
|
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
|
||||||
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
|
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
|
||||||
fork_urls: ForkUrls,
|
fork_urls: ForkUrls,
|
||||||
|
fork_indexes: ForkIndexes,
|
||||||
env: ResolverEnvironment,
|
env: ResolverEnvironment,
|
||||||
|
tags: Option<Tags>,
|
||||||
workspace_members: BTreeSet<PackageName>,
|
workspace_members: BTreeSet<PackageName>,
|
||||||
options: Options,
|
options: Options,
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +155,7 @@ impl NoSolutionError {
|
||||||
/// Create a new [`NoSolutionError`] from a [`pubgrub::NoSolutionError`].
|
/// Create a new [`NoSolutionError`] from a [`pubgrub::NoSolutionError`].
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
error: pubgrub::NoSolutionError<UvDependencyProvider>,
|
error: pubgrub::NoSolutionError<UvDependencyProvider>,
|
||||||
|
index: InMemoryIndex,
|
||||||
available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
|
available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
|
||||||
available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
|
available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
|
||||||
selector: CandidateSelector,
|
selector: CandidateSelector,
|
||||||
|
|
@ -160,12 +165,15 @@ impl NoSolutionError {
|
||||||
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
|
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
|
||||||
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
|
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
|
||||||
fork_urls: ForkUrls,
|
fork_urls: ForkUrls,
|
||||||
|
fork_indexes: ForkIndexes,
|
||||||
env: ResolverEnvironment,
|
env: ResolverEnvironment,
|
||||||
|
tags: Option<Tags>,
|
||||||
workspace_members: BTreeSet<PackageName>,
|
workspace_members: BTreeSet<PackageName>,
|
||||||
options: Options,
|
options: Options,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
error,
|
error,
|
||||||
|
index,
|
||||||
available_versions,
|
available_versions,
|
||||||
available_indexes,
|
available_indexes,
|
||||||
selector,
|
selector,
|
||||||
|
|
@ -175,7 +183,9 @@ impl NoSolutionError {
|
||||||
unavailable_packages,
|
unavailable_packages,
|
||||||
incomplete_packages,
|
incomplete_packages,
|
||||||
fork_urls,
|
fork_urls,
|
||||||
|
fork_indexes,
|
||||||
env,
|
env,
|
||||||
|
tags,
|
||||||
workspace_members,
|
workspace_members,
|
||||||
options,
|
options,
|
||||||
}
|
}
|
||||||
|
|
@ -328,6 +338,47 @@ impl NoSolutionError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for NoSolutionError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// Include every field except `index`, which doesn't implement `Debug`.
|
||||||
|
let Self {
|
||||||
|
error,
|
||||||
|
index: _,
|
||||||
|
available_versions,
|
||||||
|
available_indexes,
|
||||||
|
selector,
|
||||||
|
python_requirement,
|
||||||
|
index_locations,
|
||||||
|
index_capabilities,
|
||||||
|
unavailable_packages,
|
||||||
|
incomplete_packages,
|
||||||
|
fork_urls,
|
||||||
|
fork_indexes,
|
||||||
|
env,
|
||||||
|
tags,
|
||||||
|
workspace_members,
|
||||||
|
options,
|
||||||
|
} = self;
|
||||||
|
f.debug_struct("NoSolutionError")
|
||||||
|
.field("error", error)
|
||||||
|
.field("available_versions", available_versions)
|
||||||
|
.field("available_indexes", available_indexes)
|
||||||
|
.field("selector", selector)
|
||||||
|
.field("python_requirement", python_requirement)
|
||||||
|
.field("index_locations", index_locations)
|
||||||
|
.field("index_capabilities", index_capabilities)
|
||||||
|
.field("unavailable_packages", unavailable_packages)
|
||||||
|
.field("incomplete_packages", incomplete_packages)
|
||||||
|
.field("fork_urls", fork_urls)
|
||||||
|
.field("fork_indexes", fork_indexes)
|
||||||
|
.field("env", env)
|
||||||
|
.field("tags", tags)
|
||||||
|
.field("workspace_members", workspace_members)
|
||||||
|
.field("options", options)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::error::Error for NoSolutionError {}
|
impl std::error::Error for NoSolutionError {}
|
||||||
|
|
||||||
impl std::fmt::Display for NoSolutionError {
|
impl std::fmt::Display for NoSolutionError {
|
||||||
|
|
@ -337,6 +388,7 @@ impl std::fmt::Display for NoSolutionError {
|
||||||
available_versions: &self.available_versions,
|
available_versions: &self.available_versions,
|
||||||
python_requirement: &self.python_requirement,
|
python_requirement: &self.python_requirement,
|
||||||
workspace_members: &self.workspace_members,
|
workspace_members: &self.workspace_members,
|
||||||
|
tags: self.tags.as_ref(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Transform the error tree for reporting
|
// Transform the error tree for reporting
|
||||||
|
|
@ -385,6 +437,7 @@ impl std::fmt::Display for NoSolutionError {
|
||||||
let mut additional_hints = IndexSet::default();
|
let mut additional_hints = IndexSet::default();
|
||||||
formatter.generate_hints(
|
formatter.generate_hints(
|
||||||
&tree,
|
&tree,
|
||||||
|
&self.index,
|
||||||
&self.selector,
|
&self.selector,
|
||||||
&self.index_locations,
|
&self.index_locations,
|
||||||
&self.index_capabilities,
|
&self.index_capabilities,
|
||||||
|
|
@ -392,6 +445,7 @@ impl std::fmt::Display for NoSolutionError {
|
||||||
&self.unavailable_packages,
|
&self.unavailable_packages,
|
||||||
&self.incomplete_packages,
|
&self.incomplete_packages,
|
||||||
&self.fork_urls,
|
&self.fork_urls,
|
||||||
|
&self.fork_indexes,
|
||||||
&self.env,
|
&self.env,
|
||||||
&self.workspace_members,
|
&self.workspace_members,
|
||||||
&self.options,
|
&self.options,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,26 @@
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
use std::ops::Bound;
|
|
||||||
|
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use pubgrub::{DerivationTree, Derived, External, Map, Range, ReportFormatter, Term};
|
use pubgrub::{DerivationTree, Derived, External, Map, Range, ReportFormatter, Term};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::ops::Bound;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
||||||
|
use crate::candidate_selector::CandidateSelector;
|
||||||
|
use crate::error::ErrorTree;
|
||||||
|
use crate::fork_indexes::ForkIndexes;
|
||||||
|
use crate::fork_urls::ForkUrls;
|
||||||
|
use crate::prerelease::AllowPrerelease;
|
||||||
|
use crate::python_requirement::{PythonRequirement, PythonRequirementSource};
|
||||||
|
use crate::resolver::{
|
||||||
|
MetadataUnavailable, UnavailablePackage, UnavailableReason, UnavailableVersion,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
Flexibility, InMemoryIndex, Options, RequiresPython, ResolverEnvironment, VersionsResponse,
|
||||||
|
};
|
||||||
use uv_configuration::{IndexStrategy, NoBinary, NoBuild};
|
use uv_configuration::{IndexStrategy, NoBinary, NoBuild};
|
||||||
use uv_distribution_types::{
|
use uv_distribution_types::{
|
||||||
IncompatibleDist, IncompatibleSource, IncompatibleWheel, Index, IndexCapabilities,
|
IncompatibleDist, IncompatibleSource, IncompatibleWheel, Index, IndexCapabilities,
|
||||||
|
|
@ -14,28 +28,21 @@ use uv_distribution_types::{
|
||||||
};
|
};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep440::{Version, VersionSpecifiers};
|
use uv_pep440::{Version, VersionSpecifiers};
|
||||||
|
use uv_platform_tags::{AbiTag, IncompatibleTag, LanguageTag, Tags};
|
||||||
use crate::candidate_selector::CandidateSelector;
|
|
||||||
use crate::error::ErrorTree;
|
|
||||||
use crate::fork_urls::ForkUrls;
|
|
||||||
use crate::prerelease::AllowPrerelease;
|
|
||||||
use crate::python_requirement::{PythonRequirement, PythonRequirementSource};
|
|
||||||
use crate::resolver::{
|
|
||||||
MetadataUnavailable, UnavailablePackage, UnavailableReason, UnavailableVersion,
|
|
||||||
};
|
|
||||||
use crate::{Flexibility, Options, RequiresPython, ResolverEnvironment};
|
|
||||||
|
|
||||||
use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct PubGrubReportFormatter<'a> {
|
pub(crate) struct PubGrubReportFormatter<'a> {
|
||||||
/// The versions that were available for each package
|
/// The versions that were available for each package.
|
||||||
pub(crate) available_versions: &'a FxHashMap<PackageName, BTreeSet<Version>>,
|
pub(crate) available_versions: &'a FxHashMap<PackageName, BTreeSet<Version>>,
|
||||||
|
|
||||||
/// The versions that were available for each package
|
/// The versions that were available for each package.
|
||||||
pub(crate) python_requirement: &'a PythonRequirement,
|
pub(crate) python_requirement: &'a PythonRequirement,
|
||||||
|
|
||||||
|
/// The members of the workspace.
|
||||||
pub(crate) workspace_members: &'a BTreeSet<PackageName>,
|
pub(crate) workspace_members: &'a BTreeSet<PackageName>,
|
||||||
|
|
||||||
|
/// The compatible tags for the resolution.
|
||||||
|
pub(crate) tags: Option<&'a Tags>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
|
impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
|
||||||
|
|
@ -111,20 +118,25 @@ impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
|
||||||
} else {
|
} else {
|
||||||
match reason {
|
match reason {
|
||||||
UnavailableReason::Package(reason) => {
|
UnavailableReason::Package(reason) => {
|
||||||
format!(
|
let message = reason.singular_message();
|
||||||
"{}{}",
|
format!("{}{}", package, Padded::new(" ", &message, ""),)
|
||||||
Padded::new("", &package, " "),
|
|
||||||
reason.singular_message()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
UnavailableReason::Version(reason) => {
|
UnavailableReason::Version(reason) => {
|
||||||
let range = self.compatible_range(package, set);
|
let range = self.compatible_range(package, set);
|
||||||
let reason = if range.plural() {
|
let message = if range.plural() {
|
||||||
reason.plural_message()
|
reason.plural_message()
|
||||||
} else {
|
} else {
|
||||||
reason.singular_message()
|
reason.singular_message()
|
||||||
};
|
};
|
||||||
format!("{}{reason}", Padded::new("", &range, " "))
|
let context = reason.context_message(
|
||||||
|
self.tags,
|
||||||
|
self.python_requirement.target().abi_tag(),
|
||||||
|
);
|
||||||
|
if let Some(context) = context {
|
||||||
|
format!("{}{}{}", range, Padded::new(" ", &message, " "), context)
|
||||||
|
} else {
|
||||||
|
format!("{}{}", range, Padded::new(" ", &message, ""))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -513,6 +525,7 @@ impl PubGrubReportFormatter<'_> {
|
||||||
pub(crate) fn generate_hints(
|
pub(crate) fn generate_hints(
|
||||||
&self,
|
&self,
|
||||||
derivation_tree: &ErrorTree,
|
derivation_tree: &ErrorTree,
|
||||||
|
index: &InMemoryIndex,
|
||||||
selector: &CandidateSelector,
|
selector: &CandidateSelector,
|
||||||
index_locations: &IndexLocations,
|
index_locations: &IndexLocations,
|
||||||
index_capabilities: &IndexCapabilities,
|
index_capabilities: &IndexCapabilities,
|
||||||
|
|
@ -520,6 +533,7 @@ impl PubGrubReportFormatter<'_> {
|
||||||
unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>,
|
unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>,
|
||||||
incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
|
incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
|
||||||
fork_urls: &ForkUrls,
|
fork_urls: &ForkUrls,
|
||||||
|
fork_indexes: &ForkIndexes,
|
||||||
env: &ResolverEnvironment,
|
env: &ResolverEnvironment,
|
||||||
workspace_members: &BTreeSet<PackageName>,
|
workspace_members: &BTreeSet<PackageName>,
|
||||||
options: &Options,
|
options: &Options,
|
||||||
|
|
@ -555,14 +569,13 @@ impl PubGrubReportFormatter<'_> {
|
||||||
incomplete_packages,
|
incomplete_packages,
|
||||||
output_hints,
|
output_hints,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Check for unavailable versions due to `--no-build` or `--no-binary`.
|
|
||||||
if let UnavailableReason::Version(UnavailableVersion::IncompatibleDist(
|
if let UnavailableReason::Version(UnavailableVersion::IncompatibleDist(
|
||||||
incompatibility,
|
incompatibility,
|
||||||
)) = reason
|
)) = reason
|
||||||
{
|
{
|
||||||
match incompatibility {
|
match incompatibility {
|
||||||
|
// Check for unavailable versions due to `--no-build` or `--no-binary`.
|
||||||
IncompatibleDist::Wheel(IncompatibleWheel::NoBinary) => {
|
IncompatibleDist::Wheel(IncompatibleWheel::NoBinary) => {
|
||||||
output_hints.insert(PubGrubHint::NoBinary {
|
output_hints.insert(PubGrubHint::NoBinary {
|
||||||
package: package.clone(),
|
package: package.clone(),
|
||||||
|
|
@ -575,10 +588,26 @@ impl PubGrubReportFormatter<'_> {
|
||||||
option: options.build_options.no_build().clone(),
|
option: options.build_options.no_build().clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Check for unavailable versions due to incompatible tags.
|
||||||
|
IncompatibleDist::Wheel(IncompatibleWheel::Tag(tag)) => {
|
||||||
|
if let Some(hint) = self.tag_hint(
|
||||||
|
package,
|
||||||
|
name,
|
||||||
|
set,
|
||||||
|
*tag,
|
||||||
|
index,
|
||||||
|
selector,
|
||||||
|
fork_indexes,
|
||||||
|
env,
|
||||||
|
) {
|
||||||
|
output_hints.insert(hint);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
DerivationTree::External(External::NoVersions(package, set)) => {
|
DerivationTree::External(External::NoVersions(package, set)) => {
|
||||||
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
||||||
// Check for no versions due to pre-release options.
|
// Check for no versions due to pre-release options.
|
||||||
|
|
@ -661,6 +690,7 @@ impl PubGrubReportFormatter<'_> {
|
||||||
DerivationTree::Derived(derived) => {
|
DerivationTree::Derived(derived) => {
|
||||||
self.generate_hints(
|
self.generate_hints(
|
||||||
&derived.cause1,
|
&derived.cause1,
|
||||||
|
index,
|
||||||
selector,
|
selector,
|
||||||
index_locations,
|
index_locations,
|
||||||
index_capabilities,
|
index_capabilities,
|
||||||
|
|
@ -668,6 +698,7 @@ impl PubGrubReportFormatter<'_> {
|
||||||
unavailable_packages,
|
unavailable_packages,
|
||||||
incomplete_packages,
|
incomplete_packages,
|
||||||
fork_urls,
|
fork_urls,
|
||||||
|
fork_indexes,
|
||||||
env,
|
env,
|
||||||
workspace_members,
|
workspace_members,
|
||||||
options,
|
options,
|
||||||
|
|
@ -675,6 +706,7 @@ impl PubGrubReportFormatter<'_> {
|
||||||
);
|
);
|
||||||
self.generate_hints(
|
self.generate_hints(
|
||||||
&derived.cause2,
|
&derived.cause2,
|
||||||
|
index,
|
||||||
selector,
|
selector,
|
||||||
index_locations,
|
index_locations,
|
||||||
index_capabilities,
|
index_capabilities,
|
||||||
|
|
@ -682,6 +714,7 @@ impl PubGrubReportFormatter<'_> {
|
||||||
unavailable_packages,
|
unavailable_packages,
|
||||||
incomplete_packages,
|
incomplete_packages,
|
||||||
fork_urls,
|
fork_urls,
|
||||||
|
fork_indexes,
|
||||||
env,
|
env,
|
||||||
workspace_members,
|
workspace_members,
|
||||||
options,
|
options,
|
||||||
|
|
@ -691,6 +724,106 @@ impl PubGrubReportFormatter<'_> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a [`PubGrubHint`] for a package that doesn't have any wheels matching the current
|
||||||
|
/// Python version, ABI, or platform.
|
||||||
|
fn tag_hint(
|
||||||
|
&self,
|
||||||
|
package: &PubGrubPackage,
|
||||||
|
name: &PackageName,
|
||||||
|
set: &Range<Version>,
|
||||||
|
tag: IncompatibleTag,
|
||||||
|
index: &InMemoryIndex,
|
||||||
|
selector: &CandidateSelector,
|
||||||
|
fork_indexes: &ForkIndexes,
|
||||||
|
env: &ResolverEnvironment,
|
||||||
|
) -> Option<PubGrubHint> {
|
||||||
|
let response = if let Some(url) = fork_indexes.get(name) {
|
||||||
|
index.explicit().get(&(name.clone(), url.clone()))
|
||||||
|
} else {
|
||||||
|
index.implicit().get(name)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let VersionsResponse::Found(version_maps) = &*response else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let candidate = selector.select_no_preference(name, set, version_maps, env)?;
|
||||||
|
|
||||||
|
let prioritized = candidate.prioritized()?;
|
||||||
|
|
||||||
|
match tag {
|
||||||
|
IncompatibleTag::Invalid => None,
|
||||||
|
IncompatibleTag::Python => {
|
||||||
|
// Return all available language tags.
|
||||||
|
let tags = prioritized
|
||||||
|
.python_tags()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|tag| LanguageTag::from_str(tag).ok())
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
if tags.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(PubGrubHint::LanguageTags {
|
||||||
|
package: package.clone(),
|
||||||
|
version: candidate.version().clone(),
|
||||||
|
tags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IncompatibleTag::Abi | IncompatibleTag::AbiPythonVersion => {
|
||||||
|
let tags = prioritized
|
||||||
|
.abi_tags()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|tag| AbiTag::from_str(tag).ok())
|
||||||
|
// Ignore `none`, which is universally compatible.
|
||||||
|
//
|
||||||
|
// As an example, `none` can appear here if we're solving for Python 3.13, and
|
||||||
|
// the distribution includes a wheel for `cp312-none-macosx_11_0_arm64`.
|
||||||
|
//
|
||||||
|
// In that case, the wheel isn't compatible, but when solving for Python 3.13,
|
||||||
|
// the `cp312` Python tag _can_ be compatible (e.g., for `cp312-abi3-macosx_11_0_arm64.whl`),
|
||||||
|
// so this is considered an ABI incompatibility rather than Python incompatibility.
|
||||||
|
.filter(|tag| *tag != AbiTag::None)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
if tags.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(PubGrubHint::AbiTags {
|
||||||
|
package: package.clone(),
|
||||||
|
version: candidate.version().clone(),
|
||||||
|
tags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IncompatibleTag::Platform => {
|
||||||
|
// We don't want to report all available platforms, since it's plausible that there
|
||||||
|
// are wheels for the current platform, but at a different ABI. For example, when
|
||||||
|
// solving for Python 3.13 on macOS, `cp312-cp312-macosx_11_0_arm64` could be
|
||||||
|
// available along with `cp313-cp313-manylinux2014`. In this case, we'd consider
|
||||||
|
// the distribution to be platform-incompatible, since `cp313-cp313` matches the
|
||||||
|
// compatible wheel tags. But showing `macosx_11_0_arm64` here would be misleading.
|
||||||
|
//
|
||||||
|
// So, instead, we only show the platforms that are linked to otherwise-compatible
|
||||||
|
// wheels (e.g., `manylinux2014` in `cp313-cp313-manylinux2014`). In other words,
|
||||||
|
// we only show platforms for ABI-compatible wheels.
|
||||||
|
let tags = prioritized
|
||||||
|
.platform_tags(self.tags?)
|
||||||
|
.into_iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if tags.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(PubGrubHint::PlatformTags {
|
||||||
|
package: package.clone(),
|
||||||
|
version: candidate.version().clone(),
|
||||||
|
tags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn index_hints(
|
fn index_hints(
|
||||||
package: &PubGrubPackage,
|
package: &PubGrubPackage,
|
||||||
name: &PackageName,
|
name: &PackageName,
|
||||||
|
|
@ -991,6 +1124,30 @@ pub(crate) enum PubGrubHint {
|
||||||
UnauthorizedIndex { index: IndexUrl },
|
UnauthorizedIndex { index: IndexUrl },
|
||||||
/// An index returned a Forbidden (403) response.
|
/// An index returned a Forbidden (403) response.
|
||||||
ForbiddenIndex { index: IndexUrl },
|
ForbiddenIndex { index: IndexUrl },
|
||||||
|
/// No wheels are available for a package, and using source distributions was disabled.
|
||||||
|
LanguageTags {
|
||||||
|
package: PubGrubPackage,
|
||||||
|
// excluded from `PartialEq` and `Hash`
|
||||||
|
version: Version,
|
||||||
|
// excluded from `PartialEq` and `Hash`
|
||||||
|
tags: BTreeSet<LanguageTag>,
|
||||||
|
},
|
||||||
|
/// No wheels are available for a package, and using source distributions was disabled.
|
||||||
|
AbiTags {
|
||||||
|
package: PubGrubPackage,
|
||||||
|
// excluded from `PartialEq` and `Hash`
|
||||||
|
version: Version,
|
||||||
|
// excluded from `PartialEq` and `Hash`
|
||||||
|
tags: BTreeSet<AbiTag>,
|
||||||
|
},
|
||||||
|
/// No wheels are available for a package, and using source distributions was disabled.
|
||||||
|
PlatformTags {
|
||||||
|
package: PubGrubPackage,
|
||||||
|
// excluded from `PartialEq` and `Hash`
|
||||||
|
version: Version,
|
||||||
|
// excluded from `PartialEq` and `Hash`
|
||||||
|
tags: Vec<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This private enum mirrors [`PubGrubHint`] but only includes fields that should be
|
/// This private enum mirrors [`PubGrubHint`] but only includes fields that should be
|
||||||
|
|
@ -1052,6 +1209,15 @@ enum PubGrubHintCore {
|
||||||
NoBinary {
|
NoBinary {
|
||||||
package: PubGrubPackage,
|
package: PubGrubPackage,
|
||||||
},
|
},
|
||||||
|
LanguageTags {
|
||||||
|
package: PubGrubPackage,
|
||||||
|
},
|
||||||
|
AbiTags {
|
||||||
|
package: PubGrubPackage,
|
||||||
|
},
|
||||||
|
PlatformTags {
|
||||||
|
package: PubGrubPackage,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PubGrubHint> for PubGrubHintCore {
|
impl From<PubGrubHint> for PubGrubHintCore {
|
||||||
|
|
@ -1109,6 +1275,9 @@ impl From<PubGrubHint> for PubGrubHintCore {
|
||||||
PubGrubHint::ForbiddenIndex { index } => Self::ForbiddenIndex { index },
|
PubGrubHint::ForbiddenIndex { index } => Self::ForbiddenIndex { index },
|
||||||
PubGrubHint::NoBuild { package, .. } => Self::NoBuild { package },
|
PubGrubHint::NoBuild { package, .. } => Self::NoBuild { package },
|
||||||
PubGrubHint::NoBinary { package, .. } => Self::NoBinary { package },
|
PubGrubHint::NoBinary { package, .. } => Self::NoBinary { package },
|
||||||
|
PubGrubHint::LanguageTags { package, .. } => Self::LanguageTags { package },
|
||||||
|
PubGrubHint::AbiTags { package, .. } => Self::AbiTags { package },
|
||||||
|
PubGrubHint::PlatformTags { package, .. } => Self::PlatformTags { package },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1415,6 +1584,60 @@ impl std::fmt::Display for PubGrubHint {
|
||||||
package.cyan(),
|
package.cyan(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Self::LanguageTags {
|
||||||
|
package,
|
||||||
|
version,
|
||||||
|
tags,
|
||||||
|
} => {
|
||||||
|
let s = if tags.len() == 1 { "" } else { "s" };
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{} Wheels are available for `{}` ({}) with the following Python tag{s}: {}",
|
||||||
|
"hint".bold().cyan(),
|
||||||
|
":".bold(),
|
||||||
|
package.cyan(),
|
||||||
|
format!("v{version}").cyan(),
|
||||||
|
tags.iter()
|
||||||
|
.map(|tag| format!("`{}`", tag.cyan()))
|
||||||
|
.join(", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Self::AbiTags {
|
||||||
|
package,
|
||||||
|
version,
|
||||||
|
tags,
|
||||||
|
} => {
|
||||||
|
let s = if tags.len() == 1 { "" } else { "s" };
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{} Wheels are available for `{}` ({}) with the following ABI tag{s}: {}",
|
||||||
|
"hint".bold().cyan(),
|
||||||
|
":".bold(),
|
||||||
|
package.cyan(),
|
||||||
|
format!("v{version}").cyan(),
|
||||||
|
tags.iter()
|
||||||
|
.map(|tag| format!("`{}`", tag.cyan()))
|
||||||
|
.join(", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Self::PlatformTags {
|
||||||
|
package,
|
||||||
|
version,
|
||||||
|
tags,
|
||||||
|
} => {
|
||||||
|
let s = if tags.len() == 1 { "" } else { "s" };
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{} Wheels are available for `{}` ({}) on the following platform{s}: {}",
|
||||||
|
"hint".bold().cyan(),
|
||||||
|
":".bold(),
|
||||||
|
package.cyan(),
|
||||||
|
format!("v{version}").cyan(),
|
||||||
|
tags.iter()
|
||||||
|
.map(|tag| format!("`{}`", tag.cyan()))
|
||||||
|
.join(", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use pubgrub::Range;
|
||||||
use uv_distribution_filename::WheelFilename;
|
use uv_distribution_filename::WheelFilename;
|
||||||
use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifier, VersionSpecifiers};
|
use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifier, VersionSpecifiers};
|
||||||
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||||
|
use uv_platform_tags::AbiTag;
|
||||||
|
|
||||||
/// The `Requires-Python` requirement specifier.
|
/// The `Requires-Python` requirement specifier.
|
||||||
///
|
///
|
||||||
|
|
@ -303,6 +304,23 @@ impl RequiresPython {
|
||||||
&self.range
|
&self.range
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a wheel tag that's compatible with the `Requires-Python` specifier.
|
||||||
|
pub fn abi_tag(&self) -> Option<AbiTag> {
|
||||||
|
match self.range.lower().as_ref() {
|
||||||
|
Bound::Included(version) | Bound::Excluded(version) => {
|
||||||
|
let major = version.release().first().copied()?;
|
||||||
|
let major = u8::try_from(major).ok()?;
|
||||||
|
let minor = version.release().get(1).copied()?;
|
||||||
|
let minor = u8::try_from(minor).ok()?;
|
||||||
|
Some(AbiTag::CPython {
|
||||||
|
gil_disabled: false,
|
||||||
|
python_version: (major, minor),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Bound::Unbounded => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Simplifies the given markers in such a way as to assume that
|
/// Simplifies the given markers in such a way as to assume that
|
||||||
/// the Python version is constrained by this Python version bound.
|
/// the Python version is constrained by this Python version bound.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
use crate::resolver::{MetadataUnavailable, VersionFork};
|
||||||
use uv_distribution_types::IncompatibleDist;
|
use uv_distribution_types::IncompatibleDist;
|
||||||
use uv_pep440::{Version, VersionSpecifiers};
|
use uv_pep440::{Version, VersionSpecifiers};
|
||||||
|
use uv_platform_tags::{AbiTag, Tags};
|
||||||
use crate::resolver::{MetadataUnavailable, VersionFork};
|
|
||||||
|
|
||||||
/// The reason why a package or a version cannot be used.
|
/// The reason why a package or a version cannot be used.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
|
@ -80,6 +80,23 @@ impl UnavailableVersion {
|
||||||
UnavailableVersion::RequiresPython(..) => format!("require {self}"),
|
UnavailableVersion::RequiresPython(..) => format!("require {self}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn context_message(
|
||||||
|
&self,
|
||||||
|
tags: Option<&Tags>,
|
||||||
|
requires_python: Option<AbiTag>,
|
||||||
|
) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
UnavailableVersion::IncompatibleDist(invalid_dist) => {
|
||||||
|
invalid_dist.context_message(tags, requires_python)
|
||||||
|
}
|
||||||
|
UnavailableVersion::InvalidMetadata => None,
|
||||||
|
UnavailableVersion::InconsistentMetadata => None,
|
||||||
|
UnavailableVersion::InvalidStructure => None,
|
||||||
|
UnavailableVersion::Offline => None,
|
||||||
|
UnavailableVersion::RequiresPython(..) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for UnavailableVersion {
|
impl Display for UnavailableVersion {
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
|
||||||
dependency_mode: DependencyMode,
|
dependency_mode: DependencyMode,
|
||||||
hasher: HashStrategy,
|
hasher: HashStrategy,
|
||||||
env: ResolverEnvironment,
|
env: ResolverEnvironment,
|
||||||
|
tags: Option<Tags>,
|
||||||
python_requirement: PythonRequirement,
|
python_requirement: PythonRequirement,
|
||||||
conflicts: Conflicts,
|
conflicts: Conflicts,
|
||||||
workspace_members: BTreeSet<PackageName>,
|
workspace_members: BTreeSet<PackageName>,
|
||||||
|
|
@ -181,6 +182,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
|
||||||
options,
|
options,
|
||||||
hasher,
|
hasher,
|
||||||
env,
|
env,
|
||||||
|
tags.cloned(),
|
||||||
python_requirement,
|
python_requirement,
|
||||||
conflicts,
|
conflicts,
|
||||||
index,
|
index,
|
||||||
|
|
@ -202,6 +204,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
options: Options,
|
options: Options,
|
||||||
hasher: &HashStrategy,
|
hasher: &HashStrategy,
|
||||||
env: ResolverEnvironment,
|
env: ResolverEnvironment,
|
||||||
|
tags: Option<Tags>,
|
||||||
python_requirement: &PythonRequirement,
|
python_requirement: &PythonRequirement,
|
||||||
conflicts: Conflicts,
|
conflicts: Conflicts,
|
||||||
index: &InMemoryIndex,
|
index: &InMemoryIndex,
|
||||||
|
|
@ -229,6 +232,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
hasher: hasher.clone(),
|
hasher: hasher.clone(),
|
||||||
locations: locations.clone(),
|
locations: locations.clone(),
|
||||||
env,
|
env,
|
||||||
|
tags,
|
||||||
python_requirement: python_requirement.clone(),
|
python_requirement: python_requirement.clone(),
|
||||||
conflicts,
|
conflicts,
|
||||||
installed_packages,
|
installed_packages,
|
||||||
|
|
@ -346,11 +350,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
return Err(self.convert_no_solution_err(
|
return Err(self.convert_no_solution_err(
|
||||||
err,
|
err,
|
||||||
state.fork_urls,
|
state.fork_urls,
|
||||||
&state.fork_indexes,
|
state.fork_indexes,
|
||||||
state.env,
|
state.env,
|
||||||
&visited,
|
&visited,
|
||||||
&self.locations,
|
|
||||||
&self.capabilities,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(conflicts) => {
|
Ok(conflicts) => {
|
||||||
|
|
@ -1219,6 +1221,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
// If the version is incompatible because no distributions are compatible, exit early.
|
// If the version is incompatible because no distributions are compatible, exit early.
|
||||||
return Ok(Some(ResolverVersion::Unavailable(
|
return Ok(Some(ResolverVersion::Unavailable(
|
||||||
candidate.version().clone(),
|
candidate.version().clone(),
|
||||||
|
// TODO(charlie): We can avoid this clone; the candidate is dropped here and
|
||||||
|
// owns the incompatibility.
|
||||||
UnavailableVersion::IncompatibleDist(incompatibility.clone()),
|
UnavailableVersion::IncompatibleDist(incompatibility.clone()),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
@ -2338,11 +2342,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
&self,
|
&self,
|
||||||
mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
|
mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
|
||||||
fork_urls: ForkUrls,
|
fork_urls: ForkUrls,
|
||||||
fork_indexes: &ForkIndexes,
|
fork_indexes: ForkIndexes,
|
||||||
env: ResolverEnvironment,
|
env: ResolverEnvironment,
|
||||||
visited: &FxHashSet<PackageName>,
|
visited: &FxHashSet<PackageName>,
|
||||||
index_locations: &IndexLocations,
|
|
||||||
index_capabilities: &IndexCapabilities,
|
|
||||||
) -> ResolveError {
|
) -> ResolveError {
|
||||||
err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
|
err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
|
||||||
err,
|
err,
|
||||||
|
|
@ -2413,16 +2415,19 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
|
|
||||||
ResolveError::NoSolution(NoSolutionError::new(
|
ResolveError::NoSolution(NoSolutionError::new(
|
||||||
err,
|
err,
|
||||||
|
self.index.clone(),
|
||||||
available_versions,
|
available_versions,
|
||||||
available_indexes,
|
available_indexes,
|
||||||
self.selector.clone(),
|
self.selector.clone(),
|
||||||
self.python_requirement.clone(),
|
self.python_requirement.clone(),
|
||||||
index_locations.clone(),
|
self.locations.clone(),
|
||||||
index_capabilities.clone(),
|
self.capabilities.clone(),
|
||||||
unavailable_packages,
|
unavailable_packages,
|
||||||
incomplete_packages,
|
incomplete_packages,
|
||||||
fork_urls,
|
fork_urls,
|
||||||
|
fork_indexes,
|
||||||
env,
|
env,
|
||||||
|
self.tags.clone(),
|
||||||
self.workspace_members.clone(),
|
self.workspace_members.clone(),
|
||||||
self.options.clone(),
|
self.options.clone(),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -6756,7 +6756,9 @@ fn lock_requires_python_no_wheels() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python version tag and your project depends on dearpygui==1.9.1, we can conclude that your project's requirements are unsatisfiable.
|
╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python version tag (e.g., `cp312`) and your project depends on dearpygui==1.9.1, we can conclude that your project's requirements are unsatisfiable.
|
||||||
|
|
||||||
|
hint: Wheels are available for `dearpygui` (v1.9.1) with the following ABI tags: `cp37m`, `cp38`, `cp39`, `cp310`, `cp311`
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -13944,8 +13944,12 @@ fn invalid_platform() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because only open3d<=0.18.0 is available and open3d<=0.15.2 has no wheels with a matching Python ABI tag, we can conclude that open3d<=0.15.2 cannot be used.
|
╰─▶ Because only open3d<=0.18.0 is available and open3d<=0.15.2 has no wheels with a matching Python ABI tag (e.g., `cp310`), we can conclude that open3d<=0.15.2 cannot be used.
|
||||||
And because open3d>=0.16.0,<=0.18.0 has no wheels with a matching platform tag and you require open3d, we can conclude that your requirements are unsatisfiable.
|
And because open3d>=0.16.0,<=0.18.0 has no wheels with a matching platform tag (e.g., `manylinux_2_17_x86_64`) and you require open3d, we can conclude that your requirements are unsatisfiable.
|
||||||
|
|
||||||
|
hint: Wheels are available for `open3d` (v0.15.2) with the following ABI tags: `cp36m`, `cp37m`, `cp38`, `cp39`
|
||||||
|
|
||||||
|
hint: Wheels are available for `open3d` (v0.18.0) on the following platforms: `macosx_11_0_x86_64`, `macosx_13_0_arm64`, `manylinux_2_27_aarch64`, `manylinux_2_27_x86_64`, `win_amd64`
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -4088,7 +4088,7 @@ fn no_sdist_no_wheels_with_matching_abi() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching Python ABI tag, we can conclude that all versions of package-a cannot be used.
|
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching Python ABI tag (e.g., `cp38`), we can conclude that all versions of package-a cannot be used.
|
||||||
And because you require package-a, we can conclude that your requirements are unsatisfiable.
|
And because you require package-a, we can conclude that your requirements are unsatisfiable.
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
|
@ -4129,8 +4129,10 @@ fn no_sdist_no_wheels_with_matching_platform() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching platform tag, we can conclude that all versions of package-a cannot be used.
|
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching platform tag (e.g., `manylinux_2_17_x86_64`), we can conclude that all versions of package-a cannot be used.
|
||||||
And because you require package-a, we can conclude that your requirements are unsatisfiable.
|
And because you require package-a, we can conclude that your requirements are unsatisfiable.
|
||||||
|
|
||||||
|
hint: Wheels are available for `package-a` (v1.0.0) on the following platform: `macosx_10_0_ppc64`
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
assert_not_installed(
|
assert_not_installed(
|
||||||
|
|
@ -4170,8 +4172,10 @@ fn no_sdist_no_wheels_with_matching_python() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching Python implementation tag, we can conclude that all versions of package-a cannot be used.
|
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching Python implementation tag (e.g., `cp38`), we can conclude that all versions of package-a cannot be used.
|
||||||
And because you require package-a, we can conclude that your requirements are unsatisfiable.
|
And because you require package-a, we can conclude that your requirements are unsatisfiable.
|
||||||
|
|
||||||
|
hint: Wheels are available for `package-a` (v1.0.0) with the following Python tag: `graalpy310`
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
assert_not_installed(
|
assert_not_installed(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue