diff --git a/Cargo.lock b/Cargo.lock index ab2f6dc5f..cd887330d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,6 +335,18 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -877,6 +889,15 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "elsa" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714f766f3556b44e7e4776ad133fcc3445a489517c25c704ace411bb14790194" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -985,6 +1006,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.29" @@ -2589,6 +2616,7 @@ dependencies = [ "puffin-normalize", "puffin-traits", "pypi-types", + "resolvo", "sha2", "tempfile", "thiserror", @@ -2753,6 +2781,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -3000,6 +3034,19 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "resolvo" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554db165775d6858d17a9626c327b796d81db95db0a4cd6ca0efdfb7e7e3a264" +dependencies = [ + "bitvec", + "elsa", + "itertools 0.11.0", + "petgraph", + "tracing", +] + [[package]] name = "retry-policies" version = "0.2.1" @@ -3328,6 +3375,12 @@ dependencies = [ "xxhash-rust", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stacker" version = "0.1.15" @@ -3418,6 +3471,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.40" @@ -4379,6 +4438,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "xattr" version = "1.0.1" diff --git a/crates/puffin-cli/src/commands/mod.rs b/crates/puffin-cli/src/commands/mod.rs index 36ab674c5..b8177c327 100644 --- a/crates/puffin-cli/src/commands/mod.rs +++ b/crates/puffin-cli/src/commands/mod.rs @@ -4,7 +4,7 @@ use std::time::Duration; pub(crate) use add::add; pub(crate) use clean::clean; pub(crate) use freeze::freeze; -pub(crate) use pip_compile::{extra_name_with_clap_error, pip_compile}; +pub(crate) use pip_compile::{extra_name_with_clap_error, pip_compile, Resolver}; pub(crate) use pip_sync::pip_sync; pub(crate) use pip_uninstall::pip_uninstall; pub(crate) use remove::remove; diff --git a/crates/puffin-cli/src/commands/pip_compile.rs b/crates/puffin-cli/src/commands/pip_compile.rs index 04e9c41be..9dbfd63a7 100644 --- a/crates/puffin-cli/src/commands/pip_compile.rs +++ b/crates/puffin-cli/src/commands/pip_compile.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::fmt::Write; use std::io::{stdout, BufWriter}; use std::path::Path; @@ -41,6 +40,7 @@ pub(crate) async fn pip_compile( upgrade_mode: UpgradeMode, index_urls: Option, no_build: bool, + resolver: Resolver, python_version: Option, cache: &Path, mut printer: Printer, @@ -122,8 +122,8 @@ pub(crate) async fn pip_compile( // Determine the markers to use for resolution. let markers = python_version.map_or_else( - || Cow::Borrowed(venv.interpreter_info().markers()), - |python_version| Cow::Owned(python_version.markers(venv.interpreter_info().markers())), + || venv.interpreter_info().markers().clone(), + |python_version| python_version.markers(venv.interpreter_info().markers()), ); // Instantiate a client. @@ -149,21 +149,35 @@ pub(crate) async fn pip_compile( ); // Resolve the dependencies. - let resolver = - puffin_resolver::Resolver::new(manifest, &markers, &tags, &client, &build_dispatch) + let resolution = match resolver { + Resolver::Pubgrub => { + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &markers, + &tags, + &client, + &build_dispatch, + ) .with_reporter(ResolverReporter::from(printer)); - let resolution = match resolver.resolve().await { - Err(puffin_resolver::ResolveError::PubGrub(err)) => { - #[allow(clippy::print_stderr)] - { - let report = miette::Report::msg(format!("{err}")) - .context("No solution found when resolving dependencies:"); - eprint!("{report:?}"); - } - return Ok(ExitStatus::Failure); + let resolution = match resolver.resolve().await { + Err(puffin_resolver::ResolveError::PubGrub(err)) => { + #[allow(clippy::print_stderr)] + { + let report = miette::Report::msg(format!("{err}")) + .context("No solution found when resolving dependencies:"); + eprint!("{report:?}"); + } + return Ok(ExitStatus::Failure); + } + result => result, + }?; + resolution.into() } - result => result, - }?; + Resolver::Resolvo => { + puffin_resolver::resolvo::resolve(manifest, markers, tags, client, build_dispatch) + .await? + } + }; let s = if resolution.len() == 1 { "" } else { "s" }; writeln!( @@ -232,6 +246,13 @@ impl From for UpgradeMode { } } +/// Whether to allow package upgrades. +#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] +pub(crate) enum Resolver { + Pubgrub, + Resolvo, +} + pub(crate) fn extra_name_with_clap_error(arg: &str) -> Result { ExtraName::from_str(arg).map_err(|_err| { anyhow!( diff --git a/crates/puffin-cli/src/commands/reporters.rs b/crates/puffin-cli/src/commands/reporters.rs index 3de8a263d..d4bf6c4d0 100644 --- a/crates/puffin-cli/src/commands/reporters.rs +++ b/crates/puffin-cli/src/commands/reporters.rs @@ -217,7 +217,7 @@ impl From for ResolverReporter { } } -impl puffin_resolver::ResolverReporter for ResolverReporter { +impl puffin_resolver::pubgrub::ResolverReporter for ResolverReporter { fn on_progress( &self, name: &PackageName, diff --git a/crates/puffin-cli/src/main.rs b/crates/puffin-cli/src/main.rs index 6261c209e..0112b8e3f 100644 --- a/crates/puffin-cli/src/main.rs +++ b/crates/puffin-cli/src/main.rs @@ -13,7 +13,7 @@ use puffin_normalize::{ExtraName, PackageName}; use puffin_resolver::{PreReleaseMode, ResolutionMode}; use requirements::ExtrasSpecification; -use crate::commands::{extra_name_with_clap_error, ExitStatus}; +use crate::commands::{extra_name_with_clap_error, ExitStatus, Resolver}; use crate::index_urls::IndexUrls; use crate::python_version::PythonVersion; use crate::requirements::RequirementsSource; @@ -135,6 +135,10 @@ struct PipCompileArgs { #[clap(long)] no_build: bool, + /// The resolver to use when resolving dependencies. + #[arg(long, value_enum)] + resolver: Resolver, + /// The minimum Python version that should be supported. #[arg(long, short, value_enum)] python_version: Option, @@ -271,6 +275,7 @@ async fn inner() -> Result { args.upgrade.into(), index_urls, args.no_build, + args.resolver, args.python_version, &cache_dir, printer, diff --git a/crates/puffin-dispatch/src/lib.rs b/crates/puffin-dispatch/src/lib.rs index ad6577a26..995a7bfef 100644 --- a/crates/puffin-dispatch/src/lib.rs +++ b/crates/puffin-dispatch/src/lib.rs @@ -18,7 +18,7 @@ use puffin_client::RegistryClient; use puffin_distribution::Metadata; use puffin_installer::{Builder, Downloader, InstallPlan, Installer, Unzipper}; use puffin_interpreter::{InterpreterInfo, Virtualenv}; -use puffin_resolver::{DistFinder, Manifest, PreReleaseMode, ResolutionMode, Resolver}; +use puffin_resolver::{pubgrub, DistFinder, Manifest, PreReleaseMode, ResolutionMode}; use puffin_traits::BuildContext; /// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`] @@ -78,7 +78,7 @@ impl BuildContext for BuildDispatch { self.interpreter_info.platform(), self.interpreter_info.simple_version(), )?; - let resolver = Resolver::new( + let resolver = pubgrub::Resolver::new( Manifest::new( requirements.to_vec(), Vec::default(), diff --git a/crates/puffin-resolver/Cargo.toml b/crates/puffin-resolver/Cargo.toml index c3af190bc..ff442d272 100644 --- a/crates/puffin-resolver/Cargo.toml +++ b/crates/puffin-resolver/Cargo.toml @@ -29,12 +29,15 @@ anyhow = { workspace = true } bitflags = { workspace = true } clap = { workspace = true, features = ["derive"], optional = true } colored = { workspace = true } +derivative = { version = "2.2.0" } fs-err = { workspace = true, features = ["tokio"] } futures = { workspace = true } fxhash = { workspace = true } itertools = { workspace = true } once_cell = { workspace = true } petgraph = { workspace = true } +resolvo = { version = "0.2.0" } +sha2 = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } @@ -43,8 +46,6 @@ tracing = { workspace = true } url = { workspace = true } waitmap = { workspace = true } zip = { workspace = true } -derivative = { version = "2.2.0" } -sha2 = { workspace = true } [dev-dependencies] gourgeist = { path = "../gourgeist" } diff --git a/crates/puffin-resolver/src/database.rs b/crates/puffin-resolver/src/database.rs new file mode 100644 index 000000000..fd2acd9d1 --- /dev/null +++ b/crates/puffin-resolver/src/database.rs @@ -0,0 +1,422 @@ +use std::collections::hash_map::RandomState; +use std::collections::BTreeMap; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; + +use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use futures::{StreamExt, TryFutureExt}; +use fxhash::FxHashSet; +use tracing::{debug, error, info}; +use url::Url; +use waitmap::{Ref, WaitMap}; + +use distribution_filename::{SourceDistFilename, WheelFilename}; +use pep440_rs::Version; +use platform_tags::Tags; +use puffin_client::RegistryClient; +use puffin_distribution::{ + BuiltDist, DirectUrlSourceDist, Dist, GitSourceDist, Identifier, Metadata, SourceDist, +}; +use puffin_normalize::PackageName; +use puffin_traits::BuildContext; +use pypi_types::{Metadata21, SimpleJson}; + +use crate::distribution::{BuiltDistFetcher, SourceDistFetcher}; +use crate::file::{DistFile, SdistFile, WheelFile}; +use crate::locks::Locks; +use crate::ResolveError; + +pub(crate) type VersionMap = BTreeMap; + +pub(crate) struct Database { + client: RegistryClient, + tags: Tags, + build_context: Context, + index: Arc, + locks: Arc, + in_flight: Arc>, +} + +impl Database { + pub(crate) fn new(tags: Tags, client: RegistryClient, build_context: Context) -> Self { + Self { + client, + tags, + build_context, + index: Arc::default(), + locks: Arc::default(), + in_flight: Arc::default(), + } + } + + pub(crate) async fn listen( + &self, + receiver: UnboundedReceiver, + ) -> Result<(), ResolveError> { + self.fetch(receiver).await + } + + /// Emit a request to fetch the metadata for a registry-based package. + pub(crate) fn fetch_package( + &self, + sender: &UnboundedSender, + package_name: &PackageName, + ) -> Result { + Ok( + if self.in_flight.lock().unwrap().insert_package(package_name) { + sender.unbounded_send(Request::Package(package_name.clone()))?; + true + } else { + false + }, + ) + } + + /// Emit a request to fetch the metadata for a direct URL-based package. + pub(crate) fn fetch_url( + &self, + sender: &UnboundedSender, + package_name: &PackageName, + url: &Url, + ) -> Result { + Ok(if self.in_flight.lock().unwrap().insert_url(url) { + sender.unbounded_send(Request::Dist(Dist::from_url( + package_name.clone(), + url.clone(), + )))?; + true + } else { + false + }) + } + + /// Emit a request to fetch the metadata for an individual distribution. + pub(crate) fn fetch_file( + &self, + sender: &UnboundedSender, + package_name: &PackageName, + version: &Version, + file: &DistFile, + ) -> Result { + Ok(if self.in_flight.lock().unwrap().insert_file(file) { + let distribution = + Dist::from_registry(package_name.clone(), version.clone(), file.clone().into()); + sender.unbounded_send(Request::Dist(distribution))?; + true + } else { + false + }) + } + + pub(crate) fn get_package( + &self, + package_name: &PackageName, + ) -> Option> { + self.index.packages.get(package_name) + } + + pub(crate) async fn wait_package( + &self, + sender: &UnboundedSender, + package_name: &PackageName, + ) -> Ref { + self.fetch_package(sender, package_name) + .expect("Failed to emit request"); + self.index.packages.wait(package_name).await.unwrap() + } + + pub(crate) async fn wait_url( + &self, + sender: &UnboundedSender, + package_name: &PackageName, + url: &Url, + ) -> Ref { + self.fetch_url(sender, package_name, url) + .expect("Failed to emit request"); + self.index + .distributions + .wait(&url.distribution_id()) + .await + .unwrap() + } + + pub(crate) async fn wait_file( + &self, + sender: &UnboundedSender, + package_name: &PackageName, + version: &Version, + file: &DistFile, + ) -> Ref { + self.fetch_file(sender, package_name, version, file) + .expect("Failed to emit request"); + self.index + .distributions + .wait(&file.distribution_id()) + .await + .unwrap() + } + + /// Fetch the metadata for a stream of packages and versions. + async fn fetch(&self, request_stream: UnboundedReceiver) -> Result<(), ResolveError> { + let mut response_stream = request_stream + .map(|request| self.process_request(request)) + .buffer_unordered(50); + + while let Some(response) = response_stream.next().await { + match response? { + Response::Package(package_name, metadata) => { + info!("Received package metadata for: {package_name}"); + + // Group the distributions by version and kind, discarding any incompatible + // distributions. + let mut version_map: VersionMap = BTreeMap::new(); + for file in metadata.files { + if let Ok(filename) = WheelFilename::from_str(file.filename.as_str()) { + if filename.is_compatible(&self.tags) { + match version_map.entry(filename.version) { + std::collections::btree_map::Entry::Occupied(mut entry) => { + if matches!(entry.get(), DistFile::Sdist(_)) { + // Wheels get precedence over source distributions. + entry.insert(DistFile::from(WheelFile(file))); + } + } + std::collections::btree_map::Entry::Vacant(entry) => { + entry.insert(DistFile::from(WheelFile(file))); + } + } + } + } else if let Ok(filename) = + SourceDistFilename::parse(file.filename.as_str(), &package_name) + { + if let std::collections::btree_map::Entry::Vacant(entry) = + version_map.entry(filename.version) + { + entry.insert(DistFile::from(SdistFile(file))); + } + } + } + + self.index + .packages + .insert(package_name.clone(), version_map); + } + Response::Dist(Dist::Built(distribution), metadata, ..) => { + info!("Received built distribution metadata for: {distribution}"); + self.index + .distributions + .insert(distribution.distribution_id(), metadata); + } + Response::Dist(Dist::Source(distribution), metadata, precise) => { + info!("Received source distribution metadata for: {distribution}"); + self.index + .distributions + .insert(distribution.distribution_id(), metadata); + if let Some(precise) = precise { + match distribution { + SourceDist::DirectUrl(sdist) => { + self.index.redirects.insert(sdist.url.clone(), precise); + } + SourceDist::Git(sdist) => { + self.index.redirects.insert(sdist.url.clone(), precise); + } + SourceDist::Registry(_) => {} + } + } + } + } + } + + Ok::<(), ResolveError>(()) + } + + async fn process_request(&self, request: Request) -> Result { + match request { + // Fetch package metadata from the registry. + Request::Package(package_name) => { + info!("Fetching package metadata for: {package_name}"); + + self.client + .simple(package_name.clone()) + .map_ok(move |metadata| Response::Package(package_name, metadata)) + .map_err(ResolveError::Client) + .await + } + + // Fetch wheel metadata. + Request::Dist(Dist::Built(distribution)) => { + info!("Fetching built distribution metadata for: {distribution}"); + + let metadata = match &distribution { + BuiltDist::Registry(wheel) => { + self.client + .wheel_metadata(wheel.file.clone()) + .map_err(ResolveError::Client) + .await? + } + BuiltDist::DirectUrl(wheel) => { + let fetcher = BuiltDistFetcher::new(self.build_context.cache()); + match fetcher.find_dist_info(wheel, &self.tags) { + Ok(Some(metadata)) => { + debug!("Found wheel metadata in cache: {wheel}"); + metadata + } + Ok(None) => { + debug!("Downloading wheel: {wheel}"); + fetcher.download_wheel(wheel, &self.client).await.map_err( + |err| ResolveError::from_built_dist(distribution.clone(), err), + )? + } + Err(err) => { + error!("Failed to read wheel from cache: {err}"); + fetcher.download_wheel(wheel, &self.client).await.map_err( + |err| ResolveError::from_built_dist(distribution.clone(), err), + )? + } + } + } + }; + + if metadata.name != *distribution.name() { + return Err(ResolveError::NameMismatch { + metadata: metadata.name, + given: distribution.name().clone(), + }); + } + + Ok(Response::Dist(Dist::Built(distribution), metadata, None)) + } + + // Fetch source distribution metadata. + Request::Dist(Dist::Source(sdist)) => { + info!("Fetching source distribution metadata for: {sdist}"); + + let lock = self.locks.acquire(&sdist).await; + let _guard = lock.lock().await; + + let fetcher = SourceDistFetcher::new(&self.build_context); + + let precise = fetcher + .precise(&sdist) + .await + .map_err(|err| ResolveError::from_source_dist(sdist.clone(), err))?; + + let metadata = { + // Insert the `precise`, if it exists. + let sdist = match sdist.clone() { + SourceDist::DirectUrl(sdist) => { + SourceDist::DirectUrl(DirectUrlSourceDist { + url: precise.clone().unwrap_or_else(|| sdist.url.clone()), + ..sdist + }) + } + SourceDist::Git(sdist) => SourceDist::Git(GitSourceDist { + url: precise.clone().unwrap_or_else(|| sdist.url.clone()), + ..sdist + }), + sdist @ SourceDist::Registry(_) => sdist, + }; + + match fetcher.find_dist_info(&sdist, &self.tags) { + Ok(Some(metadata)) => { + debug!("Found source distribution metadata in cache: {sdist}"); + metadata + } + Ok(None) => { + debug!("Downloading source distribution: {sdist}"); + fetcher + .download_and_build_sdist(&sdist, &self.client) + .await + .map_err(|err| ResolveError::from_source_dist(sdist.clone(), err))? + } + Err(err) => { + error!("Failed to read source distribution from cache: {err}",); + fetcher + .download_and_build_sdist(&sdist, &self.client) + .await + .map_err(|err| ResolveError::from_source_dist(sdist.clone(), err))? + } + } + }; + + if metadata.name != *sdist.name() { + return Err(ResolveError::NameMismatch { + metadata: metadata.name, + given: sdist.name().clone(), + }); + } + + Ok(Response::Dist(Dist::Source(sdist), metadata, precise)) + } + } + } +} + +/// Fetch the metadata for an item +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub(crate) enum Request { + /// A request to fetch the metadata for a package. + Package(PackageName), + /// A request to fetch the metadata for a built or source distribution. + Dist(Dist), +} + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub(crate) enum Response { + /// The returned metadata for a package hosted on a registry. + Package(PackageName, SimpleJson), + /// The returned metadata for a distribution. + Dist(Dist, Metadata21, Option), +} + +/// In-memory index of in-flight network requests. Any request in an [`InFlight`] state will be +/// eventually be inserted into an [`Index`]. +#[derive(Debug, Default)] +struct InFlight { + /// The set of requested [`PackageName`]s. + packages: FxHashSet, + /// The set of requested registry-based files, represented by their SHAs. + files: FxHashSet, + /// The set of requested URLs. + urls: FxHashSet, +} + +impl InFlight { + fn insert_package(&mut self, package_name: &PackageName) -> bool { + self.packages.insert(package_name.clone()) + } + + fn insert_file(&mut self, file: &DistFile) -> bool { + match file { + DistFile::Wheel(file) => self.files.insert(file.hashes.sha256.clone()), + DistFile::Sdist(file) => self.files.insert(file.hashes.sha256.clone()), + } + } + + fn insert_url(&mut self, url: &Url) -> bool { + self.urls.insert(url.clone()) + } +} + +/// In-memory index of package metadata. +struct Index { + /// A map from package name to the metadata for that package. + packages: WaitMap, + + /// A map from distribution SHA to metadata for that distribution. + distributions: WaitMap, + + /// A map from source URL to precise URL. + redirects: WaitMap, +} + +impl Default for Index { + fn default() -> Self { + Self { + packages: WaitMap::new(), + distributions: WaitMap::new(), + redirects: WaitMap::new(), + } + } +} diff --git a/crates/puffin-resolver/src/error.rs b/crates/puffin-resolver/src/error.rs index 48089c963..d036471d7 100644 --- a/crates/puffin-resolver/src/error.rs +++ b/crates/puffin-resolver/src/error.rs @@ -9,8 +9,7 @@ use pep508_rs::Requirement; use puffin_distribution::{BuiltDist, SourceDist}; use puffin_normalize::PackageName; -use crate::pubgrub::{PubGrubPackage, PubGrubVersion}; -use crate::ResolutionFailureReporter; +use crate::pubgrub::{PubGrubPackage, PubGrubVersion, ResolutionFailureReporter}; #[derive(Error, Debug)] pub enum ResolveError { @@ -32,6 +31,9 @@ pub enum ResolveError { #[error(transparent)] PubGrub(#[from] RichPubGrubError), + #[error(transparent)] + Resolvo(anyhow::Error), + #[error("Package metadata name `{metadata}` does not match given name `{given}`")] NameMismatch { given: PackageName, diff --git a/crates/puffin-resolver/src/file.rs b/crates/puffin-resolver/src/file.rs index 64249244a..2b16ffe0a 100644 --- a/crates/puffin-resolver/src/file.rs +++ b/crates/puffin-resolver/src/file.rs @@ -1,3 +1,4 @@ +use puffin_distribution::Identifier; use std::ops::Deref; use pypi_types::File; @@ -72,3 +73,19 @@ impl From for File { } } } + +impl Identifier for DistFile { + fn distribution_id(&self) -> String { + match self { + DistFile::Wheel(file) => file.distribution_id(), + DistFile::Sdist(file) => file.distribution_id(), + } + } + + fn resource_id(&self) -> String { + match self { + DistFile::Wheel(file) => file.resource_id(), + DistFile::Sdist(file) => file.resource_id(), + } + } +} diff --git a/crates/puffin-resolver/src/lib.rs b/crates/puffin-resolver/src/lib.rs index 00b350816..d85812d7d 100644 --- a/crates/puffin-resolver/src/lib.rs +++ b/crates/puffin-resolver/src/lib.rs @@ -2,12 +2,10 @@ pub use error::ResolveError; pub use finder::{DistFinder, Reporter as FinderReporter}; pub use manifest::Manifest; pub use prerelease_mode::PreReleaseMode; -pub use pubgrub::ResolutionFailureReporter; pub use resolution::Graph; pub use resolution_mode::ResolutionMode; -pub use resolver::{BuildId, Reporter as ResolverReporter, Resolver}; -mod candidate_selector; +mod database; mod distribution; mod error; mod file; @@ -15,7 +13,7 @@ mod finder; mod locks; mod manifest; mod prerelease_mode; -mod pubgrub; +pub mod pubgrub; mod resolution; mod resolution_mode; -mod resolver; +pub mod resolvo; diff --git a/crates/puffin-resolver/src/candidate_selector.rs b/crates/puffin-resolver/src/pubgrub/candidate_selector.rs similarity index 99% rename from crates/puffin-resolver/src/candidate_selector.rs rename to crates/puffin-resolver/src/pubgrub/candidate_selector.rs index dfc15e98e..aa6cec10d 100644 --- a/crates/puffin-resolver/src/candidate_selector.rs +++ b/crates/puffin-resolver/src/pubgrub/candidate_selector.rs @@ -6,9 +6,9 @@ use puffin_normalize::PackageName; use crate::file::DistFile; use crate::prerelease_mode::PreReleaseStrategy; +use crate::pubgrub::resolver::VersionMap; use crate::pubgrub::PubGrubVersion; use crate::resolution_mode::ResolutionStrategy; -use crate::resolver::VersionMap; use crate::Manifest; #[derive(Debug)] diff --git a/crates/puffin-resolver/src/pubgrub/mod.rs b/crates/puffin-resolver/src/pubgrub/mod.rs index 1f52e13ed..47966789e 100644 --- a/crates/puffin-resolver/src/pubgrub/mod.rs +++ b/crates/puffin-resolver/src/pubgrub/mod.rs @@ -1,14 +1,16 @@ +pub(crate) use crate::pubgrub::candidate_selector::CandidateSelector; +pub(crate) use crate::pubgrub::dependencies::PubGrubDependencies; pub(crate) use crate::pubgrub::package::PubGrubPackage; pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority}; pub use crate::pubgrub::report::ResolutionFailureReporter; - +pub use crate::pubgrub::resolver::{BuildId, Reporter as ResolverReporter, Resolver}; pub(crate) use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION}; -pub(crate) use crate::pubgrub::dependencies::PubGrubDependencies; - +mod candidate_selector; mod dependencies; mod package; mod priority; mod report; +mod resolver; mod specifier; mod version; diff --git a/crates/puffin-resolver/src/resolver.rs b/crates/puffin-resolver/src/pubgrub/resolver.rs similarity index 99% rename from crates/puffin-resolver/src/resolver.rs rename to crates/puffin-resolver/src/pubgrub/resolver.rs index 18bd03533..009e2ceb4 100644 --- a/crates/puffin-resolver/src/resolver.rs +++ b/crates/puffin-resolver/src/pubgrub/resolver.rs @@ -30,14 +30,14 @@ use puffin_normalize::{ExtraName, PackageName}; use puffin_traits::BuildContext; use pypi_types::{File, Metadata21, SimpleJson}; -use crate::candidate_selector::CandidateSelector; use crate::distribution::{BuiltDistFetcher, SourceDistFetcher, SourceDistributionReporter}; use crate::error::ResolveError; use crate::file::{DistFile, SdistFile, WheelFile}; use crate::locks::Locks; use crate::manifest::Manifest; use crate::pubgrub::{ - PubGrubDependencies, PubGrubPackage, PubGrubPriorities, PubGrubVersion, MIN_VERSION, + CandidateSelector, PubGrubDependencies, PubGrubPackage, PubGrubPriorities, PubGrubVersion, + MIN_VERSION, }; use crate::resolution::Graph; diff --git a/crates/puffin-resolver/src/resolution.rs b/crates/puffin-resolver/src/resolution.rs index 29e976543..182ce8090 100644 --- a/crates/puffin-resolver/src/resolution.rs +++ b/crates/puffin-resolver/src/resolution.rs @@ -2,6 +2,7 @@ use std::hash::BuildHasherDefault; use colored::Colorize; use fxhash::FxHashMap; +use itertools::Itertools; use petgraph::visit::EdgeRef; use pubgrub::range::Range; use pubgrub::solver::{Kind, State}; @@ -48,6 +49,26 @@ impl Resolution { } } +impl std::fmt::Display for Resolution { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for dist in self.0.values().sorted_unstable_by_key(|dist| dist.name()) { + writeln!(f, "{dist}")?; + } + Ok(()) + } +} + +impl From for Resolution { + fn from(value: Graph) -> Self { + let mut packages = + FxHashMap::with_capacity_and_hasher(value.len(), BuildHasherDefault::default()); + for package in value.0.node_indices().map(|node| &value.0[node]) { + packages.insert(package.name().clone(), package.clone()); + } + Self(packages) + } +} + /// A complete resolution graph in which every node represents a pinned package and every edge /// represents a dependency between two pinned packages. #[derive(Debug)] diff --git a/crates/puffin-resolver/src/resolvo/mod.rs b/crates/puffin-resolver/src/resolvo/mod.rs new file mode 100644 index 000000000..b03b663ae --- /dev/null +++ b/crates/puffin-resolver/src/resolvo/mod.rs @@ -0,0 +1,8 @@ +pub(crate) use package::ResolvoPackage; +pub use resolver::resolve; +pub(crate) use version::{ResolvoVersion, ResolvoVersionSet}; + +mod package; +mod provider; +mod resolver; +mod version; diff --git a/crates/puffin-resolver/src/resolvo/package.rs b/crates/puffin-resolver/src/resolvo/package.rs new file mode 100644 index 000000000..1149bf2b8 --- /dev/null +++ b/crates/puffin-resolver/src/resolvo/package.rs @@ -0,0 +1,34 @@ +use puffin_normalize::{ExtraName, PackageName}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) enum ResolvoPackage { + Package(PackageName), + Extra(PackageName, ExtraName), +} + +impl ResolvoPackage { + /// Return the [`PackageName`] of the [`ResolvoPackage`]. + pub(crate) fn name(&self) -> &PackageName { + match self { + ResolvoPackage::Package(name) => name, + ResolvoPackage::Extra(name, ..) => name, + } + } + + /// Return the [`ExtraName`] of the [`ResolvoPackage`], if any. + pub(crate) fn extra(&self) -> Option<&ExtraName> { + match self { + ResolvoPackage::Package(_) => None, + ResolvoPackage::Extra(_, extra) => Some(extra), + } + } +} + +impl std::fmt::Display for ResolvoPackage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ResolvoPackage::Package(name) => write!(f, "{name}"), + ResolvoPackage::Extra(name, extra) => write!(f, "{name}[{extra}]"), + } + } +} diff --git a/crates/puffin-resolver/src/resolvo/provider.rs b/crates/puffin-resolver/src/resolvo/provider.rs new file mode 100644 index 000000000..d7b1e2c20 --- /dev/null +++ b/crates/puffin-resolver/src/resolvo/provider.rs @@ -0,0 +1,229 @@ +use std::sync::Arc; + +use futures::channel::mpsc::UnboundedSender; +use resolvo::{Candidates, Dependencies, NameId, Pool, SolvableId, SolverCache}; +use tokio::runtime::Handle; +use tracing::info; + +use pep440_rs::{VersionSpecifier, VersionSpecifiers}; +use pep508_rs::{MarkerEnvironment, VersionOrUrl}; +use puffin_distribution::Dist; +use puffin_traits::BuildContext; + +use crate::database::{Database, Request}; +use crate::file::DistFile; +use crate::resolvo::{ResolvoPackage, ResolvoVersion, ResolvoVersionSet}; + +/// A [`resolvo::DependencyProvider`] that uses a [`Database`] to fetch dependencies. +pub(crate) struct ResolvoDependencyProvider { + database: Arc>, + sender: UnboundedSender, + markers: MarkerEnvironment, + pool: Pool, +} + +impl ResolvoDependencyProvider { + /// Initialize a new [`ResolvoDependencyProvider`] with the given [`Database`]. + pub(crate) fn new( + database: Arc>, + sender: UnboundedSender, + markers: MarkerEnvironment, + ) -> Self { + Self { + database, + sender, + markers, + pool: Pool::new(), + } + } + + /// Return the underlying [`Pool`]. + pub(crate) fn pool(&self) -> &Pool { + &self.pool + } + + /// Convert a [`SolvableId`] into a [`Dist`]. + pub(crate) fn dist(&self, solvable: SolvableId) -> Dist { + let solvable = self.pool.resolve_solvable(solvable); + let package = self.pool.resolve_package_name(solvable.name_id()); + match solvable.inner() { + ResolvoVersion::Version(version) => { + let metadata = self.database.get_package(package.name()).unwrap(); + let version_map = metadata.value(); + let file = version_map.get(&version.clone()).unwrap(); + match file { + DistFile::Wheel(file) => Dist::from_registry( + package.name().clone(), + version.clone(), + file.clone().into(), + ), + DistFile::Sdist(file) => Dist::from_registry( + package.name().clone(), + version.clone(), + file.clone().into(), + ), + } + } + ResolvoVersion::Url(url) => Dist::from_url(package.name().clone(), url.clone()), + } + } +} + +impl resolvo::DependencyProvider + for &ResolvoDependencyProvider +{ + fn pool(&self) -> &Pool { + &self.pool + } + + /// Sort candidates such that the highest version is preferred. + fn sort_candidates( + &self, + solver: &SolverCache, + solvables: &mut [SolvableId], + ) { + solvables.sort_by_key(|&solvable| { + let solvable = solver.pool().resolve_solvable(solvable); + std::cmp::Reverse(solvable.inner()) + }); + } + + /// Return all candidate distributions for a given package. + fn get_candidates(&self, name: NameId) -> Option { + let package = self.pool.resolve_package_name(name); + let package_name = package.name(); + + info!("Fetching candidates for: {package_name}"); + + // Get the metadata for this package, which includes the `VersionMap`. + let entry = tokio::task::block_in_place(|| { + Handle::current().block_on(self.database.wait_package(&self.sender, package.name())) + }); + let version_map = entry.value(); + + // Create a candidate for each version in the `VersionMap`. + let mut candidates = Candidates::default(); + for version in version_map.keys() { + // TODO(charlie): Implement proper pre-release support. + if version.any_prerelease() { + continue; + } + let solvable_id = self + .pool + .intern_solvable(name, ResolvoVersion::Version(version.clone())); + candidates.candidates.push(solvable_id); + } + + Some(candidates) + } + + fn get_dependencies(&self, solvable: SolvableId) -> Dependencies { + let solvable = self.pool.resolve_solvable(solvable); + let package = self.pool.resolve_package_name(solvable.name_id()); + let package_name = package.name(); + let extra = package.extra(); + + info!("Fetching dependencies for: {package_name}"); + + let entry = match solvable.inner() { + ResolvoVersion::Version(version) => { + let metadata = self.database.get_package(package_name).unwrap(); + let version_map = metadata.value(); + let file = version_map.get(&version.clone()).unwrap(); + tokio::task::block_in_place(|| { + Handle::current().block_on(self.database.wait_file( + &self.sender, + package_name, + version, + file, + )) + }) + } + ResolvoVersion::Url(url) => tokio::task::block_in_place(|| { + Handle::current().block_on(self.database.wait_url(&self.sender, package_name, url)) + }), + }; + let metadata = entry.value(); + + let mut dependencies = Dependencies::default(); + + match package { + ResolvoPackage::Package(package_name) => { + // Ensure that extra packages are pinned to the same version as the base package. + for extra in &metadata.provides_extras { + let solvable = self.pool.intern_package_name(ResolvoPackage::Extra( + package_name.clone(), + extra.clone(), + )); + let specifiers = + VersionSpecifiers::from_iter([VersionSpecifier::equals_version( + metadata.version.clone(), + )]); + let version_set_id = self.pool.intern_version_set( + solvable, + Some(VersionOrUrl::VersionSpecifier(specifiers)).into(), + ); + dependencies.constrains.push(version_set_id); + } + } + ResolvoPackage::Extra(package_name, _extra) => { + // Mark the extra as a dependency of the base package. + let ResolvoVersion::Version(package_version) = solvable.inner() else { + unreachable!("extra should only be set for registry packages"); + }; + + let base_name_id = self + .pool + .lookup_package_name(&ResolvoPackage::Package(package_name.clone())) + .expect("extra should have base"); + let specifiers = VersionSpecifiers::from_iter([VersionSpecifier::equals_version( + package_version.clone(), + )]); + let version_set_id = self.pool.intern_version_set( + base_name_id, + Some(VersionOrUrl::VersionSpecifier(specifiers)).into(), + ); + dependencies.requirements.push(version_set_id); + } + } + + // Iterate over all declared requirements. + for requirement in &metadata.requires_dist { + // If the requirement isn't relevant for the current platform, skip it. + if let Some(extra) = extra { + if !requirement.evaluate_markers(&self.markers, &[extra.as_ref()]) { + continue; + } + } else { + if !requirement.evaluate_markers(&self.markers, &[]) { + continue; + } + } + + // Add a dependency on the package itself. + let dependency_name_id = self + .pool + .intern_package_name(ResolvoPackage::Package(requirement.name.clone())); + let version_set_id = self.pool.intern_version_set( + dependency_name_id, + requirement.version_or_url.clone().into(), + ); + dependencies.requirements.push(version_set_id); + + // Add an additional package for each extra. + for extra in requirement.extras.iter().flatten() { + let dependency_name_id = self.pool.intern_package_name(ResolvoPackage::Extra( + requirement.name.clone(), + extra.clone(), + )); + let version_set_id = self.pool.intern_version_set( + dependency_name_id, + requirement.version_or_url.clone().into(), + ); + dependencies.requirements.push(version_set_id); + } + } + + dependencies + } +} diff --git a/crates/puffin-resolver/src/resolvo/resolver.rs b/crates/puffin-resolver/src/resolvo/resolver.rs new file mode 100644 index 000000000..74e544136 --- /dev/null +++ b/crates/puffin-resolver/src/resolvo/resolver.rs @@ -0,0 +1,101 @@ +use std::sync::Arc; + +use anyhow::Result; +use fxhash::FxHashMap; +use resolvo::DefaultSolvableDisplay; +use resolvo::Solver; + +use pep508_rs::MarkerEnvironment; +use platform_tags::Tags; +use puffin_client::RegistryClient; +use puffin_traits::BuildContext; + +use crate::database::Database; +use crate::resolution::Resolution; +use crate::resolvo::provider::ResolvoDependencyProvider; +use crate::resolvo::ResolvoPackage; +use crate::{Manifest, ResolveError}; + +/// Resolve a [`Manifest`] into a [`Resolution`]. +pub async fn resolve( + manifest: Manifest, + markers: MarkerEnvironment, + tags: Tags, + client: RegistryClient, + build_context: Context, +) -> Result { + let database = Arc::new(Database::new(tags, client, build_context)); + + // 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). + let (request_sink, request_stream) = futures::channel::mpsc::unbounded(); + + // Run the fetcher. + let requests_fut = database.listen(request_stream); + + // Construct a provider + let provider = ResolvoDependencyProvider::new(database.clone(), request_sink, markers); + + // Generate the root requirements. + let pool = provider.pool(); + let mut root_requirements = Vec::with_capacity(manifest.requirements.len()); + for requirement in &manifest.requirements { + let package_name = + pool.intern_package_name(ResolvoPackage::Package(requirement.name.clone())); + let version_set_id = + pool.intern_version_set(package_name, requirement.version_or_url.clone().into()); + root_requirements.push(version_set_id); + + for extra in requirement.extras.iter().flatten() { + let dependency_package_name = pool.intern_package_name(ResolvoPackage::Extra( + requirement.name.clone(), + extra.clone(), + )); + let version_set_id = pool.intern_version_set( + dependency_package_name, + requirement.version_or_url.clone().into(), + ); + root_requirements.push(version_set_id); + } + } + + // Run the solver. + let resolve_fut = tokio::task::spawn_blocking(move || solve(&provider, root_requirements)); + + // The requests stream should terminate before the solver. + requests_fut.await?; + let resolution = resolve_fut.await??; + + Ok(resolution) +} + +/// Run the Resolvo solver. +fn solve( + provider: &ResolvoDependencyProvider, + root_requirements: Vec, +) -> Result { + // Run the solver itself. + let mut solver = Solver::new(provider); + let solvables = match solver.solve(root_requirements) { + Ok(solvables) => solvables, + Err(err) => { + return Err(ResolveError::Resolvo(anyhow::anyhow!( + "{}", + err.display_user_friendly(&solver, &DefaultSolvableDisplay) + .to_string() + .trim() + ))); + } + }; + + // Convert the solution to a `Resolution`. + let pool = provider.pool(); + let mut packages = FxHashMap::default(); + for solvable_id in solvables { + let solvable = pool.resolve_solvable(solvable_id); + let package = pool.resolve_package_name(solvable.name_id()); + packages.insert(package.name().clone(), provider.dist(solvable_id)); + } + + Ok(Resolution::new(packages)) +} diff --git a/crates/puffin-resolver/src/resolvo/version.rs b/crates/puffin-resolver/src/resolvo/version.rs new file mode 100644 index 000000000..eba714ec2 --- /dev/null +++ b/crates/puffin-resolver/src/resolvo/version.rs @@ -0,0 +1,57 @@ +use resolvo::VersionSet; + +/// A wrapper around [`pep508_rs::VersionOrUrl`] that implements [`VersionSet`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct ResolvoVersionSet(Option); + +impl From> for ResolvoVersionSet { + fn from(value: Option) -> Self { + Self(value) + } +} + +impl std::fmt::Display for ResolvoVersionSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.0 { + None => write!(f, "*"), + Some(pep508_rs::VersionOrUrl::VersionSpecifier(specifiers)) => { + write!(f, "{specifiers}") + } + Some(pep508_rs::VersionOrUrl::Url(url)) => write!(f, "{url}"), + } + } +} + +#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] +#[allow(dead_code)] +pub(crate) enum ResolvoVersion { + Version(pep440_rs::Version), + Url(url::Url), +} + +impl VersionSet for ResolvoVersionSet { + type V = ResolvoVersion; + + fn contains(&self, version: &Self::V) -> bool { + match (self.0.as_ref(), version) { + ( + Some(pep508_rs::VersionOrUrl::VersionSpecifier(specifiers)), + ResolvoVersion::Version(version), + ) => specifiers.contains(version), + (Some(pep508_rs::VersionOrUrl::Url(url_a)), ResolvoVersion::Url(url_b)) => { + url_a == url_b + } + (None, _) => true, + _ => false, + } + } +} + +impl std::fmt::Display for ResolvoVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ResolvoVersion::Version(v) => write!(f, "{v}"), + ResolvoVersion::Url(u) => write!(f, "{u}"), + } + } +} diff --git a/crates/puffin-resolver/tests/resolver.rs b/crates/puffin-resolver/tests/resolver.rs index 874516916..21b0afe65 100644 --- a/crates/puffin-resolver/tests/resolver.rs +++ b/crates/puffin-resolver/tests/resolver.rs @@ -17,7 +17,7 @@ use platform_host::{Arch, Os, Platform}; use platform_tags::Tags; use puffin_client::RegistryClientBuilder; use puffin_interpreter::{InterpreterInfo, Virtualenv}; -use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode, Resolver}; +use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode}; use puffin_traits::BuildContext; struct DummyContext; @@ -77,7 +77,13 @@ async fn black() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -101,7 +107,13 @@ async fn black_colorama() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -125,7 +137,13 @@ async fn black_python_310() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_310, &TAGS_310, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_310, + &TAGS_310, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -151,7 +169,13 @@ async fn black_mypy_extensions() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -177,7 +201,13 @@ async fn black_mypy_extensions_extra() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -203,7 +233,13 @@ async fn black_flake8() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -227,7 +263,13 @@ async fn black_lowest() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -251,7 +293,13 @@ async fn black_lowest_direct() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -275,7 +323,13 @@ async fn black_respect_preference() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -299,7 +353,13 @@ async fn black_ignore_preference() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -323,7 +383,13 @@ async fn black_disallow_prerelease() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let err = resolver.resolve().await.unwrap_err(); insta::assert_display_snapshot!(err); @@ -347,7 +413,13 @@ async fn black_allow_prerelease_if_necessary() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await.unwrap_err(); insta::assert_display_snapshot!(resolution); @@ -371,7 +443,13 @@ async fn pylint_disallow_prerelease() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -395,7 +473,13 @@ async fn pylint_allow_prerelease() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -422,7 +506,13 @@ async fn pylint_allow_explicit_prerelease_without_marker() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -449,7 +539,13 @@ async fn pylint_allow_explicit_prerelease_with_marker() -> Result<()> { None, ); - let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolver = puffin_resolver::pubgrub::Resolver::new( + manifest, + &MARKERS_311, + &TAGS_311, + &client, + &DummyContext, + ); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); diff --git a/scripts/benchmarks/requirements.in b/scripts/benchmarks/requirements.in index 7173573c4..8282247cd 100644 --- a/scripts/benchmarks/requirements.in +++ b/scripts/benchmarks/requirements.in @@ -2,10 +2,7 @@ # A small set of pure-Python packages. ### packaging>=23.1 -pygls>=1.0.1 -lsprotocol>=2023.0.0a1 ruff>=0.0.274 -flask @ git+https://github.com/pallets/flask.git@d92b64a typing_extensions scipy numpy @@ -23,10 +20,8 @@ trio<0.20 trio-websocket trio-asyncio trio-typing -trio-protocol fastapi typer pydantic uvicorn traitlets -