Use a custom PubGrub error type to always show resolution report (#365)

Closes https://github.com/astral-sh/puffin/issues/356.

The example from the issue now renders as:

```
❯ cargo run --bin puffin-dev -q -- resolve-cli tensorflow-cpu-aws
puffin-dev failed
  Caused by: No solution found when resolving build dependencies for source distribution:
  Caused by: Because there is no available version for tensorflow-cpu-aws and root depends on tensorflow-cpu-aws, version solving failed.
```
This commit is contained in:
Charlie Marsh 2023-11-08 06:57:26 -08:00 committed by GitHub
parent 4eed03d8e7
commit 4fe583257e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 12 deletions

View file

@ -7,7 +7,7 @@ use anyhow::{anyhow, Result};
use colored::Colorize; use colored::Colorize;
use fs_err::File; use fs_err::File;
use itertools::Itertools; use itertools::Itertools;
use pubgrub::report::Reporter;
use tracing::debug; use tracing::debug;
use pep508_rs::Requirement; use pep508_rs::Requirement;
@ -17,7 +17,7 @@ use puffin_client::RegistryClientBuilder;
use puffin_dispatch::BuildDispatch; use puffin_dispatch::BuildDispatch;
use puffin_interpreter::Virtualenv; use puffin_interpreter::Virtualenv;
use puffin_normalize::ExtraName; use puffin_normalize::ExtraName;
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionFailureReporter, ResolutionMode}; use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode};
use std::str::FromStr; use std::str::FromStr;
use crate::commands::reporters::ResolverReporter; use crate::commands::reporters::ResolverReporter;
@ -149,14 +149,11 @@ pub(crate) async fn pip_compile(
) )
.with_reporter(ResolverReporter::from(printer)); .with_reporter(ResolverReporter::from(printer));
let resolution = match resolver.resolve().await { let resolution = match resolver.resolve().await {
Err(puffin_resolver::ResolveError::PubGrub(pubgrub::error::PubGrubError::NoSolution( Err(puffin_resolver::ResolveError::PubGrub(err)) => {
derivation_tree,
))) => {
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
{ {
let report = let report = miette::Report::msg(format!("{err}"))
miette::Report::msg(ResolutionFailureReporter::report(&derivation_tree)) .context("No solution found when resolving dependencies:");
.context("No solution found when resolving dependencies:");
eprint!("{report:?}"); eprint!("{report:?}");
} }
return Ok(ExitStatus::Failure); return Ok(ExitStatus::Failure);

View file

@ -85,7 +85,7 @@ impl BuildContext for BuildDispatch {
self, self,
); );
let resolution_graph = resolver.resolve().await.context( let resolution_graph = resolver.resolve().await.context(
"No solution found when resolving build dependencies for source distribution build", "No solution found when resolving build dependencies for source distribution:",
)?; )?;
Ok(resolution_graph.requirements()) Ok(resolution_graph.requirements())
}) })

View file

@ -1,4 +1,7 @@
use std::fmt::Formatter;
use pubgrub::range::Range; use pubgrub::range::Range;
use pubgrub::report::Reporter;
use thiserror::Error; use thiserror::Error;
use url::Url; use url::Url;
@ -6,6 +9,7 @@ use pep508_rs::Requirement;
use puffin_normalize::PackageName; use puffin_normalize::PackageName;
use crate::pubgrub::{PubGrubPackage, PubGrubVersion}; use crate::pubgrub::{PubGrubPackage, PubGrubVersion};
use crate::ResolutionFailureReporter;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ResolveError { pub enum ResolveError {
@ -25,7 +29,7 @@ pub enum ResolveError {
Join(#[from] tokio::task::JoinError), Join(#[from] tokio::task::JoinError),
#[error(transparent)] #[error(transparent)]
PubGrub(#[from] pubgrub::error::PubGrubError<PubGrubPackage, Range<PubGrubVersion>>), PubGrub(#[from] RichPubGrubError),
#[error("Package metadata name `{metadata}` does not match given name `{given}`")] #[error("Package metadata name `{metadata}` does not match given name `{given}`")]
NameMismatch { NameMismatch {
@ -64,3 +68,28 @@ impl<T> From<futures::channel::mpsc::TrySendError<T>> for ResolveError {
value.into_send_error().into() value.into_send_error().into()
} }
} }
/// A wrapper around [`pubgrub::error::PubGrubError`] that displays a resolution failure report.
#[derive(Debug)]
pub struct RichPubGrubError {
source: pubgrub::error::PubGrubError<PubGrubPackage, Range<PubGrubVersion>>,
}
impl std::error::Error for RichPubGrubError {}
impl std::fmt::Display for RichPubGrubError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let pubgrub::error::PubGrubError::NoSolution(derivation_tree) = &self.source {
let report = ResolutionFailureReporter::report(derivation_tree);
write!(f, "{report}")
} else {
write!(f, "{}", self.source)
}
}
}
impl From<pubgrub::error::PubGrubError<PubGrubPackage, Range<PubGrubVersion>>> for ResolveError {
fn from(value: pubgrub::error::PubGrubError<PubGrubPackage, Range<PubGrubVersion>>) -> Self {
ResolveError::PubGrub(RichPubGrubError { source: value })
}
}

View file

@ -2,4 +2,4 @@
source: crates/puffin-resolver/tests/resolver.rs source: crates/puffin-resolver/tests/resolver.rs
expression: resolution expression: resolution
--- ---
No solution Because there is no version of black available matching <=20.0 and root depends on black<=20.0, version solving failed.

View file

@ -2,4 +2,4 @@
source: crates/puffin-resolver/tests/resolver.rs source: crates/puffin-resolver/tests/resolver.rs
expression: err expression: err
--- ---
No solution Because there is no version of black available matching <=20.0 and root depends on black<=20.0, version solving failed.