mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Show tag hints when failing to find a compatible wheel in pylock.toml
(#13136)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / build binary | linux libc (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / build binary | linux libc (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
## Summary Closes #13135.
This commit is contained in:
parent
78756de027
commit
dc5b3762f3
6 changed files with 294 additions and 184 deletions
|
@ -7,7 +7,7 @@ use uv_configuration::Upgrade;
|
|||
use uv_fs::CWD;
|
||||
use uv_git::ResolvedRepositoryReference;
|
||||
use uv_requirements_txt::RequirementsTxt;
|
||||
use uv_resolver::{Lock, LockError, Preference, PreferenceError, PylockToml, PylockTomlError};
|
||||
use uv_resolver::{Lock, LockError, Preference, PreferenceError, PylockToml, PylockTomlErrorKind};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LockedRequirements {
|
||||
|
@ -105,7 +105,7 @@ pub fn read_lock_requirements(
|
|||
pub async fn read_pylock_toml_requirements(
|
||||
output_file: &Path,
|
||||
upgrade: &Upgrade,
|
||||
) -> Result<LockedRequirements, PylockTomlError> {
|
||||
) -> Result<LockedRequirements, PylockTomlErrorKind> {
|
||||
// As an optimization, skip iterating over the lockfile is we're upgrading all packages anyway.
|
||||
if upgrade.is_all() {
|
||||
return Ok(LockedRequirements::default());
|
||||
|
|
|
@ -5,8 +5,9 @@ pub use exclusions::Exclusions;
|
|||
pub use flat_index::{FlatDistributions, FlatIndex};
|
||||
pub use fork_strategy::ForkStrategy;
|
||||
pub use lock::{
|
||||
Installable, Lock, LockError, LockVersion, Package, PackageMap, PylockToml, PylockTomlError,
|
||||
RequirementsTxtExport, ResolverManifest, SatisfiesResult, TreeDisplay, VERSION,
|
||||
Installable, Lock, LockError, LockVersion, Package, PackageMap, PylockToml,
|
||||
PylockTomlErrorKind, RequirementsTxtExport, ResolverManifest, SatisfiesResult, TreeDisplay,
|
||||
VERSION,
|
||||
};
|
||||
pub use manifest::Manifest;
|
||||
pub use options::{Flexibility, Options, OptionsBuilder};
|
||||
|
|
|
@ -15,7 +15,7 @@ use uv_pypi_types::ConflictItem;
|
|||
|
||||
use crate::graph_ops::{marker_reachability, Reachable};
|
||||
pub(crate) use crate::lock::export::pylock_toml::PylockTomlPackage;
|
||||
pub use crate::lock::export::pylock_toml::{PylockToml, PylockTomlError};
|
||||
pub use crate::lock::export::pylock_toml::{PylockToml, PylockTomlErrorKind};
|
||||
pub use crate::lock::export::requirements_txt::RequirementsTxtExport;
|
||||
use crate::universal_marker::resolve_conflicts;
|
||||
use crate::{Installable, Package};
|
||||
|
|
|
@ -36,12 +36,12 @@ use uv_pypi_types::{HashDigests, Hashes, ParsedGitUrl, VcsKind};
|
|||
use uv_small_str::SmallString;
|
||||
|
||||
use crate::lock::export::ExportableRequirements;
|
||||
use crate::lock::{each_element_on_its_line_array, Source};
|
||||
use crate::lock::{each_element_on_its_line_array, Source, WheelTagHint};
|
||||
use crate::resolution::ResolutionGraphNode;
|
||||
use crate::{Installable, LockError, RequiresPython, ResolverOutput};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PylockTomlError {
|
||||
pub enum PylockTomlErrorKind {
|
||||
#[error("Package `{0}` includes both a registry (`packages.wheels`) and a directory source (`packages.directory`)")]
|
||||
WheelWithDirectory(PackageName),
|
||||
#[error("Package `{0}` includes both a registry (`packages.wheels`) and a VCS source (`packages.vcs`)")]
|
||||
|
@ -114,6 +114,40 @@ pub enum PylockTomlError {
|
|||
Deserialize(#[from] toml::de::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PylockTomlError {
|
||||
kind: Box<PylockTomlErrorKind>,
|
||||
hint: Option<WheelTagHint>,
|
||||
}
|
||||
|
||||
impl std::error::Error for PylockTomlError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.kind.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PylockTomlError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.kind)?;
|
||||
if let Some(hint) = &self.hint {
|
||||
write!(f, "\n\n{hint}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<E> for PylockTomlError
|
||||
where
|
||||
PylockTomlErrorKind: From<E>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
PylockTomlError {
|
||||
kind: Box::new(PylockTomlErrorKind::from(err)),
|
||||
hint: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct PylockToml {
|
||||
|
@ -267,7 +301,7 @@ impl<'lock> PylockToml {
|
|||
resolution: &ResolverOutput,
|
||||
omit: &[PackageName],
|
||||
install_path: &Path,
|
||||
) -> Result<Self, PylockTomlError> {
|
||||
) -> Result<Self, PylockTomlErrorKind> {
|
||||
// The lock version is always `1.0` at time of writing.
|
||||
let lock_version = Version::new([1, 0]);
|
||||
|
||||
|
@ -354,8 +388,11 @@ impl<'lock> PylockToml {
|
|||
dist.wheels
|
||||
.iter()
|
||||
.map(|wheel| {
|
||||
let url =
|
||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
||||
let url = wheel
|
||||
.file
|
||||
.url
|
||||
.to_url()
|
||||
.map_err(PylockTomlErrorKind::ToUrl)?;
|
||||
Ok(PylockTomlWheel {
|
||||
// Optional "when the last component of path/ url would be the same value".
|
||||
name: if url
|
||||
|
@ -372,18 +409,26 @@ impl<'lock> PylockToml {
|
|||
.map(Timestamp::from_millisecond)
|
||||
.transpose()?,
|
||||
url: Some(
|
||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?,
|
||||
wheel
|
||||
.file
|
||||
.url
|
||||
.to_url()
|
||||
.map_err(PylockTomlErrorKind::ToUrl)?,
|
||||
),
|
||||
path: None,
|
||||
size: wheel.file.size,
|
||||
hashes: Hashes::from(wheel.file.hashes.clone()),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, PylockTomlError>>()?,
|
||||
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
|
||||
);
|
||||
|
||||
if let Some(sdist) = dist.sdist.as_ref() {
|
||||
let url = sdist.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
||||
let url = sdist
|
||||
.file
|
||||
.url
|
||||
.to_url()
|
||||
.map_err(PylockTomlErrorKind::ToUrl)?;
|
||||
package.sdist = Some(PylockTomlSdist {
|
||||
// Optional "when the last component of path/ url would be the same value".
|
||||
name: if url
|
||||
|
@ -456,8 +501,11 @@ impl<'lock> PylockToml {
|
|||
dist.wheels
|
||||
.iter()
|
||||
.map(|wheel| {
|
||||
let url =
|
||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
||||
let url = wheel
|
||||
.file
|
||||
.url
|
||||
.to_url()
|
||||
.map_err(PylockTomlErrorKind::ToUrl)?;
|
||||
Ok(PylockTomlWheel {
|
||||
// Optional "when the last component of path/ url would be the same value".
|
||||
name: if url
|
||||
|
@ -474,17 +522,21 @@ impl<'lock> PylockToml {
|
|||
.map(Timestamp::from_millisecond)
|
||||
.transpose()?,
|
||||
url: Some(
|
||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?,
|
||||
wheel
|
||||
.file
|
||||
.url
|
||||
.to_url()
|
||||
.map_err(PylockTomlErrorKind::ToUrl)?,
|
||||
),
|
||||
path: None,
|
||||
size: wheel.file.size,
|
||||
hashes: Hashes::from(wheel.file.hashes.clone()),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, PylockTomlError>>()?,
|
||||
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
|
||||
);
|
||||
|
||||
let url = dist.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
||||
let url = dist.file.url.to_url().map_err(PylockTomlErrorKind::ToUrl)?;
|
||||
package.sdist = Some(PylockTomlSdist {
|
||||
// Optional "when the last component of path/ url would be the same value".
|
||||
name: if url
|
||||
|
@ -536,7 +588,7 @@ impl<'lock> PylockToml {
|
|||
dev: &DependencyGroupsWithDefaults,
|
||||
annotate: bool,
|
||||
install_options: &'lock InstallOptions,
|
||||
) -> Result<Self, PylockTomlError> {
|
||||
) -> Result<Self, PylockTomlErrorKind> {
|
||||
// Extract the packages from the lock file.
|
||||
let ExportableRequirements(mut nodes) = ExportableRequirements::from_lock(
|
||||
target,
|
||||
|
@ -591,8 +643,11 @@ impl<'lock> PylockToml {
|
|||
wheels
|
||||
.into_iter()
|
||||
.map(|wheel| {
|
||||
let url =
|
||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
||||
let url = wheel
|
||||
.file
|
||||
.url
|
||||
.to_url()
|
||||
.map_err(PylockTomlErrorKind::ToUrl)?;
|
||||
Ok(PylockTomlWheel {
|
||||
// Optional "when the last component of path/ url would be the same value".
|
||||
name: if url
|
||||
|
@ -614,7 +669,7 @@ impl<'lock> PylockToml {
|
|||
hashes: Hashes::from(wheel.file.hashes),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, PylockTomlError>>()?,
|
||||
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
|
||||
)
|
||||
}
|
||||
Source::Path(..) => None,
|
||||
|
@ -728,7 +783,11 @@ impl<'lock> PylockToml {
|
|||
// Extract the `packages.sdist` field.
|
||||
let sdist = match &sdist {
|
||||
Some(SourceDist::Registry(sdist)) => {
|
||||
let url = sdist.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
||||
let url = sdist
|
||||
.file
|
||||
.url
|
||||
.to_url()
|
||||
.map_err(PylockTomlErrorKind::ToUrl)?;
|
||||
Some(PylockTomlSdist {
|
||||
// Optional "when the last component of path/ url would be the same value".
|
||||
name: if url
|
||||
|
@ -894,37 +953,43 @@ impl<'lock> PylockToml {
|
|||
) {
|
||||
// `packages.wheels` is mutually exclusive with `packages.directory`, `packages.vcs`, and `packages.archive`.
|
||||
(true, _, true, _, _) => {
|
||||
return Err(PylockTomlError::WheelWithDirectory(package.name.clone()));
|
||||
return Err(
|
||||
PylockTomlErrorKind::WheelWithDirectory(package.name.clone()).into(),
|
||||
);
|
||||
}
|
||||
(true, _, _, true, _) => {
|
||||
return Err(PylockTomlError::WheelWithVcs(package.name.clone()));
|
||||
return Err(PylockTomlErrorKind::WheelWithVcs(package.name.clone()).into());
|
||||
}
|
||||
(true, _, _, _, true) => {
|
||||
return Err(PylockTomlError::WheelWithArchive(package.name.clone()));
|
||||
return Err(PylockTomlErrorKind::WheelWithArchive(package.name.clone()).into());
|
||||
}
|
||||
// `packages.sdist` is mutually exclusive with `packages.directory`, `packages.vcs`, and `packages.archive`.
|
||||
(_, true, true, _, _) => {
|
||||
return Err(PylockTomlError::SdistWithDirectory(package.name.clone()));
|
||||
return Err(
|
||||
PylockTomlErrorKind::SdistWithDirectory(package.name.clone()).into(),
|
||||
);
|
||||
}
|
||||
(_, true, _, true, _) => {
|
||||
return Err(PylockTomlError::SdistWithVcs(package.name.clone()));
|
||||
return Err(PylockTomlErrorKind::SdistWithVcs(package.name.clone()).into());
|
||||
}
|
||||
(_, true, _, _, true) => {
|
||||
return Err(PylockTomlError::SdistWithArchive(package.name.clone()));
|
||||
return Err(PylockTomlErrorKind::SdistWithArchive(package.name.clone()).into());
|
||||
}
|
||||
// `packages.directory` is mutually exclusive with `packages.vcs`, and `packages.archive`.
|
||||
(_, _, true, true, _) => {
|
||||
return Err(PylockTomlError::DirectoryWithVcs(package.name.clone()));
|
||||
return Err(PylockTomlErrorKind::DirectoryWithVcs(package.name.clone()).into());
|
||||
}
|
||||
(_, _, true, _, true) => {
|
||||
return Err(PylockTomlError::DirectoryWithArchive(package.name.clone()));
|
||||
return Err(
|
||||
PylockTomlErrorKind::DirectoryWithArchive(package.name.clone()).into(),
|
||||
);
|
||||
}
|
||||
// `packages.vcs` is mutually exclusive with `packages.archive`.
|
||||
(_, _, _, true, true) => {
|
||||
return Err(PylockTomlError::VcsWithArchive(package.name.clone()));
|
||||
return Err(PylockTomlErrorKind::VcsWithArchive(package.name.clone()).into());
|
||||
}
|
||||
(false, false, false, false, false) => {
|
||||
return Err(PylockTomlError::MissingSource(package.name.clone()));
|
||||
return Err(PylockTomlErrorKind::MissingSource(package.name.clone()).into());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -1025,18 +1090,28 @@ impl<'lock> PylockToml {
|
|||
}
|
||||
} else {
|
||||
return match (no_binary, no_build) {
|
||||
(true, true) => Err(PylockTomlError::NoBinaryNoBuild(package.name.clone())),
|
||||
(true, true) => {
|
||||
Err(PylockTomlErrorKind::NoBinaryNoBuild(package.name.clone()).into())
|
||||
}
|
||||
(true, false) if is_wheel => {
|
||||
Err(PylockTomlError::NoBinaryWheelOnly(package.name.clone()))
|
||||
Err(PylockTomlErrorKind::NoBinaryWheelOnly(package.name.clone()).into())
|
||||
}
|
||||
(true, false) => Err(PylockTomlError::NoBinary(package.name.clone())),
|
||||
(false, true) => Err(PylockTomlError::NoBuild(package.name.clone())),
|
||||
(false, false) if is_wheel => {
|
||||
Err(PylockTomlError::IncompatibleWheelOnly(package.name.clone()))
|
||||
(true, false) => {
|
||||
Err(PylockTomlErrorKind::NoBinary(package.name.clone()).into())
|
||||
}
|
||||
(false, false) => Err(PylockTomlError::NeitherSourceDistNorWheel(
|
||||
package.name.clone(),
|
||||
)),
|
||||
(false, true) => Err(PylockTomlErrorKind::NoBuild(package.name.clone()).into()),
|
||||
(false, false) if is_wheel => Err(PylockTomlError {
|
||||
kind: Box::new(PylockTomlErrorKind::IncompatibleWheelOnly(
|
||||
package.name.clone(),
|
||||
)),
|
||||
hint: package.tag_hint(tags),
|
||||
}),
|
||||
(false, false) => Err(PylockTomlError {
|
||||
kind: Box::new(PylockTomlErrorKind::NeitherSourceDistNorWheel(
|
||||
package.name.clone(),
|
||||
)),
|
||||
hint: package.tag_hint(tags),
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1163,6 +1238,18 @@ impl PylockTomlPackage {
|
|||
best.map(|(_, i)| i)
|
||||
}
|
||||
|
||||
/// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities.
|
||||
fn tag_hint(&self, tags: &Tags) -> Option<WheelTagHint> {
|
||||
let filenames = self
|
||||
.wheels
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter_map(|wheel| wheel.filename(&self.name).ok())
|
||||
.collect::<Vec<_>>();
|
||||
let filenames = filenames.iter().map(Cow::as_ref).collect::<Vec<_>>();
|
||||
WheelTagHint::from_wheels(&self.name, self.version.as_ref(), &filenames, tags)
|
||||
}
|
||||
|
||||
/// Returns the [`ResolvedRepositoryReference`] for the package, if it is a Git source.
|
||||
pub fn as_git_ref(&self) -> Option<ResolvedRepositoryReference> {
|
||||
let vcs = self.vcs.as_ref()?;
|
||||
|
@ -1180,12 +1267,12 @@ impl PylockTomlPackage {
|
|||
|
||||
impl PylockTomlWheel {
|
||||
/// Return the [`WheelFilename`] for this wheel.
|
||||
fn filename(&self, name: &PackageName) -> Result<Cow<'_, WheelFilename>, PylockTomlError> {
|
||||
fn filename(&self, name: &PackageName) -> Result<Cow<WheelFilename>, PylockTomlErrorKind> {
|
||||
if let Some(name) = self.name.as_ref() {
|
||||
Ok(Cow::Borrowed(name))
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
let Some(filename) = path.as_ref().file_name().and_then(OsStr::to_str) else {
|
||||
return Err(PylockTomlError::PathMissingFilename(Box::<Path>::from(
|
||||
return Err(PylockTomlErrorKind::PathMissingFilename(Box::<Path>::from(
|
||||
path.clone(),
|
||||
)));
|
||||
};
|
||||
|
@ -1193,12 +1280,12 @@ impl PylockTomlWheel {
|
|||
Ok(filename)
|
||||
} else if let Some(url) = self.url.as_ref() {
|
||||
let Some(filename) = url.filename().ok() else {
|
||||
return Err(PylockTomlError::UrlMissingFilename(url.clone()));
|
||||
return Err(PylockTomlErrorKind::UrlMissingFilename(url.clone()));
|
||||
};
|
||||
let filename = WheelFilename::from_str(&filename).map(Cow::Owned)?;
|
||||
Ok(filename)
|
||||
} else {
|
||||
Err(PylockTomlError::WheelMissingPathUrl(name.clone()))
|
||||
Err(PylockTomlErrorKind::WheelMissingPathUrl(name.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1208,17 +1295,17 @@ impl PylockTomlWheel {
|
|||
install_path: &Path,
|
||||
name: &PackageName,
|
||||
index: Option<&Url>,
|
||||
) -> Result<RegistryBuiltWheel, PylockTomlError> {
|
||||
) -> Result<RegistryBuiltWheel, PylockTomlErrorKind> {
|
||||
let filename = self.filename(name)?.into_owned();
|
||||
|
||||
let file_url = if let Some(url) = self.url.as_ref() {
|
||||
UrlString::from(url)
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
let path = install_path.join(path);
|
||||
let url = Url::from_file_path(path).map_err(|()| PylockTomlError::PathToUrl)?;
|
||||
let url = Url::from_file_path(path).map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
UrlString::from(url)
|
||||
} else {
|
||||
return Err(PylockTomlError::WheelMissingPathUrl(name.clone()));
|
||||
return Err(PylockTomlErrorKind::WheelMissingPathUrl(name.clone()));
|
||||
};
|
||||
|
||||
let index = if let Some(index) = index {
|
||||
|
@ -1228,7 +1315,7 @@ impl PylockTomlWheel {
|
|||
// URL (less the filename) as the index. This isn't correct, but it's the best we can
|
||||
// do. In practice, the only effect here should be that we cache the wheel under a hash
|
||||
// of this URL (since we cache under the hash of the index).
|
||||
let mut index = file_url.to_url().map_err(PylockTomlError::ToUrl)?;
|
||||
let mut index = file_url.to_url().map_err(PylockTomlErrorKind::ToUrl)?;
|
||||
index.path_segments_mut().unwrap().pop();
|
||||
IndexUrl::from(VerbatimUrl::from_url(index))
|
||||
};
|
||||
|
@ -1258,7 +1345,7 @@ impl PylockTomlDirectory {
|
|||
&self,
|
||||
install_path: &Path,
|
||||
name: &PackageName,
|
||||
) -> Result<DirectorySourceDist, PylockTomlError> {
|
||||
) -> Result<DirectorySourceDist, PylockTomlErrorKind> {
|
||||
let path = if let Some(subdirectory) = self.subdirectory.as_ref() {
|
||||
install_path.join(&self.path).join(subdirectory)
|
||||
} else {
|
||||
|
@ -1266,7 +1353,7 @@ impl PylockTomlDirectory {
|
|||
};
|
||||
let path = uv_fs::normalize_path_buf(path);
|
||||
let url =
|
||||
VerbatimUrl::from_normalized_path(&path).map_err(|_| PylockTomlError::PathToUrl)?;
|
||||
VerbatimUrl::from_normalized_path(&path).map_err(|_| PylockTomlErrorKind::PathToUrl)?;
|
||||
Ok(DirectorySourceDist {
|
||||
name: name.clone(),
|
||||
install_path: path.into_boxed_path(),
|
||||
|
@ -1283,7 +1370,7 @@ impl PylockTomlVcs {
|
|||
&self,
|
||||
install_path: &Path,
|
||||
name: &PackageName,
|
||||
) -> Result<GitSourceDist, PylockTomlError> {
|
||||
) -> Result<GitSourceDist, PylockTomlErrorKind> {
|
||||
let subdirectory = self.subdirectory.clone().map(Box::<Path>::from);
|
||||
|
||||
// Reconstruct the `GitUrl` from the individual fields.
|
||||
|
@ -1292,9 +1379,9 @@ impl PylockTomlVcs {
|
|||
url.clone()
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
Url::from_directory_path(install_path.join(path))
|
||||
.map_err(|()| PylockTomlError::PathToUrl)?
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?
|
||||
} else {
|
||||
return Err(PylockTomlError::VcsMissingPathUrl(name.clone()));
|
||||
return Err(PylockTomlErrorKind::VcsMissingPathUrl(name.clone()));
|
||||
};
|
||||
url.set_fragment(None);
|
||||
url.set_query(None);
|
||||
|
@ -1326,23 +1413,23 @@ impl PylockTomlVcs {
|
|||
|
||||
impl PylockTomlSdist {
|
||||
/// Return the filename for this sdist.
|
||||
fn filename(&self, name: &PackageName) -> Result<Cow<'_, SmallString>, PylockTomlError> {
|
||||
fn filename(&self, name: &PackageName) -> Result<Cow<'_, SmallString>, PylockTomlErrorKind> {
|
||||
if let Some(name) = self.name.as_ref() {
|
||||
Ok(Cow::Borrowed(name))
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
let Some(filename) = path.as_ref().file_name().and_then(OsStr::to_str) else {
|
||||
return Err(PylockTomlError::PathMissingFilename(Box::<Path>::from(
|
||||
return Err(PylockTomlErrorKind::PathMissingFilename(Box::<Path>::from(
|
||||
path.clone(),
|
||||
)));
|
||||
};
|
||||
Ok(Cow::Owned(SmallString::from(filename)))
|
||||
} else if let Some(url) = self.url.as_ref() {
|
||||
let Some(filename) = url.filename().ok() else {
|
||||
return Err(PylockTomlError::UrlMissingFilename(url.clone()));
|
||||
return Err(PylockTomlErrorKind::UrlMissingFilename(url.clone()));
|
||||
};
|
||||
Ok(Cow::Owned(SmallString::from(filename)))
|
||||
} else {
|
||||
Err(PylockTomlError::SdistMissingPathUrl(name.clone()))
|
||||
Err(PylockTomlErrorKind::SdistMissingPathUrl(name.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1353,7 +1440,7 @@ impl PylockTomlSdist {
|
|||
name: &PackageName,
|
||||
version: Option<&Version>,
|
||||
index: Option<&Url>,
|
||||
) -> Result<RegistrySourceDist, PylockTomlError> {
|
||||
) -> Result<RegistrySourceDist, PylockTomlErrorKind> {
|
||||
let filename = self.filename(name)?.into_owned();
|
||||
let ext = SourceDistExtension::from_path(filename.as_ref())?;
|
||||
|
||||
|
@ -1368,10 +1455,10 @@ impl PylockTomlSdist {
|
|||
UrlString::from(url)
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
let path = install_path.join(path);
|
||||
let url = Url::from_file_path(path).map_err(|()| PylockTomlError::PathToUrl)?;
|
||||
let url = Url::from_file_path(path).map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
UrlString::from(url)
|
||||
} else {
|
||||
return Err(PylockTomlError::SdistMissingPathUrl(name.clone()));
|
||||
return Err(PylockTomlErrorKind::SdistMissingPathUrl(name.clone()));
|
||||
};
|
||||
|
||||
let index = if let Some(index) = index {
|
||||
|
@ -1381,7 +1468,7 @@ impl PylockTomlSdist {
|
|||
// URL (less the filename) as the index. This isn't correct, but it's the best we can
|
||||
// do. In practice, the only effect here should be that we cache the sdist under a hash
|
||||
// of this URL (since we cache under the hash of the index).
|
||||
let mut index = file_url.to_url().map_err(PylockTomlError::ToUrl)?;
|
||||
let mut index = file_url.to_url().map_err(PylockTomlErrorKind::ToUrl)?;
|
||||
index.path_segments_mut().unwrap().pop();
|
||||
IndexUrl::from(VerbatimUrl::from_url(index))
|
||||
};
|
||||
|
@ -1414,11 +1501,11 @@ impl PylockTomlArchive {
|
|||
install_path: &Path,
|
||||
name: &PackageName,
|
||||
version: Option<&Version>,
|
||||
) -> Result<Dist, PylockTomlError> {
|
||||
) -> Result<Dist, PylockTomlErrorKind> {
|
||||
if let Some(url) = self.url.as_ref() {
|
||||
let filename = url
|
||||
.filename()
|
||||
.map_err(|_| PylockTomlError::UrlMissingFilename(url.clone()))?;
|
||||
.map_err(|_| PylockTomlErrorKind::UrlMissingFilename(url.clone()))?;
|
||||
|
||||
let ext = DistExtension::from_path(filename.as_ref())?;
|
||||
match ext {
|
||||
|
@ -1446,7 +1533,7 @@ impl PylockTomlArchive {
|
|||
.file_name()
|
||||
.and_then(OsStr::to_str)
|
||||
.ok_or_else(|| {
|
||||
PylockTomlError::PathMissingFilename(Box::<Path>::from(path.clone()))
|
||||
PylockTomlErrorKind::PathMissingFilename(Box::<Path>::from(path.clone()))
|
||||
})?;
|
||||
|
||||
let ext = DistExtension::from_path(filename)?;
|
||||
|
@ -1455,7 +1542,7 @@ impl PylockTomlArchive {
|
|||
let filename = WheelFilename::from_str(filename)?;
|
||||
let install_path = install_path.join(path);
|
||||
let url = VerbatimUrl::from_absolute_path(&install_path)
|
||||
.map_err(|_| PylockTomlError::PathToUrl)?;
|
||||
.map_err(|_| PylockTomlErrorKind::PathToUrl)?;
|
||||
Ok(Dist::Built(BuiltDist::Path(PathBuiltDist {
|
||||
filename,
|
||||
install_path: install_path.into_boxed_path(),
|
||||
|
@ -1465,7 +1552,7 @@ impl PylockTomlArchive {
|
|||
DistExtension::Source(ext) => {
|
||||
let install_path = install_path.join(path);
|
||||
let url = VerbatimUrl::from_absolute_path(&install_path)
|
||||
.map_err(|_| PylockTomlError::PathToUrl)?;
|
||||
.map_err(|_| PylockTomlErrorKind::PathToUrl)?;
|
||||
Ok(Dist::Source(SourceDist::Path(PathSourceDist {
|
||||
name: name.clone(),
|
||||
version: version.cloned(),
|
||||
|
@ -1476,16 +1563,16 @@ impl PylockTomlArchive {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
return Err(PylockTomlError::ArchiveMissingPathUrl(name.clone()));
|
||||
return Err(PylockTomlErrorKind::ArchiveMissingPathUrl(name.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`PylockTomlArchive`] is a wheel.
|
||||
fn is_wheel(&self, name: &PackageName) -> Result<bool, PylockTomlError> {
|
||||
fn is_wheel(&self, name: &PackageName) -> Result<bool, PylockTomlErrorKind> {
|
||||
if let Some(url) = self.url.as_ref() {
|
||||
let filename = url
|
||||
.filename()
|
||||
.map_err(|_| PylockTomlError::UrlMissingFilename(url.clone()))?;
|
||||
.map_err(|_| PylockTomlErrorKind::UrlMissingFilename(url.clone()))?;
|
||||
|
||||
let ext = DistExtension::from_path(filename.as_ref())?;
|
||||
Ok(matches!(ext, DistExtension::Wheel))
|
||||
|
@ -1495,13 +1582,13 @@ impl PylockTomlArchive {
|
|||
.file_name()
|
||||
.and_then(OsStr::to_str)
|
||||
.ok_or_else(|| {
|
||||
PylockTomlError::PathMissingFilename(Box::<Path>::from(path.clone()))
|
||||
PylockTomlErrorKind::PathMissingFilename(Box::<Path>::from(path.clone()))
|
||||
})?;
|
||||
|
||||
let ext = DistExtension::from_path(filename)?;
|
||||
Ok(matches!(ext, DistExtension::Wheel))
|
||||
} else {
|
||||
return Err(PylockTomlError::ArchiveMissingPathUrl(name.clone()));
|
||||
return Err(PylockTomlErrorKind::ArchiveMissingPathUrl(name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ use uv_workspace::WorkspaceMember;
|
|||
use crate::fork_strategy::ForkStrategy;
|
||||
pub(crate) use crate::lock::export::PylockTomlPackage;
|
||||
pub use crate::lock::export::RequirementsTxtExport;
|
||||
pub use crate::lock::export::{PylockToml, PylockTomlError};
|
||||
pub use crate::lock::export::{PylockToml, PylockTomlErrorKind};
|
||||
pub use crate::lock::installable::Installable;
|
||||
pub use crate::lock::map::PackageMap;
|
||||
pub use crate::lock::tree::TreeDisplay;
|
||||
|
@ -2305,78 +2305,19 @@ impl Package {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generate a [`LockErrorHint`] based on wheel-tag incompatibilities.
|
||||
fn tag_hint(&self, tag_policy: TagPolicy<'_>) -> Option<LockErrorHint> {
|
||||
let incompatibility = self
|
||||
/// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities.
|
||||
fn tag_hint(&self, tag_policy: TagPolicy<'_>) -> Option<WheelTagHint> {
|
||||
let filenames = self
|
||||
.wheels
|
||||
.iter()
|
||||
.map(|wheel| {
|
||||
tag_policy.tags().compatibility(
|
||||
wheel.filename.python_tags(),
|
||||
wheel.filename.abi_tags(),
|
||||
wheel.filename.platform_tags(),
|
||||
)
|
||||
})
|
||||
.max()?;
|
||||
match incompatibility {
|
||||
TagCompatibility::Incompatible(IncompatibleTag::Python) => {
|
||||
let best = tag_policy.tags().python_tag();
|
||||
let tags = self.python_tags().collect::<BTreeSet<_>>();
|
||||
if tags.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(LockErrorHint::LanguageTags {
|
||||
package: self.id.name.clone(),
|
||||
version: self.id.version.clone(),
|
||||
tags,
|
||||
best,
|
||||
})
|
||||
}
|
||||
}
|
||||
TagCompatibility::Incompatible(IncompatibleTag::Abi) => {
|
||||
let best = tag_policy.tags().abi_tag();
|
||||
let tags = self
|
||||
.abi_tags()
|
||||
// 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(LockErrorHint::AbiTags {
|
||||
package: self.id.name.clone(),
|
||||
version: self.id.version.clone(),
|
||||
tags,
|
||||
best,
|
||||
})
|
||||
}
|
||||
}
|
||||
TagCompatibility::Incompatible(IncompatibleTag::Platform) => {
|
||||
let best = tag_policy.tags().platform_tag().cloned();
|
||||
let tags = self
|
||||
.platform_tags(tag_policy.tags())
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>();
|
||||
if tags.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(LockErrorHint::PlatformTags {
|
||||
package: self.id.name.clone(),
|
||||
version: self.id.version.clone(),
|
||||
tags,
|
||||
best,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
.map(|wheel| &wheel.filename)
|
||||
.collect::<Vec<_>>();
|
||||
WheelTagHint::from_wheels(
|
||||
&self.id.name,
|
||||
self.id.version.as_ref(),
|
||||
&filenames,
|
||||
tag_policy.tags(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert the source of this [`Package`] to a [`SourceDist`] that can be used in installation.
|
||||
|
@ -2729,43 +2670,6 @@ impl Package {
|
|||
Ok(table)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the compatible Python tags of the available wheels.
|
||||
fn python_tags(&self) -> impl Iterator<Item = LanguageTag> + '_ {
|
||||
self.wheels
|
||||
.iter()
|
||||
.flat_map(|wheel| wheel.filename.python_tags())
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the compatible Python tags of the available wheels.
|
||||
fn abi_tags(&self) -> impl Iterator<Item = AbiTag> + '_ {
|
||||
self.wheels
|
||||
.iter()
|
||||
.flat_map(|wheel| wheel.filename.abi_tags())
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// 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,
|
||||
) -> impl Iterator<Item = &'a PlatformTag> + 'a {
|
||||
self.wheels.iter().flat_map(move |wheel| {
|
||||
if wheel.filename.python_tags().iter().any(|wheel_py| {
|
||||
wheel
|
||||
.filename
|
||||
.abi_tags()
|
||||
.iter()
|
||||
.any(|wheel_abi| tags.is_compatible_abi(*wheel_py, *wheel_abi))
|
||||
}) {
|
||||
wheel.filename.platform_tags().iter()
|
||||
} else {
|
||||
[].iter()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn find_best_wheel(&self, tag_policy: TagPolicy<'_>) -> Option<usize> {
|
||||
type WheelPriority<'lock> = (TagPriority, Option<&'lock BuildTag>);
|
||||
|
||||
|
@ -4782,7 +4686,7 @@ fn normalize_requirement(
|
|||
#[derive(Debug)]
|
||||
pub struct LockError {
|
||||
kind: Box<LockErrorKind>,
|
||||
hint: Option<LockErrorHint>,
|
||||
hint: Option<WheelTagHint>,
|
||||
}
|
||||
|
||||
impl std::error::Error for LockError {
|
||||
|
@ -4822,7 +4726,7 @@ where
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum LockErrorHint {
|
||||
enum WheelTagHint {
|
||||
/// None of the available wheels for a package have a compatible Python language tag (e.g.,
|
||||
/// `cp310` in `cp310-abi3-manylinux_2_17_x86_64.whl`).
|
||||
LanguageTags {
|
||||
|
@ -4849,7 +4753,123 @@ enum LockErrorHint {
|
|||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LockErrorHint {
|
||||
impl WheelTagHint {
|
||||
/// Generate a [`WheelTagHint`] from the given (incompatible) wheels.
|
||||
fn from_wheels(
|
||||
name: &PackageName,
|
||||
version: Option<&Version>,
|
||||
filenames: &[&WheelFilename],
|
||||
tags: &Tags,
|
||||
) -> Option<WheelTagHint> {
|
||||
let incompatibility = filenames
|
||||
.iter()
|
||||
.map(|filename| {
|
||||
tags.compatibility(
|
||||
filename.python_tags(),
|
||||
filename.abi_tags(),
|
||||
filename.platform_tags(),
|
||||
)
|
||||
})
|
||||
.max()?;
|
||||
match incompatibility {
|
||||
TagCompatibility::Incompatible(IncompatibleTag::Python) => {
|
||||
let best = tags.python_tag();
|
||||
let tags = Self::python_tags(filenames.iter().copied()).collect::<BTreeSet<_>>();
|
||||
if tags.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(WheelTagHint::LanguageTags {
|
||||
package: name.clone(),
|
||||
version: version.cloned(),
|
||||
tags,
|
||||
best,
|
||||
})
|
||||
}
|
||||
}
|
||||
TagCompatibility::Incompatible(IncompatibleTag::Abi) => {
|
||||
let best = tags.abi_tag();
|
||||
let tags = Self::abi_tags(filenames.iter().copied())
|
||||
// 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(WheelTagHint::AbiTags {
|
||||
package: name.clone(),
|
||||
version: version.cloned(),
|
||||
tags,
|
||||
best,
|
||||
})
|
||||
}
|
||||
}
|
||||
TagCompatibility::Incompatible(IncompatibleTag::Platform) => {
|
||||
let best = tags.platform_tag().cloned();
|
||||
let tags = Self::platform_tags(filenames.iter().copied(), tags)
|
||||
.cloned()
|
||||
.collect::<BTreeSet<_>>();
|
||||
if tags.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(WheelTagHint::PlatformTags {
|
||||
package: name.clone(),
|
||||
version: version.cloned(),
|
||||
tags,
|
||||
best,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the compatible Python tags of the available wheels.
|
||||
fn python_tags<'a>(
|
||||
filenames: impl Iterator<Item = &'a WheelFilename> + 'a,
|
||||
) -> impl Iterator<Item = LanguageTag> + 'a {
|
||||
filenames
|
||||
.flat_map(uv_distribution_filename::WheelFilename::python_tags)
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the compatible Python tags of the available wheels.
|
||||
fn abi_tags<'a>(
|
||||
filenames: impl Iterator<Item = &'a WheelFilename> + 'a,
|
||||
) -> impl Iterator<Item = AbiTag> + 'a {
|
||||
filenames
|
||||
.flat_map(uv_distribution_filename::WheelFilename::abi_tags)
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Returns the set of platform tags for the distribution that are ABI-compatible with the given
|
||||
/// tags.
|
||||
fn platform_tags<'a>(
|
||||
filenames: impl Iterator<Item = &'a WheelFilename> + 'a,
|
||||
tags: &'a Tags,
|
||||
) -> impl Iterator<Item = &'a PlatformTag> + 'a {
|
||||
filenames.flat_map(move |filename| {
|
||||
if filename.python_tags().iter().any(|wheel_py| {
|
||||
filename
|
||||
.abi_tags()
|
||||
.iter()
|
||||
.any(|wheel_abi| tags.is_compatible_abi(*wheel_py, *wheel_abi))
|
||||
}) {
|
||||
filename.platform_tags().iter()
|
||||
} else {
|
||||
[].iter()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WheelTagHint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::LanguageTags {
|
||||
|
|
|
@ -5892,6 +5892,8 @@ fn pep_751_wheel_only() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
error: Package `torch` can't be installed because it doesn't have a source distribution or wheel for the current platform
|
||||
|
||||
hint: You're using CPython 3.8 (`cp38`), but `torch` (v2.2.1) only has wheels with the following Python implementation tag: `cp312`
|
||||
"
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue