From 1ad6aa8a237e453dfa5988144361c3a0ffe8dce6 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 8 May 2024 10:40:15 +0200 Subject: [PATCH] Use generic pubgrub incompatibility reason (#3335) Pubgrub got a new feature where all unavailability is a custom, instead of the reasonless `UnavailableDependencies` and our custom `String` type previously (https://github.com/pubgrub-rs/pubgrub/pull/208). This PR introduces a `UnavailableReason` that tracks either an entire version being unusable, or a specific version. The error messages now also track this difference properly. The pubgrub commit is our main rebased onto the merged https://github.com/pubgrub-rs/pubgrub/pull/208, i'll push `konsti/main-rebase-generic-reason` to `main` after checking for rebase problems. --- Cargo.lock | 2 +- Cargo.toml | 2 +- .../src/prioritized_distribution.rs | 38 ++-- crates/uv-resolver/src/dependency_provider.rs | 5 +- crates/uv-resolver/src/error.rs | 9 +- crates/uv-resolver/src/pubgrub/report.rs | 70 +++--- crates/uv-resolver/src/resolver/mod.rs | 202 +++++++++++------- crates/uv/tests/pip_compile.rs | 10 +- crates/uv/tests/pip_install.rs | 8 +- crates/uv/tests/pip_install_scenarios.rs | 24 +-- crates/uv/tests/pip_sync.rs | 10 +- 11 files changed, 215 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a95a13504..6c6e8f175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2833,7 +2833,7 @@ dependencies = [ [[package]] name = "pubgrub" version = "0.2.1" -source = "git+https://github.com/astral-sh/pubgrub?rev=c26e485213e39582c6f2e4d45c0328422670e7a7#c26e485213e39582c6f2e4d45c0328422670e7a7" +source = "git+https://github.com/astral-sh/pubgrub?rev=0e684a874c9fb8f74738cd8875524c80e3d4820b#0e684a874c9fb8f74738cd8875524c80e3d4820b" dependencies = [ "indexmap", "log", diff --git a/Cargo.toml b/Cargo.toml index b4b317cce..93f1d8aca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ path-absolutize = { version = "3.1.1" } pathdiff = { version = "0.2.1" } petgraph = { version = "0.6.4" } platform-info = { version = "2.0.2" } -pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "c26e485213e39582c6f2e4d45c0328422670e7a7" } +pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "0e684a874c9fb8f74738cd8875524c80e3d4820b" } pyo3 = { version = "0.21.0" } pyo3-log = { version = "0.10.0" } rand = { version = "0.8.5" } diff --git a/crates/distribution-types/src/prioritized_distribution.rs b/crates/distribution-types/src/prioritized_distribution.rs index 0a13a4b28..fb758d025 100644 --- a/crates/distribution-types/src/prioritized_distribution.rs +++ b/crates/distribution-types/src/prioritized_distribution.rs @@ -53,59 +53,59 @@ impl Display for IncompatibleDist { match self { Self::Wheel(incompatibility) => match incompatibility { IncompatibleWheel::NoBinary => { - f.write_str("no source distribution is available and using wheels is disabled") + f.write_str("has no available source distribution and using wheels is disabled") } IncompatibleWheel::Tag(tag) => match tag { IncompatibleTag::Invalid => { - f.write_str("no wheels are available with valid tags") - } - IncompatibleTag::Python => { - f.write_str("no wheels are available with a matching Python implementation") + f.write_str("has no wheels are available with valid tags") } + IncompatibleTag::Python => f.write_str( + "has no wheels are available with a matching Python implementation", + ), IncompatibleTag::Abi => { - f.write_str("no wheels are available with a matching Python ABI") + f.write_str("has no wheels are available with a matching Python ABI") } IncompatibleTag::Platform => { - f.write_str("no wheels are available with a matching platform") + f.write_str("has no wheels are available with a matching platform") } }, IncompatibleWheel::Yanked(yanked) => match yanked { - Yanked::Bool(_) => f.write_str("it was yanked"), + Yanked::Bool(_) => f.write_str("was yanked"), Yanked::Reason(reason) => write!( f, - "it was yanked (reason: {})", + "was yanked (reason: {})", reason.trim().trim_end_matches('.') ), }, IncompatibleWheel::ExcludeNewer(ts) => match ts { - Some(_) => f.write_str("it was published after the exclude newer time"), - None => f.write_str("it has no publish time"), + Some(_) => f.write_str("was published after the exclude newer time"), + None => f.write_str("has no publish time"), }, IncompatibleWheel::RequiresPython(python) => { - write!(f, "it requires at python {python}") + write!(f, "requires at python {python}") } }, Self::Source(incompatibility) => match incompatibility { IncompatibleSource::NoBuild => { - f.write_str("no wheels are usable and building from source is disabled") + f.write_str("has no usable wheels and building from source is disabled") } IncompatibleSource::Yanked(yanked) => match yanked { - Yanked::Bool(_) => f.write_str("it was yanked"), + Yanked::Bool(_) => f.write_str("was yanked"), Yanked::Reason(reason) => write!( f, - "it was yanked (reason: {})", + "was yanked (reason: {})", reason.trim().trim_end_matches('.') ), }, IncompatibleSource::ExcludeNewer(ts) => match ts { - Some(_) => f.write_str("it was published after the exclude newer time"), - None => f.write_str("it has no publish time"), + Some(_) => f.write_str("was published after the exclude newer time"), + None => f.write_str("has no publish time"), }, IncompatibleSource::RequiresPython(python) => { - write!(f, "it requires python {python}") + write!(f, "requires python {python}") } }, - Self::Unavailable => f.write_str("no distributions are available"), + Self::Unavailable => f.write_str("has no available distributions"), } } } diff --git a/crates/uv-resolver/src/dependency_provider.rs b/crates/uv-resolver/src/dependency_provider.rs index 148eca5fe..448c966f4 100644 --- a/crates/uv-resolver/src/dependency_provider.rs +++ b/crates/uv-resolver/src/dependency_provider.rs @@ -6,6 +6,7 @@ use pubgrub::solver::{Dependencies, DependencyProvider}; use pep440_rs::Version; use crate::pubgrub::{PubGrubPackage, PubGrubPriority}; +use crate::resolver::UnavailableReason; /// We don't use a dependency provider, we interact with state directly, but we still need this one /// for type @@ -15,6 +16,8 @@ impl DependencyProvider for UvDependencyProvider { type P = PubGrubPackage; type V = Version; type VS = Range; + type M = UnavailableReason; + fn prioritize(&self, _package: &Self::P, _range: &Self::VS) -> Self::Priority { unimplemented!() } @@ -34,7 +37,7 @@ impl DependencyProvider for UvDependencyProvider { &self, _package: &Self::P, _version: &Self::V, - ) -> Result>, Self::Err> { + ) -> Result, Self::M>, Self::Err> { unimplemented!() } } diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 6acafedd5..daf4a57c4 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -23,7 +23,8 @@ use crate::dependency_provider::UvDependencyProvider; use crate::pubgrub::{PubGrubPackage, PubGrubPython, PubGrubReportFormatter}; use crate::python_requirement::PythonRequirement; use crate::resolver::{ - IncompletePackage, SharedMap, SharedSet, UnavailablePackage, VersionsResponse, + IncompletePackage, SharedMap, SharedSet, UnavailablePackage, UnavailableReason, + VersionsResponse, }; #[derive(Debug, thiserror::Error)] @@ -119,7 +120,9 @@ impl From> for ResolveError { /// Given a [`DerivationTree`], collapse any [`External::FromDependencyOf`] incompatibilities /// wrap an [`PubGrubPackage::Extra`] package. -fn collapse_extra_proxies(derivation_tree: &mut DerivationTree>) { +fn collapse_extra_proxies( + derivation_tree: &mut DerivationTree, UnavailableReason>, +) { match derivation_tree { DerivationTree::External(_) => {} DerivationTree::Derived(derived) => { @@ -193,7 +196,7 @@ impl From> for ResolveError { /// A wrapper around [`pubgrub::error::PubGrubError::NoSolution`] that displays a resolution failure report. #[derive(Debug)] pub struct NoSolutionError { - derivation_tree: DerivationTree>, + derivation_tree: DerivationTree, UnavailableReason>, available_versions: IndexMap>, selector: Option, python_requirement: Option, diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 10f7f69f8..b5acf8dd0 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -17,7 +17,7 @@ use uv_normalize::PackageName; use crate::candidate_selector::CandidateSelector; use crate::python_requirement::PythonRequirement; -use crate::resolver::{IncompletePackage, UnavailablePackage}; +use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason}; use super::PubGrubPackage; @@ -30,15 +30,20 @@ pub(crate) struct PubGrubReportFormatter<'a> { pub(crate) python_requirement: Option<&'a PythonRequirement>, } -impl ReportFormatter> for PubGrubReportFormatter<'_> { +impl ReportFormatter, UnavailableReason> + for PubGrubReportFormatter<'_> +{ type Output = String; - fn format_external(&self, external: &External>) -> Self::Output { + fn format_external( + &self, + external: &External, UnavailableReason>, + ) -> Self::Output { match external { External::NotRoot(package, version) => { format!("we are solving dependencies of {package} {version}") } - External::NoVersions(package, set, reason) => { + External::NoVersions(package, set) => { if matches!(package, PubGrubPackage::Python(_)) { if let Some(python) = self.python_requirement { if python.target() == python.installed() { @@ -79,16 +84,6 @@ impl ReportFormatter> for PubGrubReportFormatter< } let set = self.simplify_set(set, package); - // Check for a reason - if let Some(reason) = reason { - let formatted = if set.as_ref() == &Range::full() { - format!("{package} {reason}") - } else { - format!("{package}{set} {reason}") - }; - return formatted; - } - if set.as_ref() == &Range::full() { format!("there are no versions of {package}") } else if set.as_singleton().is_some() { @@ -112,17 +107,26 @@ impl ReportFormatter> for PubGrubReportFormatter< } } } - External::Unavailable(package, set, reason) => match package { + External::Custom(package, set, reason) => match package { PubGrubPackage::Root(Some(name)) => { format!("{name} cannot be used because {reason}") } PubGrubPackage::Root(None) => { format!("your requirements cannot be used because {reason}") } - _ => format!( - "{}is unusable because {reason}", - Padded::new("", &PackageRange::compatibility(package, set), " ") - ), + _ => match reason { + UnavailableReason::Package(reason) => { + // While there may be a term attached, this error applies to the entire + // package, so we show it for the entire package + format!("{}{reason}", Padded::new("", &package, " ")) + } + UnavailableReason::Version(reason) => { + format!( + "{}{reason}", + Padded::new("", &PackageRange::compatibility(package, set), " ") + ) + } + }, }, External::FromDependencyOf(package, package_set, dependency, dependency_set) => { let package_set = self.simplify_set(package_set, package); @@ -198,8 +202,8 @@ impl ReportFormatter> for PubGrubReportFormatter< /// Simplest case, we just combine two external incompatibilities. fn explain_both_external( &self, - external1: &External>, - external2: &External>, + external1: &External, UnavailableReason>, + external2: &External, UnavailableReason>, current_terms: &Map>>, ) -> String { let external = self.format_both_external(external1, external2); @@ -216,9 +220,9 @@ impl ReportFormatter> for PubGrubReportFormatter< fn explain_both_ref( &self, ref_id1: usize, - derived1: &Derived>, + derived1: &Derived, UnavailableReason>, ref_id2: usize, - derived2: &Derived>, + derived2: &Derived, UnavailableReason>, current_terms: &Map>>, ) -> String { // TODO: order should be chosen to make it more logical. @@ -243,8 +247,8 @@ impl ReportFormatter> for PubGrubReportFormatter< fn explain_ref_and_external( &self, ref_id: usize, - derived: &Derived>, - external: &External>, + derived: &Derived, UnavailableReason>, + external: &External, UnavailableReason>, current_terms: &Map>>, ) -> String { // TODO: order should be chosen to make it more logical. @@ -265,7 +269,7 @@ impl ReportFormatter> for PubGrubReportFormatter< /// Add an external cause to the chain of explanations. fn and_explain_external( &self, - external: &External>, + external: &External, UnavailableReason>, current_terms: &Map>>, ) -> String { let external = self.format_external(external); @@ -282,7 +286,7 @@ impl ReportFormatter> for PubGrubReportFormatter< fn and_explain_ref( &self, ref_id: usize, - derived: &Derived>, + derived: &Derived, UnavailableReason>, current_terms: &Map>>, ) -> String { let derived = self.format_terms(&derived.terms); @@ -299,8 +303,8 @@ impl ReportFormatter> for PubGrubReportFormatter< /// Add an already explained incompat to the chain of explanations. fn and_explain_prior_and_external( &self, - prior_external: &External>, - external: &External>, + prior_external: &External, UnavailableReason>, + external: &External, UnavailableReason>, current_terms: &Map>>, ) -> String { let external = self.format_both_external(prior_external, external); @@ -318,8 +322,8 @@ impl PubGrubReportFormatter<'_> { /// Format two external incompatibilities, combining them if possible. fn format_both_external( &self, - external1: &External>, - external2: &External>, + external1: &External, UnavailableReason>, + external2: &External, UnavailableReason>, ) -> String { match (external1, external2) { ( @@ -387,7 +391,7 @@ impl PubGrubReportFormatter<'_> { /// their requirements. pub(crate) fn hints( &self, - derivation_tree: &DerivationTree>, + derivation_tree: &DerivationTree, UnavailableReason>, selector: &Option, index_locations: &Option, unavailable_packages: &FxHashMap, @@ -404,7 +408,7 @@ impl PubGrubReportFormatter<'_> { let mut hints = IndexSet::default(); match derivation_tree { DerivationTree::External(external) => match external { - External::Unavailable(package, set, _) | External::NoVersions(package, set, _) => { + External::Custom(package, set, _) | External::NoVersions(package, set) => { // Check for no versions due to pre-release options if let Some(selector) = selector { let any_prerelease = set.iter().any(|(start, end)| { diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 2a7da763e..e3c95ec76 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -66,16 +66,62 @@ mod provider; mod reporter; mod urls; -/// The package version is unavailable and cannot be used -/// Unlike [`PackageUnavailable`] this applies to a single version of the package -#[derive(Debug, Clone)] +/// The reason why a package or a version cannot be used. +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) enum UnavailableReason { + /// The entire package cannot be used. + Package(UnavailablePackage), + /// A single version cannot be used. + Version(UnavailableVersion), +} + +impl Display for UnavailableReason { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Version(version) => Display::fmt(version, f), + Self::Package(package) => Display::fmt(package, f), + } + } +} + +/// The package version is unavailable and cannot be used. Unlike [`PackageUnavailable`], this +/// applies to a single version of the package. +/// +/// Most variant are from [`MetadataResponse`] without the error source (since we don't format +/// the source). +#[derive(Debug, Clone, Eq, PartialEq)] pub(crate) enum UnavailableVersion { /// Version is incompatible because it has no usable distributions IncompatibleDist(IncompatibleDist), + /// The wheel metadata was found, but could not be parsed. + InvalidMetadata, + /// The wheel metadata was found, but the metadata was inconsistent. + InconsistentMetadata, + /// The wheel has an invalid structure. + InvalidStructure, + /// The wheel metadata was not found in the cache and the network is not available. + Offline, + /// Forward any kind of resolver error. + ResolverError(String), +} + +impl Display for UnavailableVersion { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + UnavailableVersion::IncompatibleDist(invalid_dist) => Display::fmt(invalid_dist, f), + UnavailableVersion::InvalidMetadata => f.write_str("has invalid metadata"), + UnavailableVersion::InconsistentMetadata => f.write_str("has inconsistent metadata"), + UnavailableVersion::InvalidStructure => f.write_str("has an invalid package format"), + UnavailableVersion::Offline => f.write_str( + "network connectivity is disabled, but the metadata wasn't found in the cache", + ), + UnavailableVersion::ResolverError(err) => f.write_str(err), + } + } } /// The package is unavailable and cannot be used. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub(crate) enum UnavailablePackage { /// Index lookups were disabled (i.e., `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`). NoIndex, @@ -89,6 +135,24 @@ pub(crate) enum UnavailablePackage { InvalidStructure(String), } +impl UnavailablePackage { + pub(crate) fn as_str(&self) -> &'static str { + match self { + UnavailablePackage::NoIndex => "was not found in the provided package locations", + UnavailablePackage::Offline => "was not found in the cache", + UnavailablePackage::NotFound => "was not found in the package registry", + UnavailablePackage::InvalidMetadata(_) => "has invalid metadata", + UnavailablePackage::InvalidStructure(_) => "has an invalid package format", + } + } +} + +impl Display for UnavailablePackage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + /// The package is unavailable at specific versions. #[derive(Debug, Clone)] pub(crate) enum IncompletePackage { @@ -359,84 +423,61 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide .term_intersection_for_package(&next) .expect("a package was chosen but we don't have a term."); - let reason = { - if let PubGrubPackage::Package(ref package_name, _, _) = next { - // Check if the decision was due to the package being unavailable - self.unavailable_packages - .borrow() - .get(package_name) - .map(|entry| match *entry { - UnavailablePackage::NoIndex => { - "was not found in the provided package locations" - } - UnavailablePackage::Offline => "was not found in the cache", - UnavailablePackage::NotFound => { - "was not found in the package registry" - } - UnavailablePackage::InvalidMetadata(_) => { - "was found, but the metadata could not be parsed" - } - UnavailablePackage::InvalidStructure(_) => { - "was found, but has an invalid format" - } - }) - } else { - None + // Check if the decision was due to the package being unavailable + if let PubGrubPackage::Package(ref package_name, _, _) = next { + if let Some(entry) = self.unavailable_packages.borrow().get(package_name) { + state.add_incompatibility(Incompatibility::custom_term( + next.clone(), + term_intersection.clone(), + UnavailableReason::Package(entry.clone()), + )); + continue; } - }; + } - let inc = Incompatibility::no_versions( + state.add_incompatibility(Incompatibility::no_versions( next.clone(), term_intersection.clone(), - reason.map(ToString::to_string), - ); - - state.add_incompatibility(inc); + )); continue; } Some(version) => version, }; let version = match version { ResolverVersion::Available(version) => version, - ResolverVersion::Unavailable(version, unavailable) => { - let reason = match unavailable { - // Incompatible requires-python versions are special in that we track - // them as incompatible dependencies instead of marking the package version - // as unavailable directly - UnavailableVersion::IncompatibleDist( - IncompatibleDist::Source(IncompatibleSource::RequiresPython( - requires_python, - )) - | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython( - requires_python, - )), - ) => { - let python_version = requires_python - .iter() - .map(PubGrubSpecifier::try_from) - .fold_ok(Range::full(), |range, specifier| { - range.intersection(&specifier.into()) - })?; + ResolverVersion::Unavailable(version, reason) => { + // Incompatible requires-python versions are special in that we track + // them as incompatible dependencies instead of marking the package version + // as unavailable directly + if let UnavailableVersion::IncompatibleDist( + IncompatibleDist::Source(IncompatibleSource::RequiresPython( + requires_python, + )) + | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python)), + ) = reason + { + let python_version = requires_python + .iter() + .map(PubGrubSpecifier::try_from) + .fold_ok(Range::full(), |range, specifier| { + range.intersection(&specifier.into()) + })?; - let package = &next; - for kind in [PubGrubPython::Installed, PubGrubPython::Target] { - state.add_incompatibility(Incompatibility::from_dependency( - package.clone(), - Range::singleton(version.clone()), - (PubGrubPackage::Python(kind), python_version.clone()), - )); - } - state.partial_solution.add_decision(next.clone(), version); - continue; - } - UnavailableVersion::IncompatibleDist(incompatibility) => { - incompatibility.to_string() + let package = &next; + for kind in [PubGrubPython::Installed, PubGrubPython::Target] { + state.add_incompatibility(Incompatibility::from_dependency( + package.clone(), + Range::singleton(version.clone()), + (PubGrubPackage::Python(kind), python_version.clone()), + )); } + state.partial_solution.add_decision(next.clone(), version); + continue; }; - state.add_incompatibility(Incompatibility::unavailable( + state.add_incompatibility(Incompatibility::custom_version( next.clone(), version.clone(), - reason, + UnavailableReason::Version(reason), )); continue; } @@ -467,10 +508,10 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide .await? { Dependencies::Unavailable(reason) => { - state.add_incompatibility(Incompatibility::unavailable( + state.add_incompatibility(Incompatibility::custom_version( package.clone(), version.clone(), - reason.clone(), + UnavailableReason::Version(reason), )); continue; } @@ -816,7 +857,9 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide let mut constraints = match constraints { Ok(constraints) => constraints, Err(err) => { - return Ok(Dependencies::Unavailable(uncapitalize(err.to_string()))); + return Ok(Dependencies::Unavailable( + UnavailableVersion::ResolverError(uncapitalize(err.to_string())), + )); } }; @@ -939,9 +982,9 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide false, "Dependencies were requested for a package that is not available" ); - return Ok(Dependencies::Unavailable( - "The package is unavailable".to_string(), - )); + return Err(ResolveError::Failure(format!( + "The package is unavailable: {package_name}" + ))); } // Wait for the metadata to be available. @@ -962,10 +1005,7 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide .or_default() .borrow_mut() .insert(version.clone(), IncompletePackage::Offline); - return Ok(Dependencies::Unavailable( - "network connectivity is disabled, but the metadata wasn't found in the cache" - .to_string(), - )); + return Ok(Dependencies::Unavailable(UnavailableVersion::Offline)); } MetadataResponse::InvalidMetadata(err) => { warn!("Unable to extract metadata for {package_name}: {err}"); @@ -979,7 +1019,7 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide IncompletePackage::InvalidMetadata(err.to_string()), ); return Ok(Dependencies::Unavailable( - "the package metadata could not be parsed".to_string(), + UnavailableVersion::InvalidMetadata, )); } MetadataResponse::InconsistentMetadata(err) => { @@ -994,7 +1034,7 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide IncompletePackage::InconsistentMetadata(err.to_string()), ); return Ok(Dependencies::Unavailable( - "the package metadata was inconsistent".to_string(), + UnavailableVersion::InconsistentMetadata, )); } MetadataResponse::InvalidStructure(err) => { @@ -1009,7 +1049,7 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide IncompletePackage::InvalidStructure(err.to_string()), ); return Ok(Dependencies::Unavailable( - "the package has an invalid format".to_string(), + UnavailableVersion::InvalidStructure, )); } }; @@ -1342,7 +1382,7 @@ enum Response { #[derive(Clone)] enum Dependencies { /// Package dependencies are not available. - Unavailable(String), + Unavailable(UnavailableVersion), /// Container for all available package versions. Available(Vec<(PubGrubPackage, Range)>), } diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 5628a3210..0f02606ff 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -2668,7 +2668,7 @@ fn compile_yanked_version_indirect() -> Result<()> { attrs<=20.3.0 attrs==21.1.0 attrs>=21.2.0 - and attrs==21.1.0 is unusable because it was yanked (reason: Installable but not importable on Python 3.4), we can conclude that attrs>20.3.0,<21.2.0 cannot be used. + and attrs==21.1.0 was yanked (reason: Installable but not importable on Python 3.4), we can conclude that attrs>20.3.0,<21.2.0 cannot be used. And because you require attrs>20.3.0,<21.2.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -4668,7 +4668,7 @@ fn offline_registry() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because black==23.10.1 was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because black was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -4898,7 +4898,7 @@ fn invalid_metadata_requires_python() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because validation==2.0.0 is unusable because the package metadata could not be parsed and you require validation==2.0.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because validation==2.0.0 has invalid metadata and you require validation==2.0.0, we can conclude that the requirements are unsatisfiable. hint: Metadata for validation==2.0.0 could not be parsed: Failed to parse version: Unexpected end of version specifier, expected operator: @@ -4930,7 +4930,7 @@ fn invalid_metadata_multiple_dist_info() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because validation==3.0.0 is unusable because the package has an invalid format and you require validation==3.0.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because validation==3.0.0 has an invalid package format and you require validation==3.0.0, we can conclude that the requirements are unsatisfiable. hint: The structure of validation==3.0.0 was invalid: Multiple .dist-info directories found: validation-2.0.0, validation-3.0.0 @@ -5171,7 +5171,7 @@ fn index_url_in_requirements() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because anyio<4 was not found in the package registry and you require anyio<4, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because anyio was not found in the package registry and you require anyio<4, we can conclude that the requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index ec18e3cd2..8e3885b86 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -1018,7 +1018,7 @@ fn install_no_index_version() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because flask==3.0.0 was not found in the provided package locations and you require flask==3.0.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because flask was not found in the provided package locations and you require flask==3.0.0, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### @@ -1558,7 +1558,7 @@ fn only_binary_requirements_txt() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because django-allauth==0.51.0 is unusable because no wheels are usable and building from source is disabled and you require django-allauth==0.51.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because django-allauth==0.51.0 has no usable wheels and building from source is disabled and you require django-allauth==0.51.0, we can conclude that the requirements are unsatisfiable. "### ); } @@ -3890,7 +3890,7 @@ fn already_installed_local_version_of_remote_package() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because anyio==4.2.0 was not found in the provided package locations and you require anyio==4.2.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because anyio was not found in the provided package locations and you require anyio==4.2.0, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### @@ -4188,7 +4188,7 @@ fn already_installed_remote_url() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because uv-public-pypackage==0.2.0 was not found in the provided package locations and you require uv-public-pypackage==0.2.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because uv-public-pypackage was not found in the provided package locations and you require uv-public-pypackage==0.2.0, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "###); diff --git a/crates/uv/tests/pip_install_scenarios.rs b/crates/uv/tests/pip_install_scenarios.rs index 789ec3a44..af35a31b6 100644 --- a/crates/uv/tests/pip_install_scenarios.rs +++ b/crates/uv/tests/pip_install_scenarios.rs @@ -1419,7 +1419,7 @@ fn local_used_without_sdist() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because package-a==1.2.3 is unusable because no wheels are available with a matching Python ABI and you require package-a==1.2.3, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because package-a==1.2.3 has no wheels are available with a matching Python ABI and you require package-a==1.2.3, we can conclude that the requirements are unsatisfiable. "###); // The version '1.2.3+foo' satisfies the constraint '==1.2.3'. @@ -1793,7 +1793,7 @@ fn local_transitive_confounding() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because package-b==2.0.0 is unusable because no wheels are available with a matching Python ABI and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because package-b==2.0.0 has no wheels are available with a matching Python ABI and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used. And because only package-a==1.0.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4204,7 +4204,7 @@ fn no_sdist_no_wheels_with_matching_platform() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no wheels are available with a matching platform, we can conclude that all versions of package-a cannot be used. + ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels are available with a matching platform, we can conclude that all versions of package-a cannot be used. And because you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4245,7 +4245,7 @@ fn no_sdist_no_wheels_with_matching_python() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no wheels are available with a matching Python implementation, we can conclude that all versions of package-a cannot be used. + ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels are available with a matching Python implementation, we can conclude that all versions of package-a cannot be used. And because you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4286,7 +4286,7 @@ fn no_sdist_no_wheels_with_matching_abi() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no wheels are available with a matching Python ABI, we can conclude that all versions of package-a cannot be used. + ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels are available with a matching Python ABI, we can conclude that all versions of package-a cannot be used. And because you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4329,7 +4329,7 @@ fn no_wheels_no_build() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no wheels are usable and building from source is disabled, we can conclude that all versions of package-a cannot be used. + ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no usable wheels and building from source is disabled, we can conclude that all versions of package-a cannot be used. And because you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4368,7 +4368,7 @@ fn only_wheels_no_binary() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no source distribution is available and using wheels is disabled, we can conclude that all versions of package-a cannot be used. + ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no available source distribution and using wheels is disabled, we can conclude that all versions of package-a cannot be used. And because you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4485,7 +4485,7 @@ fn package_only_yanked() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because it was yanked (reason: Yanked for testing), we can conclude that all versions of package-a cannot be used. + ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 was yanked (reason: Yanked for testing), we can conclude that all versions of package-a cannot be used. And because you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4527,7 +4527,7 @@ fn package_only_yanked_in_range() { ╰─▶ Because only the following versions of package-a are available: package-a<=0.1.0 package-a==1.0.0 - and package-a==1.0.0 is unusable because it was yanked (reason: Yanked for testing), we can conclude that package-a>0.1.0 cannot be used. + and package-a==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-a>0.1.0 cannot be used. And because you require package-a>0.1.0, we can conclude that the requirements are unsatisfiable. "###); @@ -4667,7 +4667,7 @@ fn transitive_package_only_yanked() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 is unusable because it was yanked (reason: Yanked for testing), we can conclude that all versions of package-b cannot be used. + ╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 was yanked (reason: Yanked for testing), we can conclude that all versions of package-b cannot be used. And because package-a==0.1.0 depends on package-b, we can conclude that package-a==0.1.0 cannot be used. And because only package-a==0.1.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4719,7 +4719,7 @@ fn transitive_package_only_yanked_in_range() { ╰─▶ Because only the following versions of package-b are available: package-b<=0.1 package-b==1.0.0 - and package-b==1.0.0 is unusable because it was yanked (reason: Yanked for testing), we can conclude that package-b>0.1 cannot be used. + and package-b==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-b>0.1 cannot be used. And because package-a==0.1.0 depends on package-b>0.1, we can conclude that package-a==0.1.0 cannot be used. And because only package-a==0.1.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable. "###); @@ -4840,7 +4840,7 @@ fn transitive_yanked_and_unyanked_dependency() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because package-c==2.0.0 is unusable because it was yanked (reason: Yanked for testing) and package-a==1.0.0 depends on package-c==2.0.0, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because package-c==2.0.0 was yanked (reason: Yanked for testing) and package-a==1.0.0 depends on package-c==2.0.0, we can conclude that package-a==1.0.0 cannot be used. And because only package-a==1.0.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable. "###); diff --git a/crates/uv/tests/pip_sync.rs b/crates/uv/tests/pip_sync.rs index 3cf9837a3..bd56a9a62 100644 --- a/crates/uv/tests/pip_sync.rs +++ b/crates/uv/tests/pip_sync.rs @@ -850,7 +850,7 @@ fn install_no_index() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because markupsafe==2.1.3 was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because markupsafe was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### @@ -902,7 +902,7 @@ fn install_no_index_cached() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because markupsafe==2.1.3 was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because markupsafe was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `) "### @@ -1107,7 +1107,7 @@ fn mismatched_name() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because foo was found, but has an invalid format and you require foo, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because foo has an invalid package format and you require foo, we can conclude that the requirements are unsatisfiable. hint: The structure of foo was invalid: The .dist-info directory tomli-2.0.1 does not start with the normalized package name: foo @@ -2766,7 +2766,7 @@ fn offline() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because black==23.10.1 was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because black was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -4658,7 +4658,7 @@ fn require_hashes_registry_valid_hash() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because example-a-961b4c22==1.0.0 was not found in the package registry and you require example-a-961b4c22==1.0.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because example-a-961b4c22 was not found in the package registry and you require example-a-961b4c22==1.0.0, we can conclude that the requirements are unsatisfiable. "### );