diff --git a/crates/uv-resolver/src/prerelease_mode.rs b/crates/uv-resolver/src/prerelease_mode.rs index 5f5a84188334..e0ec0f6041e2 100644 --- a/crates/uv-resolver/src/prerelease_mode.rs +++ b/crates/uv-resolver/src/prerelease_mode.rs @@ -95,4 +95,15 @@ impl PreReleaseStrategy { ), } } + + /// Returns `true` if a [`PackageName`] is allowed to have pre-release versions. + pub(crate) fn allows(&self, package: &PackageName) -> bool { + match self { + Self::Disallow => false, + Self::Allow => true, + Self::IfNecessary => false, + Self::Explicit(packages) => packages.contains(package), + Self::IfNecessaryOrExplicit(packages) => packages.contains(package), + } + } } diff --git a/crates/uv-resolver/src/pubgrub/dependencies.rs b/crates/uv-resolver/src/pubgrub/dependencies.rs index d024d3fcfa8b..a434c224b7e9 100644 --- a/crates/uv-resolver/src/pubgrub/dependencies.rs +++ b/crates/uv-resolver/src/pubgrub/dependencies.rs @@ -9,6 +9,7 @@ use uv_normalize::{ExtraName, PackageName}; use crate::constraints::Constraints; use crate::overrides::Overrides; +use crate::prerelease_mode::PreReleaseStrategy; use crate::pubgrub::specifier::PubGrubSpecifier; use crate::pubgrub::PubGrubPackage; use crate::resolver::Urls; @@ -19,6 +20,7 @@ pub struct PubGrubDependencies(Vec<(PubGrubPackage, Range)>); impl PubGrubDependencies { /// Generate a set of `PubGrub` dependencies from a set of requirements. + #[allow(clippy::too_many_arguments)] pub(crate) fn from_requirements( requirements: &[Requirement], constraints: &Constraints, @@ -26,6 +28,7 @@ impl PubGrubDependencies { source_name: Option<&PackageName>, source_extra: Option<&ExtraName>, urls: &Urls, + prerelease: &PreReleaseStrategy, env: &MarkerEnvironment, ) -> Result { let mut dependencies = Vec::default(); @@ -44,12 +47,12 @@ impl PubGrubDependencies { } // Add the package, plus any extra variants. - for result in std::iter::once(to_pubgrub(requirement, None, urls)).chain( + for result in std::iter::once(to_pubgrub(requirement, None, urls, prerelease)).chain( requirement .extras .clone() .into_iter() - .map(|extra| to_pubgrub(requirement, Some(extra), urls)), + .map(|extra| to_pubgrub(requirement, Some(extra), urls, prerelease)), ) { let (mut package, version) = result?; @@ -80,13 +83,15 @@ impl PubGrubDependencies { } // Add the package, plus any extra variants. - for result in std::iter::once(to_pubgrub(constraint, None, urls)).chain( - constraint - .extras - .clone() - .into_iter() - .map(|extra| to_pubgrub(constraint, Some(extra), urls)), - ) { + for result in std::iter::once(to_pubgrub(constraint, None, urls, prerelease)) + .chain( + constraint + .extras + .clone() + .into_iter() + .map(|extra| to_pubgrub(constraint, Some(extra), urls, prerelease)), + ) + { let (mut package, version) = result?; // Detect self-dependencies. @@ -132,6 +137,7 @@ fn to_pubgrub( requirement: &Requirement, extra: Option, urls: &Urls, + prerelease: &PreReleaseStrategy, ) -> Result<(PubGrubPackage, Range), ResolveError> { match requirement.version_or_url.as_ref() { // The requirement has no specifier (e.g., `flask`). @@ -142,9 +148,12 @@ fn to_pubgrub( // The requirement has a specifier (e.g., `flask>=1.0`). Some(VersionOrUrl::VersionSpecifier(specifiers)) => { + // If pre-releases are allowed for a given package, markers are interpreted slightly + // differently. + let allow_prerelease = prerelease.allows(&requirement.name); let version = specifiers .iter() - .map(PubGrubSpecifier::try_from) + .map(|specifier| PubGrubSpecifier::for_package(specifier, allow_prerelease)) .fold_ok(Range::full(), |range, specifier| { range.intersection(&specifier.into()) })?; diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index aca97ed0c775..d1d9dc0fb3d6 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -16,7 +16,6 @@ use rustc_hash::FxHashMap; use uv_normalize::PackageName; use crate::candidate_selector::CandidateSelector; -use crate::prerelease_mode::PreReleaseStrategy; use crate::python_requirement::PythonRequirement; use crate::resolver::UnavailablePackage; @@ -346,25 +345,10 @@ impl PubGrubReportFormatter<'_> { ) -> IndexSet { /// Returns `true` if pre-releases were allowed for a package. fn allowed_prerelease(package: &PubGrubPackage, selector: &CandidateSelector) -> bool { - match selector.prerelease_strategy() { - PreReleaseStrategy::Disallow => false, - PreReleaseStrategy::Allow => true, - PreReleaseStrategy::IfNecessary => false, - PreReleaseStrategy::Explicit(packages) => { - if let PubGrubPackage::Package(package, ..) = package { - packages.contains(package) - } else { - false - } - } - PreReleaseStrategy::IfNecessaryOrExplicit(packages) => { - if let PubGrubPackage::Package(package, ..) = package { - packages.contains(package) - } else { - false - } - } - } + let PubGrubPackage::Package(package, ..) = package else { + return false; + }; + selector.prerelease_strategy().allows(package) } let mut hints = IndexSet::default(); diff --git a/crates/uv-resolver/src/pubgrub/specifier.rs b/crates/uv-resolver/src/pubgrub/specifier.rs index d0c645ab7b6a..8d12c58407df 100644 --- a/crates/uv-resolver/src/pubgrub/specifier.rs +++ b/crates/uv-resolver/src/pubgrub/specifier.rs @@ -16,11 +16,11 @@ impl From for Range { } } -impl TryFrom<&VersionSpecifier> for PubGrubSpecifier { - type Error = ResolveError; - - /// Convert a PEP 508 specifier to a `PubGrub`-compatible version range. - fn try_from(specifier: &VersionSpecifier) -> Result { +impl PubGrubSpecifier { + pub(crate) fn for_package( + specifier: &VersionSpecifier, + allow_prerelease: bool, + ) -> Result { let ranges = match specifier.operator() { Operator::Equal => { let version = specifier.version().clone(); @@ -46,7 +46,7 @@ impl TryFrom<&VersionSpecifier> for PubGrubSpecifier { } Operator::LessThan => { let version = specifier.version().clone(); - if version.any_prerelease() { + if !allow_prerelease || version.any_prerelease() { Range::strictly_lower_than(version) } else { // Per PEP 440: "The exclusive ordered comparison for PubGrubSpecifier { Ok(Self(ranges)) } } + +impl TryFrom<&VersionSpecifier> for PubGrubSpecifier { + type Error = ResolveError; + + /// Convert a PEP 508 specifier to a `PubGrub`-compatible version range. + fn try_from(specifier: &VersionSpecifier) -> Result { + Self::for_package(specifier, false) + } +} diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index ef80eca9e6b1..08460c846bf2 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -748,6 +748,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> { None, None, &self.urls, + self.selector.prerelease_strategy(), self.markers, ); @@ -823,6 +824,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> { Some(package_name), extra.as_ref(), &self.urls, + self.selector.prerelease_strategy(), self.markers, )?; @@ -879,6 +881,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> { Some(package_name), extra.as_ref(), &self.urls, + self.selector.prerelease_strategy(), self.markers, )?; diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index a8b6a620bff0..2857465e06b0 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -2075,17 +2075,12 @@ fn compile_yanked_version_indirect() -> Result<()> { ╰─▶ Because only the following versions of attrs are available: attrs<=20.3.0 attrs==21.1.0 - attrs>=21.2.0a0 + attrs>=21.2.0 and attrs==21.1.0 is unusable because it was yanked (reason: Installable but not importable on Python 3.4), we can conclude that - attrs>20.3.0,<21.2.0a0 cannot be used. - And because you require attrs>20.3.0,<21.2.0a0, we can conclude that the + attrs>20.3.0,<21.2.0 cannot be used. + And because you require attrs>20.3.0,<21.2.0, we can conclude that the requirements are unsatisfiable. - - hint: attrs was requested with a pre-release marker (e.g., any of: - attrs>20.3.0,<21.1.0 - attrs>21.1.0,<21.2.0a0 - ), but pre-releases weren't enabled (try: `--prerelease=allow`) "### ); @@ -3862,11 +3857,8 @@ fn compile_constraints_incompatible_url() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only anyio>=4a0 is available and you require anyio<4a0, we can + ╰─▶ Because only anyio>=4 is available and you require anyio<4, we can conclude that the requirements are unsatisfiable. - - hint: anyio was requested with a pre-release marker (e.g., anyio<4a0), - but pre-releases weren't enabled (try: `--prerelease=allow`) "### ); @@ -3889,11 +3881,8 @@ fn index_url_in_requirements() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because anyio<4a0 was not found in the package registry and you require - anyio<4a0, we can conclude that the requirements are unsatisfiable. - - hint: anyio was requested with a pre-release marker (e.g., anyio<4a0), - but pre-releases weren't enabled (try: `--prerelease=allow`) + ╰─▶ Because anyio<4 was not found in the package registry and you require + anyio<4, we can conclude that the requirements are unsatisfiable. "### ); @@ -4296,7 +4285,7 @@ fn override_with_incompatible_constraint() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because you require anyio>=3.0.0 and you require anyio<3.0.0a0, we can + ╰─▶ Because you require anyio>=3.0.0 and you require anyio<3.0.0, we can conclude that the requirements are unsatisfiable. "### );