mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-01 17:42:19 +00:00
Simplify pep440 -> version ranges conversion (#8683)
This commit is contained in:
parent
d0afd10ca4
commit
c1a0fb35e8
24 changed files with 286 additions and 415 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4329,6 +4329,7 @@ dependencies = [
|
||||||
"uv-pep508",
|
"uv-pep508",
|
||||||
"uv-pypi-types",
|
"uv-pypi-types",
|
||||||
"uv-warnings",
|
"uv-warnings",
|
||||||
|
"version-ranges",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
|
@ -31,6 +31,7 @@ spdx = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
version-ranges = { workspace = true }
|
||||||
walkdir = { workspace = true }
|
walkdir = { workspace = true }
|
||||||
zip = { workspace = true }
|
zip = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,11 @@ use std::str::FromStr;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
use uv_pep440::{Version, VersionRangesSpecifier, VersionSpecifiers};
|
use uv_pep440::{Version, VersionSpecifiers};
|
||||||
use uv_pep508::{Requirement, VersionOrUrl};
|
use uv_pep508::{Requirement, VersionOrUrl};
|
||||||
use uv_pypi_types::{Metadata23, VerbatimParsedUrl};
|
use uv_pypi_types::{Metadata23, VerbatimParsedUrl};
|
||||||
use uv_warnings::warn_user_once;
|
use uv_warnings::warn_user_once;
|
||||||
|
use version_ranges::Ranges;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
|
@ -134,9 +135,9 @@ impl PyProjectToml {
|
||||||
);
|
);
|
||||||
passed = false;
|
passed = false;
|
||||||
}
|
}
|
||||||
VersionRangesSpecifier::from_pep440_specifiers(specifier)
|
Ranges::from(specifier.clone())
|
||||||
.ok()
|
.bounding_range()
|
||||||
.and_then(|specifier| Some(specifier.bounding_range()?.1 != Bound::Unbounded))
|
.map(|bounding_range| bounding_range.1 != Bound::Unbounded)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ serde = { workspace = true, features = ["derive"] }
|
||||||
tracing = { workspace = true, optional = true }
|
tracing = { workspace = true, optional = true }
|
||||||
unicode-width = { workspace = true }
|
unicode-width = { workspace = true }
|
||||||
unscanny = { workspace = true }
|
unscanny = { workspace = true }
|
||||||
|
# Adds conversions from [`VersionSpecifiers`] to [`version_ranges::Ranges`]
|
||||||
version-ranges = { workspace = true, optional = true }
|
version-ranges = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
#[cfg(feature = "version-ranges")]
|
#[cfg(feature = "version-ranges")]
|
||||||
pub use version_ranges_specifier::{VersionRangesSpecifier, VersionRangesSpecifierError};
|
pub use version_ranges::{release_specifier_to_range, release_specifiers_to_ranges};
|
||||||
pub use {
|
pub use {
|
||||||
version::{
|
version::{
|
||||||
LocalSegment, Operator, OperatorParseError, Prerelease, PrereleaseKind, Version,
|
LocalSegment, Operator, OperatorParseError, Prerelease, PrereleaseKind, Version,
|
||||||
|
@ -42,4 +42,4 @@ mod version_specifier;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
#[cfg(feature = "version-ranges")]
|
#[cfg(feature = "version-ranges")]
|
||||||
mod version_ranges_specifier;
|
mod version_ranges;
|
||||||
|
|
|
@ -32,6 +32,8 @@ pub enum Operator {
|
||||||
/// `!= 1.2.*`
|
/// `!= 1.2.*`
|
||||||
NotEqualStar,
|
NotEqualStar,
|
||||||
/// `~=`
|
/// `~=`
|
||||||
|
///
|
||||||
|
/// Invariant: With `~=`, there are always at least 2 release segments.
|
||||||
TildeEqual,
|
TildeEqual,
|
||||||
/// `<`
|
/// `<`
|
||||||
LessThan,
|
LessThan,
|
||||||
|
|
192
crates/uv-pep440/src/version_ranges.rs
Normal file
192
crates/uv-pep440/src/version_ranges.rs
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
//! Convert [`VersionSpecifiers`] to [`version_ranges::Ranges`].
|
||||||
|
|
||||||
|
use version_ranges::Ranges;
|
||||||
|
|
||||||
|
use crate::{Operator, Prerelease, Version, VersionSpecifier, VersionSpecifiers};
|
||||||
|
|
||||||
|
impl From<VersionSpecifiers> for Ranges<Version> {
|
||||||
|
/// Convert [`VersionSpecifiers`] to a PubGrub-compatible version range, using PEP 440
|
||||||
|
/// semantics.
|
||||||
|
fn from(specifiers: VersionSpecifiers) -> Self {
|
||||||
|
let mut range = Ranges::full();
|
||||||
|
for specifier in specifiers {
|
||||||
|
range = range.intersection(&Self::from(specifier));
|
||||||
|
}
|
||||||
|
range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VersionSpecifier> for Ranges<Version> {
|
||||||
|
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using PEP 440
|
||||||
|
/// semantics.
|
||||||
|
fn from(specifier: VersionSpecifier) -> Self {
|
||||||
|
let VersionSpecifier { operator, version } = specifier;
|
||||||
|
match operator {
|
||||||
|
Operator::Equal => Ranges::singleton(version),
|
||||||
|
Operator::ExactEqual => Ranges::singleton(version),
|
||||||
|
Operator::NotEqual => Ranges::singleton(version).complement(),
|
||||||
|
Operator::TildeEqual => {
|
||||||
|
let [rest @ .., last, _] = version.release() else {
|
||||||
|
unreachable!("~= must have at least two segments");
|
||||||
|
};
|
||||||
|
let upper = Version::new(rest.iter().chain([&(last + 1)]))
|
||||||
|
.with_epoch(version.epoch())
|
||||||
|
.with_dev(Some(0));
|
||||||
|
|
||||||
|
Ranges::from_range_bounds(version..upper)
|
||||||
|
}
|
||||||
|
Operator::LessThan => {
|
||||||
|
if version.any_prerelease() {
|
||||||
|
Ranges::strictly_lower_than(version)
|
||||||
|
} else {
|
||||||
|
// Per PEP 440: "The exclusive ordered comparison <V MUST NOT allow a
|
||||||
|
// pre-release of the specified version unless the specified version is itself a
|
||||||
|
// pre-release."
|
||||||
|
Ranges::strictly_lower_than(version.with_min(Some(0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Operator::LessThanEqual => Ranges::lower_than(version),
|
||||||
|
Operator::GreaterThan => {
|
||||||
|
// Per PEP 440: "The exclusive ordered comparison >V MUST NOT allow a post-release of
|
||||||
|
// the given version unless V itself is a post release."
|
||||||
|
|
||||||
|
if let Some(dev) = version.dev() {
|
||||||
|
Ranges::higher_than(version.with_dev(Some(dev + 1)))
|
||||||
|
} else if let Some(post) = version.post() {
|
||||||
|
Ranges::higher_than(version.with_post(Some(post + 1)))
|
||||||
|
} else {
|
||||||
|
Ranges::strictly_higher_than(version.with_max(Some(0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Operator::GreaterThanEqual => Ranges::higher_than(version),
|
||||||
|
Operator::EqualStar => {
|
||||||
|
let low = version.with_dev(Some(0));
|
||||||
|
let mut high = low.clone();
|
||||||
|
if let Some(post) = high.post() {
|
||||||
|
high = high.with_post(Some(post + 1));
|
||||||
|
} else if let Some(pre) = high.pre() {
|
||||||
|
high = high.with_pre(Some(Prerelease {
|
||||||
|
kind: pre.kind,
|
||||||
|
number: pre.number + 1,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
let mut release = high.release().to_vec();
|
||||||
|
*release.last_mut().unwrap() += 1;
|
||||||
|
high = high.with_release(release);
|
||||||
|
}
|
||||||
|
Ranges::from_range_bounds(low..high)
|
||||||
|
}
|
||||||
|
Operator::NotEqualStar => {
|
||||||
|
let low = version.with_dev(Some(0));
|
||||||
|
let mut high = low.clone();
|
||||||
|
if let Some(post) = high.post() {
|
||||||
|
high = high.with_post(Some(post + 1));
|
||||||
|
} else if let Some(pre) = high.pre() {
|
||||||
|
high = high.with_pre(Some(Prerelease {
|
||||||
|
kind: pre.kind,
|
||||||
|
number: pre.number + 1,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
let mut release = high.release().to_vec();
|
||||||
|
*release.last_mut().unwrap() += 1;
|
||||||
|
high = high.with_release(release);
|
||||||
|
}
|
||||||
|
Ranges::from_range_bounds(low..high).complement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the [`VersionSpecifiers`] to a PubGrub-compatible version range, using release-only
|
||||||
|
/// semantics.
|
||||||
|
///
|
||||||
|
/// Assumes that the range will only be tested against versions that consist solely of release
|
||||||
|
/// segments (e.g., `3.12.0`, but not `3.12.0b1`).
|
||||||
|
///
|
||||||
|
/// These semantics are used for testing Python compatibility (e.g., `requires-python` against
|
||||||
|
/// the user's installed Python version). In that context, it's more intuitive that `3.13.0b0`
|
||||||
|
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
||||||
|
///
|
||||||
|
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
||||||
|
pub fn release_specifiers_to_ranges(specifiers: VersionSpecifiers) -> Ranges<Version> {
|
||||||
|
let mut range = Ranges::full();
|
||||||
|
for specifier in specifiers {
|
||||||
|
range = range.intersection(&release_specifier_to_range(specifier));
|
||||||
|
}
|
||||||
|
range
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using release-only
|
||||||
|
/// semantics.
|
||||||
|
///
|
||||||
|
/// Assumes that the range will only be tested against versions that consist solely of release
|
||||||
|
/// segments (e.g., `3.12.0`, but not `3.12.0b1`).
|
||||||
|
///
|
||||||
|
/// These semantics are used for testing Python compatibility (e.g., `requires-python` against
|
||||||
|
/// the user's installed Python version). In that context, it's more intuitive that `3.13.0b0`
|
||||||
|
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
||||||
|
///
|
||||||
|
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
||||||
|
pub fn release_specifier_to_range(specifier: VersionSpecifier) -> Ranges<Version> {
|
||||||
|
let VersionSpecifier { operator, version } = specifier;
|
||||||
|
match operator {
|
||||||
|
Operator::Equal => {
|
||||||
|
let version = version.only_release();
|
||||||
|
Ranges::singleton(version)
|
||||||
|
}
|
||||||
|
Operator::ExactEqual => {
|
||||||
|
let version = version.only_release();
|
||||||
|
Ranges::singleton(version)
|
||||||
|
}
|
||||||
|
Operator::NotEqual => {
|
||||||
|
let version = version.only_release();
|
||||||
|
Ranges::singleton(version).complement()
|
||||||
|
}
|
||||||
|
Operator::TildeEqual => {
|
||||||
|
let [rest @ .., last, _] = version.release() else {
|
||||||
|
unreachable!("~= must have at least two segments");
|
||||||
|
};
|
||||||
|
let upper = Version::new(rest.iter().chain([&(last + 1)]));
|
||||||
|
let version = version.only_release();
|
||||||
|
Ranges::from_range_bounds(version..upper)
|
||||||
|
}
|
||||||
|
Operator::LessThan => {
|
||||||
|
let version = version.only_release();
|
||||||
|
Ranges::strictly_lower_than(version)
|
||||||
|
}
|
||||||
|
Operator::LessThanEqual => {
|
||||||
|
let version = version.only_release();
|
||||||
|
Ranges::lower_than(version)
|
||||||
|
}
|
||||||
|
Operator::GreaterThan => {
|
||||||
|
let version = version.only_release();
|
||||||
|
Ranges::strictly_higher_than(version)
|
||||||
|
}
|
||||||
|
Operator::GreaterThanEqual => {
|
||||||
|
let version = version.only_release();
|
||||||
|
Ranges::higher_than(version)
|
||||||
|
}
|
||||||
|
Operator::EqualStar => {
|
||||||
|
let low = version.only_release();
|
||||||
|
let high = {
|
||||||
|
let mut high = low.clone();
|
||||||
|
let mut release = high.release().to_vec();
|
||||||
|
*release.last_mut().unwrap() += 1;
|
||||||
|
high = high.with_release(release);
|
||||||
|
high
|
||||||
|
};
|
||||||
|
Ranges::from_range_bounds(low..high)
|
||||||
|
}
|
||||||
|
Operator::NotEqualStar => {
|
||||||
|
let low = version.only_release();
|
||||||
|
let high = {
|
||||||
|
let mut high = low.clone();
|
||||||
|
let mut release = high.release().to_vec();
|
||||||
|
*release.last_mut().unwrap() += 1;
|
||||||
|
high = high.with_release(release);
|
||||||
|
high
|
||||||
|
};
|
||||||
|
Ranges::from_range_bounds(low..high).complement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,273 +0,0 @@
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::ops::Bound;
|
|
||||||
|
|
||||||
use version_ranges::Ranges;
|
|
||||||
|
|
||||||
use crate::{Operator, Prerelease, Version, VersionSpecifier, VersionSpecifiers};
|
|
||||||
|
|
||||||
/// The conversion between PEP 440 [`VersionSpecifier`] and version-ranges
|
|
||||||
/// [`VersionRangesSpecifier`] failed.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum VersionRangesSpecifierError {
|
|
||||||
/// The `~=` operator requires at least two release segments
|
|
||||||
InvalidTildeEquals(VersionSpecifier),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for VersionRangesSpecifierError {}
|
|
||||||
|
|
||||||
impl Display for VersionRangesSpecifierError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::InvalidTildeEquals(specifier) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"The `~=` operator requires at least two release segments: `{specifier}`"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A range of versions that can be used to satisfy a requirement.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct VersionRangesSpecifier(Ranges<Version>);
|
|
||||||
|
|
||||||
impl VersionRangesSpecifier {
|
|
||||||
/// Returns an iterator over the bounds of the [`VersionRangesSpecifier`].
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&Bound<Version>, &Bound<Version>)> {
|
|
||||||
self.0.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the bounding [`Ranges`] of the [`VersionRangesSpecifier`].
|
|
||||||
pub fn bounding_range(&self) -> Option<(Bound<&Version>, Bound<&Version>)> {
|
|
||||||
self.0.bounding_range()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Ranges<Version>> for VersionRangesSpecifier {
|
|
||||||
fn from(range: Ranges<Version>) -> Self {
|
|
||||||
VersionRangesSpecifier(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<VersionRangesSpecifier> for Ranges<Version> {
|
|
||||||
/// Convert a PubGrub specifier to a range of versions.
|
|
||||||
fn from(specifier: VersionRangesSpecifier) -> Self {
|
|
||||||
specifier.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VersionRangesSpecifier {
|
|
||||||
/// Convert [`VersionSpecifiers`] to a PubGrub-compatible version range, using PEP 440
|
|
||||||
/// semantics.
|
|
||||||
pub fn from_pep440_specifiers(
|
|
||||||
specifiers: &VersionSpecifiers,
|
|
||||||
) -> Result<Self, VersionRangesSpecifierError> {
|
|
||||||
let mut range = Ranges::full();
|
|
||||||
for specifier in specifiers.iter() {
|
|
||||||
range = range.intersection(&Self::from_pep440_specifier(specifier)?.into());
|
|
||||||
}
|
|
||||||
Ok(Self(range))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using PEP 440
|
|
||||||
/// semantics.
|
|
||||||
pub fn from_pep440_specifier(
|
|
||||||
specifier: &VersionSpecifier,
|
|
||||||
) -> Result<Self, VersionRangesSpecifierError> {
|
|
||||||
let ranges = match specifier.operator() {
|
|
||||||
Operator::Equal => {
|
|
||||||
let version = specifier.version().clone();
|
|
||||||
Ranges::singleton(version)
|
|
||||||
}
|
|
||||||
Operator::ExactEqual => {
|
|
||||||
let version = specifier.version().clone();
|
|
||||||
Ranges::singleton(version)
|
|
||||||
}
|
|
||||||
Operator::NotEqual => {
|
|
||||||
let version = specifier.version().clone();
|
|
||||||
Ranges::singleton(version).complement()
|
|
||||||
}
|
|
||||||
Operator::TildeEqual => {
|
|
||||||
let [rest @ .., last, _] = specifier.version().release() else {
|
|
||||||
return Err(VersionRangesSpecifierError::InvalidTildeEquals(
|
|
||||||
specifier.clone(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let upper = Version::new(rest.iter().chain([&(last + 1)]))
|
|
||||||
.with_epoch(specifier.version().epoch())
|
|
||||||
.with_dev(Some(0));
|
|
||||||
let version = specifier.version().clone();
|
|
||||||
Ranges::from_range_bounds(version..upper)
|
|
||||||
}
|
|
||||||
Operator::LessThan => {
|
|
||||||
let version = specifier.version().clone();
|
|
||||||
if version.any_prerelease() {
|
|
||||||
Ranges::strictly_lower_than(version)
|
|
||||||
} else {
|
|
||||||
// Per PEP 440: "The exclusive ordered comparison <V MUST NOT allow a
|
|
||||||
// pre-release of the specified version unless the specified version is itself a
|
|
||||||
// pre-release."
|
|
||||||
Ranges::strictly_lower_than(version.with_min(Some(0)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operator::LessThanEqual => {
|
|
||||||
let version = specifier.version().clone();
|
|
||||||
Ranges::lower_than(version)
|
|
||||||
}
|
|
||||||
Operator::GreaterThan => {
|
|
||||||
// Per PEP 440: "The exclusive ordered comparison >V MUST NOT allow a post-release of
|
|
||||||
// the given version unless V itself is a post release."
|
|
||||||
let version = specifier.version().clone();
|
|
||||||
if let Some(dev) = version.dev() {
|
|
||||||
Ranges::higher_than(version.with_dev(Some(dev + 1)))
|
|
||||||
} else if let Some(post) = version.post() {
|
|
||||||
Ranges::higher_than(version.with_post(Some(post + 1)))
|
|
||||||
} else {
|
|
||||||
Ranges::strictly_higher_than(version.with_max(Some(0)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Operator::GreaterThanEqual => {
|
|
||||||
let version = specifier.version().clone();
|
|
||||||
Ranges::higher_than(version)
|
|
||||||
}
|
|
||||||
Operator::EqualStar => {
|
|
||||||
let low = specifier.version().clone().with_dev(Some(0));
|
|
||||||
let mut high = low.clone();
|
|
||||||
if let Some(post) = high.post() {
|
|
||||||
high = high.with_post(Some(post + 1));
|
|
||||||
} else if let Some(pre) = high.pre() {
|
|
||||||
high = high.with_pre(Some(Prerelease {
|
|
||||||
kind: pre.kind,
|
|
||||||
number: pre.number + 1,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
let mut release = high.release().to_vec();
|
|
||||||
*release.last_mut().unwrap() += 1;
|
|
||||||
high = high.with_release(release);
|
|
||||||
}
|
|
||||||
Ranges::from_range_bounds(low..high)
|
|
||||||
}
|
|
||||||
Operator::NotEqualStar => {
|
|
||||||
let low = specifier.version().clone().with_dev(Some(0));
|
|
||||||
let mut high = low.clone();
|
|
||||||
if let Some(post) = high.post() {
|
|
||||||
high = high.with_post(Some(post + 1));
|
|
||||||
} else if let Some(pre) = high.pre() {
|
|
||||||
high = high.with_pre(Some(Prerelease {
|
|
||||||
kind: pre.kind,
|
|
||||||
number: pre.number + 1,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
let mut release = high.release().to_vec();
|
|
||||||
*release.last_mut().unwrap() += 1;
|
|
||||||
high = high.with_release(release);
|
|
||||||
}
|
|
||||||
Ranges::from_range_bounds(low..high).complement()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self(ranges))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the [`VersionSpecifiers`] to a PubGrub-compatible version range, using release-only
|
|
||||||
/// semantics.
|
|
||||||
///
|
|
||||||
/// Assumes that the range will only be tested against versions that consist solely of release
|
|
||||||
/// segments (e.g., `3.12.0`, but not `3.12.0b1`).
|
|
||||||
///
|
|
||||||
/// These semantics are used for testing Python compatibility (e.g., `requires-python` against
|
|
||||||
/// the user's installed Python version). In that context, it's more intuitive that `3.13.0b0`
|
|
||||||
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
|
||||||
///
|
|
||||||
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
|
||||||
pub fn from_release_specifiers(
|
|
||||||
specifiers: &VersionSpecifiers,
|
|
||||||
) -> Result<Self, VersionRangesSpecifierError> {
|
|
||||||
let mut range = Ranges::full();
|
|
||||||
for specifier in specifiers.iter() {
|
|
||||||
range = range.intersection(&Self::from_release_specifier(specifier)?.into());
|
|
||||||
}
|
|
||||||
Ok(Self(range))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using release-only
|
|
||||||
/// semantics.
|
|
||||||
///
|
|
||||||
/// Assumes that the range will only be tested against versions that consist solely of release
|
|
||||||
/// segments (e.g., `3.12.0`, but not `3.12.0b1`).
|
|
||||||
///
|
|
||||||
/// These semantics are used for testing Python compatibility (e.g., `requires-python` against
|
|
||||||
/// the user's installed Python version). In that context, it's more intuitive that `3.13.0b0`
|
|
||||||
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
|
||||||
///
|
|
||||||
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
|
||||||
pub fn from_release_specifier(
|
|
||||||
specifier: &VersionSpecifier,
|
|
||||||
) -> Result<Self, VersionRangesSpecifierError> {
|
|
||||||
let ranges = match specifier.operator() {
|
|
||||||
Operator::Equal => {
|
|
||||||
let version = specifier.version().only_release();
|
|
||||||
Ranges::singleton(version)
|
|
||||||
}
|
|
||||||
Operator::ExactEqual => {
|
|
||||||
let version = specifier.version().only_release();
|
|
||||||
Ranges::singleton(version)
|
|
||||||
}
|
|
||||||
Operator::NotEqual => {
|
|
||||||
let version = specifier.version().only_release();
|
|
||||||
Ranges::singleton(version).complement()
|
|
||||||
}
|
|
||||||
Operator::TildeEqual => {
|
|
||||||
let [rest @ .., last, _] = specifier.version().release() else {
|
|
||||||
return Err(VersionRangesSpecifierError::InvalidTildeEquals(
|
|
||||||
specifier.clone(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let upper = Version::new(rest.iter().chain([&(last + 1)]));
|
|
||||||
let version = specifier.version().only_release();
|
|
||||||
Ranges::from_range_bounds(version..upper)
|
|
||||||
}
|
|
||||||
Operator::LessThan => {
|
|
||||||
let version = specifier.version().only_release();
|
|
||||||
Ranges::strictly_lower_than(version)
|
|
||||||
}
|
|
||||||
Operator::LessThanEqual => {
|
|
||||||
let version = specifier.version().only_release();
|
|
||||||
Ranges::lower_than(version)
|
|
||||||
}
|
|
||||||
Operator::GreaterThan => {
|
|
||||||
let version = specifier.version().only_release();
|
|
||||||
Ranges::strictly_higher_than(version)
|
|
||||||
}
|
|
||||||
Operator::GreaterThanEqual => {
|
|
||||||
let version = specifier.version().only_release();
|
|
||||||
Ranges::higher_than(version)
|
|
||||||
}
|
|
||||||
Operator::EqualStar => {
|
|
||||||
let low = specifier.version().only_release();
|
|
||||||
let high = {
|
|
||||||
let mut high = low.clone();
|
|
||||||
let mut release = high.release().to_vec();
|
|
||||||
*release.last_mut().unwrap() += 1;
|
|
||||||
high = high.with_release(release);
|
|
||||||
high
|
|
||||||
};
|
|
||||||
Ranges::from_range_bounds(low..high)
|
|
||||||
}
|
|
||||||
Operator::NotEqualStar => {
|
|
||||||
let low = specifier.version().only_release();
|
|
||||||
let high = {
|
|
||||||
let mut high = low.clone();
|
|
||||||
let mut release = high.release().to_vec();
|
|
||||||
*release.last_mut().unwrap() += 1;
|
|
||||||
high = high.with_release(release);
|
|
||||||
high
|
|
||||||
};
|
|
||||||
Ranges::from_range_bounds(low..high).complement()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(Self(ranges))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -112,6 +112,15 @@ impl FromIterator<VersionSpecifier> for VersionSpecifiers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for VersionSpecifiers {
|
||||||
|
type Item = VersionSpecifier;
|
||||||
|
type IntoIter = std::vec::IntoIter<VersionSpecifier>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for VersionSpecifiers {
|
impl FromStr for VersionSpecifiers {
|
||||||
type Err = VersionSpecifiersParseError;
|
type Err = VersionSpecifiersParseError;
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,7 @@ use std::sync::MutexGuard;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use uv_pep440::{Operator, VersionRangesSpecifier};
|
use uv_pep440::{release_specifier_to_range, Operator, Version, VersionSpecifier};
|
||||||
use uv_pep440::{Version, VersionSpecifier};
|
|
||||||
use version_ranges::Ranges;
|
use version_ranges::Ranges;
|
||||||
|
|
||||||
use crate::marker::MarkerValueExtra;
|
use crate::marker::MarkerValueExtra;
|
||||||
|
@ -744,11 +743,9 @@ impl Edges {
|
||||||
|
|
||||||
/// Returns the [`Edges`] for a version specifier.
|
/// Returns the [`Edges`] for a version specifier.
|
||||||
fn from_specifier(specifier: VersionSpecifier) -> Edges {
|
fn from_specifier(specifier: VersionSpecifier) -> Edges {
|
||||||
let specifier =
|
let specifier = release_specifier_to_range(normalize_specifier(specifier));
|
||||||
VersionRangesSpecifier::from_release_specifier(&normalize_specifier(specifier))
|
|
||||||
.unwrap();
|
|
||||||
Edges::Version {
|
Edges::Version {
|
||||||
edges: Edges::from_range(&specifier.into()),
|
edges: Edges::from_range(&specifier),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -763,10 +760,8 @@ impl Edges {
|
||||||
for version in versions {
|
for version in versions {
|
||||||
let specifier = VersionSpecifier::equals_version(version.clone());
|
let specifier = VersionSpecifier::equals_version(version.clone());
|
||||||
let specifier = python_version_to_full_version(specifier)?;
|
let specifier = python_version_to_full_version(specifier)?;
|
||||||
let pubgrub_specifier =
|
let pubgrub_specifier = release_specifier_to_range(normalize_specifier(specifier));
|
||||||
VersionRangesSpecifier::from_release_specifier(&normalize_specifier(specifier))
|
range = range.union(&pubgrub_specifier);
|
||||||
.unwrap();
|
|
||||||
range = range.union(&pubgrub_specifier.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if negated {
|
if negated {
|
||||||
|
|
|
@ -19,7 +19,7 @@ use uv_distribution_types::{
|
||||||
BuiltDist, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, SourceDist,
|
BuiltDist, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, SourceDist,
|
||||||
};
|
};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep440::{Version, VersionRangesSpecifierError};
|
use uv_pep440::Version;
|
||||||
use uv_pep508::MarkerTree;
|
use uv_pep508::MarkerTree;
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
|
@ -37,9 +37,6 @@ pub enum ResolveError {
|
||||||
#[error("Attempted to wait on an unregistered task: `{_0}`")]
|
#[error("Attempted to wait on an unregistered task: `{_0}`")]
|
||||||
UnregisteredTask(String),
|
UnregisteredTask(String),
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
VersionRangesSpecifier(#[from] VersionRangesSpecifierError),
|
|
||||||
|
|
||||||
#[error("Overrides contain conflicting URLs for package `{0}`:\n- {1}\n- {2}")]
|
#[error("Overrides contain conflicting URLs for package `{0}`:\n- {1}\n- {2}")]
|
||||||
ConflictingOverrideUrls(PackageName, String, String),
|
ConflictingOverrideUrls(PackageName, String, String),
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub use options::{Flexibility, Options, OptionsBuilder};
|
||||||
pub use preferences::{Preference, PreferenceError, Preferences};
|
pub use preferences::{Preference, PreferenceError, Preferences};
|
||||||
pub use prerelease::PrereleaseMode;
|
pub use prerelease::PrereleaseMode;
|
||||||
pub use python_requirement::PythonRequirement;
|
pub use python_requirement::PythonRequirement;
|
||||||
pub use requires_python::{RequiresPython, RequiresPythonError, RequiresPythonRange};
|
pub use requires_python::{RequiresPython, RequiresPythonRange};
|
||||||
pub use resolution::{
|
pub use resolution::{
|
||||||
AnnotationStyle, ConflictingDistributionError, DisplayResolutionGraph, ResolutionGraph,
|
AnnotationStyle, ConflictingDistributionError, DisplayResolutionGraph, ResolutionGraph,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use pubgrub::Ranges;
|
||||||
use pubgrub::Range;
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
use uv_pep440::{Version, VersionRangesSpecifier, VersionSpecifiers};
|
use uv_pep440::{Version, VersionSpecifiers};
|
||||||
use uv_pypi_types::{
|
use uv_pypi_types::{
|
||||||
ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, Requirement,
|
ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, Requirement,
|
||||||
RequirementSource, VerbatimParsedUrl,
|
RequirementSource, VerbatimParsedUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner};
|
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner};
|
||||||
use crate::ResolveError;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub(crate) struct PubGrubDependency {
|
pub(crate) struct PubGrubDependency {
|
||||||
pub(crate) package: PubGrubPackage,
|
pub(crate) package: PubGrubPackage,
|
||||||
pub(crate) version: Range<Version>,
|
pub(crate) version: Ranges<Version>,
|
||||||
|
|
||||||
/// The original version specifiers from the requirement.
|
/// The original version specifiers from the requirement.
|
||||||
pub(crate) specifier: Option<VersionSpecifiers>,
|
pub(crate) specifier: Option<VersionSpecifiers>,
|
||||||
|
@ -32,12 +30,12 @@ impl PubGrubDependency {
|
||||||
pub(crate) fn from_requirement<'a>(
|
pub(crate) fn from_requirement<'a>(
|
||||||
requirement: &'a Requirement,
|
requirement: &'a Requirement,
|
||||||
source_name: Option<&'a PackageName>,
|
source_name: Option<&'a PackageName>,
|
||||||
) -> impl Iterator<Item = Result<Self, ResolveError>> + 'a {
|
) -> impl Iterator<Item = Self> + 'a {
|
||||||
// Add the package, plus any extra variants.
|
// Add the package, plus any extra variants.
|
||||||
iter::once(None)
|
iter::once(None)
|
||||||
.chain(requirement.extras.clone().into_iter().map(Some))
|
.chain(requirement.extras.clone().into_iter().map(Some))
|
||||||
.map(|extra| PubGrubRequirement::from_requirement(requirement, extra))
|
.map(|extra| PubGrubRequirement::from_requirement(requirement, extra))
|
||||||
.filter_map_ok(move |requirement| {
|
.filter_map(move |requirement| {
|
||||||
let PubGrubRequirement {
|
let PubGrubRequirement {
|
||||||
package,
|
package,
|
||||||
version,
|
version,
|
||||||
|
@ -87,7 +85,7 @@ impl PubGrubDependency {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct PubGrubRequirement {
|
pub(crate) struct PubGrubRequirement {
|
||||||
pub(crate) package: PubGrubPackage,
|
pub(crate) package: PubGrubPackage,
|
||||||
pub(crate) version: Range<Version>,
|
pub(crate) version: Ranges<Version>,
|
||||||
pub(crate) specifier: Option<VersionSpecifiers>,
|
pub(crate) specifier: Option<VersionSpecifiers>,
|
||||||
pub(crate) url: Option<VerbatimParsedUrl>,
|
pub(crate) url: Option<VerbatimParsedUrl>,
|
||||||
}
|
}
|
||||||
|
@ -95,10 +93,7 @@ pub(crate) struct PubGrubRequirement {
|
||||||
impl PubGrubRequirement {
|
impl PubGrubRequirement {
|
||||||
/// Convert a [`Requirement`] to a PubGrub-compatible package and range, while returning the URL
|
/// Convert a [`Requirement`] to a PubGrub-compatible package and range, while returning the URL
|
||||||
/// on the [`Requirement`], if any.
|
/// on the [`Requirement`], if any.
|
||||||
pub(crate) fn from_requirement(
|
pub(crate) fn from_requirement(requirement: &Requirement, extra: Option<ExtraName>) -> Self {
|
||||||
requirement: &Requirement,
|
|
||||||
extra: Option<ExtraName>,
|
|
||||||
) -> Result<Self, ResolveError> {
|
|
||||||
let (verbatim_url, parsed_url) = match &requirement.source {
|
let (verbatim_url, parsed_url) = match &requirement.source {
|
||||||
RequirementSource::Registry { specifier, .. } => {
|
RequirementSource::Registry { specifier, .. } => {
|
||||||
return Self::from_registry_requirement(specifier, extra, requirement);
|
return Self::from_registry_requirement(specifier, extra, requirement);
|
||||||
|
@ -159,29 +154,27 @@ impl PubGrubRequirement {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Self {
|
||||||
package: PubGrubPackage::from_package(
|
package: PubGrubPackage::from_package(
|
||||||
requirement.name.clone(),
|
requirement.name.clone(),
|
||||||
extra,
|
extra,
|
||||||
requirement.marker.clone(),
|
requirement.marker.clone(),
|
||||||
),
|
),
|
||||||
version: Range::full(),
|
version: Ranges::full(),
|
||||||
specifier: None,
|
specifier: None,
|
||||||
url: Some(VerbatimParsedUrl {
|
url: Some(VerbatimParsedUrl {
|
||||||
parsed_url,
|
parsed_url,
|
||||||
verbatim: verbatim_url.clone(),
|
verbatim: verbatim_url.clone(),
|
||||||
}),
|
}),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_registry_requirement(
|
fn from_registry_requirement(
|
||||||
specifier: &VersionSpecifiers,
|
specifier: &VersionSpecifiers,
|
||||||
extra: Option<ExtraName>,
|
extra: Option<ExtraName>,
|
||||||
requirement: &Requirement,
|
requirement: &Requirement,
|
||||||
) -> Result<PubGrubRequirement, ResolveError> {
|
) -> PubGrubRequirement {
|
||||||
let version = VersionRangesSpecifier::from_pep440_specifiers(specifier)?.into();
|
Self {
|
||||||
|
|
||||||
let requirement = Self {
|
|
||||||
package: PubGrubPackage::from_package(
|
package: PubGrubPackage::from_package(
|
||||||
requirement.name.clone(),
|
requirement.name.clone(),
|
||||||
extra,
|
extra,
|
||||||
|
@ -189,9 +182,7 @@ impl PubGrubRequirement {
|
||||||
),
|
),
|
||||||
specifier: Some(specifier.clone()),
|
specifier: Some(specifier.clone()),
|
||||||
url: None,
|
url: None,
|
||||||
version,
|
version: Ranges::from(specifier.clone()),
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(requirement)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,12 @@
|
||||||
use itertools::Itertools;
|
|
||||||
use pubgrub::Range;
|
use pubgrub::Range;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::Bound;
|
use std::collections::Bound;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use uv_distribution_filename::WheelFilename;
|
use uv_distribution_filename::WheelFilename;
|
||||||
use uv_pep440::{
|
use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifier, VersionSpecifiers};
|
||||||
Version, VersionRangesSpecifier, VersionRangesSpecifierError, VersionSpecifier,
|
|
||||||
VersionSpecifiers,
|
|
||||||
};
|
|
||||||
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum RequiresPythonError {
|
|
||||||
#[error(transparent)]
|
|
||||||
VersionRangesSpecifier(#[from] VersionRangesSpecifierError),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `Requires-Python` requirement specifier.
|
/// The `Requires-Python` requirement specifier.
|
||||||
///
|
///
|
||||||
/// See: <https://packaging.python.org/en/latest/guides/dropping-older-python-versions/>
|
/// See: <https://packaging.python.org/en/latest/guides/dropping-older-python-versions/>
|
||||||
|
@ -54,16 +44,15 @@ impl RequiresPython {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [`RequiresPython`] from a version specifier.
|
/// Returns a [`RequiresPython`] from a version specifier.
|
||||||
pub fn from_specifiers(specifiers: &VersionSpecifiers) -> Result<Self, RequiresPythonError> {
|
pub fn from_specifiers(specifiers: &VersionSpecifiers) -> Self {
|
||||||
let (lower_bound, upper_bound) =
|
let (lower_bound, upper_bound) = release_specifiers_to_ranges(specifiers.clone())
|
||||||
VersionRangesSpecifier::from_release_specifiers(specifiers)?
|
.bounding_range()
|
||||||
.bounding_range()
|
.map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned()))
|
||||||
.map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned()))
|
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
|
||||||
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
|
Self {
|
||||||
Ok(Self {
|
|
||||||
specifiers: specifiers.clone(),
|
specifiers: specifiers.clone(),
|
||||||
range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)),
|
range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [`RequiresPython`] to express the intersection of the given version specifiers.
|
/// Returns a [`RequiresPython`] to express the intersection of the given version specifiers.
|
||||||
|
@ -71,23 +60,19 @@ impl RequiresPython {
|
||||||
/// For example, given `>=3.8` and `>=3.9`, this would return `>=3.9`.
|
/// For example, given `>=3.8` and `>=3.9`, this would return `>=3.9`.
|
||||||
pub fn intersection<'a>(
|
pub fn intersection<'a>(
|
||||||
specifiers: impl Iterator<Item = &'a VersionSpecifiers>,
|
specifiers: impl Iterator<Item = &'a VersionSpecifiers>,
|
||||||
) -> Result<Option<Self>, RequiresPythonError> {
|
) -> Option<Self> {
|
||||||
// Convert to PubGrub range and perform an intersection.
|
// Convert to PubGrub range and perform an intersection.
|
||||||
let range = specifiers
|
let range = specifiers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(VersionRangesSpecifier::from_release_specifiers)
|
.map(|specifier| release_specifiers_to_ranges(specifier.clone()))
|
||||||
.fold_ok(None, |range: Option<Range<Version>>, requires_python| {
|
.fold(None, |range: Option<Range<Version>>, requires_python| {
|
||||||
if let Some(range) = range {
|
if let Some(range) = range {
|
||||||
Some(range.intersection(&requires_python.into()))
|
Some(range.intersection(&requires_python))
|
||||||
} else {
|
} else {
|
||||||
Some(requires_python.into())
|
Some(requires_python)
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let Some(range) = range else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract the bounds.
|
// Extract the bounds.
|
||||||
let (lower_bound, upper_bound) = range
|
let (lower_bound, upper_bound) = range
|
||||||
.bounding_range()
|
.bounding_range()
|
||||||
|
@ -102,10 +87,10 @@ impl RequiresPython {
|
||||||
// Convert back to PEP 440 specifiers.
|
// Convert back to PEP 440 specifiers.
|
||||||
let specifiers = VersionSpecifiers::from_release_only_bounds(range.iter());
|
let specifiers = VersionSpecifiers::from_release_only_bounds(range.iter());
|
||||||
|
|
||||||
Ok(Some(Self {
|
Some(Self {
|
||||||
specifiers,
|
specifiers,
|
||||||
range: RequiresPythonRange(lower_bound, upper_bound),
|
range: RequiresPythonRange(lower_bound, upper_bound),
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Narrow the [`RequiresPython`] by computing the intersection with the given range.
|
/// Narrow the [`RequiresPython`] by computing the intersection with the given range.
|
||||||
|
@ -260,14 +245,10 @@ impl RequiresPython {
|
||||||
/// N.B. This operation should primarily be used when evaluating the compatibility of a
|
/// N.B. This operation should primarily be used when evaluating the compatibility of a
|
||||||
/// project's `Requires-Python` specifier against a dependency's `Requires-Python` specifier.
|
/// project's `Requires-Python` specifier against a dependency's `Requires-Python` specifier.
|
||||||
pub fn is_contained_by(&self, target: &VersionSpecifiers) -> bool {
|
pub fn is_contained_by(&self, target: &VersionSpecifiers) -> bool {
|
||||||
let Ok(target) = VersionRangesSpecifier::from_release_specifiers(target) else {
|
let target = release_specifiers_to_ranges(target.clone())
|
||||||
return false;
|
.bounding_range()
|
||||||
};
|
.map(|bounding_range| bounding_range.0.cloned())
|
||||||
let target = target
|
.unwrap_or(Bound::Unbounded);
|
||||||
.iter()
|
|
||||||
.next()
|
|
||||||
.map(|(lower, _)| lower)
|
|
||||||
.unwrap_or(&Bound::Unbounded);
|
|
||||||
|
|
||||||
// We want, e.g., `self.range.lower()` to be `>=3.8` and `target` to be `>=3.7`.
|
// We want, e.g., `self.range.lower()` to be `>=3.8` and `target` to be `>=3.7`.
|
||||||
//
|
//
|
||||||
|
@ -508,12 +489,10 @@ impl serde::Serialize for RequiresPython {
|
||||||
impl<'de> serde::Deserialize<'de> for RequiresPython {
|
impl<'de> serde::Deserialize<'de> for RequiresPython {
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
let specifiers = VersionSpecifiers::deserialize(deserializer)?;
|
let specifiers = VersionSpecifiers::deserialize(deserializer)?;
|
||||||
let (lower_bound, upper_bound) =
|
let (lower_bound, upper_bound) = release_specifiers_to_ranges(specifiers.clone())
|
||||||
VersionRangesSpecifier::from_release_specifiers(&specifiers)
|
.bounding_range()
|
||||||
.map_err(serde::de::Error::custom)?
|
.map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned()))
|
||||||
.bounding_range()
|
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
|
||||||
.map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned()))
|
|
||||||
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
specifiers,
|
specifiers,
|
||||||
range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)),
|
range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)),
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::RequiresPython;
|
||||||
#[test]
|
#[test]
|
||||||
fn requires_python_included() {
|
fn requires_python_included() {
|
||||||
let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap();
|
let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap();
|
||||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||||
let wheel_names = &[
|
let wheel_names = &[
|
||||||
"bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl",
|
"bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl",
|
||||||
"black-24.4.2-cp310-cp310-win_amd64.whl",
|
"black-24.4.2-cp310-cp310-win_amd64.whl",
|
||||||
|
@ -30,7 +30,7 @@ fn requires_python_included() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap();
|
let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap();
|
||||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||||
let wheel_names = &["dearpygui-1.11.1-cp312-cp312-win_amd64.whl"];
|
let wheel_names = &["dearpygui-1.11.1-cp312-cp312-win_amd64.whl"];
|
||||||
for wheel_name in wheel_names {
|
for wheel_name in wheel_names {
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -40,7 +40,7 @@ fn requires_python_included() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let version_specifiers = VersionSpecifiers::from_str("==3.12.6").unwrap();
|
let version_specifiers = VersionSpecifiers::from_str("==3.12.6").unwrap();
|
||||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||||
let wheel_names = &["lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl"];
|
let wheel_names = &["lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl"];
|
||||||
for wheel_name in wheel_names {
|
for wheel_name in wheel_names {
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -50,7 +50,7 @@ fn requires_python_included() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let version_specifiers = VersionSpecifiers::from_str("==3.12").unwrap();
|
let version_specifiers = VersionSpecifiers::from_str("==3.12").unwrap();
|
||||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||||
let wheel_names = &["lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl"];
|
let wheel_names = &["lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl"];
|
||||||
for wheel_name in wheel_names {
|
for wheel_name in wheel_names {
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -63,7 +63,7 @@ fn requires_python_included() {
|
||||||
#[test]
|
#[test]
|
||||||
fn requires_python_dropped() {
|
fn requires_python_dropped() {
|
||||||
let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap();
|
let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap();
|
||||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||||
let wheel_names = &[
|
let wheel_names = &[
|
||||||
"PySocks-1.7.1-py27-none-any.whl",
|
"PySocks-1.7.1-py27-none-any.whl",
|
||||||
"black-24.4.2-cp39-cp39-win_amd64.whl",
|
"black-24.4.2-cp39-cp39-win_amd64.whl",
|
||||||
|
@ -83,7 +83,7 @@ fn requires_python_dropped() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap();
|
let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap();
|
||||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||||
let wheel_names = &["dearpygui-1.11.1-cp310-cp310-win_amd64.whl"];
|
let wheel_names = &["dearpygui-1.11.1-cp310-cp310-win_amd64.whl"];
|
||||||
for wheel_name in wheel_names {
|
for wheel_name in wheel_names {
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -152,7 +152,7 @@ fn is_exact_without_patch() {
|
||||||
];
|
];
|
||||||
for (version, expected) in test_cases {
|
for (version, expected) in test_cases {
|
||||||
let version_specifiers = VersionSpecifiers::from_str(version).unwrap();
|
let version_specifiers = VersionSpecifiers::from_str(version).unwrap();
|
||||||
let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap();
|
let requires_python = RequiresPython::from_specifiers(&version_specifiers);
|
||||||
assert_eq!(requires_python.is_exact_without_patch(), expected);
|
assert_eq!(requires_python.is_exact_without_patch(), expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use dashmap::DashMap;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use pubgrub::{Incompatibility, Range, State};
|
use pubgrub::{Incompatibility, Range, Ranges, State};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
@ -34,7 +34,7 @@ use uv_distribution_types::{
|
||||||
};
|
};
|
||||||
use uv_git::GitResolver;
|
use uv_git::GitResolver;
|
||||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||||
use uv_pep440::{Version, VersionRangesSpecifier, MIN_VERSION};
|
use uv_pep440::{release_specifiers_to_ranges, Version, MIN_VERSION};
|
||||||
use uv_pep508::MarkerTree;
|
use uv_pep508::MarkerTree;
|
||||||
use uv_platform_tags::Tags;
|
use uv_platform_tags::Tags;
|
||||||
use uv_pypi_types::{Requirement, ResolutionMetadata, VerbatimParsedUrl};
|
use uv_pypi_types::{Requirement, ResolutionMetadata, VerbatimParsedUrl};
|
||||||
|
@ -465,7 +465,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
let version = match version {
|
let version = match version {
|
||||||
ResolverVersion::Available(version) => version,
|
ResolverVersion::Available(version) => version,
|
||||||
ResolverVersion::Unavailable(version, reason) => {
|
ResolverVersion::Unavailable(version, reason) => {
|
||||||
state.add_unavailable_version(version, reason)?;
|
state.add_unavailable_version(version, reason);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1241,7 +1241,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
requirements
|
requirements
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|requirement| PubGrubDependency::from_requirement(requirement, None))
|
.flat_map(|requirement| PubGrubDependency::from_requirement(requirement, None))
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect()
|
||||||
}
|
}
|
||||||
PubGrubPackageInner::Package {
|
PubGrubPackageInner::Package {
|
||||||
name,
|
name,
|
||||||
|
@ -1349,12 +1349,12 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
python_requirement,
|
python_requirement,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut dependencies = requirements
|
let mut dependencies: Vec<_> = requirements
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|requirement| {
|
.flat_map(|requirement| {
|
||||||
PubGrubDependency::from_requirement(requirement, Some(name))
|
PubGrubDependency::from_requirement(requirement, Some(name))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect();
|
||||||
|
|
||||||
// If a package has metadata for an enabled dependency group,
|
// If a package has metadata for an enabled dependency group,
|
||||||
// add a dependency from it to the same package with the group
|
// add a dependency from it to the same package with the group
|
||||||
|
@ -1610,10 +1610,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
extras: constraint.extras.clone(),
|
extras: constraint.extras.clone(),
|
||||||
source: constraint.source.clone(),
|
source: constraint.source.clone(),
|
||||||
origin: constraint.origin.clone(),
|
origin: constraint.origin.clone(),
|
||||||
marker
|
marker,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
let requires_python = python_requirement.target();
|
let requires_python = python_requirement.target();
|
||||||
let python_marker = python_requirement.to_marker_tree();
|
let python_marker = python_requirement.to_marker_tree();
|
||||||
|
@ -1644,7 +1643,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
extras: constraint.extras.clone(),
|
extras: constraint.extras.clone(),
|
||||||
source: constraint.source.clone(),
|
source: constraint.source.clone(),
|
||||||
origin: constraint.origin.clone(),
|
origin: constraint.origin.clone(),
|
||||||
marker
|
marker,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2223,14 +2222,10 @@ impl ForkState {
|
||||||
.map(|specifier| {
|
.map(|specifier| {
|
||||||
Locals::map(local, specifier)
|
Locals::map(local, specifier)
|
||||||
.map_err(ResolveError::InvalidVersion)
|
.map_err(ResolveError::InvalidVersion)
|
||||||
.and_then(|specifier| {
|
.map(Ranges::from)
|
||||||
Ok(VersionRangesSpecifier::from_pep440_specifier(
|
|
||||||
&specifier,
|
|
||||||
)?)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.fold_ok(Range::full(), |range, specifier| {
|
.fold_ok(Range::full(), |range, specifier| {
|
||||||
range.intersection(&specifier.into())
|
range.intersection(&specifier)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Add the local version.
|
// Add the local version.
|
||||||
|
@ -2288,11 +2283,7 @@ impl ForkState {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_unavailable_version(
|
fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) {
|
||||||
&mut self,
|
|
||||||
version: Version,
|
|
||||||
reason: UnavailableVersion,
|
|
||||||
) -> Result<(), ResolveError> {
|
|
||||||
// Incompatible requires-python versions are special in that we track
|
// Incompatible requires-python versions are special in that we track
|
||||||
// them as incompatible dependencies instead of marking the package version
|
// them as incompatible dependencies instead of marking the package version
|
||||||
// as unavailable directly.
|
// as unavailable directly.
|
||||||
|
@ -2301,9 +2292,6 @@ impl ForkState {
|
||||||
| IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
|
| IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
|
||||||
) = reason
|
) = reason
|
||||||
{
|
{
|
||||||
let python_version: Range<Version> =
|
|
||||||
VersionRangesSpecifier::from_release_specifiers(&requires_python)?.into();
|
|
||||||
|
|
||||||
let package = &self.next;
|
let package = &self.next;
|
||||||
self.pubgrub
|
self.pubgrub
|
||||||
.add_incompatibility(Incompatibility::from_dependency(
|
.add_incompatibility(Incompatibility::from_dependency(
|
||||||
|
@ -2314,13 +2302,13 @@ impl ForkState {
|
||||||
PythonRequirementKind::Installed => PubGrubPython::Installed,
|
PythonRequirementKind::Installed => PubGrubPython::Installed,
|
||||||
PythonRequirementKind::Target => PubGrubPython::Target,
|
PythonRequirementKind::Target => PubGrubPython::Target,
|
||||||
})),
|
})),
|
||||||
python_version.clone(),
|
release_specifiers_to_ranges(requires_python),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
self.pubgrub
|
self.pubgrub
|
||||||
.partial_solution
|
.partial_solution
|
||||||
.add_decision(self.next.clone(), version);
|
.add_decision(self.next.clone(), version);
|
||||||
return Ok(());
|
return;
|
||||||
};
|
};
|
||||||
self.pubgrub
|
self.pubgrub
|
||||||
.add_incompatibility(Incompatibility::custom_version(
|
.add_incompatibility(Incompatibility::custom_version(
|
||||||
|
@ -2328,7 +2316,6 @@ impl ForkState {
|
||||||
version.clone(),
|
version.clone(),
|
||||||
UnavailableReason::Version(reason),
|
UnavailableReason::Version(reason),
|
||||||
));
|
));
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subset the current markers with the new markers and update the python requirements fields
|
/// Subset the current markers with the new markers and update the python requirements fields
|
||||||
|
|
|
@ -396,7 +396,7 @@ async fn build_package(
|
||||||
// (3) `Requires-Python` in `pyproject.toml`
|
// (3) `Requires-Python` in `pyproject.toml`
|
||||||
if interpreter_request.is_none() {
|
if interpreter_request.is_none() {
|
||||||
if let Ok(workspace) = workspace {
|
if let Ok(workspace) = workspace {
|
||||||
interpreter_request = find_requires_python(workspace)?
|
interpreter_request = find_requires_python(workspace)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(RequiresPython::specifiers)
|
.map(RequiresPython::specifiers)
|
||||||
.map(|specifiers| {
|
.map(|specifiers| {
|
||||||
|
|
|
@ -771,7 +771,4 @@ pub(crate) enum Error {
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Anyhow(#[from] anyhow::Error),
|
Anyhow(#[from] anyhow::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
VersionRangesSpecifier(#[from] uv_pep440::VersionRangesSpecifierError),
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,7 +362,7 @@ async fn init_project(
|
||||||
}
|
}
|
||||||
ref
|
ref
|
||||||
python_request @ PythonRequest::Version(VersionRequest::Range(ref specifiers, _)) => {
|
python_request @ PythonRequest::Version(VersionRequest::Range(ref specifiers, _)) => {
|
||||||
let requires_python = RequiresPython::from_specifiers(specifiers)?;
|
let requires_python = RequiresPython::from_specifiers(specifiers);
|
||||||
|
|
||||||
let python_request = if no_pin_python {
|
let python_request = if no_pin_python {
|
||||||
None
|
None
|
||||||
|
@ -417,10 +417,7 @@ async fn init_project(
|
||||||
(requires_python, python_request)
|
(requires_python, python_request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(requires_python) = workspace
|
} else if let Some(requires_python) = workspace.as_ref().and_then(find_requires_python) {
|
||||||
.as_ref()
|
|
||||||
.and_then(|workspace| find_requires_python(workspace).ok().flatten())
|
|
||||||
{
|
|
||||||
// (2) `Requires-Python` from the workspace
|
// (2) `Requires-Python` from the workspace
|
||||||
let python_request = PythonRequest::Version(VersionRequest::Range(
|
let python_request = PythonRequest::Version(VersionRequest::Range(
|
||||||
requires_python.specifiers().clone(),
|
requires_python.specifiers().clone(),
|
||||||
|
|
|
@ -390,7 +390,7 @@ async fn do_lock(
|
||||||
|
|
||||||
// Determine the supported Python range. If no range is defined, and warn and default to the
|
// Determine the supported Python range. If no range is defined, and warn and default to the
|
||||||
// current minor version.
|
// current minor version.
|
||||||
let requires_python = find_requires_python(workspace)?;
|
let requires_python = find_requires_python(workspace);
|
||||||
|
|
||||||
let requires_python = if let Some(requires_python) = requires_python {
|
let requires_python = if let Some(requires_python) = requires_python {
|
||||||
if requires_python.is_unbounded() {
|
if requires_python.is_unbounded() {
|
||||||
|
|
|
@ -176,9 +176,6 @@ pub(crate) enum ProjectError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Operation(#[from] pip::operations::Error),
|
Operation(#[from] pip::operations::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
RequiresPython(#[from] uv_resolver::RequiresPythonError),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Interpreter(#[from] uv_python::InterpreterError),
|
Interpreter(#[from] uv_python::InterpreterError),
|
||||||
|
|
||||||
|
@ -208,9 +205,7 @@ pub(crate) enum ProjectError {
|
||||||
///
|
///
|
||||||
/// For a [`Workspace`] with multiple packages, the `Requires-Python` bound is the union of the
|
/// For a [`Workspace`] with multiple packages, the `Requires-Python` bound is the union of the
|
||||||
/// `Requires-Python` bounds of all the packages.
|
/// `Requires-Python` bounds of all the packages.
|
||||||
pub(crate) fn find_requires_python(
|
pub(crate) fn find_requires_python(workspace: &Workspace) -> Option<RequiresPython> {
|
||||||
workspace: &Workspace,
|
|
||||||
) -> Result<Option<RequiresPython>, uv_resolver::RequiresPythonError> {
|
|
||||||
RequiresPython::intersection(workspace.packages().values().filter_map(|member| {
|
RequiresPython::intersection(workspace.packages().values().filter_map(|member| {
|
||||||
member
|
member
|
||||||
.pyproject_toml()
|
.pyproject_toml()
|
||||||
|
@ -341,7 +336,7 @@ impl WorkspacePython {
|
||||||
python_request: Option<PythonRequest>,
|
python_request: Option<PythonRequest>,
|
||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
) -> Result<Self, ProjectError> {
|
) -> Result<Self, ProjectError> {
|
||||||
let requires_python = find_requires_python(workspace)?;
|
let requires_python = find_requires_python(workspace);
|
||||||
|
|
||||||
let (source, python_request) = if let Some(request) = python_request {
|
let (source, python_request) = if let Some(request) = python_request {
|
||||||
// (1) Explicit request from user
|
// (1) Explicit request from user
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub(crate) async fn find(
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(project) = project {
|
if let Some(project) = project {
|
||||||
request = find_requires_python(project.workspace())?
|
request = find_requires_python(project.workspace())
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(RequiresPython::specifiers)
|
.map(RequiresPython::specifiers)
|
||||||
.map(|specifiers| {
|
.map(|specifiers| {
|
||||||
|
|
|
@ -253,7 +253,7 @@ fn assert_pin_compatible_with_project(pin: &Pin, virtual_project: &VirtualProjec
|
||||||
project_workspace.project_name(),
|
project_workspace.project_name(),
|
||||||
project_workspace.workspace().install_path().display()
|
project_workspace.workspace().install_path().display()
|
||||||
);
|
);
|
||||||
let requires_python = find_requires_python(project_workspace.workspace())?;
|
let requires_python = find_requires_python(project_workspace.workspace());
|
||||||
(requires_python, "project")
|
(requires_python, "project")
|
||||||
}
|
}
|
||||||
VirtualProject::NonProject(workspace) => {
|
VirtualProject::NonProject(workspace) => {
|
||||||
|
@ -261,7 +261,7 @@ fn assert_pin_compatible_with_project(pin: &Pin, virtual_project: &VirtualProjec
|
||||||
"Discovered virtual workspace at: {}",
|
"Discovered virtual workspace at: {}",
|
||||||
workspace.install_path().display()
|
workspace.install_path().display()
|
||||||
);
|
);
|
||||||
let requires_python = find_requires_python(workspace)?;
|
let requires_python = find_requires_python(workspace);
|
||||||
(requires_python, "workspace")
|
(requires_python, "workspace")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -198,7 +198,6 @@ async fn venv_impl(
|
||||||
if interpreter_request.is_none() {
|
if interpreter_request.is_none() {
|
||||||
if let Some(project) = project {
|
if let Some(project) = project {
|
||||||
interpreter_request = find_requires_python(project.workspace())
|
interpreter_request = find_requires_python(project.workspace())
|
||||||
.into_diagnostic()?
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(RequiresPython::specifiers)
|
.map(RequiresPython::specifiers)
|
||||||
.map(|specifiers| {
|
.map(|specifiers| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue