mirror of
https://github.com/astral-sh/uv
synced 2026-06-21 13:47:25 +00:00
Further improve free-threading ABI incompatibility errors (#17491)
Expanding tag coverage from #17442
This commit is contained in:
@@ -18,7 +18,7 @@ use uv_distribution_types::{
|
||||
};
|
||||
use uv_fs::Simplified;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_platform_tags::{IncompatibleTag, TagCompatibility, Tags};
|
||||
use uv_platform_tags::{AbiTag, IncompatibleTag, TagCompatibility, Tags};
|
||||
use uv_pypi_types::VerbatimParsedUrl;
|
||||
use uv_python::PythonEnvironment;
|
||||
use uv_types::HashStrategy;
|
||||
@@ -587,10 +587,25 @@ fn generate_wheel_compatibility_hint(filename: &WheelFilename, tags: &Tags) -> O
|
||||
let wheel_abi = filename
|
||||
.abi_tags()
|
||||
.iter()
|
||||
.map(|tag| format!("`{}`", tag.cyan()))
|
||||
.map(|tag| match tag {
|
||||
AbiTag::CPython {
|
||||
gil_disabled: false,
|
||||
python_version: (major, minor),
|
||||
} => {
|
||||
format!("the CPython {}.{} ABI (`{}`)", major, minor, tag.cyan())
|
||||
}
|
||||
AbiTag::Abi3 => format!("the stable ABI (`{}`)", tag.cyan()),
|
||||
_ => {
|
||||
if let Some(pretty) = tag.pretty() {
|
||||
format!("the {} ABI (`{}`)", pretty.cyan(), tag.cyan())
|
||||
} else {
|
||||
format!("`{}`", tag.cyan())
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let message = if let Some(current) = tags.abi_tag() {
|
||||
let current = if let Some(current) = tags.abi_tag() {
|
||||
if let Some(pretty) = current.pretty() {
|
||||
format!("{} (`{}`)", pretty.cyan(), current.cyan())
|
||||
} else {
|
||||
@@ -600,7 +615,7 @@ fn generate_wheel_compatibility_hint(filename: &WheelFilename, tags: &Tags) -> O
|
||||
"free-threaded Python".to_string()
|
||||
};
|
||||
Some(format!(
|
||||
"The wheel uses the stable ABI ({wheel_abi}), but you're using {message}, which is incompatible"
|
||||
"You're using {current}, but the wheel was built for {wheel_abi}, which requires a GIL-enabled interpreter"
|
||||
))
|
||||
}
|
||||
IncompatibleTag::Abi => {
|
||||
@@ -809,7 +824,39 @@ mod tests {
|
||||
let hint = generate_wheel_compatibility_hint(&filename, &tags).unwrap();
|
||||
|
||||
let hint = anstream::adapter::strip_str(&hint);
|
||||
insta::assert_snapshot!(hint, @"The wheel uses the stable ABI (`abi3`), but you're using free-threaded CPython 3.14 (`cp314t`), which is incompatible");
|
||||
insta::assert_snapshot!(hint, @"You're using free-threaded CPython 3.14 (`cp314t`), but the wheel was built for the stable ABI (`abi3`), which requires a GIL-enabled interpreter");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gil_enabled_cpython_on_free_threaded_python_hint() {
|
||||
// Create a Tags object for free-threaded Python 3.14
|
||||
let platform = Platform::new(
|
||||
Os::Manylinux {
|
||||
major: 2,
|
||||
minor: 28,
|
||||
},
|
||||
Arch::X86_64,
|
||||
);
|
||||
let tags = Tags::from_env(
|
||||
&platform,
|
||||
(3, 14), // python_version
|
||||
"cpython", // implementation_name
|
||||
(3, 14), // implementation_version
|
||||
true, // manylinux_compatible
|
||||
true, // gil_disabled (free-threaded)
|
||||
false, // is_cross
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Create a wheel filename with cp314 ABI tag (same version, GIL-enabled)
|
||||
let filename =
|
||||
WheelFilename::from_str("foo-1.0-cp314-cp314-manylinux_2_17_x86_64.whl").unwrap();
|
||||
|
||||
// Generate the hint
|
||||
let hint = generate_wheel_compatibility_hint(&filename, &tags).unwrap();
|
||||
|
||||
let hint = anstream::adapter::strip_str(&hint);
|
||||
insta::assert_snapshot!(hint, @"You're using free-threaded CPython 3.14 (`cp314t`), but the wheel was built for the CPython 3.14 ABI (`cp314`), which requires a GIL-enabled interpreter");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -16,7 +16,7 @@ use uv_distribution_types::{
|
||||
use uv_git_types::{GitLfs, GitOid};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_platform_tags::{IncompatibleTag, TagCompatibility, Tags};
|
||||
use uv_platform_tags::{AbiTag, IncompatibleTag, TagCompatibility, Tags};
|
||||
use uv_pypi_types::{DirInfo, DirectUrl, VcsInfo, VcsKind};
|
||||
|
||||
use crate::InstallationStrategy;
|
||||
@@ -460,10 +460,25 @@ fn generate_dist_compatibility_hint(wheel_tags: &ExpandedTags, tags: &Tags) -> O
|
||||
IncompatibleTag::FreethreadedAbi => {
|
||||
let wheel_abi = wheel_tags
|
||||
.abi_tags()
|
||||
.map(|tag| format!("`{tag}`"))
|
||||
.map(|tag| match tag {
|
||||
AbiTag::CPython {
|
||||
gil_disabled: false,
|
||||
python_version: (major, minor),
|
||||
} => {
|
||||
format!("the CPython {major}.{minor} ABI (`{tag}`)")
|
||||
}
|
||||
AbiTag::Abi3 => format!("the stable ABI (`{tag}`)"),
|
||||
_ => {
|
||||
if let Some(pretty) = tag.pretty() {
|
||||
format!("the {pretty} ABI (`{tag}`)")
|
||||
} else {
|
||||
format!("`{tag}`")
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let message = if let Some(current) = tags.abi_tag() {
|
||||
let current = if let Some(current) = tags.abi_tag() {
|
||||
if let Some(pretty) = current.pretty() {
|
||||
format!("{pretty} (`{current}`)")
|
||||
} else {
|
||||
@@ -473,7 +488,7 @@ fn generate_dist_compatibility_hint(wheel_tags: &ExpandedTags, tags: &Tags) -> O
|
||||
"free-threaded Python".to_string()
|
||||
};
|
||||
Some(format!(
|
||||
"The distribution uses the stable ABI ({wheel_abi}), but you're using {message}, which is incompatible"
|
||||
"You're using {current}, but the distribution was built for {wheel_abi}, which requires a GIL-enabled interpreter"
|
||||
))
|
||||
}
|
||||
IncompatibleTag::Abi => {
|
||||
|
||||
@@ -296,9 +296,17 @@ impl Tags {
|
||||
wheel_abi_tags: &[AbiTag],
|
||||
wheel_platform_tags: &[PlatformTag],
|
||||
) -> TagCompatibility {
|
||||
// The stable ABI (abi3) is not supported on free-threaded Python
|
||||
if self.is_freethreaded && wheel_abi_tags.iter().all(|abi| *abi == AbiTag::Abi3) {
|
||||
return TagCompatibility::Incompatible(IncompatibleTag::FreethreadedAbi);
|
||||
// On free-threaded Python, check if any wheel ABI tag is compatible.
|
||||
// Only `none` (pure Python) and free-threaded CPython ABIs (e.g., `cp313t`) are compatible.
|
||||
if self.is_freethreaded {
|
||||
let has_compatible_abi = wheel_abi_tags.iter().any(|abi| match abi {
|
||||
AbiTag::None => true,
|
||||
AbiTag::CPython { gil_disabled, .. } => *gil_disabled,
|
||||
_ => false,
|
||||
});
|
||||
if !has_compatible_abi {
|
||||
return TagCompatibility::Incompatible(IncompatibleTag::FreethreadedAbi);
|
||||
}
|
||||
}
|
||||
|
||||
let mut max_compatibility = TagCompatibility::Incompatible(IncompatibleTag::Invalid);
|
||||
|
||||
@@ -13773,13 +13773,11 @@ fn record_uses_forward_slashes() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that abi3 wheels are rejected on free-threaded Python with a helpful error message.
|
||||
/// Test ABI compatibility checking on free-threaded Python.
|
||||
///
|
||||
/// The stable ABI (abi3) is not supported by free-threaded Python, so we should provide
|
||||
/// a clear error message when a user tries to install an abi3 wheel.
|
||||
/// Free-threaded Python has a different ABI, so wheels must be built specifically for it.
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn abi3_wheel_on_freethreaded_python() {
|
||||
fn abi_compatibility_on_freethreaded_python() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_filtered_python_keys()
|
||||
.with_managed_python_dirs()
|
||||
@@ -13804,12 +13802,14 @@ fn abi3_wheel_on_freethreaded_python() {
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Try to install an abi3 wheel - this should fail with a helpful error
|
||||
// An abi3 wheel should fail with a helpful error
|
||||
let wheel_path = context
|
||||
.workspace_root
|
||||
.join("test/links/abi3_package-1.0.0-cp37-abi3-manylinux_2_17_x86_64.whl");
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg(wheel_path), @r"
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--python-platform").arg("linux")
|
||||
.arg(wheel_path), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
@@ -13819,6 +13819,45 @@ fn abi3_wheel_on_freethreaded_python() {
|
||||
error: Failed to determine installation plan
|
||||
Caused by: A path dependency is incompatible with the current platform: [WORKSPACE]/test/links/abi3_package-1.0.0-cp37-abi3-manylinux_2_17_x86_64.whl
|
||||
|
||||
hint: The wheel uses the stable ABI (`abi3`), but you're using free-threaded CPython 3.14 (`cp314t`), which is incompatible
|
||||
hint: You're using free-threaded CPython 3.14 (`cp314t`), but the wheel was built for the stable ABI (`abi3`), which requires a GIL-enabled interpreter
|
||||
");
|
||||
|
||||
// A GIL-enabled wheel for the same Python version should also fail
|
||||
let wheel_path = context
|
||||
.workspace_root
|
||||
.join("test/links/cpython_package-1.0.0-cp314-cp314-manylinux_2_17_x86_64.whl");
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--python-platform").arg("linux")
|
||||
.arg(wheel_path), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
error: Failed to determine installation plan
|
||||
Caused by: A path dependency is incompatible with the current platform: [WORKSPACE]/test/links/cpython_package-1.0.0-cp314-cp314-manylinux_2_17_x86_64.whl
|
||||
|
||||
hint: You're using free-threaded CPython 3.14 (`cp314t`), but the wheel was built for the CPython 3.14 ABI (`cp314`), which requires a GIL-enabled interpreter
|
||||
");
|
||||
|
||||
// A wheel with both cp314t (compatible) and abi3 (incompatible) should succeed
|
||||
let wheel_path = context
|
||||
.workspace_root
|
||||
.join("test/links/multi_abi_package-1.0.0-cp314-cp314t.abi3-manylinux_2_17_x86_64.whl");
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--python-platform").arg("linux")
|
||||
.arg(wheel_path), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ multi-abi-package==1.0.0 (from file://[WORKSPACE]/test/links/multi_abi_package-1.0.0-cp314-cp314t.abi3-manylinux_2_17_x86_64.whl)
|
||||
");
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user