mirror of https://github.com/astral-sh/uv
Show tag hints when failing to find a compatible wheel in `pylock.toml` (#13136)
## Summary Closes #13135.
This commit is contained in:
parent
78756de027
commit
dc5b3762f3
|
|
@ -7,7 +7,7 @@ use uv_configuration::Upgrade;
|
||||||
use uv_fs::CWD;
|
use uv_fs::CWD;
|
||||||
use uv_git::ResolvedRepositoryReference;
|
use uv_git::ResolvedRepositoryReference;
|
||||||
use uv_requirements_txt::RequirementsTxt;
|
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)]
|
#[derive(Debug, Default)]
|
||||||
pub struct LockedRequirements {
|
pub struct LockedRequirements {
|
||||||
|
|
@ -105,7 +105,7 @@ pub fn read_lock_requirements(
|
||||||
pub async fn read_pylock_toml_requirements(
|
pub async fn read_pylock_toml_requirements(
|
||||||
output_file: &Path,
|
output_file: &Path,
|
||||||
upgrade: &Upgrade,
|
upgrade: &Upgrade,
|
||||||
) -> Result<LockedRequirements, PylockTomlError> {
|
) -> Result<LockedRequirements, PylockTomlErrorKind> {
|
||||||
// As an optimization, skip iterating over the lockfile is we're upgrading all packages anyway.
|
// As an optimization, skip iterating over the lockfile is we're upgrading all packages anyway.
|
||||||
if upgrade.is_all() {
|
if upgrade.is_all() {
|
||||||
return Ok(LockedRequirements::default());
|
return Ok(LockedRequirements::default());
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ pub use exclusions::Exclusions;
|
||||||
pub use flat_index::{FlatDistributions, FlatIndex};
|
pub use flat_index::{FlatDistributions, FlatIndex};
|
||||||
pub use fork_strategy::ForkStrategy;
|
pub use fork_strategy::ForkStrategy;
|
||||||
pub use lock::{
|
pub use lock::{
|
||||||
Installable, Lock, LockError, LockVersion, Package, PackageMap, PylockToml, PylockTomlError,
|
Installable, Lock, LockError, LockVersion, Package, PackageMap, PylockToml,
|
||||||
RequirementsTxtExport, ResolverManifest, SatisfiesResult, TreeDisplay, VERSION,
|
PylockTomlErrorKind, RequirementsTxtExport, ResolverManifest, SatisfiesResult, TreeDisplay,
|
||||||
|
VERSION,
|
||||||
};
|
};
|
||||||
pub use manifest::Manifest;
|
pub use manifest::Manifest;
|
||||||
pub use options::{Flexibility, Options, OptionsBuilder};
|
pub use options::{Flexibility, Options, OptionsBuilder};
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use uv_pypi_types::ConflictItem;
|
||||||
|
|
||||||
use crate::graph_ops::{marker_reachability, Reachable};
|
use crate::graph_ops::{marker_reachability, Reachable};
|
||||||
pub(crate) use crate::lock::export::pylock_toml::PylockTomlPackage;
|
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;
|
pub use crate::lock::export::requirements_txt::RequirementsTxtExport;
|
||||||
use crate::universal_marker::resolve_conflicts;
|
use crate::universal_marker::resolve_conflicts;
|
||||||
use crate::{Installable, Package};
|
use crate::{Installable, Package};
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,12 @@ use uv_pypi_types::{HashDigests, Hashes, ParsedGitUrl, VcsKind};
|
||||||
use uv_small_str::SmallString;
|
use uv_small_str::SmallString;
|
||||||
|
|
||||||
use crate::lock::export::ExportableRequirements;
|
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::resolution::ResolutionGraphNode;
|
||||||
use crate::{Installable, LockError, RequiresPython, ResolverOutput};
|
use crate::{Installable, LockError, RequiresPython, ResolverOutput};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[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`)")]
|
#[error("Package `{0}` includes both a registry (`packages.wheels`) and a directory source (`packages.directory`)")]
|
||||||
WheelWithDirectory(PackageName),
|
WheelWithDirectory(PackageName),
|
||||||
#[error("Package `{0}` includes both a registry (`packages.wheels`) and a VCS source (`packages.vcs`)")]
|
#[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),
|
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)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct PylockToml {
|
pub struct PylockToml {
|
||||||
|
|
@ -267,7 +301,7 @@ impl<'lock> PylockToml {
|
||||||
resolution: &ResolverOutput,
|
resolution: &ResolverOutput,
|
||||||
omit: &[PackageName],
|
omit: &[PackageName],
|
||||||
install_path: &Path,
|
install_path: &Path,
|
||||||
) -> Result<Self, PylockTomlError> {
|
) -> Result<Self, PylockTomlErrorKind> {
|
||||||
// The lock version is always `1.0` at time of writing.
|
// The lock version is always `1.0` at time of writing.
|
||||||
let lock_version = Version::new([1, 0]);
|
let lock_version = Version::new([1, 0]);
|
||||||
|
|
||||||
|
|
@ -354,8 +388,11 @@ impl<'lock> PylockToml {
|
||||||
dist.wheels
|
dist.wheels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|wheel| {
|
.map(|wheel| {
|
||||||
let url =
|
let url = wheel
|
||||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
.file
|
||||||
|
.url
|
||||||
|
.to_url()
|
||||||
|
.map_err(PylockTomlErrorKind::ToUrl)?;
|
||||||
Ok(PylockTomlWheel {
|
Ok(PylockTomlWheel {
|
||||||
// Optional "when the last component of path/ url would be the same value".
|
// Optional "when the last component of path/ url would be the same value".
|
||||||
name: if url
|
name: if url
|
||||||
|
|
@ -372,18 +409,26 @@ impl<'lock> PylockToml {
|
||||||
.map(Timestamp::from_millisecond)
|
.map(Timestamp::from_millisecond)
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
url: Some(
|
url: Some(
|
||||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?,
|
wheel
|
||||||
|
.file
|
||||||
|
.url
|
||||||
|
.to_url()
|
||||||
|
.map_err(PylockTomlErrorKind::ToUrl)?,
|
||||||
),
|
),
|
||||||
path: None,
|
path: None,
|
||||||
size: wheel.file.size,
|
size: wheel.file.size,
|
||||||
hashes: Hashes::from(wheel.file.hashes.clone()),
|
hashes: Hashes::from(wheel.file.hashes.clone()),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, PylockTomlError>>()?,
|
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(sdist) = dist.sdist.as_ref() {
|
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 {
|
package.sdist = Some(PylockTomlSdist {
|
||||||
// Optional "when the last component of path/ url would be the same value".
|
// Optional "when the last component of path/ url would be the same value".
|
||||||
name: if url
|
name: if url
|
||||||
|
|
@ -456,8 +501,11 @@ impl<'lock> PylockToml {
|
||||||
dist.wheels
|
dist.wheels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|wheel| {
|
.map(|wheel| {
|
||||||
let url =
|
let url = wheel
|
||||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
.file
|
||||||
|
.url
|
||||||
|
.to_url()
|
||||||
|
.map_err(PylockTomlErrorKind::ToUrl)?;
|
||||||
Ok(PylockTomlWheel {
|
Ok(PylockTomlWheel {
|
||||||
// Optional "when the last component of path/ url would be the same value".
|
// Optional "when the last component of path/ url would be the same value".
|
||||||
name: if url
|
name: if url
|
||||||
|
|
@ -474,17 +522,21 @@ impl<'lock> PylockToml {
|
||||||
.map(Timestamp::from_millisecond)
|
.map(Timestamp::from_millisecond)
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
url: Some(
|
url: Some(
|
||||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?,
|
wheel
|
||||||
|
.file
|
||||||
|
.url
|
||||||
|
.to_url()
|
||||||
|
.map_err(PylockTomlErrorKind::ToUrl)?,
|
||||||
),
|
),
|
||||||
path: None,
|
path: None,
|
||||||
size: wheel.file.size,
|
size: wheel.file.size,
|
||||||
hashes: Hashes::from(wheel.file.hashes.clone()),
|
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 {
|
package.sdist = Some(PylockTomlSdist {
|
||||||
// Optional "when the last component of path/ url would be the same value".
|
// Optional "when the last component of path/ url would be the same value".
|
||||||
name: if url
|
name: if url
|
||||||
|
|
@ -536,7 +588,7 @@ impl<'lock> PylockToml {
|
||||||
dev: &DependencyGroupsWithDefaults,
|
dev: &DependencyGroupsWithDefaults,
|
||||||
annotate: bool,
|
annotate: bool,
|
||||||
install_options: &'lock InstallOptions,
|
install_options: &'lock InstallOptions,
|
||||||
) -> Result<Self, PylockTomlError> {
|
) -> Result<Self, PylockTomlErrorKind> {
|
||||||
// Extract the packages from the lock file.
|
// Extract the packages from the lock file.
|
||||||
let ExportableRequirements(mut nodes) = ExportableRequirements::from_lock(
|
let ExportableRequirements(mut nodes) = ExportableRequirements::from_lock(
|
||||||
target,
|
target,
|
||||||
|
|
@ -591,8 +643,11 @@ impl<'lock> PylockToml {
|
||||||
wheels
|
wheels
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|wheel| {
|
.map(|wheel| {
|
||||||
let url =
|
let url = wheel
|
||||||
wheel.file.url.to_url().map_err(PylockTomlError::ToUrl)?;
|
.file
|
||||||
|
.url
|
||||||
|
.to_url()
|
||||||
|
.map_err(PylockTomlErrorKind::ToUrl)?;
|
||||||
Ok(PylockTomlWheel {
|
Ok(PylockTomlWheel {
|
||||||
// Optional "when the last component of path/ url would be the same value".
|
// Optional "when the last component of path/ url would be the same value".
|
||||||
name: if url
|
name: if url
|
||||||
|
|
@ -614,7 +669,7 @@ impl<'lock> PylockToml {
|
||||||
hashes: Hashes::from(wheel.file.hashes),
|
hashes: Hashes::from(wheel.file.hashes),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, PylockTomlError>>()?,
|
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Source::Path(..) => None,
|
Source::Path(..) => None,
|
||||||
|
|
@ -728,7 +783,11 @@ impl<'lock> PylockToml {
|
||||||
// Extract the `packages.sdist` field.
|
// Extract the `packages.sdist` field.
|
||||||
let sdist = match &sdist {
|
let sdist = match &sdist {
|
||||||
Some(SourceDist::Registry(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 {
|
Some(PylockTomlSdist {
|
||||||
// Optional "when the last component of path/ url would be the same value".
|
// Optional "when the last component of path/ url would be the same value".
|
||||||
name: if url
|
name: if url
|
||||||
|
|
@ -894,37 +953,43 @@ impl<'lock> PylockToml {
|
||||||
) {
|
) {
|
||||||
// `packages.wheels` is mutually exclusive with `packages.directory`, `packages.vcs`, and `packages.archive`.
|
// `packages.wheels` is mutually exclusive with `packages.directory`, `packages.vcs`, and `packages.archive`.
|
||||||
(true, _, true, _, _) => {
|
(true, _, true, _, _) => {
|
||||||
return Err(PylockTomlError::WheelWithDirectory(package.name.clone()));
|
return Err(
|
||||||
|
PylockTomlErrorKind::WheelWithDirectory(package.name.clone()).into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(true, _, _, true, _) => {
|
(true, _, _, true, _) => {
|
||||||
return Err(PylockTomlError::WheelWithVcs(package.name.clone()));
|
return Err(PylockTomlErrorKind::WheelWithVcs(package.name.clone()).into());
|
||||||
}
|
}
|
||||||
(true, _, _, _, true) => {
|
(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`.
|
// `packages.sdist` is mutually exclusive with `packages.directory`, `packages.vcs`, and `packages.archive`.
|
||||||
(_, true, true, _, _) => {
|
(_, true, true, _, _) => {
|
||||||
return Err(PylockTomlError::SdistWithDirectory(package.name.clone()));
|
return Err(
|
||||||
|
PylockTomlErrorKind::SdistWithDirectory(package.name.clone()).into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(_, true, _, true, _) => {
|
(_, true, _, true, _) => {
|
||||||
return Err(PylockTomlError::SdistWithVcs(package.name.clone()));
|
return Err(PylockTomlErrorKind::SdistWithVcs(package.name.clone()).into());
|
||||||
}
|
}
|
||||||
(_, true, _, _, true) => {
|
(_, 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`.
|
// `packages.directory` is mutually exclusive with `packages.vcs`, and `packages.archive`.
|
||||||
(_, _, true, true, _) => {
|
(_, _, true, true, _) => {
|
||||||
return Err(PylockTomlError::DirectoryWithVcs(package.name.clone()));
|
return Err(PylockTomlErrorKind::DirectoryWithVcs(package.name.clone()).into());
|
||||||
}
|
}
|
||||||
(_, _, true, _, true) => {
|
(_, _, 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`.
|
// `packages.vcs` is mutually exclusive with `packages.archive`.
|
||||||
(_, _, _, true, true) => {
|
(_, _, _, true, true) => {
|
||||||
return Err(PylockTomlError::VcsWithArchive(package.name.clone()));
|
return Err(PylockTomlErrorKind::VcsWithArchive(package.name.clone()).into());
|
||||||
}
|
}
|
||||||
(false, false, false, false, false) => {
|
(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 {
|
} else {
|
||||||
return match (no_binary, no_build) {
|
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 => {
|
(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())),
|
(true, false) => {
|
||||||
(false, true) => Err(PylockTomlError::NoBuild(package.name.clone())),
|
Err(PylockTomlErrorKind::NoBinary(package.name.clone()).into())
|
||||||
(false, false) if is_wheel => {
|
|
||||||
Err(PylockTomlError::IncompatibleWheelOnly(package.name.clone()))
|
|
||||||
}
|
}
|
||||||
(false, false) => Err(PylockTomlError::NeitherSourceDistNorWheel(
|
(false, true) => Err(PylockTomlErrorKind::NoBuild(package.name.clone()).into()),
|
||||||
|
(false, false) if is_wheel => Err(PylockTomlError {
|
||||||
|
kind: Box::new(PylockTomlErrorKind::IncompatibleWheelOnly(
|
||||||
package.name.clone(),
|
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)
|
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.
|
/// Returns the [`ResolvedRepositoryReference`] for the package, if it is a Git source.
|
||||||
pub fn as_git_ref(&self) -> Option<ResolvedRepositoryReference> {
|
pub fn as_git_ref(&self) -> Option<ResolvedRepositoryReference> {
|
||||||
let vcs = self.vcs.as_ref()?;
|
let vcs = self.vcs.as_ref()?;
|
||||||
|
|
@ -1180,12 +1267,12 @@ impl PylockTomlPackage {
|
||||||
|
|
||||||
impl PylockTomlWheel {
|
impl PylockTomlWheel {
|
||||||
/// Return the [`WheelFilename`] for this wheel.
|
/// 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() {
|
if let Some(name) = self.name.as_ref() {
|
||||||
Ok(Cow::Borrowed(name))
|
Ok(Cow::Borrowed(name))
|
||||||
} else if let Some(path) = self.path.as_ref() {
|
} else if let Some(path) = self.path.as_ref() {
|
||||||
let Some(filename) = path.as_ref().file_name().and_then(OsStr::to_str) else {
|
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(),
|
path.clone(),
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
|
|
@ -1193,12 +1280,12 @@ impl PylockTomlWheel {
|
||||||
Ok(filename)
|
Ok(filename)
|
||||||
} else if let Some(url) = self.url.as_ref() {
|
} else if let Some(url) = self.url.as_ref() {
|
||||||
let Some(filename) = url.filename().ok() else {
|
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)?;
|
let filename = WheelFilename::from_str(&filename).map(Cow::Owned)?;
|
||||||
Ok(filename)
|
Ok(filename)
|
||||||
} else {
|
} else {
|
||||||
Err(PylockTomlError::WheelMissingPathUrl(name.clone()))
|
Err(PylockTomlErrorKind::WheelMissingPathUrl(name.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1208,17 +1295,17 @@ impl PylockTomlWheel {
|
||||||
install_path: &Path,
|
install_path: &Path,
|
||||||
name: &PackageName,
|
name: &PackageName,
|
||||||
index: Option<&Url>,
|
index: Option<&Url>,
|
||||||
) -> Result<RegistryBuiltWheel, PylockTomlError> {
|
) -> Result<RegistryBuiltWheel, PylockTomlErrorKind> {
|
||||||
let filename = self.filename(name)?.into_owned();
|
let filename = self.filename(name)?.into_owned();
|
||||||
|
|
||||||
let file_url = if let Some(url) = self.url.as_ref() {
|
let file_url = if let Some(url) = self.url.as_ref() {
|
||||||
UrlString::from(url)
|
UrlString::from(url)
|
||||||
} else if let Some(path) = self.path.as_ref() {
|
} else if let Some(path) = self.path.as_ref() {
|
||||||
let path = install_path.join(path);
|
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)
|
UrlString::from(url)
|
||||||
} else {
|
} else {
|
||||||
return Err(PylockTomlError::WheelMissingPathUrl(name.clone()));
|
return Err(PylockTomlErrorKind::WheelMissingPathUrl(name.clone()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let index = if let Some(index) = index {
|
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
|
// 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
|
// 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).
|
// 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();
|
index.path_segments_mut().unwrap().pop();
|
||||||
IndexUrl::from(VerbatimUrl::from_url(index))
|
IndexUrl::from(VerbatimUrl::from_url(index))
|
||||||
};
|
};
|
||||||
|
|
@ -1258,7 +1345,7 @@ impl PylockTomlDirectory {
|
||||||
&self,
|
&self,
|
||||||
install_path: &Path,
|
install_path: &Path,
|
||||||
name: &PackageName,
|
name: &PackageName,
|
||||||
) -> Result<DirectorySourceDist, PylockTomlError> {
|
) -> Result<DirectorySourceDist, PylockTomlErrorKind> {
|
||||||
let path = if let Some(subdirectory) = self.subdirectory.as_ref() {
|
let path = if let Some(subdirectory) = self.subdirectory.as_ref() {
|
||||||
install_path.join(&self.path).join(subdirectory)
|
install_path.join(&self.path).join(subdirectory)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1266,7 +1353,7 @@ impl PylockTomlDirectory {
|
||||||
};
|
};
|
||||||
let path = uv_fs::normalize_path_buf(path);
|
let path = uv_fs::normalize_path_buf(path);
|
||||||
let url =
|
let url =
|
||||||
VerbatimUrl::from_normalized_path(&path).map_err(|_| PylockTomlError::PathToUrl)?;
|
VerbatimUrl::from_normalized_path(&path).map_err(|_| PylockTomlErrorKind::PathToUrl)?;
|
||||||
Ok(DirectorySourceDist {
|
Ok(DirectorySourceDist {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
install_path: path.into_boxed_path(),
|
install_path: path.into_boxed_path(),
|
||||||
|
|
@ -1283,7 +1370,7 @@ impl PylockTomlVcs {
|
||||||
&self,
|
&self,
|
||||||
install_path: &Path,
|
install_path: &Path,
|
||||||
name: &PackageName,
|
name: &PackageName,
|
||||||
) -> Result<GitSourceDist, PylockTomlError> {
|
) -> Result<GitSourceDist, PylockTomlErrorKind> {
|
||||||
let subdirectory = self.subdirectory.clone().map(Box::<Path>::from);
|
let subdirectory = self.subdirectory.clone().map(Box::<Path>::from);
|
||||||
|
|
||||||
// Reconstruct the `GitUrl` from the individual fields.
|
// Reconstruct the `GitUrl` from the individual fields.
|
||||||
|
|
@ -1292,9 +1379,9 @@ impl PylockTomlVcs {
|
||||||
url.clone()
|
url.clone()
|
||||||
} else if let Some(path) = self.path.as_ref() {
|
} else if let Some(path) = self.path.as_ref() {
|
||||||
Url::from_directory_path(install_path.join(path))
|
Url::from_directory_path(install_path.join(path))
|
||||||
.map_err(|()| PylockTomlError::PathToUrl)?
|
.map_err(|()| PylockTomlErrorKind::PathToUrl)?
|
||||||
} else {
|
} else {
|
||||||
return Err(PylockTomlError::VcsMissingPathUrl(name.clone()));
|
return Err(PylockTomlErrorKind::VcsMissingPathUrl(name.clone()));
|
||||||
};
|
};
|
||||||
url.set_fragment(None);
|
url.set_fragment(None);
|
||||||
url.set_query(None);
|
url.set_query(None);
|
||||||
|
|
@ -1326,23 +1413,23 @@ impl PylockTomlVcs {
|
||||||
|
|
||||||
impl PylockTomlSdist {
|
impl PylockTomlSdist {
|
||||||
/// Return the filename for this sdist.
|
/// 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() {
|
if let Some(name) = self.name.as_ref() {
|
||||||
Ok(Cow::Borrowed(name))
|
Ok(Cow::Borrowed(name))
|
||||||
} else if let Some(path) = self.path.as_ref() {
|
} else if let Some(path) = self.path.as_ref() {
|
||||||
let Some(filename) = path.as_ref().file_name().and_then(OsStr::to_str) else {
|
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(),
|
path.clone(),
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
Ok(Cow::Owned(SmallString::from(filename)))
|
Ok(Cow::Owned(SmallString::from(filename)))
|
||||||
} else if let Some(url) = self.url.as_ref() {
|
} else if let Some(url) = self.url.as_ref() {
|
||||||
let Some(filename) = url.filename().ok() else {
|
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)))
|
Ok(Cow::Owned(SmallString::from(filename)))
|
||||||
} else {
|
} else {
|
||||||
Err(PylockTomlError::SdistMissingPathUrl(name.clone()))
|
Err(PylockTomlErrorKind::SdistMissingPathUrl(name.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1353,7 +1440,7 @@ impl PylockTomlSdist {
|
||||||
name: &PackageName,
|
name: &PackageName,
|
||||||
version: Option<&Version>,
|
version: Option<&Version>,
|
||||||
index: Option<&Url>,
|
index: Option<&Url>,
|
||||||
) -> Result<RegistrySourceDist, PylockTomlError> {
|
) -> Result<RegistrySourceDist, PylockTomlErrorKind> {
|
||||||
let filename = self.filename(name)?.into_owned();
|
let filename = self.filename(name)?.into_owned();
|
||||||
let ext = SourceDistExtension::from_path(filename.as_ref())?;
|
let ext = SourceDistExtension::from_path(filename.as_ref())?;
|
||||||
|
|
||||||
|
|
@ -1368,10 +1455,10 @@ impl PylockTomlSdist {
|
||||||
UrlString::from(url)
|
UrlString::from(url)
|
||||||
} else if let Some(path) = self.path.as_ref() {
|
} else if let Some(path) = self.path.as_ref() {
|
||||||
let path = install_path.join(path);
|
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)
|
UrlString::from(url)
|
||||||
} else {
|
} else {
|
||||||
return Err(PylockTomlError::SdistMissingPathUrl(name.clone()));
|
return Err(PylockTomlErrorKind::SdistMissingPathUrl(name.clone()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let index = if let Some(index) = index {
|
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
|
// 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
|
// 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).
|
// 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();
|
index.path_segments_mut().unwrap().pop();
|
||||||
IndexUrl::from(VerbatimUrl::from_url(index))
|
IndexUrl::from(VerbatimUrl::from_url(index))
|
||||||
};
|
};
|
||||||
|
|
@ -1414,11 +1501,11 @@ impl PylockTomlArchive {
|
||||||
install_path: &Path,
|
install_path: &Path,
|
||||||
name: &PackageName,
|
name: &PackageName,
|
||||||
version: Option<&Version>,
|
version: Option<&Version>,
|
||||||
) -> Result<Dist, PylockTomlError> {
|
) -> Result<Dist, PylockTomlErrorKind> {
|
||||||
if let Some(url) = self.url.as_ref() {
|
if let Some(url) = self.url.as_ref() {
|
||||||
let filename = url
|
let filename = url
|
||||||
.filename()
|
.filename()
|
||||||
.map_err(|_| PylockTomlError::UrlMissingFilename(url.clone()))?;
|
.map_err(|_| PylockTomlErrorKind::UrlMissingFilename(url.clone()))?;
|
||||||
|
|
||||||
let ext = DistExtension::from_path(filename.as_ref())?;
|
let ext = DistExtension::from_path(filename.as_ref())?;
|
||||||
match ext {
|
match ext {
|
||||||
|
|
@ -1446,7 +1533,7 @@ impl PylockTomlArchive {
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(OsStr::to_str)
|
.and_then(OsStr::to_str)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
PylockTomlError::PathMissingFilename(Box::<Path>::from(path.clone()))
|
PylockTomlErrorKind::PathMissingFilename(Box::<Path>::from(path.clone()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let ext = DistExtension::from_path(filename)?;
|
let ext = DistExtension::from_path(filename)?;
|
||||||
|
|
@ -1455,7 +1542,7 @@ impl PylockTomlArchive {
|
||||||
let filename = WheelFilename::from_str(filename)?;
|
let filename = WheelFilename::from_str(filename)?;
|
||||||
let install_path = install_path.join(path);
|
let install_path = install_path.join(path);
|
||||||
let url = VerbatimUrl::from_absolute_path(&install_path)
|
let url = VerbatimUrl::from_absolute_path(&install_path)
|
||||||
.map_err(|_| PylockTomlError::PathToUrl)?;
|
.map_err(|_| PylockTomlErrorKind::PathToUrl)?;
|
||||||
Ok(Dist::Built(BuiltDist::Path(PathBuiltDist {
|
Ok(Dist::Built(BuiltDist::Path(PathBuiltDist {
|
||||||
filename,
|
filename,
|
||||||
install_path: install_path.into_boxed_path(),
|
install_path: install_path.into_boxed_path(),
|
||||||
|
|
@ -1465,7 +1552,7 @@ impl PylockTomlArchive {
|
||||||
DistExtension::Source(ext) => {
|
DistExtension::Source(ext) => {
|
||||||
let install_path = install_path.join(path);
|
let install_path = install_path.join(path);
|
||||||
let url = VerbatimUrl::from_absolute_path(&install_path)
|
let url = VerbatimUrl::from_absolute_path(&install_path)
|
||||||
.map_err(|_| PylockTomlError::PathToUrl)?;
|
.map_err(|_| PylockTomlErrorKind::PathToUrl)?;
|
||||||
Ok(Dist::Source(SourceDist::Path(PathSourceDist {
|
Ok(Dist::Source(SourceDist::Path(PathSourceDist {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
version: version.cloned(),
|
version: version.cloned(),
|
||||||
|
|
@ -1476,16 +1563,16 @@ impl PylockTomlArchive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(PylockTomlError::ArchiveMissingPathUrl(name.clone()));
|
return Err(PylockTomlErrorKind::ArchiveMissingPathUrl(name.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the [`PylockTomlArchive`] is a wheel.
|
/// 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() {
|
if let Some(url) = self.url.as_ref() {
|
||||||
let filename = url
|
let filename = url
|
||||||
.filename()
|
.filename()
|
||||||
.map_err(|_| PylockTomlError::UrlMissingFilename(url.clone()))?;
|
.map_err(|_| PylockTomlErrorKind::UrlMissingFilename(url.clone()))?;
|
||||||
|
|
||||||
let ext = DistExtension::from_path(filename.as_ref())?;
|
let ext = DistExtension::from_path(filename.as_ref())?;
|
||||||
Ok(matches!(ext, DistExtension::Wheel))
|
Ok(matches!(ext, DistExtension::Wheel))
|
||||||
|
|
@ -1495,13 +1582,13 @@ impl PylockTomlArchive {
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(OsStr::to_str)
|
.and_then(OsStr::to_str)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
PylockTomlError::PathMissingFilename(Box::<Path>::from(path.clone()))
|
PylockTomlErrorKind::PathMissingFilename(Box::<Path>::from(path.clone()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let ext = DistExtension::from_path(filename)?;
|
let ext = DistExtension::from_path(filename)?;
|
||||||
Ok(matches!(ext, DistExtension::Wheel))
|
Ok(matches!(ext, DistExtension::Wheel))
|
||||||
} else {
|
} 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;
|
use crate::fork_strategy::ForkStrategy;
|
||||||
pub(crate) use crate::lock::export::PylockTomlPackage;
|
pub(crate) use crate::lock::export::PylockTomlPackage;
|
||||||
pub use crate::lock::export::RequirementsTxtExport;
|
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::installable::Installable;
|
||||||
pub use crate::lock::map::PackageMap;
|
pub use crate::lock::map::PackageMap;
|
||||||
pub use crate::lock::tree::TreeDisplay;
|
pub use crate::lock::tree::TreeDisplay;
|
||||||
|
|
@ -2305,78 +2305,19 @@ impl Package {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a [`LockErrorHint`] based on wheel-tag incompatibilities.
|
/// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities.
|
||||||
fn tag_hint(&self, tag_policy: TagPolicy<'_>) -> Option<LockErrorHint> {
|
fn tag_hint(&self, tag_policy: TagPolicy<'_>) -> Option<WheelTagHint> {
|
||||||
let incompatibility = self
|
let filenames = self
|
||||||
.wheels
|
.wheels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|wheel| {
|
.map(|wheel| &wheel.filename)
|
||||||
tag_policy.tags().compatibility(
|
.collect::<Vec<_>>();
|
||||||
wheel.filename.python_tags(),
|
WheelTagHint::from_wheels(
|
||||||
wheel.filename.abi_tags(),
|
&self.id.name,
|
||||||
wheel.filename.platform_tags(),
|
self.id.version.as_ref(),
|
||||||
|
&filenames,
|
||||||
|
tag_policy.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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the source of this [`Package`] to a [`SourceDist`] that can be used in installation.
|
/// Convert the source of this [`Package`] to a [`SourceDist`] that can be used in installation.
|
||||||
|
|
@ -2729,43 +2670,6 @@ impl Package {
|
||||||
Ok(table)
|
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> {
|
fn find_best_wheel(&self, tag_policy: TagPolicy<'_>) -> Option<usize> {
|
||||||
type WheelPriority<'lock> = (TagPriority, Option<&'lock BuildTag>);
|
type WheelPriority<'lock> = (TagPriority, Option<&'lock BuildTag>);
|
||||||
|
|
||||||
|
|
@ -4782,7 +4686,7 @@ fn normalize_requirement(
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LockError {
|
pub struct LockError {
|
||||||
kind: Box<LockErrorKind>,
|
kind: Box<LockErrorKind>,
|
||||||
hint: Option<LockErrorHint>,
|
hint: Option<WheelTagHint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for LockError {
|
impl std::error::Error for LockError {
|
||||||
|
|
@ -4822,7 +4726,7 @@ where
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[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.,
|
/// 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`).
|
/// `cp310` in `cp310-abi3-manylinux_2_17_x86_64.whl`).
|
||||||
LanguageTags {
|
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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::LanguageTags {
|
Self::LanguageTags {
|
||||||
|
|
|
||||||
|
|
@ -5892,6 +5892,8 @@ fn pep_751_wheel_only() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: Package `torch` can't be installed because it doesn't have a source distribution or wheel for the current platform
|
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…
Reference in New Issue