use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Formatter; use std::sync::Arc; use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}; use rustc_hash::FxHashMap; use distribution_types::{BuiltDist, IndexLocations, InstalledDist, SourceDist}; use pep440_rs::Version; use pep508_rs::{MarkerTree, Requirement}; use uv_normalize::PackageName; use crate::candidate_selector::CandidateSelector; use crate::dependency_provider::UvDependencyProvider; use crate::fork_urls::ForkUrls; use crate::pubgrub::{ PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, PubGrubSpecifierError, }; use crate::python_requirement::PythonRequirement; use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason}; #[derive(Debug, thiserror::Error)] pub enum ResolveError { #[error("Failed to find a version of `{0}` that satisfies the requirement")] NotFound(Requirement), #[error(transparent)] Client(#[from] uv_client::Error), #[error("The channel closed unexpectedly")] ChannelClosed, #[error(transparent)] Join(#[from] tokio::task::JoinError), #[error("Attempted to wait on an unregistered task: `{_0}`")] UnregisteredTask(String), #[error("Package metadata name `{metadata}` does not match given name `{given}`")] NameMismatch { given: PackageName, metadata: PackageName, }, #[error(transparent)] PubGrubSpecifier(#[from] PubGrubSpecifierError), #[error("Overrides contain conflicting URLs for package `{0}`:\n- {1}\n- {2}")] ConflictingOverrideUrls(PackageName, String, String), #[error("Requirements contain conflicting URLs for package `{0}`:\n- {}", _1.join("\n- "))] ConflictingUrls(PackageName, Vec), #[error("Requirements contain conflicting URLs for package `{package_name}` in split `{fork_markers}`:\n- {}", urls.join("\n- "))] ConflictingUrlsInFork { package_name: PackageName, urls: Vec, fork_markers: MarkerTree, }, #[error("Package `{0}` attempted to resolve via URL: {1}. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{0} @ {1}` to your dependencies or constraints file.")] DisallowedUrl(PackageName, String), #[error("There are conflicting editable requirements for package `{0}`:\n- {1}\n- {2}")] ConflictingEditables(PackageName, String, String), #[error(transparent)] DistributionType(#[from] distribution_types::Error), #[error("Failed to download `{0}`")] Fetch(Box, #[source] uv_distribution::Error), #[error("Failed to download and build `{0}`")] FetchAndBuild(Box, #[source] uv_distribution::Error), #[error("Failed to read `{0}`")] Read(Box, #[source] uv_distribution::Error), // TODO(zanieb): Use `thiserror` in `InstalledDist` so we can avoid chaining `anyhow` #[error("Failed to read metadata from installed package `{0}`")] ReadInstalled(Box, #[source] anyhow::Error), #[error("Failed to build `{0}`")] Build(Box, #[source] uv_distribution::Error), #[error(transparent)] NoSolution(#[from] NoSolutionError), #[error("{package} {version} depends on itself")] SelfDependency { /// Package whose dependencies we want. package: Box, /// Version of the package for which we want the dependencies. version: Box, }, #[error("Attempted to construct an invalid version specifier")] InvalidVersion(#[from] pep440_rs::VersionSpecifierBuildError), #[error("In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `{0}`")] UnhashedPackage(PackageName), /// Something unexpected happened. #[error("{0}")] Failure(String), } impl From> for ResolveError { /// Drop the value we want to send to not leak the private type we're sending. /// The tokio error only says "channel closed", so we don't lose information. fn from(_value: tokio::sync::mpsc::error::SendError) -> Self { Self::ChannelClosed } } /// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report. #[derive(Debug)] pub struct NoSolutionError { error: pubgrub::error::NoSolutionError, available_versions: FxHashMap>, selector: CandidateSelector, python_requirement: PythonRequirement, index_locations: IndexLocations, unavailable_packages: FxHashMap, incomplete_packages: FxHashMap>, fork_urls: ForkUrls, } impl NoSolutionError { pub(crate) fn new( error: pubgrub::error::NoSolutionError, available_versions: FxHashMap>, selector: CandidateSelector, python_requirement: PythonRequirement, index_locations: IndexLocations, unavailable_packages: FxHashMap, incomplete_packages: FxHashMap>, 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 /// wrap an [`PubGrubPackageInner::Extra`] package. pub(crate) fn collapse_proxies( derivation_tree: &mut DerivationTree, UnavailableReason>, ) { match derivation_tree { DerivationTree::External(_) => {} DerivationTree::Derived(derived) => { match ( Arc::make_mut(&mut derived.cause1), Arc::make_mut(&mut derived.cause2), ) { ( DerivationTree::External(External::FromDependencyOf(package, ..)), ref mut cause, ) if matches!( &**package, PubGrubPackageInner::Extra { .. } | PubGrubPackageInner::Marker { .. } | PubGrubPackageInner::Dev { .. } ) => { Self::collapse_proxies(cause); *derivation_tree = cause.clone(); } ( ref mut cause, DerivationTree::External(External::FromDependencyOf(package, ..)), ) if matches!( &**package, PubGrubPackageInner::Extra { .. } | PubGrubPackageInner::Marker { .. } | PubGrubPackageInner::Dev { .. } ) => { Self::collapse_proxies(cause); *derivation_tree = cause.clone(); } _ => { Self::collapse_proxies(Arc::make_mut(&mut derived.cause1)); Self::collapse_proxies(Arc::make_mut(&mut derived.cause2)); } } } } } } impl std::error::Error for NoSolutionError {} impl std::fmt::Display for NoSolutionError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { // Write the derivation report. let formatter = PubGrubReportFormatter { available_versions: &self.available_versions, python_requirement: &self.python_requirement, }; let report = DefaultStringReporter::report_with_formatter(&self.error, &formatter); write!(f, "{report}")?; // Include any additional hints. for hint in formatter.hints( &self.error, &self.selector, &self.index_locations, &self.unavailable_packages, &self.incomplete_packages, &self.fork_urls, ) { write!(f, "\n\n{hint}")?; } Ok(()) } }