Add an option to use Resolvo in lieu of PubGrub

This commit is contained in:
Charlie Marsh 2023-11-10 14:33:53 -05:00
parent beadd3274a
commit 40bc049b51
22 changed files with 1135 additions and 58 deletions

68
Cargo.lock generated
View File

@ -335,6 +335,18 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -877,6 +889,15 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 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]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "0.3.6" version = "0.3.6"
@ -985,6 +1006,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.29" version = "0.3.29"
@ -2589,6 +2616,7 @@ dependencies = [
"puffin-normalize", "puffin-normalize",
"puffin-traits", "puffin-traits",
"pypi-types", "pypi-types",
"resolvo",
"sha2", "sha2",
"tempfile", "tempfile",
"thiserror", "thiserror",
@ -2753,6 +2781,12 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49" checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49"
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -3000,6 +3034,19 @@ dependencies = [
"wasm-timer", "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]] [[package]]
name = "retry-policies" name = "retry-policies"
version = "0.2.1" version = "0.2.1"
@ -3328,6 +3375,12 @@ dependencies = [
"xxhash-rust", "xxhash-rust",
] ]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "stacker" name = "stacker"
version = "0.1.15" version = "0.1.15"
@ -3418,6 +3471,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.40" version = "0.4.40"
@ -4379,6 +4438,15 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "xattr" name = "xattr"
version = "1.0.1" version = "1.0.1"

View File

@ -4,7 +4,7 @@ use std::time::Duration;
pub(crate) use add::add; pub(crate) use add::add;
pub(crate) use clean::clean; pub(crate) use clean::clean;
pub(crate) use freeze::freeze; 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_sync::pip_sync;
pub(crate) use pip_uninstall::pip_uninstall; pub(crate) use pip_uninstall::pip_uninstall;
pub(crate) use remove::remove; pub(crate) use remove::remove;

View File

@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::fmt::Write; use std::fmt::Write;
use std::io::{stdout, BufWriter}; use std::io::{stdout, BufWriter};
use std::path::Path; use std::path::Path;
@ -41,6 +40,7 @@ pub(crate) async fn pip_compile(
upgrade_mode: UpgradeMode, upgrade_mode: UpgradeMode,
index_urls: Option<IndexUrls>, index_urls: Option<IndexUrls>,
no_build: bool, no_build: bool,
resolver: Resolver,
python_version: Option<PythonVersion>, python_version: Option<PythonVersion>,
cache: &Path, cache: &Path,
mut printer: Printer, mut printer: Printer,
@ -122,8 +122,8 @@ pub(crate) async fn pip_compile(
// Determine the markers to use for resolution. // Determine the markers to use for resolution.
let markers = python_version.map_or_else( let markers = python_version.map_or_else(
|| Cow::Borrowed(venv.interpreter_info().markers()), || venv.interpreter_info().markers().clone(),
|python_version| Cow::Owned(python_version.markers(venv.interpreter_info().markers())), |python_version| python_version.markers(venv.interpreter_info().markers()),
); );
// Instantiate a client. // Instantiate a client.
@ -149,21 +149,35 @@ pub(crate) async fn pip_compile(
); );
// Resolve the dependencies. // Resolve the dependencies.
let resolver = let resolution = match resolver {
puffin_resolver::Resolver::new(manifest, &markers, &tags, &client, &build_dispatch) Resolver::Pubgrub => {
let resolver = puffin_resolver::pubgrub::Resolver::new(
manifest,
&markers,
&tags,
&client,
&build_dispatch,
)
.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(err)) => { Err(puffin_resolver::ResolveError::PubGrub(err)) => {
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
{ {
let report = miette::Report::msg(format!("{err}")) let report = miette::Report::msg(format!("{err}"))
.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);
}
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" }; let s = if resolution.len() == 1 { "" } else { "s" };
writeln!( writeln!(
@ -232,6 +246,13 @@ impl From<bool> 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> { pub(crate) fn extra_name_with_clap_error(arg: &str) -> Result<ExtraName> {
ExtraName::from_str(arg).map_err(|_err| { ExtraName::from_str(arg).map_err(|_err| {
anyhow!( anyhow!(

View File

@ -217,7 +217,7 @@ impl From<Printer> for ResolverReporter {
} }
} }
impl puffin_resolver::ResolverReporter for ResolverReporter { impl puffin_resolver::pubgrub::ResolverReporter for ResolverReporter {
fn on_progress( fn on_progress(
&self, &self,
name: &PackageName, name: &PackageName,

View File

@ -13,7 +13,7 @@ use puffin_normalize::{ExtraName, PackageName};
use puffin_resolver::{PreReleaseMode, ResolutionMode}; use puffin_resolver::{PreReleaseMode, ResolutionMode};
use requirements::ExtrasSpecification; 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::index_urls::IndexUrls;
use crate::python_version::PythonVersion; use crate::python_version::PythonVersion;
use crate::requirements::RequirementsSource; use crate::requirements::RequirementsSource;
@ -135,6 +135,10 @@ struct PipCompileArgs {
#[clap(long)] #[clap(long)]
no_build: bool, no_build: bool,
/// The resolver to use when resolving dependencies.
#[arg(long, value_enum)]
resolver: Resolver,
/// The minimum Python version that should be supported. /// The minimum Python version that should be supported.
#[arg(long, short, value_enum)] #[arg(long, short, value_enum)]
python_version: Option<PythonVersion>, python_version: Option<PythonVersion>,
@ -271,6 +275,7 @@ async fn inner() -> Result<ExitStatus> {
args.upgrade.into(), args.upgrade.into(),
index_urls, index_urls,
args.no_build, args.no_build,
args.resolver,
args.python_version, args.python_version,
&cache_dir, &cache_dir,
printer, printer,

View File

@ -18,7 +18,7 @@ use puffin_client::RegistryClient;
use puffin_distribution::Metadata; use puffin_distribution::Metadata;
use puffin_installer::{Builder, Downloader, InstallPlan, Installer, Unzipper}; use puffin_installer::{Builder, Downloader, InstallPlan, Installer, Unzipper};
use puffin_interpreter::{InterpreterInfo, Virtualenv}; 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; use puffin_traits::BuildContext;
/// The main implementation of [`BuildContext`], used by the CLI, see [`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.platform(),
self.interpreter_info.simple_version(), self.interpreter_info.simple_version(),
)?; )?;
let resolver = Resolver::new( let resolver = pubgrub::Resolver::new(
Manifest::new( Manifest::new(
requirements.to_vec(), requirements.to_vec(),
Vec::default(), Vec::default(),

View File

@ -29,12 +29,15 @@ anyhow = { workspace = true }
bitflags = { workspace = true } bitflags = { workspace = true }
clap = { workspace = true, features = ["derive"], optional = true } clap = { workspace = true, features = ["derive"], optional = true }
colored = { workspace = true } colored = { workspace = true }
derivative = { version = "2.2.0" }
fs-err = { workspace = true, features = ["tokio"] } fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true } futures = { workspace = true }
fxhash = { workspace = true } fxhash = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
petgraph = { workspace = true } petgraph = { workspace = true }
resolvo = { version = "0.2.0" }
sha2 = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
@ -43,8 +46,6 @@ tracing = { workspace = true }
url = { workspace = true } url = { workspace = true }
waitmap = { workspace = true } waitmap = { workspace = true }
zip = { workspace = true } zip = { workspace = true }
derivative = { version = "2.2.0" }
sha2 = { workspace = true }
[dev-dependencies] [dev-dependencies]
gourgeist = { path = "../gourgeist" } gourgeist = { path = "../gourgeist" }

View File

@ -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<Version, DistFile>;
pub(crate) struct Database<Context: BuildContext> {
client: RegistryClient,
tags: Tags,
build_context: Context,
index: Arc<Index>,
locks: Arc<Locks>,
in_flight: Arc<Mutex<InFlight>>,
}
impl<Context: BuildContext> Database<Context> {
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<Request>,
) -> 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<Request>,
package_name: &PackageName,
) -> Result<bool, ResolveError> {
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<Request>,
package_name: &PackageName,
url: &Url,
) -> Result<bool, ResolveError> {
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<Request>,
package_name: &PackageName,
version: &Version,
file: &DistFile,
) -> Result<bool, ResolveError> {
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<Ref<PackageName, VersionMap, RandomState>> {
self.index.packages.get(package_name)
}
pub(crate) async fn wait_package(
&self,
sender: &UnboundedSender<Request>,
package_name: &PackageName,
) -> Ref<PackageName, VersionMap, RandomState> {
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<Request>,
package_name: &PackageName,
url: &Url,
) -> Ref<String, Metadata21, RandomState> {
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<Request>,
package_name: &PackageName,
version: &Version,
file: &DistFile,
) -> Ref<String, Metadata21, RandomState> {
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<Request>) -> 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<Response, ResolveError> {
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<Url>),
}
/// 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<PackageName>,
/// The set of requested registry-based files, represented by their SHAs.
files: FxHashSet<String>,
/// The set of requested URLs.
urls: FxHashSet<Url>,
}
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<PackageName, VersionMap>,
/// A map from distribution SHA to metadata for that distribution.
distributions: WaitMap<String, Metadata21>,
/// A map from source URL to precise URL.
redirects: WaitMap<Url, Url>,
}
impl Default for Index {
fn default() -> Self {
Self {
packages: WaitMap::new(),
distributions: WaitMap::new(),
redirects: WaitMap::new(),
}
}
}

View File

@ -9,8 +9,7 @@ use pep508_rs::Requirement;
use puffin_distribution::{BuiltDist, SourceDist}; use puffin_distribution::{BuiltDist, SourceDist};
use puffin_normalize::PackageName; use puffin_normalize::PackageName;
use crate::pubgrub::{PubGrubPackage, PubGrubVersion}; use crate::pubgrub::{PubGrubPackage, PubGrubVersion, ResolutionFailureReporter};
use crate::ResolutionFailureReporter;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ResolveError { pub enum ResolveError {
@ -32,6 +31,9 @@ pub enum ResolveError {
#[error(transparent)] #[error(transparent)]
PubGrub(#[from] RichPubGrubError), PubGrub(#[from] RichPubGrubError),
#[error(transparent)]
Resolvo(anyhow::Error),
#[error("Package metadata name `{metadata}` does not match given name `{given}`")] #[error("Package metadata name `{metadata}` does not match given name `{given}`")]
NameMismatch { NameMismatch {
given: PackageName, given: PackageName,

View File

@ -1,3 +1,4 @@
use puffin_distribution::Identifier;
use std::ops::Deref; use std::ops::Deref;
use pypi_types::File; use pypi_types::File;
@ -72,3 +73,19 @@ impl From<DistFile> 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(),
}
}
}

View File

@ -2,12 +2,10 @@ pub use error::ResolveError;
pub use finder::{DistFinder, Reporter as FinderReporter}; pub use finder::{DistFinder, Reporter as FinderReporter};
pub use manifest::Manifest; pub use manifest::Manifest;
pub use prerelease_mode::PreReleaseMode; pub use prerelease_mode::PreReleaseMode;
pub use pubgrub::ResolutionFailureReporter;
pub use resolution::Graph; pub use resolution::Graph;
pub use resolution_mode::ResolutionMode; pub use resolution_mode::ResolutionMode;
pub use resolver::{BuildId, Reporter as ResolverReporter, Resolver};
mod candidate_selector; mod database;
mod distribution; mod distribution;
mod error; mod error;
mod file; mod file;
@ -15,7 +13,7 @@ mod finder;
mod locks; mod locks;
mod manifest; mod manifest;
mod prerelease_mode; mod prerelease_mode;
mod pubgrub; pub mod pubgrub;
mod resolution; mod resolution;
mod resolution_mode; mod resolution_mode;
mod resolver; pub mod resolvo;

View File

@ -6,9 +6,9 @@ use puffin_normalize::PackageName;
use crate::file::DistFile; use crate::file::DistFile;
use crate::prerelease_mode::PreReleaseStrategy; use crate::prerelease_mode::PreReleaseStrategy;
use crate::pubgrub::resolver::VersionMap;
use crate::pubgrub::PubGrubVersion; use crate::pubgrub::PubGrubVersion;
use crate::resolution_mode::ResolutionStrategy; use crate::resolution_mode::ResolutionStrategy;
use crate::resolver::VersionMap;
use crate::Manifest; use crate::Manifest;
#[derive(Debug)] #[derive(Debug)]

View File

@ -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::package::PubGrubPackage;
pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority}; pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority};
pub use crate::pubgrub::report::ResolutionFailureReporter; 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::version::{PubGrubVersion, MIN_VERSION};
pub(crate) use crate::pubgrub::dependencies::PubGrubDependencies; mod candidate_selector;
mod dependencies; mod dependencies;
mod package; mod package;
mod priority; mod priority;
mod report; mod report;
mod resolver;
mod specifier; mod specifier;
mod version; mod version;

View File

@ -30,14 +30,14 @@ use puffin_normalize::{ExtraName, PackageName};
use puffin_traits::BuildContext; use puffin_traits::BuildContext;
use pypi_types::{File, Metadata21, SimpleJson}; use pypi_types::{File, Metadata21, SimpleJson};
use crate::candidate_selector::CandidateSelector;
use crate::distribution::{BuiltDistFetcher, SourceDistFetcher, SourceDistributionReporter}; use crate::distribution::{BuiltDistFetcher, SourceDistFetcher, SourceDistributionReporter};
use crate::error::ResolveError; use crate::error::ResolveError;
use crate::file::{DistFile, SdistFile, WheelFile}; use crate::file::{DistFile, SdistFile, WheelFile};
use crate::locks::Locks; use crate::locks::Locks;
use crate::manifest::Manifest; use crate::manifest::Manifest;
use crate::pubgrub::{ use crate::pubgrub::{
PubGrubDependencies, PubGrubPackage, PubGrubPriorities, PubGrubVersion, MIN_VERSION, CandidateSelector, PubGrubDependencies, PubGrubPackage, PubGrubPriorities, PubGrubVersion,
MIN_VERSION,
}; };
use crate::resolution::Graph; use crate::resolution::Graph;

View File

@ -2,6 +2,7 @@ use std::hash::BuildHasherDefault;
use colored::Colorize; use colored::Colorize;
use fxhash::FxHashMap; use fxhash::FxHashMap;
use itertools::Itertools;
use petgraph::visit::EdgeRef; use petgraph::visit::EdgeRef;
use pubgrub::range::Range; use pubgrub::range::Range;
use pubgrub::solver::{Kind, State}; 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<Graph> 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 /// A complete resolution graph in which every node represents a pinned package and every edge
/// represents a dependency between two pinned packages. /// represents a dependency between two pinned packages.
#[derive(Debug)] #[derive(Debug)]

View File

@ -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;

View File

@ -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}]"),
}
}
}

View File

@ -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<Context: BuildContext> {
database: Arc<Database<Context>>,
sender: UnboundedSender<Request>,
markers: MarkerEnvironment,
pool: Pool<ResolvoVersionSet, ResolvoPackage>,
}
impl<Context: BuildContext> ResolvoDependencyProvider<Context> {
/// Initialize a new [`ResolvoDependencyProvider`] with the given [`Database`].
pub(crate) fn new(
database: Arc<Database<Context>>,
sender: UnboundedSender<Request>,
markers: MarkerEnvironment,
) -> Self {
Self {
database,
sender,
markers,
pool: Pool::new(),
}
}
/// Return the underlying [`Pool`].
pub(crate) fn pool(&self) -> &Pool<ResolvoVersionSet, ResolvoPackage> {
&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<Context: BuildContext> resolvo::DependencyProvider<ResolvoVersionSet, ResolvoPackage>
for &ResolvoDependencyProvider<Context>
{
fn pool(&self) -> &Pool<ResolvoVersionSet, ResolvoPackage> {
&self.pool
}
/// Sort candidates such that the highest version is preferred.
fn sort_candidates(
&self,
solver: &SolverCache<ResolvoVersionSet, ResolvoPackage, Self>,
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<Candidates> {
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
}
}

View File

@ -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<Context: BuildContext + Send + Sync + 'static>(
manifest: Manifest,
markers: MarkerEnvironment,
tags: Tags,
client: RegistryClient,
build_context: Context,
) -> Result<Resolution, ResolveError> {
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<Context: BuildContext>(
provider: &ResolvoDependencyProvider<Context>,
root_requirements: Vec<resolvo::VersionSetId>,
) -> Result<Resolution, ResolveError> {
// 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))
}

View File

@ -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<pep508_rs::VersionOrUrl>);
impl From<Option<pep508_rs::VersionOrUrl>> for ResolvoVersionSet {
fn from(value: Option<pep508_rs::VersionOrUrl>) -> 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}"),
}
}
}

View File

@ -17,7 +17,7 @@ use platform_host::{Arch, Os, Platform};
use platform_tags::Tags; use platform_tags::Tags;
use puffin_client::RegistryClientBuilder; use puffin_client::RegistryClientBuilder;
use puffin_interpreter::{InterpreterInfo, Virtualenv}; use puffin_interpreter::{InterpreterInfo, Virtualenv};
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode, Resolver}; use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode};
use puffin_traits::BuildContext; use puffin_traits::BuildContext;
struct DummyContext; struct DummyContext;
@ -77,7 +77,13 @@ async fn black() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -101,7 +107,13 @@ async fn black_colorama() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -125,7 +137,13 @@ async fn black_python_310() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -151,7 +169,13 @@ async fn black_mypy_extensions() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -177,7 +201,13 @@ async fn black_mypy_extensions_extra() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -203,7 +233,13 @@ async fn black_flake8() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -227,7 +263,13 @@ async fn black_lowest() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -251,7 +293,13 @@ async fn black_lowest_direct() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -275,7 +323,13 @@ async fn black_respect_preference() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -299,7 +353,13 @@ async fn black_ignore_preference() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -323,7 +383,13 @@ async fn black_disallow_prerelease() -> Result<()> {
None, 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(); let err = resolver.resolve().await.unwrap_err();
insta::assert_display_snapshot!(err); insta::assert_display_snapshot!(err);
@ -347,7 +413,13 @@ async fn black_allow_prerelease_if_necessary() -> Result<()> {
None, 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(); let resolution = resolver.resolve().await.unwrap_err();
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -371,7 +443,13 @@ async fn pylint_disallow_prerelease() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -395,7 +473,13 @@ async fn pylint_allow_prerelease() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -422,7 +506,13 @@ async fn pylint_allow_explicit_prerelease_without_marker() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);
@ -449,7 +539,13 @@ async fn pylint_allow_explicit_prerelease_with_marker() -> Result<()> {
None, 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?; let resolution = resolver.resolve().await?;
insta::assert_display_snapshot!(resolution); insta::assert_display_snapshot!(resolution);

View File

@ -2,10 +2,7 @@
# A small set of pure-Python packages. # A small set of pure-Python packages.
### ###
packaging>=23.1 packaging>=23.1
pygls>=1.0.1
lsprotocol>=2023.0.0a1
ruff>=0.0.274 ruff>=0.0.274
flask @ git+https://github.com/pallets/flask.git@d92b64a
typing_extensions typing_extensions
scipy scipy
numpy numpy
@ -23,10 +20,8 @@ trio<0.20
trio-websocket trio-websocket
trio-asyncio trio-asyncio
trio-typing trio-typing
trio-protocol
fastapi fastapi
typer typer
pydantic pydantic
uvicorn uvicorn
traitlets traitlets