From 6faaf4bc24e4b07d54bf2e17bd89d8213a79c8bd Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 25 Oct 2023 21:28:58 -0700 Subject: [PATCH] Respect existing versions in "lockfile" (#187) Like `pip-compile`, we now respect existing versions from the `requirements.txt` provided via `--output-file`, unless you pass a `--upgrade` flag. Closes #166. --- crates/puffin-cli/src/commands/pip_compile.rs | 53 +++++- crates/puffin-cli/src/main.rs | 5 + crates/puffin-dispatch/src/lib.rs | 11 +- crates/puffin-resolver/src/distribution.rs | 13 ++ crates/puffin-resolver/src/lib.rs | 2 +- crates/puffin-resolver/src/resolution.rs | 5 + crates/puffin-resolver/src/resolver.rs | 49 ++++-- crates/puffin-resolver/src/selector.rs | 159 ++++++++++++------ crates/puffin-resolver/tests/resolver.rs | 140 ++++++++++----- .../resolver__black_ignore_preference.snap | 16 ++ .../resolver__black_respect_preference.snap | 16 ++ 11 files changed, 347 insertions(+), 122 deletions(-) create mode 100644 crates/puffin-resolver/tests/snapshots/resolver__black_ignore_preference.snap create mode 100644 crates/puffin-resolver/tests/snapshots/resolver__black_respect_preference.snap diff --git a/crates/puffin-cli/src/commands/pip_compile.rs b/crates/puffin-cli/src/commands/pip_compile.rs index 41879f027..415ebf39a 100644 --- a/crates/puffin-cli/src/commands/pip_compile.rs +++ b/crates/puffin-cli/src/commands/pip_compile.rs @@ -7,15 +7,16 @@ use anyhow::Result; use colored::Colorize; use fs_err::File; use itertools::Itertools; +use pubgrub::report::Reporter; +use tracing::debug; + use pep508_rs::Requirement; use platform_host::Platform; use platform_tags::Tags; -use pubgrub::report::Reporter; use puffin_client::RegistryClientBuilder; use puffin_dispatch::BuildDispatch; use puffin_interpreter::Virtualenv; -use puffin_resolver::ResolutionMode; -use tracing::debug; +use puffin_resolver::{Manifest, ResolutionMode}; use crate::commands::{elapsed, ExitStatus}; use crate::index_urls::IndexUrls; @@ -25,11 +26,13 @@ use crate::requirements::RequirementsSource; const VERSION: &str = env!("CARGO_PKG_VERSION"); /// Resolve a set of requirements into a set of pinned versions. +#[allow(clippy::too_many_arguments)] pub(crate) async fn pip_compile( requirements: &[RequirementsSource], constraints: &[RequirementsSource], output_file: Option<&Path>, - mode: ResolutionMode, + resolution_mode: ResolutionMode, + upgrade_mode: UpgradeMode, index_urls: Option, cache: Option<&Path>, mut printer: Printer, @@ -47,6 +50,19 @@ pub(crate) async fn pip_compile( .map(RequirementsSource::requirements) .flatten_ok() .collect::>>()?; + let preferences: Vec = output_file + .filter(|_| upgrade_mode.is_prefer_pinned()) + .filter(|output_file| output_file.exists()) + .map(Path::to_path_buf) + .map(RequirementsSource::from) + .as_ref() + .map(RequirementsSource::requirements) + .transpose()? + .map(Iterator::collect) + .unwrap_or_default(); + + // Create a manifest of the requirements. + let manifest = Manifest::new(requirements, constraints, preferences, resolution_mode); // Detect the current Python interpreter. let platform = Platform::current()?; @@ -88,9 +104,7 @@ pub(crate) async fn pip_compile( // Resolve the dependencies. let resolver = puffin_resolver::Resolver::new( - requirements, - constraints, - mode, + manifest, venv.interpreter_info().markers(), &tags, &client, @@ -155,3 +169,28 @@ pub(crate) async fn pip_compile( Ok(ExitStatus::Success) } + +/// Whether to allow package upgrades. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum UpgradeMode { + /// Allow package upgrades, ignoring the existing lockfile. + AllowUpgrades, + /// Prefer pinned versions from the existing lockfile, if possible. + PreferPinned, +} + +impl UpgradeMode { + fn is_prefer_pinned(self) -> bool { + self == Self::PreferPinned + } +} + +impl From for UpgradeMode { + fn from(value: bool) -> Self { + if value { + Self::AllowUpgrades + } else { + Self::PreferPinned + } + } +} diff --git a/crates/puffin-cli/src/main.rs b/crates/puffin-cli/src/main.rs index 90d2f5d19..ab7641fd3 100644 --- a/crates/puffin-cli/src/main.rs +++ b/crates/puffin-cli/src/main.rs @@ -89,6 +89,10 @@ struct PipCompileArgs { /// Ignore the package index, instead relying on local archives and caches. #[clap(long, conflicts_with = "index_url", conflicts_with = "extra_index_url")] no_index: bool, + + /// Allow package upgrades, ignoring pinned versions in the existing output file. + #[clap(long)] + upgrade: bool, } #[derive(Args)] @@ -196,6 +200,7 @@ async fn main() -> ExitCode { &constraints, args.output_file.as_deref(), args.resolution.unwrap_or_default(), + args.upgrade.into(), index_urls, cache_dir, printer, diff --git a/crates/puffin-dispatch/src/lib.rs b/crates/puffin-dispatch/src/lib.rs index d1de5ece3..5276028cd 100644 --- a/crates/puffin-dispatch/src/lib.rs +++ b/crates/puffin-dispatch/src/lib.rs @@ -18,7 +18,7 @@ use puffin_installer::{ uninstall, Downloader, Installer, PartitionedRequirements, RemoteDistribution, Unzipper, }; use puffin_interpreter::{InterpreterInfo, Virtualenv}; -use puffin_resolver::{ResolutionMode, Resolver, WheelFinder}; +use puffin_resolver::{Manifest, ResolutionMode, Resolver, WheelFinder}; use puffin_traits::BuildContext; use tracing::debug; @@ -70,9 +70,12 @@ impl BuildContext for BuildDispatch { self.interpreter_info.simple_version(), )?; let resolver = Resolver::new( - requirements.to_vec(), - Vec::default(), - ResolutionMode::Highest, + Manifest::new( + requirements.to_vec(), + Vec::default(), + Vec::default(), + ResolutionMode::default(), + ), self.interpreter_info.markers(), &tags, &self.client, diff --git a/crates/puffin-resolver/src/distribution.rs b/crates/puffin-resolver/src/distribution.rs index 0831d1575..d037ff37c 100644 --- a/crates/puffin-resolver/src/distribution.rs +++ b/crates/puffin-resolver/src/distribution.rs @@ -66,6 +66,19 @@ impl From for DistributionFile { } } +impl From for DistributionFile { + fn from(file: File) -> Self { + if std::path::Path::new(file.filename.as_str()) + .extension() + .map_or(false, |ext| ext.eq_ignore_ascii_case("whl")) + { + Self::Wheel(WheelFile::from(file)) + } else { + Self::Sdist(SdistFile::from(file)) + } + } +} + impl DistributionFile { pub(crate) fn filename(&self) -> &str { match self { diff --git a/crates/puffin-resolver/src/lib.rs b/crates/puffin-resolver/src/lib.rs index 8198a7214..43f613fb4 100644 --- a/crates/puffin-resolver/src/lib.rs +++ b/crates/puffin-resolver/src/lib.rs @@ -1,6 +1,6 @@ pub use error::ResolveError; pub use resolution::PinnedPackage; -pub use resolver::Resolver; +pub use resolver::{Manifest, Resolver}; pub use selector::ResolutionMode; pub use source_distribution::BuiltSourceDistributionCache; pub use wheel_finder::{Reporter, WheelFinder}; diff --git a/crates/puffin-resolver/src/resolution.rs b/crates/puffin-resolver/src/resolution.rs index dfd94d8f0..5db3a206b 100644 --- a/crates/puffin-resolver/src/resolution.rs +++ b/crates/puffin-resolver/src/resolution.rs @@ -69,6 +69,11 @@ impl Resolution { self.0.into_values().map(|package| package.file) } + /// Return the pinned package for the given package name, if it exists. + pub fn get(&self, package_name: &PackageName) -> Option<&PinnedPackage> { + self.0.get(package_name) + } + /// Return the number of pinned packages in this resolution. pub fn len(&self) -> usize { self.0.len() diff --git a/crates/puffin-resolver/src/resolver.rs b/crates/puffin-resolver/src/resolver.rs index b14311242..8de0d46a4 100644 --- a/crates/puffin-resolver/src/resolver.rs +++ b/crates/puffin-resolver/src/resolver.rs @@ -34,15 +34,39 @@ use crate::error::ResolveError; use crate::pubgrub::package::PubGrubPackage; use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION}; use crate::pubgrub::{iter_requirements, version_range}; -use crate::resolution::{Graph, Resolution}; +use crate::resolution::Graph; use crate::selector::{CandidateSelector, ResolutionMode}; use crate::source_distribution::{download_and_build_sdist, read_dist_info}; use crate::BuiltSourceDistributionCache; +/// A manifest of requirements, constraints, and preferences. +#[derive(Debug)] +pub struct Manifest { + requirements: Vec, + constraints: Vec, + preferences: Vec, + mode: ResolutionMode, +} + +impl Manifest { + pub fn new( + requirements: Vec, + constraints: Vec, + preferences: Vec, + mode: ResolutionMode, + ) -> Self { + Self { + requirements, + constraints, + preferences, + mode, + } + } +} + pub struct Resolver<'a, Context: BuildContext> { requirements: Vec, constraints: Vec, - resolution: Option, markers: &'a MarkerEnvironment, tags: &'a Tags, client: &'a RegistryClient, @@ -54,20 +78,21 @@ pub struct Resolver<'a, Context: BuildContext> { impl<'a, Context: BuildContext> Resolver<'a, Context> { /// Initialize a new resolver. pub fn new( - requirements: Vec, - constraints: Vec, - mode: ResolutionMode, + manifest: Manifest, markers: &'a MarkerEnvironment, tags: &'a Tags, client: &'a RegistryClient, build_context: &'a Context, ) -> Self { Self { - selector: CandidateSelector::from_mode(mode, &requirements), + selector: CandidateSelector::from_mode( + manifest.mode, + &manifest.requirements, + &manifest.preferences, + ), index: Arc::new(Index::default()), - resolution: None, - requirements, - constraints, + requirements: manifest.requirements, + constraints: manifest.constraints, markers, tags, client, @@ -75,12 +100,6 @@ impl<'a, Context: BuildContext> Resolver<'a, Context> { } } - #[must_use] - pub fn with_resolution(mut self, resolution: Resolution) -> Self { - self.resolution = Some(resolution); - self - } - /// Resolve a set of requirements into a set of pinned versions. pub async fn resolve(self) -> Result { // A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version diff --git a/crates/puffin-resolver/src/selector.rs b/crates/puffin-resolver/src/selector.rs index 79e972ad1..cde952332 100644 --- a/crates/puffin-resolver/src/selector.rs +++ b/crates/puffin-resolver/src/selector.rs @@ -1,11 +1,10 @@ -use fxhash::FxHashSet; +use fxhash::{FxHashMap, FxHashSet}; use pubgrub::range::Range; -use crate::distribution::DistributionFile; -use pep508_rs::Requirement; - +use pep508_rs::{Requirement, VersionOrUrl}; use puffin_package::package_name::PackageName; +use crate::distribution::DistributionFile; use crate::pubgrub::version::PubGrubVersion; use crate::resolver::VersionMap; @@ -22,8 +21,10 @@ pub enum ResolutionMode { LowestDirect, } -#[derive(Debug, Clone)] -pub(crate) enum CandidateSelector { +/// Like [`ResolutionMode`], but with any additional information required to select a candidate, +/// like the set of direct dependencies. +#[derive(Debug)] +enum ResolutionStrategy { /// Resolve the highest compatible version of each package. Highest, /// Resolve the lowest compatible version of each package. @@ -33,9 +34,8 @@ pub(crate) enum CandidateSelector { LowestDirect(FxHashSet), } -impl CandidateSelector { - /// Return a candidate selector for the given resolution mode. - pub(crate) fn from_mode(mode: ResolutionMode, direct_dependencies: &[Requirement]) -> Self { +impl ResolutionStrategy { + fn from_mode(mode: ResolutionMode, direct_dependencies: &[Requirement]) -> Self { match mode { ResolutionMode::Highest => Self::Highest, ResolutionMode::Lowest => Self::Lowest, @@ -49,26 +49,87 @@ impl CandidateSelector { } } +/// A set of pinned packages that should be preserved during resolution, if possible. +#[derive(Debug)] +struct Preferences(FxHashMap); + +impl Preferences { + fn get(&self, package_name: &PackageName) -> Option<&PubGrubVersion> { + self.0.get(package_name) + } +} + +impl From<&[Requirement]> for Preferences { + fn from(requirements: &[Requirement]) -> Self { + Self( + requirements + .iter() + .filter_map(|requirement| { + let Some(VersionOrUrl::VersionSpecifier(version_specifiers)) = + requirement.version_or_url.as_ref() + else { + return None; + }; + let [version_specifier] = &**version_specifiers else { + return None; + }; + let package_name = PackageName::normalize(&requirement.name); + let version = PubGrubVersion::from(version_specifier.version().clone()); + Some((package_name, version)) + }) + .collect(), + ) + } +} + +#[derive(Debug)] +pub(crate) struct CandidateSelector { + strategy: ResolutionStrategy, + preferences: Preferences, +} + +impl CandidateSelector { + /// Return a candidate selector for the given resolution mode. + pub(crate) fn from_mode( + mode: ResolutionMode, + direct_dependencies: &[Requirement], + resolution: &[Requirement], + ) -> Self { + Self { + strategy: ResolutionStrategy::from_mode(mode, direct_dependencies), + preferences: Preferences::from(resolution), + } + } +} + impl CandidateSelector { /// Select a [`Candidate`] from a set of candidate versions and files. - pub(crate) fn select<'a>( + pub(crate) fn select( &self, - package_name: &'a PackageName, - range: &'a Range, - version_map: &'a VersionMap, - ) -> Option> { - match self { - CandidateSelector::Highest => { - CandidateSelector::select_highest(package_name, range, version_map) + package_name: &PackageName, + range: &Range, + version_map: &VersionMap, + ) -> Option { + if let Some(version) = self.preferences.get(package_name) { + if range.contains(version) { + if let Some(file) = version_map.get(version) { + return Some(Candidate { + package_name: package_name.clone(), + version: version.clone(), + file: file.clone(), + }); + } } - CandidateSelector::Lowest => { - CandidateSelector::select_lowest(package_name, range, version_map) - } - CandidateSelector::LowestDirect(direct_dependencies) => { + } + + match &self.strategy { + ResolutionStrategy::Highest => Self::select_highest(package_name, range, version_map), + ResolutionStrategy::Lowest => Self::select_lowest(package_name, range, version_map), + ResolutionStrategy::LowestDirect(direct_dependencies) => { if direct_dependencies.contains(package_name) { - CandidateSelector::select_lowest(package_name, range, version_map) + Self::select_lowest(package_name, range, version_map) } else { - CandidateSelector::select_highest(package_name, range, version_map) + Self::select_highest(package_name, range, version_map) } } } @@ -76,27 +137,27 @@ impl CandidateSelector { /// Select the highest-compatible [`Candidate`] from a set of candidate versions and files, /// preferring wheels over sdists. - fn select_highest<'a>( - package_name: &'a PackageName, - range: &'a Range, - version_map: &'a VersionMap, - ) -> Option> { + fn select_highest( + package_name: &PackageName, + range: &Range, + version_map: &VersionMap, + ) -> Option { let mut sdist = None; for (version, file) in version_map.iter().rev() { if range.contains(version) { match file { DistributionFile::Wheel(_) => { return Some(Candidate { - package_name, - version, - file, + package_name: package_name.clone(), + version: version.clone(), + file: file.clone(), }); } DistributionFile::Sdist(_) => { sdist = Some(Candidate { - package_name, - version, - file, + package_name: package_name.clone(), + version: version.clone(), + file: file.clone(), }); } } @@ -107,27 +168,27 @@ impl CandidateSelector { /// Select the highest-compatible [`Candidate`] from a set of candidate versions and files, /// preferring wheels over sdists. - fn select_lowest<'a>( - package_name: &'a PackageName, - range: &'a Range, - version_map: &'a VersionMap, - ) -> Option> { + fn select_lowest( + package_name: &PackageName, + range: &Range, + version_map: &VersionMap, + ) -> Option { let mut sdist = None; for (version, file) in version_map { if range.contains(version) { match file { DistributionFile::Wheel(_) => { return Some(Candidate { - package_name, - version, - file, + package_name: package_name.clone(), + version: version.clone(), + file: file.clone(), }); } DistributionFile::Sdist(_) => { sdist = Some(Candidate { - package_name, - version, - file, + package_name: package_name.clone(), + version: version.clone(), + file: file.clone(), }); } } @@ -138,11 +199,11 @@ impl CandidateSelector { } #[derive(Debug, Clone)] -pub(crate) struct Candidate<'a> { +pub(crate) struct Candidate { /// The name of the package. - pub(crate) package_name: &'a PackageName, + pub(crate) package_name: PackageName, /// The version of the package. - pub(crate) version: &'a PubGrubVersion, + pub(crate) version: PubGrubVersion, /// The file of the package. - pub(crate) file: &'a DistributionFile, + pub(crate) file: DistributionFile, } diff --git a/crates/puffin-resolver/tests/resolver.rs b/crates/puffin-resolver/tests/resolver.rs index b8f4be5c1..f4d4ac561 100644 --- a/crates/puffin-resolver/tests/resolver.rs +++ b/crates/puffin-resolver/tests/resolver.rs @@ -16,7 +16,7 @@ use platform_host::{Arch, Os, Platform}; use platform_tags::Tags; use puffin_client::RegistryClientBuilder; use puffin_interpreter::{InterpreterInfo, Virtualenv}; -use puffin_resolver::{ResolutionMode, Resolver}; +use puffin_resolver::{Manifest, ResolutionMode, Resolver}; use puffin_traits::BuildContext; struct DummyContext; @@ -66,15 +66,15 @@ async fn pylint() -> Result<()> { let requirements = vec![Requirement::from_str("pylint==2.3.0").unwrap()]; let constraints = vec![]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::default(), - &MARKERS_311, - &TAGS_311, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -90,15 +90,15 @@ async fn black() -> Result<()> { let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()]; let constraints = vec![]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::default(), - &MARKERS_311, - &TAGS_311, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -114,15 +114,15 @@ async fn black_colorama() -> Result<()> { let requirements = vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()]; let constraints = vec![]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::default(), - &MARKERS_311, - &TAGS_311, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -138,15 +138,15 @@ async fn black_python_310() -> Result<()> { let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()]; let constraints = vec![]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::default(), - &MARKERS_310, - &TAGS_310, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_310, &TAGS_310, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -164,15 +164,15 @@ async fn black_mypy_extensions() -> Result<()> { let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()]; let constraints = vec![Requirement::from_str("mypy-extensions<1").unwrap()]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::default(), - &MARKERS_311, - &TAGS_311, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -190,15 +190,15 @@ async fn black_mypy_extensions_extra() -> Result<()> { let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()]; let constraints = vec![Requirement::from_str("mypy-extensions[extra]<1").unwrap()]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::default(), - &MARKERS_311, - &TAGS_311, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -216,15 +216,15 @@ async fn black_flake8() -> Result<()> { let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()]; let constraints = vec![Requirement::from_str("flake8<1").unwrap()]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::default(), - &MARKERS_311, - &TAGS_311, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -240,15 +240,15 @@ async fn black_lowest() -> Result<()> { let requirements = vec![Requirement::from_str("black>21").unwrap()]; let constraints = vec![]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::Lowest, - &MARKERS_311, - &TAGS_311, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); @@ -264,15 +264,63 @@ async fn black_lowest_direct() -> Result<()> { let requirements = vec![Requirement::from_str("black>21").unwrap()]; let constraints = vec![]; - let resolver = Resolver::new( + let preferences = vec![]; + let manifest = Manifest::new( requirements, constraints, + preferences, ResolutionMode::LowestDirect, - &MARKERS_311, - &TAGS_311, - &client, - &DummyContext, ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolution = resolver.resolve().await?; + + insta::assert_display_snapshot!(resolution); + + Ok(()) +} + +#[tokio::test] +async fn black_respect_preference() -> Result<()> { + colored::control::set_override(false); + + let client = RegistryClientBuilder::default().build(); + + let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()]; + let constraints = vec![]; + let preferences = vec![Requirement::from_str("black==23.9.0").unwrap()]; + let manifest = Manifest::new( + requirements, + constraints, + preferences, + ResolutionMode::default(), + ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); + let resolution = resolver.resolve().await?; + + insta::assert_display_snapshot!(resolution); + + Ok(()) +} + +#[tokio::test] +async fn black_ignore_preference() -> Result<()> { + colored::control::set_override(false); + + let client = RegistryClientBuilder::default().build(); + + let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()]; + let constraints = vec![]; + let preferences = vec![Requirement::from_str("black==23.9.2").unwrap()]; + let manifest = Manifest::new( + requirements, + constraints, + preferences, + ResolutionMode::default(), + ); + + let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext); let resolution = resolver.resolve().await?; insta::assert_display_snapshot!(resolution); diff --git a/crates/puffin-resolver/tests/snapshots/resolver__black_ignore_preference.snap b/crates/puffin-resolver/tests/snapshots/resolver__black_ignore_preference.snap new file mode 100644 index 000000000..828a086c9 --- /dev/null +++ b/crates/puffin-resolver/tests/snapshots/resolver__black_ignore_preference.snap @@ -0,0 +1,16 @@ +--- +source: crates/puffin-resolver/tests/resolver.rs +expression: resolution +--- +black==23.9.1 +click==8.1.7 + # via black +mypy-extensions==1.0.0 + # via black +packaging==23.2 + # via black +pathspec==0.11.2 + # via black +platformdirs==3.11.0 + # via black + diff --git a/crates/puffin-resolver/tests/snapshots/resolver__black_respect_preference.snap b/crates/puffin-resolver/tests/snapshots/resolver__black_respect_preference.snap new file mode 100644 index 000000000..5d231bf2a --- /dev/null +++ b/crates/puffin-resolver/tests/snapshots/resolver__black_respect_preference.snap @@ -0,0 +1,16 @@ +--- +source: crates/puffin-resolver/tests/resolver.rs +expression: resolution +--- +black==23.9.0 +click==8.1.7 + # via black +mypy-extensions==1.0.0 + # via black +packaging==23.2 + # via black +pathspec==0.11.2 + # via black +platformdirs==3.11.0 + # via black +