Skip to content

Commit

Permalink
Show target Python version in error messages (#10582)
Browse files Browse the repository at this point in the history
## Summary

See: #10527 (comment)

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
charliermarsh and zanieb authored Jan 15, 2025
1 parent 1480f28 commit 04fc36f
Showing 8 changed files with 161 additions and 29 deletions.
20 changes: 20 additions & 0 deletions crates/uv-platform-tags/src/abi_tag.rs
Original file line number Diff line number Diff line change
@@ -43,6 +43,26 @@ pub enum AbiTag {
Pyston { implementation_version: (u8, u8) },
}

impl AbiTag {
/// Return a pretty string representation of the ABI tag.
pub fn pretty(self) -> Option<String> {
match self {
AbiTag::None => None,
AbiTag::Abi3 => None,
AbiTag::CPython { python_version, .. } => {
Some(format!("CPython {}.{}", python_version.0, python_version.1))
}
AbiTag::PyPy { python_version, .. } => {
Some(format!("PyPy {}.{}", python_version.0, python_version.1))
}
AbiTag::GraalPy { python_version, .. } => {
Some(format!("GraalPy {}.{}", python_version.0, python_version.1))
}
AbiTag::Pyston { .. } => Some("Pyston".to_string()),
}
}
}

impl std::fmt::Display for AbiTag {
/// Format an [`AbiTag`] as a string.
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28 changes: 28 additions & 0 deletions crates/uv-platform-tags/src/language_tag.rs
Original file line number Diff line number Diff line change
@@ -34,6 +34,34 @@ pub enum LanguageTag {
Pyston { python_version: (u8, u8) },
}

impl LanguageTag {
/// Return a pretty string representation of the language tag.
pub fn pretty(self) -> Option<String> {
match self {
Self::None => None,
Self::Python { major, minor } => {
if let Some(minor) = minor {
Some(format!("Python {major}.{minor}"))
} else {
Some(format!("Python {major}"))
}
}
Self::CPython {
python_version: (major, minor),
} => Some(format!("CPython {major}.{minor}")),
Self::PyPy {
python_version: (major, minor),
} => Some(format!("PyPy {major}.{minor}")),
Self::GraalPy {
python_version: (major, minor),
} => Some(format!("GraalPy {major}.{minor}")),
Self::Pyston {
python_version: (major, minor),
} => Some(format!("Pyston {major}.{minor}")),
}
}
}

impl std::fmt::Display for LanguageTag {
/// Format a [`LanguageTag`] as a string.
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
27 changes: 27 additions & 0 deletions crates/uv-platform-tags/src/platform_tag.rs
Original file line number Diff line number Diff line change
@@ -71,6 +71,33 @@ pub enum PlatformTag {
Solaris { release_arch: SmallString },
}

impl PlatformTag {
/// Return a pretty string representation of the language tag.
pub fn pretty(&self) -> Option<&'static str> {
match self {
PlatformTag::Any => None,
PlatformTag::Manylinux { .. } => Some("Linux"),
PlatformTag::Manylinux1 { .. } => Some("Linux"),
PlatformTag::Manylinux2010 { .. } => Some("Linux"),
PlatformTag::Manylinux2014 { .. } => Some("Linux"),
PlatformTag::Linux { .. } => Some("Linux"),
PlatformTag::Musllinux { .. } => Some("Linux"),
PlatformTag::Macos { .. } => Some("macOS"),
PlatformTag::Win32 => Some("Windows"),
PlatformTag::WinAmd64 => Some("Windows"),
PlatformTag::WinArm64 => Some("Windows"),
PlatformTag::Android { .. } => Some("Android"),
PlatformTag::FreeBsd { .. } => Some("FreeBSD"),
PlatformTag::NetBsd { .. } => Some("NetBSD"),
PlatformTag::OpenBsd { .. } => Some("OpenBSD"),
PlatformTag::Dragonfly { .. } => Some("DragonFly"),
PlatformTag::Haiku { .. } => Some("Haiku"),
PlatformTag::Illumos { .. } => Some("Illumos"),
PlatformTag::Solaris { .. } => Some("Solaris"),
}
}
}

impl PlatformTag {
/// Returns `true` if the platform is manylinux-only.
pub fn is_manylinux(&self) -> bool {
1 change: 1 addition & 0 deletions crates/uv-resolver/src/error.rs
Original file line number Diff line number Diff line change
@@ -447,6 +447,7 @@ impl std::fmt::Display for NoSolutionError {
&self.fork_urls,
&self.fork_indexes,
&self.env,
self.tags.as_ref(),
&self.workspace_members,
&self.options,
&mut additional_hints,
106 changes: 81 additions & 25 deletions crates/uv-resolver/src/pubgrub/report.rs
Original file line number Diff line number Diff line change
@@ -536,6 +536,7 @@ impl PubGrubReportFormatter<'_> {
fork_urls: &ForkUrls,
fork_indexes: &ForkIndexes,
env: &ResolverEnvironment,
tags: Option<&Tags>,
workspace_members: &BTreeSet<PackageName>,
options: &Options,
output_hints: &mut IndexSet<PubGrubHint>,
@@ -591,6 +592,7 @@ impl PubGrubReportFormatter<'_> {
selector,
fork_indexes,
env,
tags,
) {
output_hints.insert(hint);
}
@@ -686,6 +688,7 @@ impl PubGrubReportFormatter<'_> {
fork_urls,
fork_indexes,
env,
tags,
workspace_members,
options,
output_hints,
@@ -702,6 +705,7 @@ impl PubGrubReportFormatter<'_> {
fork_urls,
fork_indexes,
env,
tags,
workspace_members,
options,
output_hints,
@@ -721,6 +725,7 @@ impl PubGrubReportFormatter<'_> {
selector: &CandidateSelector,
fork_indexes: &ForkIndexes,
env: &ResolverEnvironment,
tags: Option<&Tags>,
) -> Option<PubGrubHint> {
let response = if let Some(url) = fork_indexes.get(name) {
index.explicit().get(&(name.clone(), url.clone()))
@@ -739,7 +744,7 @@ impl PubGrubReportFormatter<'_> {
match tag {
IncompatibleTag::Invalid => None,
IncompatibleTag::Python => {
// Return all available language tags.
let best = tags.and_then(Tags::python_tag);
let tags = prioritized.python_tags();
if tags.is_empty() {
None
@@ -748,10 +753,12 @@ impl PubGrubReportFormatter<'_> {
package: name.clone(),
version: candidate.version().clone(),
tags,
best,
})
}
}
IncompatibleTag::Abi | IncompatibleTag::AbiPythonVersion => {
let best = tags.and_then(Tags::abi_tag);
let tags = prioritized
.abi_tags()
.into_iter()
@@ -772,6 +779,7 @@ impl PubGrubReportFormatter<'_> {
package: name.clone(),
version: candidate.version().clone(),
tags,
best,
})
}
}
@@ -1105,6 +1113,8 @@ pub(crate) enum PubGrubHint {
version: Version,
// excluded from `PartialEq` and `Hash`
tags: BTreeSet<LanguageTag>,
// excluded from `PartialEq` and `Hash`
best: Option<LanguageTag>,
},
/// No wheels are available for a package, and using source distributions was disabled.
AbiTags {
@@ -1113,6 +1123,8 @@ pub(crate) enum PubGrubHint {
version: Version,
// excluded from `PartialEq` and `Hash`
tags: BTreeSet<AbiTag>,
// excluded from `PartialEq` and `Hash`
best: Option<AbiTag>,
},
/// No wheels are available for a package, and using source distributions was disabled.
PlatformTags {
@@ -1562,37 +1574,81 @@ impl std::fmt::Display for PubGrubHint {
package,
version,
tags,
best,
} => {
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(", "),
)
if let Some(best) = best {
let s = if tags.len() == 1 { "" } else { "s" };
let best = if let Some(pretty) = best.pretty() {
format!("{} (`{}`)", pretty.cyan(), best.cyan())
} else {
format!("{}", best.cyan())
};
write!(
f,
"{}{} You require {}, but we only found wheels for `{}` ({}) with the following Python implementation tag{s}: {}",
"hint".bold().cyan(),
":".bold(),
best,
package.cyan(),
format!("v{version}").cyan(),
tags.iter()
.map(|tag| format!("`{}`", tag.cyan()))
.join(", "),
)
} else {
let s = if tags.len() == 1 { "" } else { "s" };
write!(
f,
"{}{} Wheels are available for `{}` ({}) with the following Python implementation 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,
best,
} => {
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(", "),
)
if let Some(best) = best {
let s = if tags.len() == 1 { "" } else { "s" };
let best = if let Some(pretty) = best.pretty() {
format!("{} (`{}`)", pretty.cyan(), best.cyan())
} else {
format!("{}", best.cyan())
};
write!(
f,
"{}{} You require {}, but we only found wheels for `{}` ({}) with the following Python ABI tag{s}: {}",
"hint".bold().cyan(),
":".bold(),
best,
package.cyan(),
format!("v{version}").cyan(),
tags.iter()
.map(|tag| format!("`{}`", tag.cyan()))
.join(", "),
)
} else {
let s = if tags.len() == 1 { "" } else { "s" };
write!(
f,
"{}{} Wheels are available for `{}` ({}) with the following Python ABI tag{s}: {}",
"hint".bold().cyan(),
":".bold(),
package.cyan(),
format!("v{version}").cyan(),
tags.iter()
.map(|tag| format!("`{}`", tag.cyan()))
.join(", "),
)
}
}
Self::PlatformTags {
package,
2 changes: 1 addition & 1 deletion crates/uv/tests/it/lock.rs
Original file line number Diff line number Diff line change
@@ -6758,7 +6758,7 @@ fn lock_requires_python_no_wheels() -> Result<()> {
× No solution found when resolving dependencies:
╰─▶ 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`
hint: Wheels are available for `dearpygui` (v1.9.1) with the following Python ABI tags: `cp37m`, `cp38`, `cp39`, `cp310`, `cp311`
"###);

Ok(())
2 changes: 1 addition & 1 deletion crates/uv/tests/it/pip_compile.rs
Original file line number Diff line number Diff line change
@@ -13981,7 +13981,7 @@ fn invalid_platform() -> Result<()> {
╰─▶ 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 (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: You require CPython 3.10 (`cp310`), but we only found wheels for `open3d` (v0.15.2) with the following Python ABI tags: `cp36m`, `cp37m`, `cp38`, `cp39`
hint: Wheels are available for `open3d` (v0.18.0) on the following platforms: `manylinux_2_27_aarch64`, `manylinux_2_27_x86_64`, `macosx_11_0_x86_64`, `macosx_13_0_arm64`, `win_amd64`
"###);
4 changes: 2 additions & 2 deletions crates/uv/tests/it/pip_install_scenarios.rs
Original file line number Diff line number Diff line change
@@ -4091,7 +4091,7 @@ fn no_sdist_no_wheels_with_matching_abi() {
╰─▶ 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.
hint: Wheels are available for `package-a` (v1.0.0) with the following ABI tag: `graalpy310_graalpy240_310_native`
hint: You require CPython 3.8 (`cp38`), but we only found wheels for `package-a` (v1.0.0) with the following Python ABI tag: `graalpy310_graalpy240_310_native`
"###);

assert_not_installed(
@@ -4177,7 +4177,7 @@ fn no_sdist_no_wheels_with_matching_python() {
╰─▶ 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.
hint: Wheels are available for `package-a` (v1.0.0) with the following Python tag: `graalpy310`
hint: You require CPython 3.8 (`cp38`), but we only found wheels for `package-a` (v1.0.0) with the following Python implementation tag: `graalpy310`
"###);

assert_not_installed(

0 comments on commit 04fc36f

Please sign in to comment.