mirror of https://github.com/astral-sh/uv
Add an option to use Resolvo in lieu of PubGrub
This commit is contained in:
parent
beadd3274a
commit
40bc049b51
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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!(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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}]"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
@ -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}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue