mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Remove special casing from no solution error (#5067)
The only pubgrub error that can occur is a `NoSolutionError`, and the only place it can occur is `unit_propagation`, all other variants if `PubGrubError` are unreachable. By changing the return type on pubgrub's side (https://github.com/astral-sh/pubgrub/pull/28), we can remove the pattern matching and the `unreachable!()` asserts on `PubGrubError`. Our pubgrub error wrapper used to have a two phased initialization, first mostly stubs in `solve[_tracked]()` and then adding the actual context in `resolve()`. When constructing the error in `solve` we already have all this context, so we can unify this to a regular constructor and remove the special casing in `resolve()` and `hints()`.
This commit is contained in:
parent
00c055a6bd
commit
e34ab96e80
5 changed files with 225 additions and 358 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2700,7 +2700,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pubgrub"
|
name = "pubgrub"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "git+https://github.com/astral-sh/pubgrub?rev=b4435e2f3af10dab2336a0345b35dcd622699d06#b4435e2f3af10dab2336a0345b35dcd622699d06"
|
source = "git+https://github.com/astral-sh/pubgrub?rev=3f0ba760951ab0deeac874b98bb18fc90103fcf7#3f0ba760951ab0deeac874b98bb18fc90103fcf7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -107,7 +107,7 @@ path-slash = { version = "0.2.1" }
|
||||||
pathdiff = { version = "0.2.1" }
|
pathdiff = { version = "0.2.1" }
|
||||||
petgraph = { version = "0.6.4" }
|
petgraph = { version = "0.6.4" }
|
||||||
platform-info = { version = "2.0.2" }
|
platform-info = { version = "2.0.2" }
|
||||||
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "b4435e2f3af10dab2336a0345b35dcd622699d06" }
|
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "3f0ba760951ab0deeac874b98bb18fc90103fcf7" }
|
||||||
pyo3 = { version = "0.21.0" }
|
pyo3 = { version = "0.21.0" }
|
||||||
pyo3-log = { version = "0.10.0" }
|
pyo3-log = { version = "0.10.0" }
|
||||||
quote = { version = "1.0.36" }
|
quote = { version = "1.0.36" }
|
||||||
|
|
|
@ -2,10 +2,9 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter};
|
use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use distribution_types::{BuiltDist, IndexLocations, InstalledDist, SourceDist};
|
use distribution_types::{BuiltDist, IndexLocations, InstalledDist, SourceDist};
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
|
@ -19,9 +18,7 @@ use crate::pubgrub::{
|
||||||
PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, PubGrubSpecifierError,
|
PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, PubGrubSpecifierError,
|
||||||
};
|
};
|
||||||
use crate::python_requirement::PythonRequirement;
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::resolver::{
|
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason};
|
||||||
FxOnceMap, IncompletePackage, UnavailablePackage, UnavailableReason, VersionsResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ResolveError {
|
pub enum ResolveError {
|
||||||
|
@ -117,9 +114,45 @@ impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NoSolutionError {
|
||||||
|
error: pubgrub::error::NoSolutionError<UvDependencyProvider>,
|
||||||
|
available_versions: FxHashMap<PubGrubPackage, BTreeSet<Version>>,
|
||||||
|
selector: CandidateSelector,
|
||||||
|
python_requirement: PythonRequirement,
|
||||||
|
index_locations: IndexLocations,
|
||||||
|
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
|
||||||
|
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
|
||||||
|
fork_urls: ForkUrls,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoSolutionError {
|
||||||
|
pub(crate) fn new(
|
||||||
|
error: pubgrub::error::NoSolutionError<UvDependencyProvider>,
|
||||||
|
available_versions: FxHashMap<PubGrubPackage, BTreeSet<Version>>,
|
||||||
|
selector: CandidateSelector,
|
||||||
|
python_requirement: PythonRequirement,
|
||||||
|
index_locations: IndexLocations,
|
||||||
|
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
|
||||||
|
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
|
||||||
|
fork_urls: ForkUrls,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
error,
|
||||||
|
available_versions,
|
||||||
|
selector,
|
||||||
|
python_requirement,
|
||||||
|
index_locations,
|
||||||
|
unavailable_packages,
|
||||||
|
incomplete_packages,
|
||||||
|
fork_urls,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a [`DerivationTree`], collapse any [`External::FromDependencyOf`] incompatibilities
|
/// Given a [`DerivationTree`], collapse any [`External::FromDependencyOf`] incompatibilities
|
||||||
/// wrap an [`PubGrubPackageInner::Extra`] package.
|
/// wrap an [`PubGrubPackageInner::Extra`] package.
|
||||||
fn collapse_proxies(
|
pub(crate) fn collapse_proxies(
|
||||||
derivation_tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
derivation_tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||||
) {
|
) {
|
||||||
match derivation_tree {
|
match derivation_tree {
|
||||||
|
@ -139,7 +172,7 @@ fn collapse_proxies(
|
||||||
| PubGrubPackageInner::Dev { .. }
|
| PubGrubPackageInner::Dev { .. }
|
||||||
) =>
|
) =>
|
||||||
{
|
{
|
||||||
collapse_proxies(cause);
|
Self::collapse_proxies(cause);
|
||||||
*derivation_tree = cause.clone();
|
*derivation_tree = cause.clone();
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
|
@ -152,72 +185,17 @@ fn collapse_proxies(
|
||||||
| PubGrubPackageInner::Dev { .. }
|
| PubGrubPackageInner::Dev { .. }
|
||||||
) =>
|
) =>
|
||||||
{
|
{
|
||||||
collapse_proxies(cause);
|
Self::collapse_proxies(cause);
|
||||||
*derivation_tree = cause.clone();
|
*derivation_tree = cause.clone();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
collapse_proxies(Arc::make_mut(&mut derived.cause1));
|
Self::collapse_proxies(Arc::make_mut(&mut derived.cause1));
|
||||||
collapse_proxies(Arc::make_mut(&mut derived.cause2));
|
Self::collapse_proxies(Arc::make_mut(&mut derived.cause2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolveError {
|
|
||||||
/// Convert an error from PubGrub to a resolver error.
|
|
||||||
///
|
|
||||||
/// [`ForkUrls`] breaks the usual pattern used here since it's part of one the [`SolveState`],
|
|
||||||
/// not of the [`ResolverState`], so we have to take it from the fork that errored instead of
|
|
||||||
/// being able to add it later.
|
|
||||||
pub(crate) fn from_pubgrub_error(
|
|
||||||
value: pubgrub::error::PubGrubError<UvDependencyProvider>,
|
|
||||||
fork_urls: ForkUrls,
|
|
||||||
) -> Self {
|
|
||||||
match value {
|
|
||||||
// These are all never type variant that can never match, but never is experimental
|
|
||||||
pubgrub::error::PubGrubError::ErrorChoosingPackageVersion(_)
|
|
||||||
| pubgrub::error::PubGrubError::ErrorInShouldCancel(_)
|
|
||||||
| pubgrub::error::PubGrubError::ErrorRetrievingDependencies { .. } => {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
pubgrub::error::PubGrubError::Failure(inner) => Self::Failure(inner),
|
|
||||||
pubgrub::error::PubGrubError::NoSolution(mut derivation_tree) => {
|
|
||||||
collapse_proxies(&mut derivation_tree);
|
|
||||||
|
|
||||||
Self::NoSolution(NoSolutionError {
|
|
||||||
derivation_tree,
|
|
||||||
// The following should be populated before display for the best error messages
|
|
||||||
available_versions: FxHashMap::default(),
|
|
||||||
selector: None,
|
|
||||||
python_requirement: None,
|
|
||||||
index_locations: None,
|
|
||||||
unavailable_packages: FxHashMap::default(),
|
|
||||||
incomplete_packages: FxHashMap::default(),
|
|
||||||
fork_urls,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pubgrub::error::PubGrubError::SelfDependency { package, version } => {
|
|
||||||
Self::SelfDependency {
|
|
||||||
package: Box::new(package),
|
|
||||||
version: Box::new(version),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around [`pubgrub::error::PubGrubError::NoSolution`] that displays a resolution failure report.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NoSolutionError {
|
|
||||||
derivation_tree: DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
|
||||||
available_versions: FxHashMap<PubGrubPackage, BTreeSet<Version>>,
|
|
||||||
selector: Option<CandidateSelector>,
|
|
||||||
python_requirement: Option<PythonRequirement>,
|
|
||||||
index_locations: Option<IndexLocations>,
|
|
||||||
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
|
|
||||||
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
|
|
||||||
fork_urls: ForkUrls,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for NoSolutionError {}
|
impl std::error::Error for NoSolutionError {}
|
||||||
|
@ -227,15 +205,14 @@ impl std::fmt::Display for NoSolutionError {
|
||||||
// Write the derivation report.
|
// Write the derivation report.
|
||||||
let formatter = PubGrubReportFormatter {
|
let formatter = PubGrubReportFormatter {
|
||||||
available_versions: &self.available_versions,
|
available_versions: &self.available_versions,
|
||||||
python_requirement: self.python_requirement.as_ref(),
|
python_requirement: &self.python_requirement,
|
||||||
};
|
};
|
||||||
let report =
|
let report = DefaultStringReporter::report_with_formatter(&self.error, &formatter);
|
||||||
DefaultStringReporter::report_with_formatter(&self.derivation_tree, &formatter);
|
|
||||||
write!(f, "{report}")?;
|
write!(f, "{report}")?;
|
||||||
|
|
||||||
// Include any additional hints.
|
// Include any additional hints.
|
||||||
for hint in formatter.hints(
|
for hint in formatter.hints(
|
||||||
&self.derivation_tree,
|
&self.error,
|
||||||
&self.selector,
|
&self.selector,
|
||||||
&self.index_locations,
|
&self.index_locations,
|
||||||
&self.unavailable_packages,
|
&self.unavailable_packages,
|
||||||
|
@ -248,113 +225,3 @@ impl std::fmt::Display for NoSolutionError {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoSolutionError {
|
|
||||||
/// Update the available versions attached to the error using the given package version index.
|
|
||||||
///
|
|
||||||
/// Only packages used in the error's derivation tree will be retrieved.
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn with_available_versions(
|
|
||||||
mut self,
|
|
||||||
visited: &FxHashSet<PackageName>,
|
|
||||||
package_versions: &FxOnceMap<PackageName, Arc<VersionsResponse>>,
|
|
||||||
) -> Self {
|
|
||||||
let mut available_versions = FxHashMap::default();
|
|
||||||
for package in self.derivation_tree.packages() {
|
|
||||||
match &**package {
|
|
||||||
PubGrubPackageInner::Root { .. } => {}
|
|
||||||
PubGrubPackageInner::Python { .. } => {}
|
|
||||||
PubGrubPackageInner::Marker { .. } => {}
|
|
||||||
PubGrubPackageInner::Extra { .. } => {}
|
|
||||||
PubGrubPackageInner::Dev { .. } => {}
|
|
||||||
PubGrubPackageInner::Package { name, .. } => {
|
|
||||||
// Avoid including available versions for packages that exist in the derivation
|
|
||||||
// tree, but were never visited during resolution. We _may_ have metadata for
|
|
||||||
// these packages, but it's non-deterministic, and omitting them ensures that
|
|
||||||
// we represent the state of the resolver at the time of failure.
|
|
||||||
if visited.contains(name) {
|
|
||||||
if let Some(response) = package_versions.get(name) {
|
|
||||||
if let VersionsResponse::Found(ref version_maps) = *response {
|
|
||||||
for version_map in version_maps {
|
|
||||||
available_versions
|
|
||||||
.entry(package.clone())
|
|
||||||
.or_insert_with(BTreeSet::new)
|
|
||||||
.extend(
|
|
||||||
version_map.iter().map(|(version, _)| version.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.available_versions = available_versions;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the candidate selector attached to the error.
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn with_selector(mut self, selector: CandidateSelector) -> Self {
|
|
||||||
self.selector = Some(selector);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the index locations attached to the error.
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn with_index_locations(mut self, index_locations: &IndexLocations) -> Self {
|
|
||||||
self.index_locations = Some(index_locations.clone());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the unavailable packages attached to the error.
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn with_unavailable_packages(
|
|
||||||
mut self,
|
|
||||||
unavailable_packages: &DashMap<PackageName, UnavailablePackage>,
|
|
||||||
) -> Self {
|
|
||||||
let mut new = FxHashMap::default();
|
|
||||||
for package in self.derivation_tree.packages() {
|
|
||||||
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
|
||||||
if let Some(reason) = unavailable_packages.get(name) {
|
|
||||||
new.insert(name.clone(), reason.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.unavailable_packages = new;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the incomplete packages attached to the error.
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn with_incomplete_packages(
|
|
||||||
mut self,
|
|
||||||
incomplete_packages: &DashMap<PackageName, DashMap<Version, IncompletePackage>>,
|
|
||||||
) -> Self {
|
|
||||||
let mut new = FxHashMap::default();
|
|
||||||
for package in self.derivation_tree.packages() {
|
|
||||||
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
|
||||||
if let Some(versions) = incomplete_packages.get(name) {
|
|
||||||
for entry in versions.iter() {
|
|
||||||
let (version, reason) = entry.pair();
|
|
||||||
new.entry(name.clone())
|
|
||||||
.or_insert_with(BTreeMap::default)
|
|
||||||
.insert(version.clone(), reason.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.incomplete_packages = new;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the Python requirements attached to the error.
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn with_python_requirement(
|
|
||||||
mut self,
|
|
||||||
python_requirement: &PythonRequirement,
|
|
||||||
) -> Self {
|
|
||||||
self.python_requirement = Some(python_requirement.clone());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub(crate) struct PubGrubReportFormatter<'a> {
|
||||||
pub(crate) available_versions: &'a FxHashMap<PubGrubPackage, BTreeSet<Version>>,
|
pub(crate) available_versions: &'a FxHashMap<PubGrubPackage, BTreeSet<Version>>,
|
||||||
|
|
||||||
/// The versions that were available for each package
|
/// The versions that were available for each package
|
||||||
pub(crate) python_requirement: Option<&'a PythonRequirement>,
|
pub(crate) python_requirement: &'a PythonRequirement,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
|
impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
|
||||||
|
@ -47,12 +47,11 @@ impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
|
||||||
format!("we are solving dependencies of {package} {version}")
|
format!("we are solving dependencies of {package} {version}")
|
||||||
}
|
}
|
||||||
External::NoVersions(package, set) => {
|
External::NoVersions(package, set) => {
|
||||||
if let Some(python) = self.python_requirement {
|
|
||||||
if matches!(
|
if matches!(
|
||||||
&**package,
|
&**package,
|
||||||
PubGrubPackageInner::Python(PubGrubPython::Target)
|
PubGrubPackageInner::Python(PubGrubPython::Target)
|
||||||
) {
|
) {
|
||||||
return if let Some(target) = python.target() {
|
return if let Some(target) = self.python_requirement.target() {
|
||||||
format!(
|
format!(
|
||||||
"the requested {package} version ({target}) does not satisfy {}",
|
"the requested {package} version ({target}) does not satisfy {}",
|
||||||
PackageRange::compatibility(package, set)
|
PackageRange::compatibility(package, set)
|
||||||
|
@ -70,11 +69,10 @@ impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
|
||||||
) {
|
) {
|
||||||
return format!(
|
return format!(
|
||||||
"the current {package} version ({}) does not satisfy {}",
|
"the current {package} version ({}) does not satisfy {}",
|
||||||
python.installed(),
|
self.python_requirement.installed(),
|
||||||
PackageRange::compatibility(package, set)
|
PackageRange::compatibility(package, set)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let set = self.simplify_set(set, package);
|
let set = self.simplify_set(set, package);
|
||||||
|
|
||||||
|
@ -404,8 +402,8 @@ impl PubGrubReportFormatter<'_> {
|
||||||
pub(crate) fn hints(
|
pub(crate) fn hints(
|
||||||
&self,
|
&self,
|
||||||
derivation_tree: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
derivation_tree: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||||
selector: &Option<CandidateSelector>,
|
selector: &CandidateSelector,
|
||||||
index_locations: &Option<IndexLocations>,
|
index_locations: &IndexLocations,
|
||||||
unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>,
|
unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>,
|
||||||
incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
|
incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
|
||||||
fork_urls: &ForkUrls,
|
fork_urls: &ForkUrls,
|
||||||
|
@ -417,18 +415,13 @@ impl PubGrubReportFormatter<'_> {
|
||||||
) => {
|
) => {
|
||||||
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
||||||
// Check for no versions due to pre-release options.
|
// Check for no versions due to pre-release options.
|
||||||
if let Some(selector) = selector {
|
|
||||||
if !fork_urls.contains_key(name) {
|
if !fork_urls.contains_key(name) {
|
||||||
self.prerelease_available_hint(
|
self.prerelease_available_hint(package, name, set, selector, &mut hints);
|
||||||
package, name, set, selector, &mut hints,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
||||||
// Check for no versions due to no `--find-links` flat index
|
// Check for no versions due to no `--find-links` flat index
|
||||||
if let Some(index_locations) = index_locations {
|
|
||||||
Self::index_hints(
|
Self::index_hints(
|
||||||
package,
|
package,
|
||||||
name,
|
name,
|
||||||
|
@ -440,7 +433,6 @@ impl PubGrubReportFormatter<'_> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
DerivationTree::External(External::FromDependencyOf(
|
DerivationTree::External(External::FromDependencyOf(
|
||||||
package,
|
package,
|
||||||
package_set,
|
package_set,
|
||||||
|
@ -452,8 +444,8 @@ impl PubGrubReportFormatter<'_> {
|
||||||
&**dependency,
|
&**dependency,
|
||||||
PubGrubPackageInner::Python(PubGrubPython::Target)
|
PubGrubPackageInner::Python(PubGrubPython::Target)
|
||||||
) {
|
) {
|
||||||
if let Some(python) = self.python_requirement {
|
if let Some(PythonTarget::RequiresPython(requires_python)) =
|
||||||
if let Some(PythonTarget::RequiresPython(requires_python)) = python.target()
|
self.python_requirement.target()
|
||||||
{
|
{
|
||||||
hints.insert(PubGrubHint::RequiresPython {
|
hints.insert(PubGrubHint::RequiresPython {
|
||||||
requires_python: requires_python.clone(),
|
requires_python: requires_python.clone(),
|
||||||
|
@ -464,7 +456,6 @@ impl PubGrubReportFormatter<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
DerivationTree::External(External::NotRoot(..)) => {}
|
DerivationTree::External(External::NotRoot(..)) => {}
|
||||||
DerivationTree::Derived(derived) => {
|
DerivationTree::Derived(derived) => {
|
||||||
hints.extend(self.hints(
|
hints.extend(self.hints(
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::collections::{BTreeMap, VecDeque};
|
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::ops::Bound;
|
use std::ops::Bound;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -11,9 +11,8 @@ use std::{iter, thread};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use futures::{FutureExt, StreamExt, TryFutureExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use pubgrub::error::PubGrubError;
|
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
use pubgrub::solver::{Incompatibility, State};
|
use pubgrub::solver::{Incompatibility, State};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
@ -24,8 +23,8 @@ use tracing::{debug, enabled, instrument, trace, warn, Level};
|
||||||
|
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuiltDist, CompatibleDist, Dist, DistributionMetadata, IncompatibleDist, IncompatibleSource,
|
BuiltDist, CompatibleDist, Dist, DistributionMetadata, IncompatibleDist, IncompatibleSource,
|
||||||
IncompatibleWheel, InstalledDist, PythonRequirementKind, RemoteSource, ResolvedDist,
|
IncompatibleWheel, IndexLocations, InstalledDist, PythonRequirementKind, RemoteSource,
|
||||||
ResolvedDistRef, SourceDist, VersionOrUrlRef,
|
ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef,
|
||||||
};
|
};
|
||||||
pub(crate) use locals::Locals;
|
pub(crate) use locals::Locals;
|
||||||
use pep440_rs::{Version, MIN_VERSION};
|
use pep440_rs::{Version, MIN_VERSION};
|
||||||
|
@ -41,7 +40,7 @@ use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
|
||||||
|
|
||||||
use crate::candidate_selector::{CandidateDist, CandidateSelector};
|
use crate::candidate_selector::{CandidateDist, CandidateSelector};
|
||||||
use crate::dependency_provider::UvDependencyProvider;
|
use crate::dependency_provider::UvDependencyProvider;
|
||||||
use crate::error::ResolveError;
|
use crate::error::{NoSolutionError, ResolveError};
|
||||||
use crate::fork_urls::ForkUrls;
|
use crate::fork_urls::ForkUrls;
|
||||||
use crate::manifest::Manifest;
|
use crate::manifest::Manifest;
|
||||||
use crate::marker::{normalize, requires_python_marker};
|
use crate::marker::{normalize, requires_python_marker};
|
||||||
|
@ -245,51 +244,26 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
let (request_sink, request_stream) = mpsc::channel(300);
|
let (request_sink, request_stream) = mpsc::channel(300);
|
||||||
|
|
||||||
// Run the fetcher.
|
// Run the fetcher.
|
||||||
let requests_fut = state
|
let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
|
||||||
.clone()
|
|
||||||
.fetch(provider.clone(), request_stream)
|
|
||||||
.map_err(|err| (err, FxHashSet::default()))
|
|
||||||
.fuse();
|
|
||||||
|
|
||||||
// Spawn the PubGrub solver on a dedicated thread.
|
// Spawn the PubGrub solver on a dedicated thread.
|
||||||
let solver = state.clone();
|
let solver = state.clone();
|
||||||
|
let index_locations = provider.index_locations().clone();
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("uv-resolver".into())
|
.name("uv-resolver".into())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let result = solver.solve(request_sink);
|
let result = solver.solve(index_locations, request_sink);
|
||||||
tx.send(result).unwrap();
|
tx.send(result).unwrap();
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let resolve_fut = async move {
|
let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
|
||||||
rx.await
|
|
||||||
.map_err(|_| (ResolveError::ChannelClosed, FxHashSet::default()))
|
|
||||||
.and_then(|result| result)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wait for both to complete.
|
// Wait for both to complete.
|
||||||
match tokio::try_join!(requests_fut, resolve_fut) {
|
let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
|
||||||
Ok(((), resolution)) => {
|
|
||||||
state.on_complete();
|
state.on_complete();
|
||||||
Ok(resolution)
|
resolution
|
||||||
}
|
|
||||||
Err((err, visited)) => {
|
|
||||||
// Add version information to improve unsat error messages.
|
|
||||||
Err(if let ResolveError::NoSolution(err) = err {
|
|
||||||
ResolveError::NoSolution(
|
|
||||||
err.with_available_versions(&visited, state.index.packages())
|
|
||||||
.with_selector(state.selector.clone())
|
|
||||||
.with_python_requirement(&state.python_requirement)
|
|
||||||
.with_index_locations(provider.index_locations())
|
|
||||||
.with_unavailable_packages(&state.unavailable_packages)
|
|
||||||
.with_incomplete_packages(&state.incomplete_packages),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,19 +271,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
fn solve(
|
fn solve(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
request_sink: Sender<Request>,
|
// No solution error context.
|
||||||
) -> Result<ResolutionGraph, (ResolveError, FxHashSet<PackageName>)> {
|
index_locations: IndexLocations,
|
||||||
let mut visited = FxHashSet::default();
|
|
||||||
self.solve_tracked(&mut visited, request_sink)
|
|
||||||
.map_err(|err| (err, visited))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run the PubGrub solver, updating the `visited` set for each package visited during
|
|
||||||
/// resolution.
|
|
||||||
#[instrument(skip_all)]
|
|
||||||
fn solve_tracked(
|
|
||||||
self: Arc<Self>,
|
|
||||||
visited: &mut FxHashSet<PackageName>,
|
|
||||||
request_sink: Sender<Request>,
|
request_sink: Sender<Request>,
|
||||||
) -> Result<ResolutionGraph, ResolveError> {
|
) -> Result<ResolutionGraph, ResolveError> {
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -320,6 +283,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
debug!("Solving with target Python version: {}", target);
|
debug!("Solving with target Python version: {}", target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut visited = FxHashSet::default();
|
||||||
|
|
||||||
let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
|
let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
|
||||||
let mut prefetcher = BatchPrefetcher::default();
|
let mut prefetcher = BatchPrefetcher::default();
|
||||||
let state = ForkState {
|
let state = ForkState {
|
||||||
|
@ -351,12 +316,14 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
// Run unit propagation.
|
// Run unit propagation.
|
||||||
state
|
if let Err(err) = state.pubgrub.unit_propagation(state.next.clone()) {
|
||||||
.pubgrub
|
return Err(self.convert_no_solution_err(
|
||||||
.unit_propagation(state.next.clone())
|
err,
|
||||||
.map_err(|err| {
|
state.fork_urls.clone(),
|
||||||
ResolveError::from_pubgrub_error(err, state.fork_urls.clone())
|
&visited,
|
||||||
})?;
|
&index_locations,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-visit all candidate packages, to allow metadata to be fetched in parallel. If
|
// Pre-visit all candidate packages, to allow metadata to be fetched in parallel. If
|
||||||
// the dependency mode is direct, we only need to visit the root package.
|
// the dependency mode is direct, we only need to visit the root package.
|
||||||
|
@ -420,14 +387,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
.pubgrub
|
.pubgrub
|
||||||
.partial_solution
|
.partial_solution
|
||||||
.term_intersection_for_package(&state.next)
|
.term_intersection_for_package(&state.next)
|
||||||
.ok_or_else(|| {
|
.expect("a package was chosen but we don't have a term.");
|
||||||
ResolveError::from_pubgrub_error(
|
|
||||||
PubGrubError::Failure(
|
|
||||||
"a package was chosen but we don't have a term.".into(),
|
|
||||||
),
|
|
||||||
state.fork_urls.clone(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let decision = self.choose_version(
|
let decision = self.choose_version(
|
||||||
&state.next,
|
&state.next,
|
||||||
term_intersection.unwrap_positive(),
|
term_intersection.unwrap_positive(),
|
||||||
|
@ -435,7 +395,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
&preferences,
|
&preferences,
|
||||||
&state.fork_urls,
|
&state.fork_urls,
|
||||||
&state.python_requirement,
|
&state.python_requirement,
|
||||||
visited,
|
&mut visited,
|
||||||
&request_sink,
|
&request_sink,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -534,7 +494,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
&self.urls,
|
&self.urls,
|
||||||
dependencies.clone(),
|
dependencies.clone(),
|
||||||
&self.git,
|
&self.git,
|
||||||
&prefetcher,
|
|
||||||
)?;
|
)?;
|
||||||
// Emit a request to fetch the metadata for each registry package.
|
// Emit a request to fetch the metadata for each registry package.
|
||||||
for dependency in &dependencies {
|
for dependency in &dependencies {
|
||||||
|
@ -605,7 +564,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
&self.urls,
|
&self.urls,
|
||||||
fork.dependencies.clone(),
|
fork.dependencies.clone(),
|
||||||
&self.git,
|
&self.git,
|
||||||
&prefetcher,
|
|
||||||
)?;
|
)?;
|
||||||
// Emit a request to fetch the metadata for each registry package.
|
// Emit a request to fetch the metadata for each registry package.
|
||||||
for dependency in &fork.dependencies {
|
for dependency in &fork.dependencies {
|
||||||
|
@ -1806,6 +1764,75 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_no_solution_err(
|
||||||
|
&self,
|
||||||
|
mut err: pubgrub::error::NoSolutionError<UvDependencyProvider>,
|
||||||
|
fork_urls: ForkUrls,
|
||||||
|
visited: &FxHashSet<PackageName>,
|
||||||
|
index_locations: &IndexLocations,
|
||||||
|
) -> ResolveError {
|
||||||
|
NoSolutionError::collapse_proxies(&mut err);
|
||||||
|
|
||||||
|
let mut unavailable_packages = FxHashMap::default();
|
||||||
|
for package in err.packages() {
|
||||||
|
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
||||||
|
if let Some(reason) = self.unavailable_packages.get(name) {
|
||||||
|
unavailable_packages.insert(name.clone(), reason.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut incomplete_packages = FxHashMap::default();
|
||||||
|
for package in err.packages() {
|
||||||
|
if let PubGrubPackageInner::Package { name, .. } = &**package {
|
||||||
|
if let Some(versions) = self.incomplete_packages.get(name) {
|
||||||
|
for entry in versions.iter() {
|
||||||
|
let (version, reason) = entry.pair();
|
||||||
|
incomplete_packages
|
||||||
|
.entry(name.clone())
|
||||||
|
.or_insert_with(BTreeMap::default)
|
||||||
|
.insert(version.clone(), reason.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut available_versions = FxHashMap::default();
|
||||||
|
for package in err.packages() {
|
||||||
|
let PubGrubPackageInner::Package { name, .. } = &**package else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !visited.contains(name) {
|
||||||
|
// Avoid including available versions for packages that exist in the derivation
|
||||||
|
// tree, but were never visited during resolution. We _may_ have metadata for
|
||||||
|
// these packages, but it's non-deterministic, and omitting them ensures that
|
||||||
|
// we represent the self of the resolver at the time of failure.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(response) = self.index.packages().get(name) {
|
||||||
|
if let VersionsResponse::Found(ref version_maps) = *response {
|
||||||
|
for version_map in version_maps {
|
||||||
|
available_versions
|
||||||
|
.entry(package.clone())
|
||||||
|
.or_insert_with(BTreeSet::new)
|
||||||
|
.extend(version_map.iter().map(|(version, _)| version.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResolveError::NoSolution(NoSolutionError::new(
|
||||||
|
err,
|
||||||
|
available_versions,
|
||||||
|
self.selector.clone(),
|
||||||
|
self.python_requirement.clone(),
|
||||||
|
index_locations.clone(),
|
||||||
|
unavailable_packages,
|
||||||
|
incomplete_packages,
|
||||||
|
fork_urls,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
|
fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
|
||||||
if let Some(reporter) = self.reporter.as_ref() {
|
if let Some(reporter) = self.reporter.as_ref() {
|
||||||
match &**package {
|
match &**package {
|
||||||
|
@ -1908,25 +1935,7 @@ impl ForkState {
|
||||||
urls: &Urls,
|
urls: &Urls,
|
||||||
dependencies: Vec<PubGrubDependency>,
|
dependencies: Vec<PubGrubDependency>,
|
||||||
git: &GitResolver,
|
git: &GitResolver,
|
||||||
prefetcher: &BatchPrefetcher,
|
|
||||||
) -> Result<(), ResolveError> {
|
) -> Result<(), ResolveError> {
|
||||||
// Check for self-dependencies.
|
|
||||||
if dependencies
|
|
||||||
.iter()
|
|
||||||
.any(|dependency| dependency.package == self.next)
|
|
||||||
{
|
|
||||||
if enabled!(Level::DEBUG) {
|
|
||||||
prefetcher.log_tried_versions();
|
|
||||||
}
|
|
||||||
return Err(ResolveError::from_pubgrub_error(
|
|
||||||
PubGrubError::SelfDependency {
|
|
||||||
package: self.next.clone(),
|
|
||||||
version: version.clone(),
|
|
||||||
},
|
|
||||||
self.fork_urls.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for dependency in &dependencies {
|
for dependency in &dependencies {
|
||||||
let PubGrubDependency {
|
let PubGrubDependency {
|
||||||
package,
|
package,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue