mirror of https://github.com/astral-sh/uv
Error when built wheel is for the wrong platform (#16074)
Error when a built wheel is for the wrong platform. This can happen especially when using `--python-platform` or `--python-version` with `uv pip install`. Fixes #16019
This commit is contained in:
parent
9f58280eb8
commit
b73281d222
|
|
@ -134,7 +134,7 @@ mod resolver {
|
|||
);
|
||||
|
||||
static TAGS: LazyLock<Tags> = LazyLock::new(|| {
|
||||
Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false, false).unwrap()
|
||||
Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false, false, false).unwrap()
|
||||
});
|
||||
|
||||
pub(crate) async fn resolve(
|
||||
|
|
|
|||
|
|
@ -385,6 +385,27 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
|||
.boxed_local()
|
||||
.await?;
|
||||
|
||||
// Check that the wheel is compatible with its install target.
|
||||
//
|
||||
// When building a build dependency for a cross-install, the build dependency needs
|
||||
// to install and run on the host instead of the target. In this case the `tags` are already
|
||||
// for the host instead of the target, so this check passes.
|
||||
if !built_wheel.filename.is_compatible(tags) {
|
||||
return if tags.is_cross() {
|
||||
Err(Error::BuiltWheelIncompatibleTargetPlatform {
|
||||
filename: built_wheel.filename,
|
||||
python_platform: tags.python_platform().clone(),
|
||||
python_version: tags.python_version(),
|
||||
})
|
||||
} else {
|
||||
Err(Error::BuiltWheelIncompatibleHostPlatform {
|
||||
filename: built_wheel.filename,
|
||||
python_platform: tags.python_platform().clone(),
|
||||
python_version: tags.python_version(),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// Acquire the advisory lock.
|
||||
#[cfg(windows)]
|
||||
let _lock = {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ use zip::result::ZipError;
|
|||
|
||||
use crate::metadata::MetadataError;
|
||||
use uv_client::WrappedReqwestError;
|
||||
use uv_distribution_filename::WheelFilenameError;
|
||||
use uv_distribution_filename::{WheelFilename, WheelFilenameError};
|
||||
use uv_distribution_types::{InstalledDist, InstalledDistError, IsBuildBackendError};
|
||||
use uv_fs::{LockedFileError, Simplified};
|
||||
use uv_git::GitError;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::{Version, VersionSpecifiers};
|
||||
use uv_platform_tags::Platform;
|
||||
use uv_pypi_types::{HashAlgorithm, HashDigest};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_types::AnyErrorBuild;
|
||||
|
|
@ -77,6 +78,35 @@ pub enum Error {
|
|||
filename: Version,
|
||||
metadata: Version,
|
||||
},
|
||||
/// This shouldn't happen, it's a bug in the build backend.
|
||||
#[error(
|
||||
"The built wheel `{}` is not compatible with the current Python {}.{} on {} {}",
|
||||
filename,
|
||||
python_version.0,
|
||||
python_version.1,
|
||||
python_platform.os(),
|
||||
python_platform.arch(),
|
||||
)]
|
||||
BuiltWheelIncompatibleHostPlatform {
|
||||
filename: WheelFilename,
|
||||
python_platform: Platform,
|
||||
python_version: (u8, u8),
|
||||
},
|
||||
/// This may happen when trying to cross-install native dependencies without their build backend
|
||||
/// being aware that the target is a cross-install.
|
||||
#[error(
|
||||
"The built wheel `{}` is not compatible with the target Python {}.{} on {} {}. Consider using `--no-build` to disable building wheels.",
|
||||
filename,
|
||||
python_version.0,
|
||||
python_version.1,
|
||||
python_platform.os(),
|
||||
python_platform.arch(),
|
||||
)]
|
||||
BuiltWheelIncompatibleTargetPlatform {
|
||||
filename: WheelFilename,
|
||||
python_platform: Platform,
|
||||
python_version: (u8, u8),
|
||||
},
|
||||
#[error("Failed to parse metadata from built wheel")]
|
||||
Metadata(#[from] uv_pypi_types::MetadataError),
|
||||
#[error("Failed to read metadata: `{}`", _0.user_display())]
|
||||
|
|
|
|||
|
|
@ -2575,7 +2575,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.await
|
||||
.map_err(Error::CacheWrite)?;
|
||||
|
||||
debug!("Finished building: {source}");
|
||||
debug!("Built `{source}` into `{disk_filename}`");
|
||||
Ok((disk_filename, filename, metadata))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,13 @@ pub struct Tags {
|
|||
map: Arc<FxHashMap<LanguageTag, FxHashMap<AbiTag, FxHashMap<PlatformTag, TagPriority>>>>,
|
||||
/// The highest-priority tag for the Python version and platform.
|
||||
best: Option<(LanguageTag, AbiTag, PlatformTag)>,
|
||||
/// Python platform used to generate the tags, for error messages.
|
||||
python_platform: Platform,
|
||||
/// Python version used to generate the tags, for error messages.
|
||||
python_version: (u8, u8),
|
||||
/// Whether the tags are for a different Python interpreter than the current one, for error
|
||||
/// messages.
|
||||
is_cross: bool,
|
||||
}
|
||||
|
||||
impl Tags {
|
||||
|
|
@ -86,7 +93,12 @@ impl Tags {
|
|||
///
|
||||
/// 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.
|
||||
pub fn new(tags: Vec<(LanguageTag, AbiTag, PlatformTag)>) -> Self {
|
||||
fn new(
|
||||
tags: Vec<(LanguageTag, AbiTag, PlatformTag)>,
|
||||
python_platform: Platform,
|
||||
python_version: (u8, u8),
|
||||
is_cross: bool,
|
||||
) -> Self {
|
||||
// Store the highest-priority tag for each component.
|
||||
let best = tags.first().cloned();
|
||||
|
||||
|
|
@ -104,6 +116,9 @@ impl Tags {
|
|||
Self {
|
||||
map: Arc::new(map),
|
||||
best,
|
||||
python_platform,
|
||||
python_version,
|
||||
is_cross,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +131,7 @@ impl Tags {
|
|||
implementation_version: (u8, u8),
|
||||
manylinux_compatible: bool,
|
||||
gil_disabled: bool,
|
||||
is_cross: bool,
|
||||
) -> Result<Self, TagsError> {
|
||||
let implementation = Implementation::parse(implementation_name, gil_disabled)?;
|
||||
|
||||
|
|
@ -219,7 +235,7 @@ impl Tags {
|
|||
));
|
||||
}
|
||||
}
|
||||
Ok(Self::new(tags))
|
||||
Ok(Self::new(tags, platform.clone(), python_version, is_cross))
|
||||
}
|
||||
|
||||
/// Returns true when there exists at least one tag for this platform
|
||||
|
|
@ -320,6 +336,18 @@ impl Tags {
|
|||
.map(|abis| abis.contains_key(&abi_tag))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn python_platform(&self) -> &Platform {
|
||||
&self.python_platform
|
||||
}
|
||||
|
||||
pub fn python_version(&self) -> (u8, u8) {
|
||||
self.python_version
|
||||
}
|
||||
|
||||
pub fn is_cross(&self) -> bool {
|
||||
self.is_cross
|
||||
}
|
||||
}
|
||||
|
||||
/// The priority of a platform tag.
|
||||
|
|
@ -1467,6 +1495,7 @@ mod tests {
|
|||
(3, 9),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_snapshot!(
|
||||
|
|
@ -1530,6 +1559,7 @@ mod tests {
|
|||
(3, 9),
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_snapshot!(
|
||||
|
|
@ -2154,6 +2184,7 @@ mod tests {
|
|||
(3, 9),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_snapshot!(
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ impl Interpreter {
|
|||
self.implementation_tuple(),
|
||||
self.manylinux_compatible,
|
||||
self.gil_disabled,
|
||||
false,
|
||||
)?;
|
||||
self.tags.set(tags).expect("tags should not be set");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ use uv_workspace::WorkspaceCache;
|
|||
use uv_workspace::pyproject::ExtraBuildDependencies;
|
||||
|
||||
use crate::commands::pip::loggers::DefaultResolveLogger;
|
||||
use crate::commands::pip::{operations, resolution_environment};
|
||||
use crate::commands::pip::{operations, resolution_markers, resolution_tags};
|
||||
use crate::commands::{ExitStatus, OutputWriter, diagnostics};
|
||||
use crate::printer::Printer;
|
||||
|
||||
|
|
@ -392,8 +392,16 @@ pub(crate) async fn pip_compile(
|
|||
ResolverEnvironment::universal(environments.into_markers()),
|
||||
)
|
||||
} else {
|
||||
let (tags, marker_env) =
|
||||
resolution_environment(python_version, python_platform, &interpreter)?;
|
||||
let tags = resolution_tags(
|
||||
python_version.as_ref(),
|
||||
python_platform.as_ref(),
|
||||
&interpreter,
|
||||
)?;
|
||||
let marker_env = resolution_markers(
|
||||
python_version.as_ref(),
|
||||
python_platform.as_ref(),
|
||||
&interpreter,
|
||||
);
|
||||
(Some(tags), ResolverEnvironment::specific(marker_env))
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -42,82 +42,33 @@ pub(crate) fn resolution_tags<'env>(
|
|||
python_platform: Option<&TargetTriple>,
|
||||
interpreter: &'env Interpreter,
|
||||
) -> Result<Cow<'env, Tags>, TagsError> {
|
||||
Ok(match (python_platform, python_version.as_ref()) {
|
||||
(Some(python_platform), Some(python_version)) => Cow::Owned(Tags::from_env(
|
||||
&python_platform.platform(),
|
||||
(python_version.major(), python_version.minor()),
|
||||
interpreter.implementation_name(),
|
||||
interpreter.implementation_tuple(),
|
||||
python_platform.manylinux_compatible(),
|
||||
interpreter.gil_disabled(),
|
||||
)?),
|
||||
(Some(python_platform), None) => Cow::Owned(Tags::from_env(
|
||||
&python_platform.platform(),
|
||||
interpreter.python_tuple(),
|
||||
interpreter.implementation_name(),
|
||||
interpreter.implementation_tuple(),
|
||||
python_platform.manylinux_compatible(),
|
||||
interpreter.gil_disabled(),
|
||||
)?),
|
||||
(None, Some(python_version)) => Cow::Owned(Tags::from_env(
|
||||
interpreter.platform(),
|
||||
(python_version.major(), python_version.minor()),
|
||||
interpreter.implementation_name(),
|
||||
interpreter.implementation_tuple(),
|
||||
interpreter.manylinux_compatible(),
|
||||
interpreter.gil_disabled(),
|
||||
)?),
|
||||
(None, None) => Cow::Borrowed(interpreter.tags()?),
|
||||
})
|
||||
}
|
||||
if python_platform.is_none() && python_version.is_none() {
|
||||
return Ok(Cow::Borrowed(interpreter.tags()?));
|
||||
}
|
||||
|
||||
/// Determine the tags, markers, and interpreter to use for resolution.
|
||||
pub(crate) fn resolution_environment(
|
||||
python_version: Option<PythonVersion>,
|
||||
python_platform: Option<TargetTriple>,
|
||||
interpreter: &Interpreter,
|
||||
) -> Result<(Cow<'_, Tags>, ResolverMarkerEnvironment), TagsError> {
|
||||
let tags = match (python_platform, python_version.as_ref()) {
|
||||
(Some(python_platform), Some(python_version)) => Cow::Owned(Tags::from_env(
|
||||
let (platform, manylinux_compatible) = if let Some(python_platform) = python_platform {
|
||||
(
|
||||
&python_platform.platform(),
|
||||
(python_version.major(), python_version.minor()),
|
||||
interpreter.implementation_name(),
|
||||
interpreter.implementation_tuple(),
|
||||
python_platform.manylinux_compatible(),
|
||||
interpreter.gil_disabled(),
|
||||
)?),
|
||||
(Some(python_platform), None) => Cow::Owned(Tags::from_env(
|
||||
&python_platform.platform(),
|
||||
interpreter.python_tuple(),
|
||||
interpreter.implementation_name(),
|
||||
interpreter.implementation_tuple(),
|
||||
python_platform.manylinux_compatible(),
|
||||
interpreter.gil_disabled(),
|
||||
)?),
|
||||
(None, Some(python_version)) => Cow::Owned(Tags::from_env(
|
||||
interpreter.platform(),
|
||||
(python_version.major(), python_version.minor()),
|
||||
interpreter.implementation_name(),
|
||||
interpreter.implementation_tuple(),
|
||||
interpreter.manylinux_compatible(),
|
||||
interpreter.gil_disabled(),
|
||||
)?),
|
||||
(None, None) => Cow::Borrowed(interpreter.tags()?),
|
||||
)
|
||||
} else {
|
||||
(interpreter.platform(), interpreter.manylinux_compatible())
|
||||
};
|
||||
|
||||
// Apply the platform tags to the markers.
|
||||
let markers = match (python_platform, python_version) {
|
||||
(Some(python_platform), Some(python_version)) => ResolverMarkerEnvironment::from(
|
||||
python_version.markers(&python_platform.markers(interpreter.markers())),
|
||||
),
|
||||
(Some(python_platform), None) => {
|
||||
ResolverMarkerEnvironment::from(python_platform.markers(interpreter.markers()))
|
||||
}
|
||||
(None, Some(python_version)) => {
|
||||
ResolverMarkerEnvironment::from(python_version.markers(interpreter.markers()))
|
||||
}
|
||||
(None, None) => interpreter.resolver_marker_environment(),
|
||||
let version_tuple = if let Some(python_version) = python_version {
|
||||
(python_version.major(), python_version.minor())
|
||||
} else {
|
||||
interpreter.python_tuple()
|
||||
};
|
||||
|
||||
Ok((tags, markers))
|
||||
let tags = Tags::from_env(
|
||||
platform,
|
||||
version_tuple,
|
||||
interpreter.implementation_name(),
|
||||
interpreter.implementation_tuple(),
|
||||
manylinux_compatible,
|
||||
interpreter.gil_disabled(),
|
||||
true,
|
||||
)?;
|
||||
Ok(Cow::Owned(tags))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -561,8 +561,8 @@ impl TestContext {
|
|||
}
|
||||
|
||||
/// Add a custom filter to the `TestContext`.
|
||||
pub fn with_filter(mut self, filter: (String, String)) -> Self {
|
||||
self.filters.push(filter);
|
||||
pub fn with_filter(mut self, filter: (impl Into<String>, impl Into<String>)) -> Self {
|
||||
self.filters.push((filter.0.into(), filter.1.into()));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17921,3 +17921,150 @@ fn post_release_less_than() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// When using `uv pip compile --python-platform`, it should not matter what platform the built
|
||||
/// wheel is for. We want to ensure that uv accepts both wheels for the host platform and wheels for
|
||||
/// the target platform in `uv pip compile`. This is unlike `uv pip install --python-platform`,
|
||||
/// where a wheel for the target platform is required.
|
||||
///
|
||||
/// The main test (first snapshot) builds a Windows wheel, and targets macOS. If we run this test on
|
||||
/// Linux, the wheel tag is neither host nor target, but we don't have a reason to reject the
|
||||
/// Windows tag either.
|
||||
#[test]
|
||||
fn compile_with_python_platform_and_built_wheel_for_different_platform() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("./project")?;
|
||||
|
||||
let project = context.temp_dir.child("project");
|
||||
// This pyproject.toml uses dynamic versioning to prevent uv from reading static metadata.
|
||||
project.child("pyproject.toml").write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"sniffio; sys_platform == 'linux'",
|
||||
"tqdm; sys_platform == 'darwin'"
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
backend-path = ["."]
|
||||
build-backend = "build"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/project/__init__.py"
|
||||
"#})?;
|
||||
// Rename the built wheel to a Windows wheel
|
||||
project.child("build.py").write_str(indoc! {r#"
|
||||
import os
|
||||
|
||||
import hatchling.build
|
||||
|
||||
__all__ = ["build_sdist", "build_wheel"]
|
||||
|
||||
|
||||
def build_sdist(
|
||||
sdist_directory: str, config_settings: "Mapping[Any, Any] | None" = None
|
||||
) -> str:
|
||||
hatchling.build.build_sdist(sdist_directory, config_settings)
|
||||
|
||||
|
||||
def build_wheel(
|
||||
wheel_directory: str,
|
||||
config_settings: "Mapping[Any, Any] | None" = None,
|
||||
metadata_directory: "str | None" = None,
|
||||
) -> str:
|
||||
name = hatchling.build.build_wheel(
|
||||
wheel_directory, config_settings, metadata_directory
|
||||
)
|
||||
# Don't do this at home, ask your build backend instead so it also changes the
|
||||
# `WHEEL` file.
|
||||
new_name = "project-0.1.0-cp312-abi3-win_amd64.whl"
|
||||
os.rename(
|
||||
os.path.join(wheel_directory, name), os.path.join(wheel_directory, new_name)
|
||||
)
|
||||
return new_name
|
||||
"#})?;
|
||||
project
|
||||
.child("src/project/__init__.py")
|
||||
.write_str(r#"__version__ = "0.1.0"\n"#)?;
|
||||
|
||||
uv_snapshot!(context
|
||||
.pip_compile()
|
||||
.arg("requirements.in")
|
||||
.arg("--python-platform")
|
||||
.arg("macos"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --python-platform macos
|
||||
./project
|
||||
# via -r requirements.in
|
||||
tqdm==4.66.2
|
||||
# via project
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
uv_snapshot!(context
|
||||
.pip_compile()
|
||||
.arg("requirements.in")
|
||||
.arg("--python-platform")
|
||||
.arg("linux"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --python-platform linux
|
||||
./project
|
||||
# via -r requirements.in
|
||||
sniffio==1.3.1
|
||||
# via project
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
uv_snapshot!(context
|
||||
.pip_compile()
|
||||
.arg("requirements.in")
|
||||
.arg("--python-platform")
|
||||
.arg("windows"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --python-platform windows
|
||||
./project
|
||||
# via -r requirements.in
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
");
|
||||
|
||||
uv_snapshot!(context
|
||||
.pip_compile()
|
||||
.arg("requirements.in")
|
||||
.arg("--universal"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal
|
||||
./project
|
||||
# via -r requirements.in
|
||||
sniffio==1.3.1 ; sys_platform == 'linux'
|
||||
# via project
|
||||
tqdm==4.66.2 ; sys_platform == 'darwin'
|
||||
# via project
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use assert_fs::prelude::*;
|
|||
use flate2::write::GzEncoder;
|
||||
use fs_err as fs;
|
||||
use fs_err::File;
|
||||
use indoc::indoc;
|
||||
use indoc::{formatdoc, indoc};
|
||||
use predicates::prelude::predicate;
|
||||
use url::Url;
|
||||
use wiremock::{
|
||||
|
|
@ -13300,3 +13300,165 @@ fn install_missing_python_version_with_target() {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
/// Use a wheel that is only compatible with Python 3.13 with Python 3.12 or Python 3.13 to simulate
|
||||
/// a wheel build for the wrong platform in a cross-install scenario. Ensure that we catch this case
|
||||
/// and error accordingly. Additionally, we ensure that for a build dependency, which builds and
|
||||
/// runs on the host, not the target, we accept wheel platforms for the host.
|
||||
#[test]
|
||||
fn build_backend_wrong_wheel_platform() -> Result<()> {
|
||||
let context = TestContext::new_with_versions(&["3.12", "3.13"])
|
||||
.with_filter((r" on [^ ]+ [^ ]+\.", " on [ARCH] [OS]."))
|
||||
.with_filter((r" on [^ ]+ [^ ]+$", " on [ARCH] [OS]"));
|
||||
|
||||
let py313 = context.temp_dir.child("child");
|
||||
py313.create_dir_all()?;
|
||||
let py313_pyproject_toml = py313.child("pyproject.toml");
|
||||
py313_pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "py313"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
backend-path = ["."]
|
||||
build-backend = "build_backend"
|
||||
"#})?;
|
||||
|
||||
let build_backend = py313.child("build_backend.py");
|
||||
build_backend.write_str(indoc! {r#"
|
||||
import os
|
||||
|
||||
from hatchling.build import *
|
||||
from hatchling.build import build_wheel as build_wheel_original
|
||||
|
||||
|
||||
def build_wheel(
|
||||
wheel_directory: str,
|
||||
config_settings: "Mapping[Any, Any] | None" = None,
|
||||
metadata_directory: "str | None" = None,
|
||||
) -> str:
|
||||
filename = build_wheel_original(
|
||||
wheel_directory, config_settings, metadata_directory
|
||||
)
|
||||
py313_wheel = "py313-0.1.0-py313-none-any.whl"
|
||||
os.rename(
|
||||
os.path.join(wheel_directory, filename),
|
||||
os.path.join(wheel_directory, py313_wheel),
|
||||
)
|
||||
return py313_wheel
|
||||
"#})?;
|
||||
py313.child("src/py313/__init__.py").touch()?;
|
||||
|
||||
// Test the matrix of
|
||||
// (compatible host, incompatible host) x (compatible target, incompatible target)
|
||||
|
||||
// A Python 3.13 host with a 3.13 implicit target works.
|
||||
context.venv().arg("-p").arg("3.13").assert().success();
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg("./child"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ py313==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
");
|
||||
|
||||
// A Python 3.13 host with a 3.12 explicit target fails.
|
||||
context.venv().arg("-p").arg("3.13").assert().success();
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("./child"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
× Failed to build `py313 @ file://[TEMP_DIR]/child`
|
||||
╰─▶ The built wheel `py313-0.1.0-py313-none-any.whl` is not compatible with the target Python 3.12 on [ARCH] [OS]. Consider using `--no-build` to disable building wheels.
|
||||
");
|
||||
|
||||
// A python 3.12 host with a 3.13 explicit target works.
|
||||
context.venv().arg("-p").arg("3.13").assert().success();
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.13").arg("./child"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
~ py313==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
");
|
||||
|
||||
// A Python 3.13 host with a 3.12 explicit target fails.
|
||||
context.venv().arg("-p").arg("3.13").assert().success();
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("./child"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
× Failed to build `py313 @ file://[TEMP_DIR]/child`
|
||||
╰─▶ The built wheel `py313-0.1.0-py313-none-any.whl` is not compatible with the target Python 3.12 on [ARCH] [OS]. Consider using `--no-build` to disable building wheels.
|
||||
");
|
||||
|
||||
// Create a project that will resolve to a non-latest version of `anyio`
|
||||
let parent = &context.temp_dir;
|
||||
let pyproject_toml = parent.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! {r#"
|
||||
[project]
|
||||
name = "parent"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling", "py313 @ file://{py313}"]
|
||||
build-backend = "hatchling.build"
|
||||
"#,
|
||||
py313 = py313.path().portable_display()
|
||||
})?;
|
||||
context
|
||||
.temp_dir
|
||||
.child("src")
|
||||
.child("parent")
|
||||
.child("__init__.py")
|
||||
.touch()?;
|
||||
|
||||
// A build host of 3.13 works.
|
||||
context.venv().arg("-p").arg("3.13").assert().success();
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("."), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ parent==0.1.0 (from file://[TEMP_DIR]/)
|
||||
");
|
||||
|
||||
// A build host of 3.12 fails.
|
||||
context.venv().arg("-p").arg("3.12").assert().success();
|
||||
uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("."), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
× Failed to build `parent @ file://[TEMP_DIR]/`
|
||||
├─▶ Failed to install requirements from `build-system.requires`
|
||||
├─▶ Failed to build `py313 @ file://[TEMP_DIR]/child`
|
||||
╰─▶ The built wheel `py313-0.1.0-py313-none-any.whl` is not compatible with the current Python 3.12 on [ARCH] [OS]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue