diff --git a/crates/uv-resolver/src/candidate_selector.rs b/crates/uv-resolver/src/candidate_selector.rs index 7edc61545366..fdcc6c4722e8 100644 --- a/crates/uv-resolver/src/candidate_selector.rs +++ b/crates/uv-resolver/src/candidate_selector.rs @@ -276,7 +276,7 @@ impl CandidateSelector { "Selecting candidate for {package_name} with range {range} with {} remote versions", version_maps.iter().map(VersionMap::len).sum::(), ); - let highest = self.use_highest_version(package_name); + let highest = self.use_highest_version(package_name, env); let allow_prerelease = match self.prerelease_strategy.allows(package_name, env) { AllowPrerelease::Yes => true, @@ -359,12 +359,16 @@ impl CandidateSelector { /// By default, we select the latest version, but we also allow using the lowest version instead /// to check the lower bounds. - pub(crate) fn use_highest_version(&self, package_name: &PackageName) -> bool { + pub(crate) fn use_highest_version( + &self, + package_name: &PackageName, + env: &ResolverEnvironment, + ) -> bool { match &self.resolution_strategy { ResolutionStrategy::Highest => true, ResolutionStrategy::Lowest => false, ResolutionStrategy::LowestDirect(direct_dependencies) => { - !direct_dependencies.contains(package_name) + !direct_dependencies.contains(package_name, env) } } } diff --git a/crates/uv-resolver/src/resolution_mode.rs b/crates/uv-resolver/src/resolution_mode.rs index c105cfee412c..f9e12cb3fd0f 100644 --- a/crates/uv-resolver/src/resolution_mode.rs +++ b/crates/uv-resolver/src/resolution_mode.rs @@ -1,7 +1,4 @@ -use rustc_hash::FxHashSet; - -use uv_normalize::PackageName; - +use crate::resolver::{ForkMap, ForkSet}; use crate::{DependencyMode, Manifest, ResolverEnvironment}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)] @@ -39,7 +36,7 @@ pub(crate) enum ResolutionStrategy { Lowest, /// Resolve the lowest compatible version of any direct dependencies, and the highest /// compatible version of any transitive dependencies. - LowestDirect(FxHashSet), + LowestDirect(ForkSet), } impl ResolutionStrategy { @@ -52,12 +49,13 @@ impl ResolutionStrategy { match mode { ResolutionMode::Highest => Self::Highest, ResolutionMode::Lowest => Self::Lowest, - ResolutionMode::LowestDirect => Self::LowestDirect( - manifest - .user_requirements(env, dependencies) - .map(|requirement| requirement.name.clone()) - .collect(), - ), + ResolutionMode::LowestDirect => { + let mut first_party = ForkMap::default(); + for requirement in manifest.user_requirements(env, dependencies) { + first_party.add(&requirement, ()); + } + Self::LowestDirect(first_party) + } } } } diff --git a/crates/uv-resolver/src/resolver/batch_prefetch.rs b/crates/uv-resolver/src/resolver/batch_prefetch.rs index 3acea2600588..ec2852109969 100644 --- a/crates/uv-resolver/src/resolver/batch_prefetch.rs +++ b/crates/uv-resolver/src/resolver/batch_prefetch.rs @@ -122,7 +122,7 @@ impl BatchPrefetcher { } } BatchPrefetchStrategy::InOrder { previous } => { - let mut range = if selector.use_highest_version(name) { + let mut range = if selector.use_highest_version(name, env) { Range::strictly_lower_than(previous) } else { Range::strictly_higher_than(previous) diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 6d89cd6bd775..ba4fdafc78c1 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -72,7 +72,7 @@ pub use crate::resolver::provider::{ use crate::resolver::reporter::Facade; pub use crate::resolver::reporter::{BuildId, Reporter}; use crate::yanks::AllowedYanks; -use crate::{marker, DependencyMode, Exclusions, FlatIndex, Options}; +use crate::{marker, DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode}; mod availability; mod batch_prefetch; @@ -364,12 +364,22 @@ impl ResolverState ResolverState ResolverState ResolverState Result<()> { Ok(()) } + +/// Perform a universal resolution with a constraint, where the constraint itself has a marker. +#[test] +fn lowest_direct_fork() -> Result<()> { + let context = TestContext::new("3.10"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str(indoc::indoc! {r" + pycountry >= 22.1.10 + setuptools >= 50.0.0 ; python_version >= '3.12' + "})?; + + uv_snapshot!(context.filters(), windows_filters=false, context.pip_compile() + .arg("requirements.in") + .arg("--universal") + .arg("--resolution") + .arg("lowest-direct"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal --resolution lowest-direct + pycountry==22.1.10 + # via -r requirements.in + setuptools==50.0.0 ; python_full_version >= '3.12' + # via + # -r requirements.in + # pycountry + setuptools==69.2.0 ; python_full_version < '3.12' + # via + # -r requirements.in + # pycountry + + ----- stderr ----- + Resolved 3 packages in [TIME] + "### + ); + + Ok(()) +} + +/// Perform a universal resolution with a constraint, where the constraint itself has a marker. +#[test] +fn lowest_fork() -> Result<()> { + let context = TestContext::new("3.10"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str(indoc::indoc! {r" + pycountry >= 22.1.10 + setuptools >= 50.0.0 ; python_version >= '3.12' + "})?; + + uv_snapshot!(context.filters(), windows_filters=false, context.pip_compile() + .arg("requirements.in") + .arg("--universal") + .arg("--resolution") + .arg("lowest"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal --resolution lowest + pycountry==22.1.10 + # via -r requirements.in + setuptools==0.7.2 ; python_full_version < '3.12' + # via + # -r requirements.in + # pycountry + setuptools==50.0.0 ; python_full_version >= '3.12' + # via + # -r requirements.in + # pycountry + + ----- stderr ----- + Resolved 3 packages in [TIME] + warning: The transitive dependency `setuptools` is unpinned. Consider setting a lower bound with a constraint when using `--resolution-strategy lowest` to avoid using outdated versions. + "### + ); + + Ok(()) +}