Refactor unavailable metadata to shrink the resolver (#9769)

The resolver methods are already too large and complex, especially
`choose_version*`, so i wanted to shrink and simplify them a bit before
adding new methods to them.

I've split `MetadataResponse` into three variants: success, non-fatal
error (reported through pubgrub), fatal error (reported as error trace).
The resulting non-fatal `MetadataUnavailable` type is equivalent to the
`IncompletePackage` type, so they are now merged. (`UnavailableVersion`
is a bit different since, besides the extra `IncompatibleDist` variant,
it have no error source attached). This shows that the missing metadata
variant was unused, which I removed.

Tagging as error messages for the logging format changes.
This commit is contained in:
konsti 2024-12-10 17:46:53 +01:00 committed by GitHub
parent edf875e306
commit b751648bfe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 138 additions and 256 deletions

View file

@ -215,7 +215,7 @@ impl SitePackages {
// Determine the dependencies for the given package. // Determine the dependencies for the given package.
let Ok(metadata) = distribution.metadata() else { let Ok(metadata) = distribution.metadata() else {
diagnostics.push(SitePackagesDiagnostic::IncompletePackage { diagnostics.push(SitePackagesDiagnostic::MetadataUnavailable {
package: package.clone(), package: package.clone(),
path: distribution.path().to_owned(), path: distribution.path().to_owned(),
}); });
@ -405,7 +405,7 @@ impl IntoIterator for SitePackages {
#[derive(Debug)] #[derive(Debug)]
pub enum SitePackagesDiagnostic { pub enum SitePackagesDiagnostic {
IncompletePackage { MetadataUnavailable {
/// The package that is missing metadata. /// The package that is missing metadata.
package: PackageName, package: PackageName,
/// The path to the package. /// The path to the package.
@ -445,7 +445,7 @@ impl Diagnostic for SitePackagesDiagnostic {
/// Convert the diagnostic into a user-facing message. /// Convert the diagnostic into a user-facing message.
fn message(&self) -> String { fn message(&self) -> String {
match self { match self {
Self::IncompletePackage { package, path } => format!( Self::MetadataUnavailable { package, path } => format!(
"The package `{package}` is broken or incomplete (unable to read `METADATA`). Consider recreating the virtualenv, or removing the package directory at: {}.", path.display(), "The package `{package}` is broken or incomplete (unable to read `METADATA`). Consider recreating the virtualenv, or removing the package directory at: {}.", path.display(),
), ),
Self::IncompatiblePythonVersion { Self::IncompatiblePythonVersion {
@ -482,7 +482,7 @@ impl Diagnostic for SitePackagesDiagnostic {
/// Returns `true` if the [`PackageName`] is involved in this diagnostic. /// Returns `true` if the [`PackageName`] is involved in this diagnostic.
fn includes(&self, name: &PackageName) -> bool { fn includes(&self, name: &PackageName) -> bool {
match self { match self {
Self::IncompletePackage { package, .. } => name == package, Self::MetadataUnavailable { package, .. } => name == package,
Self::IncompatiblePythonVersion { package, .. } => name == package, Self::IncompatiblePythonVersion { package, .. } => name == package,
Self::MissingDependency { package, .. } => name == package, Self::MissingDependency { package, .. } => name == package,
Self::IncompatibleDependency { Self::IncompatibleDependency {

View file

@ -24,7 +24,7 @@ use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter
use crate::python_requirement::PythonRequirement; use crate::python_requirement::PythonRequirement;
use crate::resolution::ConflictingDistributionError; use crate::resolution::ConflictingDistributionError;
use crate::resolver::{ use crate::resolver::{
IncompletePackage, ResolverEnvironment, UnavailablePackage, UnavailableReason, MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
}; };
use crate::Options; use crate::Options;
@ -145,7 +145,7 @@ pub struct NoSolutionError {
index_locations: IndexLocations, index_locations: IndexLocations,
index_capabilities: IndexCapabilities, index_capabilities: IndexCapabilities,
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>, unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>, incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
fork_urls: ForkUrls, fork_urls: ForkUrls,
env: ResolverEnvironment, env: ResolverEnvironment,
workspace_members: BTreeSet<PackageName>, workspace_members: BTreeSet<PackageName>,
@ -163,7 +163,7 @@ impl NoSolutionError {
index_locations: IndexLocations, index_locations: IndexLocations,
index_capabilities: IndexCapabilities, index_capabilities: IndexCapabilities,
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>, unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>, incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
fork_urls: ForkUrls, fork_urls: ForkUrls,
env: ResolverEnvironment, env: ResolverEnvironment,
workspace_members: BTreeSet<PackageName>, workspace_members: BTreeSet<PackageName>,

View file

@ -18,7 +18,7 @@ use crate::error::ErrorTree;
use crate::fork_urls::ForkUrls; use crate::fork_urls::ForkUrls;
use crate::prerelease::AllowPrerelease; use crate::prerelease::AllowPrerelease;
use crate::python_requirement::{PythonRequirement, PythonRequirementSource}; use crate::python_requirement::{PythonRequirement, PythonRequirementSource};
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason}; use crate::resolver::{MetadataUnavailable, UnavailablePackage, UnavailableReason};
use crate::{Flexibility, Options, RequiresPython, ResolverEnvironment}; use crate::{Flexibility, Options, RequiresPython, ResolverEnvironment};
use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
@ -548,7 +548,7 @@ impl PubGrubReportFormatter<'_> {
index_capabilities: &IndexCapabilities, index_capabilities: &IndexCapabilities,
available_indexes: &FxHashMap<PackageName, BTreeSet<IndexUrl>>, available_indexes: &FxHashMap<PackageName, BTreeSet<IndexUrl>>,
unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>, unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>, incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
fork_urls: &ForkUrls, fork_urls: &ForkUrls,
env: &ResolverEnvironment, env: &ResolverEnvironment,
workspace_members: &BTreeSet<PackageName>, workspace_members: &BTreeSet<PackageName>,
@ -679,7 +679,7 @@ impl PubGrubReportFormatter<'_> {
index_capabilities: &IndexCapabilities, index_capabilities: &IndexCapabilities,
available_indexes: &FxHashMap<PackageName, BTreeSet<IndexUrl>>, available_indexes: &FxHashMap<PackageName, BTreeSet<IndexUrl>>,
unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>, unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>, incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
hints: &mut IndexSet<PubGrubHint>, hints: &mut IndexSet<PubGrubHint>,
) { ) {
let no_find_links = index_locations.flat_indexes().peekable().peek().is_none(); let no_find_links = index_locations.flat_indexes().peekable().peek().is_none();
@ -694,11 +694,6 @@ impl PubGrubReportFormatter<'_> {
Some(UnavailablePackage::Offline) => { Some(UnavailablePackage::Offline) => {
hints.insert(PubGrubHint::Offline); hints.insert(PubGrubHint::Offline);
} }
Some(UnavailablePackage::MissingMetadata) => {
hints.insert(PubGrubHint::MissingPackageMetadata {
package: package.clone(),
});
}
Some(UnavailablePackage::InvalidMetadata(reason)) => { Some(UnavailablePackage::InvalidMetadata(reason)) => {
hints.insert(PubGrubHint::InvalidPackageMetadata { hints.insert(PubGrubHint::InvalidPackageMetadata {
package: package.clone(), package: package.clone(),
@ -720,37 +715,31 @@ impl PubGrubReportFormatter<'_> {
for (version, incomplete) in versions.iter().rev() { for (version, incomplete) in versions.iter().rev() {
if set.contains(version) { if set.contains(version) {
match incomplete { match incomplete {
IncompletePackage::Offline => { MetadataUnavailable::Offline => {
hints.insert(PubGrubHint::Offline); hints.insert(PubGrubHint::Offline);
} }
IncompletePackage::MissingMetadata => { MetadataUnavailable::InvalidMetadata(reason) => {
hints.insert(PubGrubHint::MissingVersionMetadata {
package: package.clone(),
version: version.clone(),
});
}
IncompletePackage::InvalidMetadata(reason) => {
hints.insert(PubGrubHint::InvalidVersionMetadata { hints.insert(PubGrubHint::InvalidVersionMetadata {
package: package.clone(), package: package.clone(),
version: version.clone(), version: version.clone(),
reason: reason.clone(), reason: reason.to_string(),
}); });
} }
IncompletePackage::InconsistentMetadata(reason) => { MetadataUnavailable::InconsistentMetadata(reason) => {
hints.insert(PubGrubHint::InconsistentVersionMetadata { hints.insert(PubGrubHint::InconsistentVersionMetadata {
package: package.clone(), package: package.clone(),
version: version.clone(), version: version.clone(),
reason: reason.clone(), reason: reason.to_string(),
}); });
} }
IncompletePackage::InvalidStructure(reason) => { MetadataUnavailable::InvalidStructure(reason) => {
hints.insert(PubGrubHint::InvalidVersionStructure { hints.insert(PubGrubHint::InvalidVersionStructure {
package: package.clone(), package: package.clone(),
version: version.clone(), version: version.clone(),
reason: reason.clone(), reason: reason.to_string(),
}); });
} }
IncompletePackage::RequiresPython(requires_python, python_version) => { MetadataUnavailable::RequiresPython(requires_python, python_version) => {
hints.insert(PubGrubHint::IncompatibleBuildRequirement { hints.insert(PubGrubHint::IncompatibleBuildRequirement {
package: package.clone(), package: package.clone(),
version: version.clone(), version: version.clone(),
@ -882,8 +871,6 @@ pub(crate) enum PubGrubHint {
NoIndex, NoIndex,
/// A package was not found in the registry, but network access was disabled. /// A package was not found in the registry, but network access was disabled.
Offline, Offline,
/// Metadata for a package could not be found.
MissingPackageMetadata { package: PubGrubPackage },
/// Metadata for a package could not be parsed. /// Metadata for a package could not be parsed.
InvalidPackageMetadata { InvalidPackageMetadata {
package: PubGrubPackage, package: PubGrubPackage,
@ -896,12 +883,6 @@ pub(crate) enum PubGrubHint {
// excluded from `PartialEq` and `Hash` // excluded from `PartialEq` and `Hash`
reason: String, reason: String,
}, },
/// Metadata for a package version could not be found.
MissingVersionMetadata {
package: PubGrubPackage,
// excluded from `PartialEq` and `Hash`
version: Version,
},
/// Metadata for a package version could not be parsed. /// Metadata for a package version could not be parsed.
InvalidVersionMetadata { InvalidVersionMetadata {
package: PubGrubPackage, package: PubGrubPackage,
@ -992,18 +973,12 @@ enum PubGrubHintCore {
}, },
NoIndex, NoIndex,
Offline, Offline,
MissingPackageMetadata {
package: PubGrubPackage,
},
InvalidPackageMetadata { InvalidPackageMetadata {
package: PubGrubPackage, package: PubGrubPackage,
}, },
InvalidPackageStructure { InvalidPackageStructure {
package: PubGrubPackage, package: PubGrubPackage,
}, },
MissingVersionMetadata {
package: PubGrubPackage,
},
InvalidVersionMetadata { InvalidVersionMetadata {
package: PubGrubPackage, package: PubGrubPackage,
}, },
@ -1052,18 +1027,12 @@ impl From<PubGrubHint> for PubGrubHintCore {
} }
PubGrubHint::NoIndex => Self::NoIndex, PubGrubHint::NoIndex => Self::NoIndex,
PubGrubHint::Offline => Self::Offline, PubGrubHint::Offline => Self::Offline,
PubGrubHint::MissingPackageMetadata { package, .. } => {
Self::MissingPackageMetadata { package }
}
PubGrubHint::InvalidPackageMetadata { package, .. } => { PubGrubHint::InvalidPackageMetadata { package, .. } => {
Self::InvalidPackageMetadata { package } Self::InvalidPackageMetadata { package }
} }
PubGrubHint::InvalidPackageStructure { package, .. } => { PubGrubHint::InvalidPackageStructure { package, .. } => {
Self::InvalidPackageStructure { package } Self::InvalidPackageStructure { package }
} }
PubGrubHint::MissingVersionMetadata { package, .. } => {
Self::MissingVersionMetadata { package }
}
PubGrubHint::InvalidVersionMetadata { package, .. } => { PubGrubHint::InvalidVersionMetadata { package, .. } => {
Self::InvalidVersionMetadata { package } Self::InvalidVersionMetadata { package }
} }
@ -1162,15 +1131,6 @@ impl std::fmt::Display for PubGrubHint {
":".bold(), ":".bold(),
) )
} }
Self::MissingPackageMetadata { package } => {
write!(
f,
"{}{} Metadata for `{}` could not be found, as the wheel is missing a `METADATA` file",
"hint".bold().cyan(),
":".bold(),
package.bold()
)
}
Self::InvalidPackageMetadata { package, reason } => { Self::InvalidPackageMetadata { package, reason } => {
write!( write!(
f, f,
@ -1191,16 +1151,6 @@ impl std::fmt::Display for PubGrubHint {
textwrap::indent(reason, " ") textwrap::indent(reason, " ")
) )
} }
Self::MissingVersionMetadata { package, version } => {
write!(
f,
"{}{} Metadata for `{}` ({}) could not be found, as the wheel is missing a `METADATA` file",
"hint".bold().cyan(),
":".bold(),
package.cyan(),
format!("v{version}").cyan(),
)
}
Self::InvalidVersionMetadata { Self::InvalidVersionMetadata {
package, package,
version, version,

View file

@ -1,5 +1,6 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::resolver::MetadataUnavailable;
use uv_distribution_types::IncompatibleDist; use uv_distribution_types::IncompatibleDist;
use uv_pep440::{Version, VersionSpecifiers}; use uv_pep440::{Version, VersionSpecifiers};
@ -21,17 +22,15 @@ impl Display for UnavailableReason {
} }
} }
/// The package version is unavailable and cannot be used. Unlike [`PackageUnavailable`], this /// The package version is unavailable and cannot be used. Unlike [`MetadataUnavailable`], this
/// applies to a single version of the package. /// applies to a single version of the package.
/// ///
/// Most variant are from [`MetadataResponse`] without the error source (since we don't format /// Most variant are from [`MetadataResponse`] without the error source, since we don't format
/// the source). /// the source and we want to merge unavailable messages across versions.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) enum UnavailableVersion { pub(crate) enum UnavailableVersion {
/// Version is incompatible because it has no usable distributions /// Version is incompatible because it has no usable distributions
IncompatibleDist(IncompatibleDist), IncompatibleDist(IncompatibleDist),
/// The wheel metadata was not found.
MissingMetadata,
/// The wheel metadata was found, but could not be parsed. /// The wheel metadata was found, but could not be parsed.
InvalidMetadata, InvalidMetadata,
/// The wheel metadata was found, but the metadata was inconsistent. /// The wheel metadata was found, but the metadata was inconsistent.
@ -49,7 +48,6 @@ impl UnavailableVersion {
pub(crate) fn message(&self) -> String { pub(crate) fn message(&self) -> String {
match self { match self {
UnavailableVersion::IncompatibleDist(invalid_dist) => format!("{invalid_dist}"), UnavailableVersion::IncompatibleDist(invalid_dist) => format!("{invalid_dist}"),
UnavailableVersion::MissingMetadata => "not include a `METADATA` file".into(),
UnavailableVersion::InvalidMetadata => "invalid metadata".into(), UnavailableVersion::InvalidMetadata => "invalid metadata".into(),
UnavailableVersion::InconsistentMetadata => "inconsistent metadata".into(), UnavailableVersion::InconsistentMetadata => "inconsistent metadata".into(),
UnavailableVersion::InvalidStructure => "an invalid package format".into(), UnavailableVersion::InvalidStructure => "an invalid package format".into(),
@ -63,7 +61,6 @@ impl UnavailableVersion {
pub(crate) fn singular_message(&self) -> String { pub(crate) fn singular_message(&self) -> String {
match self { match self {
UnavailableVersion::IncompatibleDist(invalid_dist) => invalid_dist.singular_message(), UnavailableVersion::IncompatibleDist(invalid_dist) => invalid_dist.singular_message(),
UnavailableVersion::MissingMetadata => format!("does {self}"),
UnavailableVersion::InvalidMetadata => format!("has {self}"), UnavailableVersion::InvalidMetadata => format!("has {self}"),
UnavailableVersion::InconsistentMetadata => format!("has {self}"), UnavailableVersion::InconsistentMetadata => format!("has {self}"),
UnavailableVersion::InvalidStructure => format!("has {self}"), UnavailableVersion::InvalidStructure => format!("has {self}"),
@ -75,7 +72,6 @@ impl UnavailableVersion {
pub(crate) fn plural_message(&self) -> String { pub(crate) fn plural_message(&self) -> String {
match self { match self {
UnavailableVersion::IncompatibleDist(invalid_dist) => invalid_dist.plural_message(), UnavailableVersion::IncompatibleDist(invalid_dist) => invalid_dist.plural_message(),
UnavailableVersion::MissingMetadata => format!("do {self}"),
UnavailableVersion::InvalidMetadata => format!("have {self}"), UnavailableVersion::InvalidMetadata => format!("have {self}"),
UnavailableVersion::InconsistentMetadata => format!("have {self}"), UnavailableVersion::InconsistentMetadata => format!("have {self}"),
UnavailableVersion::InvalidStructure => format!("have {self}"), UnavailableVersion::InvalidStructure => format!("have {self}"),
@ -91,6 +87,22 @@ impl Display for UnavailableVersion {
} }
} }
impl From<&MetadataUnavailable> for UnavailableVersion {
fn from(reason: &MetadataUnavailable) -> Self {
match reason {
MetadataUnavailable::Offline => UnavailableVersion::Offline,
MetadataUnavailable::InvalidMetadata(_) => UnavailableVersion::InvalidMetadata,
MetadataUnavailable::InconsistentMetadata(_) => {
UnavailableVersion::InconsistentMetadata
}
MetadataUnavailable::InvalidStructure(_) => UnavailableVersion::InvalidStructure,
MetadataUnavailable::RequiresPython(requires_python, _python_version) => {
UnavailableVersion::RequiresPython(requires_python.clone())
}
}
}
}
/// The package is unavailable and cannot be used. /// The package is unavailable and cannot be used.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) enum UnavailablePackage { pub(crate) enum UnavailablePackage {
@ -100,8 +112,6 @@ pub(crate) enum UnavailablePackage {
Offline, Offline,
/// The package was not found in the registry. /// The package was not found in the registry.
NotFound, NotFound,
/// The package metadata was not found.
MissingMetadata,
/// The package metadata was found, but could not be parsed. /// The package metadata was found, but could not be parsed.
InvalidMetadata(String), InvalidMetadata(String),
/// The package has an invalid structure. /// The package has an invalid structure.
@ -114,7 +124,6 @@ impl UnavailablePackage {
UnavailablePackage::NoIndex => "not found in the provided package locations", UnavailablePackage::NoIndex => "not found in the provided package locations",
UnavailablePackage::Offline => "not found in the cache", UnavailablePackage::Offline => "not found in the cache",
UnavailablePackage::NotFound => "not found in the package registry", UnavailablePackage::NotFound => "not found in the package registry",
UnavailablePackage::MissingMetadata => "not include a `METADATA` file",
UnavailablePackage::InvalidMetadata(_) => "invalid metadata", UnavailablePackage::InvalidMetadata(_) => "invalid metadata",
UnavailablePackage::InvalidStructure(_) => "an invalid package format", UnavailablePackage::InvalidStructure(_) => "an invalid package format",
} }
@ -125,7 +134,6 @@ impl UnavailablePackage {
UnavailablePackage::NoIndex => format!("was {self}"), UnavailablePackage::NoIndex => format!("was {self}"),
UnavailablePackage::Offline => format!("was {self}"), UnavailablePackage::Offline => format!("was {self}"),
UnavailablePackage::NotFound => format!("was {self}"), UnavailablePackage::NotFound => format!("was {self}"),
UnavailablePackage::MissingMetadata => format!("does {self}"),
UnavailablePackage::InvalidMetadata(_) => format!("has {self}"), UnavailablePackage::InvalidMetadata(_) => format!("has {self}"),
UnavailablePackage::InvalidStructure(_) => format!("has {self}"), UnavailablePackage::InvalidStructure(_) => format!("has {self}"),
} }
@ -138,22 +146,20 @@ impl Display for UnavailablePackage {
} }
} }
/// The package is unavailable at specific versions. impl From<&MetadataUnavailable> for UnavailablePackage {
#[derive(Debug, Clone)] fn from(reason: &MetadataUnavailable) -> Self {
pub(crate) enum IncompletePackage { match reason {
/// Network requests were disabled (i.e., `--offline`), and the wheel metadata was not found in the cache. MetadataUnavailable::Offline => Self::Offline,
Offline, MetadataUnavailable::InvalidMetadata(err) => Self::InvalidMetadata(err.to_string()),
/// The wheel metadata was not found. MetadataUnavailable::InconsistentMetadata(err) => {
MissingMetadata, Self::InvalidMetadata(err.to_string())
/// The wheel metadata was found, but could not be parsed. }
InvalidMetadata(String), MetadataUnavailable::InvalidStructure(err) => Self::InvalidStructure(err.to_string()),
/// The wheel metadata was found, but the metadata was inconsistent. MetadataUnavailable::RequiresPython(..) => {
InconsistentMetadata(String), unreachable!("`requires-python` is only known upfront for registry distributions")
/// The wheel has an invalid structure. }
InvalidStructure(String), }
/// The source distribution has a `requires-python` requirement that is not met by the installed }
/// Python version (and static metadata is not available).
RequiresPython(VersionSpecifiers, Version),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -55,7 +55,7 @@ use crate::python_requirement::PythonRequirement;
use crate::resolution::ResolverOutput; use crate::resolution::ResolverOutput;
use crate::resolution_mode::ResolutionStrategy; use crate::resolution_mode::ResolutionStrategy;
pub(crate) use crate::resolver::availability::{ pub(crate) use crate::resolver::availability::{
IncompletePackage, ResolverVersion, UnavailablePackage, UnavailableReason, UnavailableVersion, ResolverVersion, UnavailablePackage, UnavailableReason, UnavailableVersion,
}; };
use crate::resolver::batch_prefetch::BatchPrefetcher; use crate::resolver::batch_prefetch::BatchPrefetcher;
pub use crate::resolver::derivation::DerivationChainBuilder; pub use crate::resolver::derivation::DerivationChainBuilder;
@ -64,6 +64,7 @@ pub use crate::resolver::environment::ResolverEnvironment;
pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet}; pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
pub(crate) use crate::resolver::urls::Urls; pub(crate) use crate::resolver::urls::Urls;
use crate::universal_marker::{ConflictMarker, UniversalMarker}; use crate::universal_marker::{ConflictMarker, UniversalMarker};
pub(crate) use provider::MetadataUnavailable;
pub use crate::resolver::index::InMemoryIndex; pub use crate::resolver::index::InMemoryIndex;
use crate::resolver::indexes::Indexes; use crate::resolver::indexes::Indexes;
@ -118,7 +119,7 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
/// Incompatibilities for packages that are entirely unavailable. /// Incompatibilities for packages that are entirely unavailable.
unavailable_packages: DashMap<PackageName, UnavailablePackage>, unavailable_packages: DashMap<PackageName, UnavailablePackage>,
/// Incompatibilities for packages that are unavailable at specific versions. /// Incompatibilities for packages that are unavailable at specific versions.
incomplete_packages: DashMap<PackageName, DashMap<Version, IncompletePackage>>, incomplete_packages: DashMap<PackageName, DashMap<Version, MetadataUnavailable>>,
/// The options that were used to configure this resolver. /// The options that were used to configure this resolver.
options: Options, options: Options,
/// The reporter to use for this resolver. /// The reporter to use for this resolver.
@ -354,6 +355,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
state.priorities.get(&state.pubgrub.package_store[id]) state.priorities.get(&state.pubgrub.package_store[id])
}) })
else { else {
// All packages have been assigned, the fork has been successfully resolved
if tracing::enabled!(Level::DEBUG) { if tracing::enabled!(Level::DEBUG) {
prefetcher.log_tried_versions(); prefetcher.log_tried_versions();
} }
@ -919,40 +921,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// If we failed to fetch the metadata for a URL, we can't proceed. // If we failed to fetch the metadata for a URL, we can't proceed.
let metadata = match &*response { let metadata = match &*response {
MetadataResponse::Found(archive) => &archive.metadata, MetadataResponse::Found(archive) => &archive.metadata,
MetadataResponse::Offline => { MetadataResponse::Unavailable(reason) => {
self.unavailable_packages self.unavailable_packages
.insert(name.clone(), UnavailablePackage::Offline); .insert(name.clone(), reason.into());
return Ok(None); return Ok(None);
} }
MetadataResponse::MissingMetadata => {
self.unavailable_packages
.insert(name.clone(), UnavailablePackage::MissingMetadata);
return Ok(None);
}
MetadataResponse::InvalidMetadata(err) => {
self.unavailable_packages.insert(
name.clone(),
UnavailablePackage::InvalidMetadata(err.to_string()),
);
return Ok(None);
}
MetadataResponse::InconsistentMetadata(err) => {
self.unavailable_packages.insert(
name.clone(),
UnavailablePackage::InvalidMetadata(err.to_string()),
);
return Ok(None);
}
MetadataResponse::InvalidStructure(err) => {
self.unavailable_packages.insert(
name.clone(),
UnavailablePackage::InvalidStructure(err.to_string()),
);
return Ok(None);
}
MetadataResponse::RequiresPython(..) => {
unreachable!("`requires-python` is only known upfront for registry distributions")
}
MetadataResponse::Error(dist, err) => { MetadataResponse::Error(dist, err) => {
// TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't // TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't
// critical since we fetch URL dependencies _prior_ to invoking the resolver. // critical since we fetch URL dependencies _prior_ to invoking the resolver.
@ -1318,82 +1291,20 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
let metadata = match &*response { let metadata = match &*response {
MetadataResponse::Found(archive) => &archive.metadata, MetadataResponse::Found(archive) => &archive.metadata,
MetadataResponse::Offline => { MetadataResponse::Unavailable(reason) => {
let unavailable_version = UnavailableVersion::from(reason);
let message = unavailable_version.singular_message();
if let Some(err) = reason.source() {
// Show the detailed error for metadata parse errors.
warn!("{name} {message}: {err}");
} else {
warn!("{name} {message}");
}
self.incomplete_packages self.incomplete_packages
.entry(name.clone()) .entry(name.clone())
.or_default() .or_default()
.insert(version.clone(), IncompletePackage::Offline); .insert(version.clone(), reason.clone());
return Ok(Dependencies::Unavailable(UnavailableVersion::Offline)); return Ok(Dependencies::Unavailable(unavailable_version));
}
MetadataResponse::MissingMetadata => {
self.incomplete_packages
.entry(name.clone())
.or_default()
.insert(version.clone(), IncompletePackage::MissingMetadata);
return Ok(Dependencies::Unavailable(
UnavailableVersion::MissingMetadata,
));
}
MetadataResponse::InvalidMetadata(err) => {
warn!("Unable to extract metadata for {name}: {err}");
self.incomplete_packages
.entry(name.clone())
.or_default()
.insert(
version.clone(),
IncompletePackage::InvalidMetadata(err.to_string()),
);
return Ok(Dependencies::Unavailable(
UnavailableVersion::InvalidMetadata,
));
}
MetadataResponse::InconsistentMetadata(err) => {
warn!("Unable to extract metadata for {name}: {err}");
self.incomplete_packages
.entry(name.clone())
.or_default()
.insert(
version.clone(),
IncompletePackage::InconsistentMetadata(err.to_string()),
);
return Ok(Dependencies::Unavailable(
UnavailableVersion::InconsistentMetadata,
));
}
MetadataResponse::InvalidStructure(err) => {
warn!("Unable to extract metadata for {name}: {err}");
self.incomplete_packages
.entry(name.clone())
.or_default()
.insert(
version.clone(),
IncompletePackage::InvalidStructure(err.to_string()),
);
return Ok(Dependencies::Unavailable(
UnavailableVersion::InvalidStructure,
));
}
MetadataResponse::RequiresPython(requires_python, python_version) => {
warn!(
"Unable to extract metadata for {name}: {}",
uv_distribution::Error::RequiresPython(
requires_python.clone(),
python_version.clone()
)
);
self.incomplete_packages
.entry(name.clone())
.or_default()
.insert(
version.clone(),
IncompletePackage::RequiresPython(
requires_python.clone(),
python_version.clone(),
),
);
return Ok(Dependencies::Unavailable(
UnavailableVersion::RequiresPython(requires_python.clone()),
));
} }
MetadataResponse::Error(dist, err) => { MetadataResponse::Error(dist, err) => {
let chain = DerivationChainBuilder::from_state(id, version, pubgrub) let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
@ -1856,37 +1767,20 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
))), ))),
); );
} }
Some(Response::Dist { Some(Response::Dist { dist, metadata }) => {
dist: Dist::Built(dist), let dist_kind = match dist {
metadata, Dist::Built(_) => "built",
}) => { Dist::Source(_) => "source",
trace!("Received built distribution metadata for: {dist}"); };
match &metadata { trace!("Received {dist_kind} distribution metadata for: {dist}");
MetadataResponse::InvalidMetadata(err) => { if let MetadataResponse::Unavailable(reason) = &metadata {
warn!("Unable to extract metadata for {dist}: {err}"); let message = UnavailableVersion::from(reason).singular_message();
if let Some(err) = reason.source() {
// Show the detailed error for metadata parse errors.
warn!("{dist} {message}: {err}");
} else {
warn!("{dist} {message}");
} }
MetadataResponse::InvalidStructure(err) => {
warn!("Unable to extract metadata for {dist}: {err}");
}
_ => {}
}
self.index
.distributions()
.done(dist.version_id(), Arc::new(metadata));
}
Some(Response::Dist {
dist: Dist::Source(dist),
metadata,
}) => {
trace!("Received source distribution metadata for: {dist}");
match &metadata {
MetadataResponse::InvalidMetadata(err) => {
warn!("Unable to extract metadata for {dist}: {err}");
}
MetadataResponse::InvalidStructure(err) => {
warn!("Unable to extract metadata for {dist}: {err}");
}
_ => {}
} }
self.index self.index
.distributions() .distributions()

View file

@ -34,21 +34,43 @@ pub enum VersionsResponse {
pub enum MetadataResponse { pub enum MetadataResponse {
/// The wheel metadata was found and parsed successfully. /// The wheel metadata was found and parsed successfully.
Found(ArchiveMetadata), Found(ArchiveMetadata),
/// The wheel metadata was not found. /// A non-fatal error.
MissingMetadata, Unavailable(MetadataUnavailable),
/// The wheel metadata was found, but could not be parsed. /// The distribution could not be built or downloaded, a fatal error.
InvalidMetadata(Box<uv_pypi_types::MetadataError>), Error(Box<Dist>, Arc<uv_distribution::Error>),
/// The wheel metadata was found, but the metadata was inconsistent. }
InconsistentMetadata(Box<uv_distribution::Error>),
/// The wheel has an invalid structure. /// Non-fatal metadata fetching error.
InvalidStructure(Box<uv_metadata::Error>), ///
/// This is also the unavailability reasons for a package, while version unavailability is separate
/// in [`UnavailableVersion`].
#[derive(Debug, Clone)]
pub enum MetadataUnavailable {
/// The wheel metadata was not found in the cache and the network is not available. /// The wheel metadata was not found in the cache and the network is not available.
Offline, Offline,
/// The wheel metadata was found, but could not be parsed.
InvalidMetadata(Arc<uv_pypi_types::MetadataError>),
/// The wheel metadata was found, but the metadata was inconsistent.
InconsistentMetadata(Arc<uv_distribution::Error>),
/// The wheel has an invalid structure.
InvalidStructure(Arc<uv_metadata::Error>),
/// The source distribution has a `requires-python` requirement that is not met by the installed /// The source distribution has a `requires-python` requirement that is not met by the installed
/// Python version (and static metadata is not available). /// Python version (and static metadata is not available).
RequiresPython(VersionSpecifiers, Version), RequiresPython(VersionSpecifiers, Version),
/// The distribution could not be built or downloaded. }
Error(Box<Dist>, Arc<uv_distribution::Error>),
impl MetadataUnavailable {
/// Like [`std::error::Error::source`], but we don't want to derive the std error since our
/// formatting system is more custom.
pub(crate) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
MetadataUnavailable::Offline => None,
MetadataUnavailable::InvalidMetadata(err) => Some(err),
MetadataUnavailable::InconsistentMetadata(err) => Some(err),
MetadataUnavailable::InvalidStructure(err) => Some(err),
MetadataUnavailable::RequiresPython(_, _) => None,
}
}
} }
pub trait ResolverProvider { pub trait ResolverProvider {
@ -189,29 +211,39 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a,
Ok(metadata) => Ok(MetadataResponse::Found(metadata)), Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
Err(err) => match err { Err(err) => match err {
uv_distribution::Error::Client(client) => match client.into_kind() { uv_distribution::Error::Client(client) => match client.into_kind() {
uv_client::ErrorKind::Offline(_) => Ok(MetadataResponse::Offline), uv_client::ErrorKind::Offline(_) => {
Ok(MetadataResponse::Unavailable(MetadataUnavailable::Offline))
}
uv_client::ErrorKind::MetadataParseError(_, _, err) => { uv_client::ErrorKind::MetadataParseError(_, _, err) => {
Ok(MetadataResponse::InvalidMetadata(err)) Ok(MetadataResponse::Unavailable(
} MetadataUnavailable::InvalidMetadata(Arc::new(*err)),
uv_client::ErrorKind::Metadata(_, err) => { ))
Ok(MetadataResponse::InvalidStructure(Box::new(err)))
} }
uv_client::ErrorKind::Metadata(_, err) => Ok(MetadataResponse::Unavailable(
MetadataUnavailable::InvalidStructure(Arc::new(err)),
)),
kind => Err(uv_client::Error::from(kind).into()), kind => Err(uv_client::Error::from(kind).into()),
}, },
uv_distribution::Error::WheelMetadataVersionMismatch { .. } => { uv_distribution::Error::WheelMetadataVersionMismatch { .. } => {
Ok(MetadataResponse::InconsistentMetadata(Box::new(err))) Ok(MetadataResponse::Unavailable(
MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
))
} }
uv_distribution::Error::WheelMetadataNameMismatch { .. } => { uv_distribution::Error::WheelMetadataNameMismatch { .. } => {
Ok(MetadataResponse::InconsistentMetadata(Box::new(err))) Ok(MetadataResponse::Unavailable(
} MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
uv_distribution::Error::Metadata(err) => { ))
Ok(MetadataResponse::InvalidMetadata(Box::new(err)))
}
uv_distribution::Error::WheelMetadata(_, err) => {
Ok(MetadataResponse::InvalidStructure(err))
} }
uv_distribution::Error::Metadata(err) => Ok(MetadataResponse::Unavailable(
MetadataUnavailable::InvalidMetadata(Arc::new(err)),
)),
uv_distribution::Error::WheelMetadata(_, err) => Ok(MetadataResponse::Unavailable(
MetadataUnavailable::InvalidStructure(Arc::new(*err)),
)),
uv_distribution::Error::RequiresPython(requires_python, version) => { uv_distribution::Error::RequiresPython(requires_python, version) => {
Ok(MetadataResponse::RequiresPython(requires_python, version)) Ok(MetadataResponse::Unavailable(
MetadataUnavailable::RequiresPython(requires_python, version),
))
} }
err => Ok(MetadataResponse::Error( err => Ok(MetadataResponse::Error(
Box::new(dist.clone()), Box::new(dist.clone()),