Surface request stream errors in the resolver (#107)

Closes https://github.com/astral-sh/puffin/issues/105.
This commit is contained in:
Charlie Marsh 2023-10-16 13:26:46 -04:00 committed by GitHub
parent 7e8ffeb2df
commit bae52d5edd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 49 deletions

View file

@ -10,12 +10,18 @@ pub enum ResolveError {
#[error("Failed to find a version of {0} that satisfies the requirement")] #[error("Failed to find a version of {0} that satisfies the requirement")]
NotFound(Requirement), NotFound(Requirement),
#[error("The request stream terminated unexpectedly")]
StreamTermination,
#[error(transparent)] #[error(transparent)]
Client(#[from] puffin_client::PypiClientError), Client(#[from] puffin_client::PypiClientError),
#[error(transparent)] #[error(transparent)]
TrySend(#[from] futures::channel::mpsc::SendError), TrySend(#[from] futures::channel::mpsc::SendError),
#[error(transparent)]
Join(#[from] tokio::task::JoinError),
#[error(transparent)] #[error(transparent)]
PubGrub(#[from] pubgrub::error::PubGrubError<PubGrubPackage, PubGrubVersion>), PubGrub(#[from] pubgrub::error::PubGrubError<PubGrubPackage, PubGrubVersion>),
} }

View file

@ -7,11 +7,6 @@ use once_cell::sync::Lazy;
pub struct PubGrubVersion(pep440_rs::Version); pub struct PubGrubVersion(pep440_rs::Version);
impl PubGrubVersion { impl PubGrubVersion {
/// Returns `true` if this is a pre-release version.
pub fn is_prerelease(&self) -> bool {
self.0.pre.is_some() || self.0.dev.is_some()
}
/// Returns the smallest PEP 440 version that is larger than `self`. /// Returns the smallest PEP 440 version that is larger than `self`.
pub fn next(&self) -> PubGrubVersion { pub fn next(&self) -> PubGrubVersion {
let mut next = self.clone(); let mut next = self.clone();

View file

@ -7,11 +7,12 @@ use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use futures::future::Either; use futures::future::Either;
use futures::{StreamExt, TryFutureExt}; use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt};
use pubgrub::error::PubGrubError; use pubgrub::error::PubGrubError;
use pubgrub::range::Range; use pubgrub::range::Range;
use pubgrub::solver::{DependencyConstraints, Incompatibility, State}; use pubgrub::solver::{DependencyConstraints, Incompatibility, State};
use pubgrub::type_aliases::SelectedDependencies; use pubgrub::type_aliases::SelectedDependencies;
use tokio::select;
use tracing::{debug, trace}; use tracing::{debug, trace};
use waitmap::WaitMap; use waitmap::WaitMap;
@ -60,7 +61,7 @@ impl<'a> Resolver<'a> {
// A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version // A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version
// metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version). // metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version).
let (request_sink, request_stream) = futures::channel::mpsc::unbounded(); let (request_sink, request_stream) = futures::channel::mpsc::unbounded();
tokio::spawn({ let requests_fut = tokio::spawn({
let cache = cache.clone(); let cache = cache.clone();
let client = client.clone(); let client = client.clone();
async move { async move {
@ -101,7 +102,7 @@ impl<'a> Resolver<'a> {
} }
} }
Ok::<(), anyhow::Error>(()) Ok::<(), ResolveError>(())
} }
}); });
@ -112,52 +113,39 @@ impl<'a> Resolver<'a> {
request_sink.unbounded_send(Request::Package(package_name))?; request_sink.unbounded_send(Request::Package(package_name))?;
} }
// Track the packages and versions that we've requested. // Run the solver.
let mut requested_packages = HashSet::new(); let resolve_fut = self.solve(&cache, &request_sink);
let mut requested_versions = HashSet::new();
let mut pins = HashMap::new();
let selected_dependencies = self let requests_fut = requests_fut.fuse();
.solve( let resolve_fut = resolve_fut.fuse();
&cache, pin_mut!(requests_fut, resolve_fut);
&mut pins,
&mut requested_packages,
&mut requested_versions,
&request_sink,
)
.await?;
// Map to our own internal resolution type. let resolution = select! {
let mut resolution = BTreeMap::new(); result = requests_fut => {
for (package, version) in selected_dependencies { result??;
let PubGrubPackage::Package(package_name, None) = package else { return Err(ResolveError::StreamTermination);
continue; }
}; resolution = resolve_fut => {
resolution?
}
};
let version = pep440_rs::Version::from(version); Ok(resolution.into())
let file = pins
.get(&package_name)
.and_then(|versions| versions.get(&version))
.unwrap()
.clone();
let pinned_package = PinnedPackage::new(package_name.clone(), version, file);
resolution.insert(package_name, pinned_package);
}
Ok(Resolution::new(resolution))
} }
/// Run the `PubGrub` solver. /// Run the `PubGrub` solver.
async fn solve( async fn solve(
&self, &self,
cache: &SolverCache, cache: &SolverCache,
pins: &mut HashMap<PackageName, HashMap<pep440_rs::Version, File>>,
requested_packages: &mut HashSet<PackageName>,
requested_versions: &mut HashSet<String>,
request_sink: &futures::channel::mpsc::UnboundedSender<Request>, request_sink: &futures::channel::mpsc::UnboundedSender<Request>,
) -> Result<SelectedDependencies<PubGrubPackage, PubGrubVersion>, ResolveError> { ) -> Result<PubGrubResolution, ResolveError> {
let root = PubGrubPackage::Root; let root = PubGrubPackage::Root;
// Keep track of the packages for which we've requested metadata.
let mut requested_packages = HashSet::new();
let mut requested_versions = HashSet::new();
let mut pins = HashMap::new();
// Start the solve. // Start the solve.
let mut state = State::init(root.clone(), MIN_VERSION.clone()); let mut state = State::init(root.clone(), MIN_VERSION.clone());
let mut added_dependencies: HashMap<PubGrubPackage, HashSet<PubGrubVersion>> = let mut added_dependencies: HashMap<PubGrubPackage, HashSet<PubGrubVersion>> =
@ -170,11 +158,15 @@ impl<'a> Resolver<'a> {
// Fetch the list of candidates. // Fetch the list of candidates.
let Some(potential_packages) = state.partial_solution.potential_packages() else { let Some(potential_packages) = state.partial_solution.potential_packages() else {
return state.partial_solution.extract_solution().ok_or_else(|| { let Some(selected_dependencies) = state.partial_solution.extract_solution() else {
PubGrubError::Failure( return Err(PubGrubError::Failure(
"How did we end up with no package to choose but no solution?".into(), "How did we end up with no package to choose but no solution?".into(),
) )
.into() .into());
};
return Ok(PubGrubResolution {
selected_dependencies,
pins,
}); });
}; };
@ -184,8 +176,8 @@ impl<'a> Resolver<'a> {
.choose_package_version( .choose_package_version(
potential_packages, potential_packages,
cache, cache,
pins, &mut pins,
requested_versions, &mut requested_versions,
request_sink, request_sink,
) )
.await?; .await?;
@ -218,8 +210,8 @@ impl<'a> Resolver<'a> {
package, package,
&version, &version,
cache, cache,
pins, &mut pins,
requested_packages, &mut requested_packages,
request_sink, request_sink,
) )
.await? .await?
@ -516,3 +508,33 @@ enum Dependencies {
/// Container for all available package versions. /// Container for all available package versions.
Known(DependencyConstraints<PubGrubPackage, PubGrubVersion>), Known(DependencyConstraints<PubGrubPackage, PubGrubVersion>),
} }
#[derive(Debug)]
struct PubGrubResolution {
/// The selected dependencies.
selected_dependencies: SelectedDependencies<PubGrubPackage, PubGrubVersion>,
/// The selected file (source or built distribution) for each package.
pins: HashMap<PackageName, HashMap<pep440_rs::Version, File>>,
}
impl From<PubGrubResolution> for Resolution {
fn from(value: PubGrubResolution) -> Self {
let mut packages = BTreeMap::new();
for (package, version) in value.selected_dependencies {
let PubGrubPackage::Package(package_name, None) = package else {
continue;
};
let version = pep440_rs::Version::from(version);
let file = value
.pins
.get(&package_name)
.and_then(|versions| versions.get(&version))
.unwrap()
.clone();
let pinned_package = PinnedPackage::new(package_name.clone(), version, file);
packages.insert(package_name, pinned_package);
}
Resolution::new(packages)
}
}