mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 02:22:19 +00:00
Implement PEP 440-compliant local version semantics (#8797)
Implement a full working version of local version semantics. The (AFAIA) major move towards this was implemented in #2430. This added support such that the version specifier `torch==2.1.0+cpu` would install `torch@2.1.0+cpu` and consider `torch@2.1.0+cpu` a valid way to satisfy the requirement `torch==2.1.0` in further dependency resolution. In this feature, we more fully support local version semantics. Namely, we now allow `torch==2.1.0` to install `torch@2.1.0+cpu` regardless of whether `torch@2.1.0` (no local tag) actually exists. We do this by adding an internal-only `Max` value to local versions that compare greater to all other local versions. Then we can translate `torch==2.1.0` into bounds: greater than 2.1.0 with no local tag and less than 2.1.0 with the `Max` local tag. Depends on https://github.com/astral-sh/packse/pull/227.
This commit is contained in:
parent
8ef5949294
commit
c49c7bdf97
15 changed files with 631 additions and 840 deletions
|
@ -27,8 +27,9 @@
|
|||
pub use version_ranges::{release_specifier_to_range, release_specifiers_to_ranges};
|
||||
pub use {
|
||||
version::{
|
||||
LocalSegment, Operator, OperatorParseError, Prerelease, PrereleaseKind, Version,
|
||||
VersionParseError, VersionPattern, VersionPatternParseError, MIN_VERSION,
|
||||
LocalSegment, LocalVersion, LocalVersionSlice, Operator, OperatorParseError, Prerelease,
|
||||
PrereleaseKind, Version, VersionParseError, VersionPattern, VersionPatternParseError,
|
||||
MIN_VERSION,
|
||||
},
|
||||
version_specifier::{
|
||||
VersionSpecifier, VersionSpecifierBuildError, VersionSpecifiers,
|
||||
|
|
|
@ -388,10 +388,10 @@ impl Version {
|
|||
|
||||
/// Returns the local segments in this version, if any exist.
|
||||
#[inline]
|
||||
pub fn local(&self) -> &[LocalSegment] {
|
||||
pub fn local(&self) -> LocalVersionSlice {
|
||||
match *self.inner {
|
||||
VersionInner::Small { ref small } => small.local(),
|
||||
VersionInner::Full { ref full } => &full.local,
|
||||
VersionInner::Full { ref full } => full.local.as_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -530,15 +530,28 @@ impl Version {
|
|||
/// Set the local segments and return the updated version.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_local(mut self, value: Vec<LocalSegment>) -> Self {
|
||||
pub fn with_local_segments(mut self, value: Vec<LocalSegment>) -> Self {
|
||||
if value.is_empty() {
|
||||
self.without_local()
|
||||
} else {
|
||||
self.make_full().local = value;
|
||||
self.make_full().local = LocalVersion::Segments(value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the local version and return the updated version.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_local(mut self, value: LocalVersion) -> Self {
|
||||
match value {
|
||||
LocalVersion::Segments(segments) => self.with_local_segments(segments),
|
||||
LocalVersion::Max => {
|
||||
self.make_full().local = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For PEP 440 specifier matching: "Except where specifically noted below,
|
||||
/// local version identifiers MUST NOT be permitted in version specifiers,
|
||||
/// and local version labels MUST be ignored entirely when checking if
|
||||
|
@ -615,7 +628,7 @@ impl Version {
|
|||
pre: small.pre(),
|
||||
post: small.post(),
|
||||
dev: small.dev(),
|
||||
local: vec![],
|
||||
local: LocalVersion::Segments(vec![]),
|
||||
};
|
||||
*self = Self {
|
||||
inner: Arc::new(VersionInner::Full { full }),
|
||||
|
@ -712,14 +725,12 @@ impl std::fmt::Display for Version {
|
|||
let local = if self.local().is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(
|
||||
"+{}",
|
||||
self.local()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(".")
|
||||
)
|
||||
match self.local() {
|
||||
LocalVersionSlice::Segments(_) => {
|
||||
format!("+{}", self.local())
|
||||
}
|
||||
LocalVersionSlice::Max => String::new(),
|
||||
}
|
||||
};
|
||||
write!(f, "{epoch}{release}{pre}{post}{dev}{local}")
|
||||
}
|
||||
|
@ -1195,10 +1206,10 @@ impl VersionSmall {
|
|||
|
||||
#[inline]
|
||||
#[allow(clippy::unused_self)]
|
||||
fn local(&self) -> &[LocalSegment] {
|
||||
fn local(&self) -> LocalVersionSlice {
|
||||
// A "small" version is never used if the version has a non-zero number
|
||||
// of local segments.
|
||||
&[]
|
||||
LocalVersionSlice::Segments(&[])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -1283,7 +1294,7 @@ struct VersionFull {
|
|||
///
|
||||
/// Local versions allow multiple segments separated by periods, such as `deadbeef.1.2.3`, see
|
||||
/// [`LocalSegment`] for details on the semantics.
|
||||
local: Vec<LocalSegment>,
|
||||
local: LocalVersion,
|
||||
/// An internal-only segment that does not exist in PEP 440, used to
|
||||
/// represent the smallest possible version of a release, preceding any
|
||||
/// `dev`, `pre`, `post` or releases.
|
||||
|
@ -1414,6 +1425,93 @@ impl std::fmt::Display for Prerelease {
|
|||
}
|
||||
}
|
||||
|
||||
/// Either a sequence of local segments or [`LocalVersion::Sentinel`], an internal-only value that
|
||||
/// compares greater than all other local versions.
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
||||
#[cfg_attr(
|
||||
feature = "rkyv",
|
||||
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
|
||||
)]
|
||||
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
|
||||
pub enum LocalVersion {
|
||||
/// A sequence of local segments.
|
||||
Segments(Vec<LocalSegment>),
|
||||
/// An internal-only value that compares greater to all other local versions.
|
||||
Max,
|
||||
}
|
||||
|
||||
/// Like [`LocalVersion`], but using a slice
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
|
||||
pub enum LocalVersionSlice<'a> {
|
||||
/// Like [`LocalVersion::Segments`]
|
||||
Segments(&'a [LocalSegment]),
|
||||
/// Like [`LocalVersion::Sentinel`]
|
||||
Max,
|
||||
}
|
||||
|
||||
impl LocalVersion {
|
||||
/// Convert the local version segments into a slice.
|
||||
pub fn as_slice(&self) -> LocalVersionSlice<'_> {
|
||||
match self {
|
||||
LocalVersion::Segments(segments) => LocalVersionSlice::Segments(segments),
|
||||
LocalVersion::Max => LocalVersionSlice::Max,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the local version segments, if they exist.
|
||||
pub fn clear(&mut self) {
|
||||
match self {
|
||||
Self::Segments(segments) => segments.clear(),
|
||||
Self::Max => *self = Self::Segments(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Output the local version identifier string.
|
||||
///
|
||||
/// [`LocalVersionSlice::Max`] maps to `"[max]"` which is otherwise an illegal local
|
||||
/// version because `[` and `]` are not allowed.
|
||||
impl std::fmt::Display for LocalVersionSlice<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LocalVersionSlice::Segments(segments) => {
|
||||
for (i, segment) in segments.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ".")?;
|
||||
}
|
||||
write!(f, "{segment}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
LocalVersionSlice::Max => write!(f, "[max]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for LocalVersionSlice<'_> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for LocalVersionSlice<'_> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(LocalVersionSlice::Segments(lv1), LocalVersionSlice::Segments(lv2)) => lv1.cmp(lv2),
|
||||
(LocalVersionSlice::Segments(_), LocalVersionSlice::Max) => Ordering::Less,
|
||||
(LocalVersionSlice::Max, LocalVersionSlice::Segments(_)) => Ordering::Greater,
|
||||
(LocalVersionSlice::Max, LocalVersionSlice::Max) => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalVersionSlice<'_> {
|
||||
/// Whether the local version is absent
|
||||
pub fn is_empty(&self) -> bool {
|
||||
matches!(self, Self::Segments(&[]))
|
||||
}
|
||||
}
|
||||
|
||||
/// A part of the [local version identifier](<https://peps.python.org/pep-0440/#local-version-identifiers>)
|
||||
///
|
||||
/// Local versions are a mess:
|
||||
|
@ -1855,7 +1953,7 @@ impl<'a> Parser<'a> {
|
|||
.with_pre(self.pre)
|
||||
.with_post(self.post)
|
||||
.with_dev(self.dev)
|
||||
.with_local(self.local);
|
||||
.with_local(LocalVersion::Segments(self.local));
|
||||
VersionPattern {
|
||||
version,
|
||||
wildcard: self.wildcard,
|
||||
|
@ -2326,7 +2424,7 @@ pub(crate) fn compare_release(this: &[u64], other: &[u64]) -> Ordering {
|
|||
/// implementation
|
||||
///
|
||||
/// [pep440-suffix-ordering]: https://peps.python.org/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering
|
||||
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, &[LocalSegment]) {
|
||||
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, LocalVersionSlice) {
|
||||
// If the version is a "max" version, use a post version larger than any possible post version.
|
||||
let post = if version.max().is_some() {
|
||||
Some(u64::MAX)
|
||||
|
|
|
@ -125,46 +125,50 @@ fn test_packaging_versions() {
|
|||
("1.1.dev1", Version::new([1, 1]).with_dev(Some(1))),
|
||||
(
|
||||
"1.2+123abc",
|
||||
Version::new([1, 2]).with_local(vec![LocalSegment::String("123abc".to_string())]),
|
||||
Version::new([1, 2])
|
||||
.with_local_segments(vec![LocalSegment::String("123abc".to_string())]),
|
||||
),
|
||||
(
|
||||
"1.2+123abc456",
|
||||
Version::new([1, 2]).with_local(vec![LocalSegment::String("123abc456".to_string())]),
|
||||
Version::new([1, 2])
|
||||
.with_local_segments(vec![LocalSegment::String("123abc456".to_string())]),
|
||||
),
|
||||
(
|
||||
"1.2+abc",
|
||||
Version::new([1, 2]).with_local(vec![LocalSegment::String("abc".to_string())]),
|
||||
Version::new([1, 2]).with_local_segments(vec![LocalSegment::String("abc".to_string())]),
|
||||
),
|
||||
(
|
||||
"1.2+abc123",
|
||||
Version::new([1, 2]).with_local(vec![LocalSegment::String("abc123".to_string())]),
|
||||
Version::new([1, 2])
|
||||
.with_local_segments(vec![LocalSegment::String("abc123".to_string())]),
|
||||
),
|
||||
(
|
||||
"1.2+abc123def",
|
||||
Version::new([1, 2]).with_local(vec![LocalSegment::String("abc123def".to_string())]),
|
||||
Version::new([1, 2])
|
||||
.with_local_segments(vec![LocalSegment::String("abc123def".to_string())]),
|
||||
),
|
||||
(
|
||||
"1.2+1234.abc",
|
||||
Version::new([1, 2]).with_local(vec![
|
||||
Version::new([1, 2]).with_local_segments(vec![
|
||||
LocalSegment::Number(1234),
|
||||
LocalSegment::String("abc".to_string()),
|
||||
]),
|
||||
),
|
||||
(
|
||||
"1.2+123456",
|
||||
Version::new([1, 2]).with_local(vec![LocalSegment::Number(123_456)]),
|
||||
Version::new([1, 2]).with_local_segments(vec![LocalSegment::Number(123_456)]),
|
||||
),
|
||||
(
|
||||
"1.2.r32+123456",
|
||||
Version::new([1, 2])
|
||||
.with_post(Some(32))
|
||||
.with_local(vec![LocalSegment::Number(123_456)]),
|
||||
.with_local_segments(vec![LocalSegment::Number(123_456)]),
|
||||
),
|
||||
(
|
||||
"1.2.rev33+123456",
|
||||
Version::new([1, 2])
|
||||
.with_post(Some(33))
|
||||
.with_local(vec![LocalSegment::Number(123_456)]),
|
||||
.with_local_segments(vec![LocalSegment::Number(123_456)]),
|
||||
),
|
||||
// Explicit epoch of 1
|
||||
(
|
||||
|
@ -316,35 +320,35 @@ fn test_packaging_versions() {
|
|||
"1!1.2+123abc",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(1)
|
||||
.with_local(vec![LocalSegment::String("123abc".to_string())]),
|
||||
.with_local_segments(vec![LocalSegment::String("123abc".to_string())]),
|
||||
),
|
||||
(
|
||||
"1!1.2+123abc456",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(1)
|
||||
.with_local(vec![LocalSegment::String("123abc456".to_string())]),
|
||||
.with_local_segments(vec![LocalSegment::String("123abc456".to_string())]),
|
||||
),
|
||||
(
|
||||
"1!1.2+abc",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(1)
|
||||
.with_local(vec![LocalSegment::String("abc".to_string())]),
|
||||
.with_local_segments(vec![LocalSegment::String("abc".to_string())]),
|
||||
),
|
||||
(
|
||||
"1!1.2+abc123",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(1)
|
||||
.with_local(vec![LocalSegment::String("abc123".to_string())]),
|
||||
.with_local_segments(vec![LocalSegment::String("abc123".to_string())]),
|
||||
),
|
||||
(
|
||||
"1!1.2+abc123def",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(1)
|
||||
.with_local(vec![LocalSegment::String("abc123def".to_string())]),
|
||||
.with_local_segments(vec![LocalSegment::String("abc123def".to_string())]),
|
||||
),
|
||||
(
|
||||
"1!1.2+1234.abc",
|
||||
Version::new([1, 2]).with_epoch(1).with_local(vec![
|
||||
Version::new([1, 2]).with_epoch(1).with_local_segments(vec![
|
||||
LocalSegment::Number(1234),
|
||||
LocalSegment::String("abc".to_string()),
|
||||
]),
|
||||
|
@ -353,28 +357,28 @@ fn test_packaging_versions() {
|
|||
"1!1.2+123456",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(1)
|
||||
.with_local(vec![LocalSegment::Number(123_456)]),
|
||||
.with_local_segments(vec![LocalSegment::Number(123_456)]),
|
||||
),
|
||||
(
|
||||
"1!1.2.r32+123456",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(1)
|
||||
.with_post(Some(32))
|
||||
.with_local(vec![LocalSegment::Number(123_456)]),
|
||||
.with_local_segments(vec![LocalSegment::Number(123_456)]),
|
||||
),
|
||||
(
|
||||
"1!1.2.rev33+123456",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(1)
|
||||
.with_post(Some(33))
|
||||
.with_local(vec![LocalSegment::Number(123_456)]),
|
||||
.with_local_segments(vec![LocalSegment::Number(123_456)]),
|
||||
),
|
||||
(
|
||||
"98765!1.2.rev33+123456",
|
||||
Version::new([1, 2])
|
||||
.with_epoch(98765)
|
||||
.with_post(Some(33))
|
||||
.with_local(vec![LocalSegment::Number(123_456)]),
|
||||
.with_local_segments(vec![LocalSegment::Number(123_456)]),
|
||||
),
|
||||
];
|
||||
for (string, structured) in versions {
|
||||
|
@ -879,50 +883,50 @@ fn parse_version_valid() {
|
|||
// local tests
|
||||
assert_eq!(
|
||||
p("5+2"),
|
||||
Version::new([5]).with_local(vec![LocalSegment::Number(2)])
|
||||
Version::new([5]).with_local_segments(vec![LocalSegment::Number(2)])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5+a"),
|
||||
Version::new([5]).with_local(vec![LocalSegment::String("a".to_string())])
|
||||
Version::new([5]).with_local_segments(vec![LocalSegment::String("a".to_string())])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5+abc.123"),
|
||||
Version::new([5]).with_local(vec![
|
||||
Version::new([5]).with_local_segments(vec![
|
||||
LocalSegment::String("abc".to_string()),
|
||||
LocalSegment::Number(123),
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5+123.abc"),
|
||||
Version::new([5]).with_local(vec![
|
||||
Version::new([5]).with_local_segments(vec![
|
||||
LocalSegment::Number(123),
|
||||
LocalSegment::String("abc".to_string()),
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5+18446744073709551615.abc"),
|
||||
Version::new([5]).with_local(vec![
|
||||
Version::new([5]).with_local_segments(vec![
|
||||
LocalSegment::Number(18_446_744_073_709_551_615),
|
||||
LocalSegment::String("abc".to_string()),
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5+18446744073709551616.abc"),
|
||||
Version::new([5]).with_local(vec![
|
||||
Version::new([5]).with_local_segments(vec![
|
||||
LocalSegment::String("18446744073709551616".to_string()),
|
||||
LocalSegment::String("abc".to_string()),
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5+ABC.123"),
|
||||
Version::new([5]).with_local(vec![
|
||||
Version::new([5]).with_local_segments(vec![
|
||||
LocalSegment::String("abc".to_string()),
|
||||
LocalSegment::Number(123),
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5+ABC-123.4_5_xyz-MNO"),
|
||||
Version::new([5]).with_local(vec![
|
||||
Version::new([5]).with_local_segments(vec![
|
||||
LocalSegment::String("abc".to_string()),
|
||||
LocalSegment::Number(123),
|
||||
LocalSegment::Number(4),
|
||||
|
@ -933,21 +937,21 @@ fn parse_version_valid() {
|
|||
);
|
||||
assert_eq!(
|
||||
p("5.6.7+abc-00123"),
|
||||
Version::new([5, 6, 7]).with_local(vec![
|
||||
Version::new([5, 6, 7]).with_local_segments(vec![
|
||||
LocalSegment::String("abc".to_string()),
|
||||
LocalSegment::Number(123),
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5.6.7+abc-foo00123"),
|
||||
Version::new([5, 6, 7]).with_local(vec![
|
||||
Version::new([5, 6, 7]).with_local_segments(vec![
|
||||
LocalSegment::String("abc".to_string()),
|
||||
LocalSegment::String("foo00123".to_string()),
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
p("5.6.7+abc-00123a"),
|
||||
Version::new([5, 6, 7]).with_local(vec![
|
||||
Version::new([5, 6, 7]).with_local_segments(vec![
|
||||
LocalSegment::String("abc".to_string()),
|
||||
LocalSegment::String("00123a".to_string()),
|
||||
])
|
||||
|
@ -992,7 +996,7 @@ fn parse_version_valid() {
|
|||
assert_eq!(p(" 5 "), Version::new([5]));
|
||||
assert_eq!(
|
||||
p(" 5.6.7+abc.123.xyz "),
|
||||
Version::new([5, 6, 7]).with_local(vec![
|
||||
Version::new([5, 6, 7]).with_local_segments(vec![
|
||||
LocalSegment::String("abc".to_string()),
|
||||
LocalSegment::Number(123),
|
||||
LocalSegment::String("xyz".to_string())
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
use version_ranges::Ranges;
|
||||
|
||||
use crate::{Operator, Prerelease, Version, VersionSpecifier, VersionSpecifiers};
|
||||
use crate::{
|
||||
LocalVersion, LocalVersionSlice, Operator, Prerelease, Version, VersionSpecifier,
|
||||
VersionSpecifiers,
|
||||
};
|
||||
|
||||
impl From<VersionSpecifiers> for Ranges<Version> {
|
||||
/// Convert [`VersionSpecifiers`] to a PubGrub-compatible version range, using PEP 440
|
||||
|
@ -22,9 +25,23 @@ impl From<VersionSpecifier> for Ranges<Version> {
|
|||
fn from(specifier: VersionSpecifier) -> Self {
|
||||
let VersionSpecifier { operator, version } = specifier;
|
||||
match operator {
|
||||
Operator::Equal => Ranges::singleton(version),
|
||||
Operator::Equal => match version.local() {
|
||||
LocalVersionSlice::Segments(&[]) => {
|
||||
let low = version;
|
||||
let high = low.clone().with_local(LocalVersion::Max);
|
||||
Ranges::between(low, high)
|
||||
}
|
||||
LocalVersionSlice::Segments(_) => Ranges::singleton(version),
|
||||
LocalVersionSlice::Max => unreachable!(
|
||||
"found `LocalVersionSlice::Sentinel`, which should be an internal-only value"
|
||||
),
|
||||
},
|
||||
Operator::ExactEqual => Ranges::singleton(version),
|
||||
Operator::NotEqual => Ranges::singleton(version).complement(),
|
||||
Operator::NotEqual => Ranges::from(VersionSpecifier {
|
||||
operator: Operator::Equal,
|
||||
version,
|
||||
})
|
||||
.complement(),
|
||||
Operator::TildeEqual => {
|
||||
let [rest @ .., last, _] = version.release() else {
|
||||
unreachable!("~= must have at least two segments");
|
||||
|
@ -45,7 +62,7 @@ impl From<VersionSpecifier> for Ranges<Version> {
|
|||
Ranges::strictly_lower_than(version.with_min(Some(0)))
|
||||
}
|
||||
}
|
||||
Operator::LessThanEqual => Ranges::lower_than(version),
|
||||
Operator::LessThanEqual => Ranges::lower_than(version.with_local(LocalVersion::Max)),
|
||||
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."
|
||||
|
|
|
@ -652,12 +652,7 @@ impl std::fmt::Display for VersionSpecifierBuildError {
|
|||
operator: ref op,
|
||||
ref version,
|
||||
} => {
|
||||
let local = version
|
||||
.local()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(".");
|
||||
let local = version.local();
|
||||
write!(
|
||||
f,
|
||||
"Operator {op} is incompatible with versions \
|
||||
|
|
|
@ -579,7 +579,8 @@ fn test_invalid_specifier() {
|
|||
ParseErrorKind::InvalidSpecifier(
|
||||
BuildErrorKind::OperatorLocalCombo {
|
||||
operator: Operator::TildeEqual,
|
||||
version: Version::new([1, 0]).with_local(vec![LocalSegment::Number(5)]),
|
||||
version: Version::new([1, 0])
|
||||
.with_local_segments(vec![LocalSegment::Number(5)]),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
|
@ -591,7 +592,7 @@ fn test_invalid_specifier() {
|
|||
BuildErrorKind::OperatorLocalCombo {
|
||||
operator: Operator::GreaterThanEqual,
|
||||
version: Version::new([1, 0])
|
||||
.with_local(vec![LocalSegment::String("deadbeef".to_string())]),
|
||||
.with_local_segments(vec![LocalSegment::String("deadbeef".to_string())]),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
|
@ -603,7 +604,7 @@ fn test_invalid_specifier() {
|
|||
BuildErrorKind::OperatorLocalCombo {
|
||||
operator: Operator::LessThanEqual,
|
||||
version: Version::new([1, 0])
|
||||
.with_local(vec![LocalSegment::String("abc123".to_string())]),
|
||||
.with_local_segments(vec![LocalSegment::String("abc123".to_string())]),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
|
@ -615,7 +616,7 @@ fn test_invalid_specifier() {
|
|||
BuildErrorKind::OperatorLocalCombo {
|
||||
operator: Operator::GreaterThan,
|
||||
version: Version::new([1, 0])
|
||||
.with_local(vec![LocalSegment::String("watwat".to_string())]),
|
||||
.with_local_segments(vec![LocalSegment::String("watwat".to_string())]),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
|
@ -626,8 +627,10 @@ fn test_invalid_specifier() {
|
|||
ParseErrorKind::InvalidSpecifier(
|
||||
BuildErrorKind::OperatorLocalCombo {
|
||||
operator: Operator::LessThan,
|
||||
version: Version::new([1, 0])
|
||||
.with_local(vec![LocalSegment::Number(1), LocalSegment::Number(0)]),
|
||||
version: Version::new([1, 0]).with_local_segments(vec![
|
||||
LocalSegment::Number(1),
|
||||
LocalSegment::Number(0),
|
||||
]),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
|
|
|
@ -2125,7 +2125,9 @@ impl FromStr for VersionRequest {
|
|||
return Err(Error::InvalidVersionRequest(s.to_string()));
|
||||
}
|
||||
|
||||
let [uv_pep440::LocalSegment::String(local)] = version.local() else {
|
||||
let uv_pep440::LocalVersionSlice::Segments([uv_pep440::LocalSegment::String(local)]) =
|
||||
version.local()
|
||||
else {
|
||||
return Err(Error::InvalidVersionRequest(s.to_string()));
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, Bound};
|
||||
use std::fmt::Formatter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use pubgrub::{DefaultStringReporter, DerivationTree, Derived, External, Range, Reporter};
|
||||
use pubgrub::{
|
||||
DefaultStringReporter, DerivationTree, Derived, External, Range, Ranges, Reporter, Term,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::trace;
|
||||
|
||||
use uv_distribution_types::{
|
||||
BuiltDist, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, SourceDist,
|
||||
};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::{LocalVersionSlice, Version};
|
||||
use uv_static::EnvVars;
|
||||
|
||||
use crate::candidate_selector::CandidateSelector;
|
||||
use crate::dependency_provider::UvDependencyProvider;
|
||||
|
@ -16,13 +26,6 @@ use crate::resolver::{
|
|||
IncompletePackage, ResolverEnvironment, UnavailablePackage, UnavailableReason,
|
||||
};
|
||||
use crate::Options;
|
||||
use tracing::trace;
|
||||
use uv_distribution_types::{
|
||||
BuiltDist, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, SourceDist,
|
||||
};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ResolveError {
|
||||
|
@ -221,6 +224,178 @@ impl NoSolutionError {
|
|||
.expect("derivation tree should contain at least one external term")
|
||||
}
|
||||
|
||||
/// Simplifies the version ranges on any incompatibilities to remove the `[max]` sentinel.
|
||||
///
|
||||
/// The `[max]` sentinel is used to represent the maximum local version of a package, to
|
||||
/// implement PEP 440 semantics for local version equality. For example, `1.0.0+foo` needs to
|
||||
/// satisfy `==1.0.0`.
|
||||
pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
|
||||
/// Remove local versions sentinels (`+[max]`) from the interval.
|
||||
fn strip_sentinel(
|
||||
mut lower: Bound<Version>,
|
||||
mut upper: Bound<Version>,
|
||||
) -> (Bound<Version>, Bound<Version>) {
|
||||
match (&lower, &upper) {
|
||||
(Bound::Unbounded, Bound::Unbounded) => {}
|
||||
(Bound::Unbounded, Bound::Included(v)) => {
|
||||
// `<=1.0.0+[max]` is equivalent to `<=1.0.0`
|
||||
if v.local() == LocalVersionSlice::Max {
|
||||
upper = Bound::Included(v.clone().without_local());
|
||||
}
|
||||
}
|
||||
(Bound::Unbounded, Bound::Excluded(v)) => {
|
||||
// `<1.0.0+[max]` is equivalent to `<1.0.0`
|
||||
if v.local() == LocalVersionSlice::Max {
|
||||
upper = Bound::Excluded(v.clone().without_local());
|
||||
}
|
||||
}
|
||||
(Bound::Included(v), Bound::Unbounded) => {
|
||||
// `>=1.0.0+[max]` is equivalent to `>1.0.0`
|
||||
if v.local() == LocalVersionSlice::Max {
|
||||
lower = Bound::Excluded(v.clone().without_local());
|
||||
}
|
||||
}
|
||||
(Bound::Included(v), Bound::Included(b)) => {
|
||||
// `>=1.0.0+[max]` is equivalent to `>1.0.0`
|
||||
if v.local() == LocalVersionSlice::Max {
|
||||
lower = Bound::Excluded(v.clone().without_local());
|
||||
}
|
||||
// `<=1.0.0+[max]` is equivalent to `<=1.0.0`
|
||||
if b.local() == LocalVersionSlice::Max {
|
||||
upper = Bound::Included(b.clone().without_local());
|
||||
}
|
||||
}
|
||||
(Bound::Included(v), Bound::Excluded(b)) => {
|
||||
// `>=1.0.0+[max]` is equivalent to `>1.0.0`
|
||||
if v.local() == LocalVersionSlice::Max {
|
||||
lower = Bound::Excluded(v.clone().without_local());
|
||||
}
|
||||
// `<1.0.0+[max]` is equivalent to `<1.0.0`
|
||||
if b.local() == LocalVersionSlice::Max {
|
||||
upper = Bound::Included(b.clone().without_local());
|
||||
}
|
||||
}
|
||||
(Bound::Excluded(v), Bound::Unbounded) => {
|
||||
// `>1.0.0+[max]` is equivalent to `>1.0.0`
|
||||
if v.local() == LocalVersionSlice::Max {
|
||||
lower = Bound::Excluded(v.clone().without_local());
|
||||
}
|
||||
}
|
||||
(Bound::Excluded(v), Bound::Included(b)) => {
|
||||
// `>1.0.0+[max]` is equivalent to `>1.0.0`
|
||||
if v.local() == LocalVersionSlice::Max {
|
||||
lower = Bound::Excluded(v.clone().without_local());
|
||||
}
|
||||
// `<=1.0.0+[max]` is equivalent to `<=1.0.0`
|
||||
if b.local() == LocalVersionSlice::Max {
|
||||
upper = Bound::Included(b.clone().without_local());
|
||||
}
|
||||
}
|
||||
(Bound::Excluded(v), Bound::Excluded(b)) => {
|
||||
// `>1.0.0+[max]` is equivalent to `>1.0.0`
|
||||
if v.local() == LocalVersionSlice::Max {
|
||||
lower = Bound::Excluded(v.clone().without_local());
|
||||
}
|
||||
// `<1.0.0+[max]` is equivalent to `<1.0.0`
|
||||
if b.local() == LocalVersionSlice::Max {
|
||||
upper = Bound::Excluded(b.clone().without_local());
|
||||
}
|
||||
}
|
||||
}
|
||||
(lower, upper)
|
||||
}
|
||||
|
||||
/// Remove local versions sentinels (`+[max]`) from the version ranges.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn strip_sentinels(versions: Ranges<Version>) -> Ranges<Version> {
|
||||
let mut range = Ranges::empty();
|
||||
for (lower, upper) in versions.iter() {
|
||||
let (lower, upper) = strip_sentinel(lower.clone(), upper.clone());
|
||||
range = range.union(&Range::from_range_bounds((lower, upper)));
|
||||
}
|
||||
range
|
||||
}
|
||||
|
||||
/// Returns `true` if the range appears to be, e.g., `>1.0.0, <1.0.0+[max]`.
|
||||
fn is_sentinel(versions: &Ranges<Version>) -> bool {
|
||||
versions.iter().all(|(lower, upper)| {
|
||||
let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
|
||||
return false;
|
||||
};
|
||||
if lower.local() == LocalVersionSlice::Max {
|
||||
return false;
|
||||
}
|
||||
if upper.local() != LocalVersionSlice::Max {
|
||||
return false;
|
||||
}
|
||||
*lower == upper.clone().without_local()
|
||||
})
|
||||
}
|
||||
|
||||
fn strip(derivation_tree: ErrorTree) -> Option<ErrorTree> {
|
||||
match derivation_tree {
|
||||
DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree),
|
||||
DerivationTree::External(External::NoVersions(package, versions)) => {
|
||||
if is_sentinel(&versions) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let versions = strip_sentinels(versions);
|
||||
Some(DerivationTree::External(External::NoVersions(
|
||||
package, versions,
|
||||
)))
|
||||
}
|
||||
DerivationTree::External(External::FromDependencyOf(
|
||||
package1,
|
||||
versions1,
|
||||
package2,
|
||||
versions2,
|
||||
)) => {
|
||||
let versions1 = strip_sentinels(versions1);
|
||||
let versions2 = strip_sentinels(versions2);
|
||||
Some(DerivationTree::External(External::FromDependencyOf(
|
||||
package1, versions1, package2, versions2,
|
||||
)))
|
||||
}
|
||||
DerivationTree::External(External::Custom(package, versions, reason)) => {
|
||||
let versions = strip_sentinels(versions);
|
||||
Some(DerivationTree::External(External::Custom(
|
||||
package, versions, reason,
|
||||
)))
|
||||
}
|
||||
DerivationTree::Derived(mut derived) => {
|
||||
let cause1 = strip((*derived.cause1).clone());
|
||||
let cause2 = strip((*derived.cause2).clone());
|
||||
match (cause1, cause2) {
|
||||
(Some(cause1), Some(cause2)) => Some(DerivationTree::Derived(Derived {
|
||||
cause1: Arc::new(cause1),
|
||||
cause2: Arc::new(cause2),
|
||||
terms: std::mem::take(&mut derived.terms)
|
||||
.into_iter()
|
||||
.map(|(pkg, term)| {
|
||||
let term = match term {
|
||||
Term::Positive(versions) => {
|
||||
Term::Positive(strip_sentinels(versions))
|
||||
}
|
||||
Term::Negative(versions) => {
|
||||
Term::Negative(strip_sentinels(versions))
|
||||
}
|
||||
};
|
||||
(pkg, term)
|
||||
})
|
||||
.collect(),
|
||||
shared_id: derived.shared_id,
|
||||
})),
|
||||
(Some(cause), None) | (None, Some(cause)) => Some(cause),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strip(derivation_tree).expect("derivation tree should contain at least one term")
|
||||
}
|
||||
|
||||
/// Initialize a [`NoSolutionHeader`] for this error.
|
||||
pub fn header(&self) -> NoSolutionHeader {
|
||||
NoSolutionHeader::new(self.env.clone())
|
||||
|
|
|
@ -1964,7 +1964,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
index_locations: &IndexLocations,
|
||||
index_capabilities: &IndexCapabilities,
|
||||
) -> ResolveError {
|
||||
err = NoSolutionError::collapse_proxies(err);
|
||||
err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
|
||||
err,
|
||||
));
|
||||
|
||||
let mut unavailable_packages = FxHashMap::default();
|
||||
for package in err.packages() {
|
||||
|
|
|
@ -2780,7 +2780,7 @@ fn fork_non_local_fork_marker_direct() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0 and package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible.
|
||||
╰─▶ Because package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible.
|
||||
And because your project depends on package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0, we can conclude that your project's requirements are unsatisfiable.
|
||||
"###
|
||||
);
|
||||
|
@ -2852,11 +2852,11 @@ fn fork_non_local_fork_marker_transitive() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}>=2.0.0 and only package-c{sys_platform == 'darwin'}<=2.0.0 is available, we can conclude that package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}==2.0.0.
|
||||
And because only the following versions of package-c{sys_platform == 'linux'} are available:
|
||||
╰─▶ Because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and only the following versions of package-c{sys_platform == 'linux'} are available:
|
||||
package-c{sys_platform == 'linux'}==1.0.0
|
||||
package-c{sys_platform == 'linux'}>2.0.0
|
||||
and package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible.
|
||||
we can conclude that package-a==1.0.0 depends on package-c{sys_platform == 'linux'}==1.0.0.
|
||||
And because only package-c{sys_platform == 'darwin'}<=2.0.0 is available and package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}>=2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible.
|
||||
And because your project depends on package-a==1.0.0 and package-b==1.0.0, we can conclude that your project's requirements are unsatisfiable.
|
||||
"###
|
||||
);
|
||||
|
|
|
@ -7179,7 +7179,7 @@ fn universal_transitive_disjoint_locals() -> Result<()> {
|
|||
# -r requirements.in
|
||||
# torchvision
|
||||
# triton
|
||||
torchvision==0.15.1
|
||||
torchvision==0.15.1+rocm5.4.2
|
||||
# via -r requirements.in
|
||||
triton==2.0.0 ; platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
|
@ -7452,30 +7452,33 @@ fn universal_disjoint_base_or_local_requirement() -> Result<()> {
|
|||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal
|
||||
cmake==3.28.4 ; python_full_version >= '3.11' and python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via triton
|
||||
cmake==3.28.4 ; python_full_version < '3.11' or (python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux')
|
||||
# via
|
||||
# pytorch-triton-rocm
|
||||
# triton
|
||||
.
|
||||
# via -r requirements.in
|
||||
filelock==3.13.1
|
||||
# via
|
||||
# pytorch-triton-rocm
|
||||
# torch
|
||||
# triton
|
||||
jinja2==3.1.3
|
||||
# via torch
|
||||
lit==18.1.2 ; python_full_version >= '3.11' and python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via triton
|
||||
lit==18.1.2 ; python_full_version < '3.11' or (python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux')
|
||||
# via
|
||||
# pytorch-triton-rocm
|
||||
# triton
|
||||
markupsafe==2.1.5
|
||||
# via jinja2
|
||||
mpmath==1.3.0
|
||||
# via sympy
|
||||
networkx==3.2.1
|
||||
# via torch
|
||||
pytorch-triton-rocm==2.0.2 ; python_full_version < '3.11'
|
||||
# via torch
|
||||
sympy==1.12
|
||||
# via torch
|
||||
torch==2.0.0 ; python_full_version < '3.11'
|
||||
# via
|
||||
# -r requirements.in
|
||||
# example
|
||||
torch==2.0.0+cpu ; python_full_version >= '3.13'
|
||||
# via
|
||||
# -r requirements.in
|
||||
|
@ -7485,13 +7488,18 @@ fn universal_disjoint_base_or_local_requirement() -> Result<()> {
|
|||
# -r requirements.in
|
||||
# example
|
||||
# triton
|
||||
torch==2.0.0+rocm5.4.2 ; python_full_version < '3.11'
|
||||
# via
|
||||
# -r requirements.in
|
||||
# example
|
||||
# pytorch-triton-rocm
|
||||
triton==2.0.0 ; python_full_version >= '3.11' and python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
typing-extensions==4.10.0
|
||||
# via torch
|
||||
|
||||
----- stderr -----
|
||||
Resolved 14 packages in [TIME]
|
||||
Resolved 15 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -7539,6 +7547,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> {
|
|||
# via -r requirements.in
|
||||
filelock==3.13.1
|
||||
# via
|
||||
# pytorch-triton-rocm
|
||||
# torch
|
||||
# triton
|
||||
fsspec==2024.3.1 ; platform_machine != 'x86_64'
|
||||
|
@ -7557,6 +7566,8 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> {
|
|||
# via sympy
|
||||
networkx==3.2.1
|
||||
# via torch
|
||||
pytorch-triton-rocm==2.3.0 ; platform_machine != 'x86_64'
|
||||
# via torch
|
||||
sympy==1.12
|
||||
# via torch
|
||||
tbb==2021.11.0 ; platform_machine != 'x86_64' and platform_system == 'Windows'
|
||||
|
@ -7566,7 +7577,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> {
|
|||
# -r requirements.in
|
||||
# example
|
||||
# triton
|
||||
torch==2.3.0 ; platform_machine != 'x86_64'
|
||||
torch==2.3.0+rocm6.0 ; platform_machine != 'x86_64'
|
||||
# via -r requirements.in
|
||||
triton==2.0.0 ; platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
|
@ -7574,7 +7585,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> {
|
|||
# via torch
|
||||
|
||||
----- stderr -----
|
||||
Resolved 17 packages in [TIME]
|
||||
Resolved 18 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -7613,6 +7624,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> {
|
|||
# via -r requirements.in
|
||||
filelock==3.13.1
|
||||
# via
|
||||
# pytorch-triton-rocm
|
||||
# torch
|
||||
# triton
|
||||
fsspec==2024.3.1 ; platform_machine != 'x86_64'
|
||||
|
@ -7631,6 +7643,8 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> {
|
|||
# via sympy
|
||||
networkx==3.2.1
|
||||
# via torch
|
||||
pytorch-triton-rocm==2.3.0 ; platform_machine != 'x86_64'
|
||||
# via torch
|
||||
sympy==1.12
|
||||
# via torch
|
||||
tbb==2021.11.0 ; platform_machine != 'x86_64' and platform_system == 'Windows'
|
||||
|
@ -7640,7 +7654,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> {
|
|||
# -r requirements.in
|
||||
# example
|
||||
# triton
|
||||
torch==2.3.0 ; platform_machine != 'x86_64'
|
||||
torch==2.3.0+rocm6.0 ; platform_machine != 'x86_64'
|
||||
# via -r requirements.in
|
||||
triton==2.0.0 ; platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
|
@ -7648,7 +7662,7 @@ fn universal_nested_overlapping_local_requirement() -> Result<()> {
|
|||
# via torch
|
||||
|
||||
----- stderr -----
|
||||
Resolved 17 packages in [TIME]
|
||||
Resolved 18 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -7698,6 +7712,7 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> {
|
|||
# via -r requirements.in
|
||||
filelock==3.13.1
|
||||
# via
|
||||
# pytorch-triton-rocm
|
||||
# torch
|
||||
# triton
|
||||
fsspec==2024.3.1 ; os_name != 'Linux'
|
||||
|
@ -7716,36 +7731,7 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> {
|
|||
# via sympy
|
||||
networkx==3.2.1
|
||||
# via torch
|
||||
nvidia-cublas-cu12==12.1.3.1 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via
|
||||
# nvidia-cudnn-cu12
|
||||
# nvidia-cusolver-cu12
|
||||
# torch
|
||||
nvidia-cuda-cupti-cu12==12.1.105 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
nvidia-cuda-nvrtc-cu12==12.1.105 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
nvidia-cuda-runtime-cu12==12.1.105 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
nvidia-cudnn-cu12==8.9.2.26 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
nvidia-cufft-cu12==11.0.2.54 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
nvidia-curand-cu12==10.3.2.106 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
nvidia-cusolver-cu12==11.4.5.107 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
nvidia-cusparse-cu12==12.1.0.106 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via
|
||||
# nvidia-cusolver-cu12
|
||||
# torch
|
||||
nvidia-nccl-cu12==2.20.5 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
nvidia-nvjitlink-cu12==12.4.99 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via
|
||||
# nvidia-cusolver-cu12
|
||||
# nvidia-cusparse-cu12
|
||||
nvidia-nvtx-cu12==12.1.105 ; os_name != 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
pytorch-triton-rocm==2.3.0 ; os_name != 'Linux'
|
||||
# via torch
|
||||
sympy==1.12
|
||||
# via torch
|
||||
|
@ -7760,7 +7746,7 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> {
|
|||
# -r requirements.in
|
||||
# example
|
||||
# triton
|
||||
torch==2.3.0 ; os_name != 'Linux'
|
||||
torch==2.3.0+rocm6.0 ; os_name != 'Linux'
|
||||
# via -r requirements.in
|
||||
triton==2.0.0 ; os_name == 'Linux' and platform_machine == 'x86_64' and platform_system == 'Linux'
|
||||
# via torch
|
||||
|
@ -7768,7 +7754,7 @@ fn universal_nested_disjoint_local_requirement() -> Result<()> {
|
|||
# via torch
|
||||
|
||||
----- stderr -----
|
||||
Resolved 30 packages in [TIME]
|
||||
Resolved 19 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -8515,14 +8501,20 @@ fn universal_marker_propagation() -> Result<()> {
|
|||
# via requests
|
||||
charset-normalizer==3.3.2
|
||||
# via requests
|
||||
cmake==3.28.4 ; platform_machine == 'x86_64'
|
||||
# via pytorch-triton-rocm
|
||||
filelock==3.13.1
|
||||
# via torch
|
||||
# via
|
||||
# pytorch-triton-rocm
|
||||
# torch
|
||||
fsspec==2024.3.1 ; platform_machine != 'x86_64'
|
||||
# via torch
|
||||
idna==3.6
|
||||
# via requests
|
||||
jinja2==3.1.3
|
||||
# via torch
|
||||
lit==18.1.2 ; platform_machine == 'x86_64'
|
||||
# via pytorch-triton-rocm
|
||||
markupsafe==2.1.5
|
||||
# via jinja2
|
||||
mpmath==1.3.0
|
||||
|
@ -8533,15 +8525,20 @@ fn universal_marker_propagation() -> Result<()> {
|
|||
# via torchvision
|
||||
pillow==10.2.0
|
||||
# via torchvision
|
||||
pytorch-triton-rocm==2.0.2 ; platform_machine == 'x86_64'
|
||||
# via torch
|
||||
pytorch-triton-rocm==2.2.0 ; platform_machine != 'x86_64'
|
||||
# via torch
|
||||
requests==2.31.0
|
||||
# via torchvision
|
||||
sympy==1.12
|
||||
# via torch
|
||||
torch==2.0.0 ; platform_machine == 'x86_64'
|
||||
torch==2.0.0+rocm5.4.2 ; platform_machine == 'x86_64'
|
||||
# via
|
||||
# -r requirements.in
|
||||
# pytorch-triton-rocm
|
||||
# torchvision
|
||||
torch==2.2.0 ; platform_machine != 'x86_64'
|
||||
torch==2.2.0+rocm5.7 ; platform_machine != 'x86_64'
|
||||
# via
|
||||
# -r requirements.in
|
||||
# torchvision
|
||||
|
@ -8556,7 +8553,7 @@ fn universal_marker_propagation() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
warning: The requested Python version 3.8 is not available; 3.12.[X] will be used to build dependencies instead.
|
||||
Resolved 19 packages in [TIME]
|
||||
Resolved 23 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
@ -334,10 +334,10 @@ fn dependency_excludes_non_contiguous_range_of_compatible_versions() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available:
|
||||
╰─▶ Because only the following versions of package-a are available:
|
||||
package-a==1.0.0
|
||||
package-a>2.0.0,<=3.0.0
|
||||
we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1)
|
||||
and package-a==1.0.0 depends on package-b==1.0.0, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1)
|
||||
|
||||
Because only the following versions of package-c are available:
|
||||
package-c==1.0.0
|
||||
|
@ -445,10 +445,10 @@ fn dependency_excludes_range_of_compatible_versions() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available:
|
||||
╰─▶ Because only the following versions of package-a are available:
|
||||
package-a==1.0.0
|
||||
package-a>2.0.0,<=3.0.0
|
||||
we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1)
|
||||
and package-a==1.0.0 depends on package-b==1.0.0, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1)
|
||||
|
||||
Because only the following versions of package-c are available:
|
||||
package-c==1.0.0
|
||||
|
@ -529,17 +529,17 @@ fn excluded_only_compatible_version() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available:
|
||||
╰─▶ Because only the following versions of package-a are available:
|
||||
package-a==1.0.0
|
||||
package-a==2.0.0
|
||||
package-a==3.0.0
|
||||
we can conclude that package-a<2.0.0 depends on package-b==1.0.0.
|
||||
and package-a==1.0.0 depends on package-b==1.0.0, we can conclude that package-a<2.0.0 depends on package-b==1.0.0.
|
||||
And because package-a==3.0.0 depends on package-b==3.0.0, we can conclude that all of:
|
||||
package-a<2.0.0
|
||||
package-a>2.0.0
|
||||
depend on one of:
|
||||
package-b<=1.0.0
|
||||
package-b>=3.0.0
|
||||
package-b==1.0.0
|
||||
package-b==3.0.0
|
||||
|
||||
And because you require one of:
|
||||
package-a<2.0.0
|
||||
|
@ -1276,8 +1276,10 @@ fn transitive_incompatible_with_transitive() {
|
|||
/// │ └── python3.8
|
||||
/// ├── root
|
||||
/// │ └── requires a>=1.2.3
|
||||
/// │ ├── satisfied by a-1.2.3+bar
|
||||
/// │ └── satisfied by a-1.2.3+foo
|
||||
/// └── a
|
||||
/// ├── a-1.2.3+bar
|
||||
/// └── a-1.2.3+foo
|
||||
/// ```
|
||||
#[test]
|
||||
|
@ -1354,8 +1356,10 @@ fn local_greater_than() {
|
|||
/// │ └── python3.8
|
||||
/// ├── root
|
||||
/// │ └── requires a<=1.2.3
|
||||
/// │ ├── satisfied by a-1.2.3+bar
|
||||
/// │ └── satisfied by a-1.2.3+foo
|
||||
/// └── a
|
||||
/// ├── a-1.2.3+bar
|
||||
/// └── a-1.2.3+foo
|
||||
/// ```
|
||||
#[test]
|
||||
|
@ -1369,19 +1373,22 @@ fn local_less_than_or_equal() {
|
|||
uv_snapshot!(filters, command(&context)
|
||||
.arg("local-less-than-or-equal-a<=1.2.3")
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-a==1.2.3+foo is available and you require package-a<=1.2.3, we can conclude that your requirements are unsatisfiable.
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ package-a==1.2.3+foo
|
||||
"###);
|
||||
|
||||
// The version '1.2.3+foo' satisfies the constraint '<=1.2.3'.
|
||||
assert_not_installed(
|
||||
assert_installed(
|
||||
&context.venv,
|
||||
"local_less_than_or_equal_a",
|
||||
"1.2.3+foo",
|
||||
&context.temp_dir,
|
||||
);
|
||||
}
|
||||
|
@ -1500,14 +1507,14 @@ fn local_not_used_with_sdist() {
|
|||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ package-a==1.2.3
|
||||
+ package-a==1.2.3+foo
|
||||
"###);
|
||||
|
||||
// The version '1.2.3' with an sdist satisfies the constraint '==1.2.3'.
|
||||
assert_installed(
|
||||
&context.venv,
|
||||
"local_not_used_with_sdist_a",
|
||||
"1.2.3",
|
||||
"1.2.3+foo",
|
||||
&context.temp_dir,
|
||||
);
|
||||
}
|
||||
|
@ -1520,8 +1527,10 @@ fn local_not_used_with_sdist() {
|
|||
/// │ └── python3.8
|
||||
/// ├── root
|
||||
/// │ └── requires a==1.2.3
|
||||
/// │ ├── satisfied by a-1.2.3+bar
|
||||
/// │ └── satisfied by a-1.2.3+foo
|
||||
/// └── a
|
||||
/// ├── a-1.2.3+bar
|
||||
/// └── a-1.2.3+foo
|
||||
/// ```
|
||||
#[test]
|
||||
|
@ -1535,17 +1544,24 @@ fn local_simple() {
|
|||
uv_snapshot!(filters, command(&context)
|
||||
.arg("local-simple-a==1.2.3")
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because there is no version of package-a==1.2.3 and you require package-a==1.2.3, we can conclude that your requirements are unsatisfiable.
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ package-a==1.2.3+foo
|
||||
"###);
|
||||
|
||||
// The version '1.2.3+foo' satisfies the constraint '==1.2.3'.
|
||||
assert_not_installed(&context.venv, "local_simple_a", &context.temp_dir);
|
||||
assert_installed(
|
||||
&context.venv,
|
||||
"local_simple_a",
|
||||
"1.2.3+foo",
|
||||
&context.temp_dir,
|
||||
);
|
||||
}
|
||||
|
||||
/// A dependency depends on a conflicting local version of a direct dependency, but we can backtrack to a compatible version.
|
||||
|
@ -1563,14 +1579,14 @@ fn local_simple() {
|
|||
/// ├── a
|
||||
/// │ ├── a-1.0.0
|
||||
/// │ │ └── requires b==2.0.0
|
||||
/// │ │ ├── satisfied by b-2.0.0+foo
|
||||
/// │ │ └── satisfied by b-2.0.0+bar
|
||||
/// │ │ ├── satisfied by b-2.0.0+bar
|
||||
/// │ │ └── satisfied by b-2.0.0+foo
|
||||
/// │ └── a-2.0.0
|
||||
/// │ └── requires b==2.0.0+bar
|
||||
/// │ └── satisfied by b-2.0.0+bar
|
||||
/// └── b
|
||||
/// ├── b-2.0.0+foo
|
||||
/// └── b-2.0.0+bar
|
||||
/// ├── b-2.0.0+bar
|
||||
/// └── b-2.0.0+foo
|
||||
/// ```
|
||||
#[test]
|
||||
fn local_transitive_backtrack() {
|
||||
|
@ -1627,8 +1643,8 @@ fn local_transitive_backtrack() {
|
|||
/// │ └── requires b==2.0.0+bar
|
||||
/// │ └── satisfied by b-2.0.0+bar
|
||||
/// └── b
|
||||
/// ├── b-2.0.0+foo
|
||||
/// └── b-2.0.0+bar
|
||||
/// ├── b-2.0.0+bar
|
||||
/// └── b-2.0.0+foo
|
||||
/// ```
|
||||
#[test]
|
||||
fn local_transitive_conflicting() {
|
||||
|
@ -1677,9 +1693,11 @@ fn local_transitive_conflicting() {
|
|||
/// │ └── a-1.0.0
|
||||
/// │ └── requires b==2.0.0
|
||||
/// │ ├── satisfied by b-2.0.0
|
||||
/// │ ├── satisfied by b-2.0.0+bar
|
||||
/// │ └── satisfied by b-2.0.0+foo
|
||||
/// └── b
|
||||
/// ├── b-2.0.0
|
||||
/// ├── b-2.0.0+bar
|
||||
/// └── b-2.0.0+foo
|
||||
/// ```
|
||||
#[test]
|
||||
|
@ -1693,20 +1711,29 @@ fn local_transitive_confounding() {
|
|||
uv_snapshot!(filters, command(&context)
|
||||
.arg("local-transitive-confounding-a")
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-b==2.0.0 has no wheels with a matching Python ABI tag and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
|
||||
And because only package-a==1.0.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ package-a==1.0.0
|
||||
+ package-b==2.0.0+foo
|
||||
"###);
|
||||
|
||||
// The version '2.0.0+foo' satisfies the constraint '==2.0.0'.
|
||||
assert_not_installed(
|
||||
assert_installed(
|
||||
&context.venv,
|
||||
"local_transitive_confounding_a",
|
||||
"1.0.0",
|
||||
&context.temp_dir,
|
||||
);
|
||||
assert_installed(
|
||||
&context.venv,
|
||||
"local_transitive_confounding_b",
|
||||
"2.0.0+foo",
|
||||
&context.temp_dir,
|
||||
);
|
||||
}
|
||||
|
@ -1725,8 +1752,10 @@ fn local_transitive_confounding() {
|
|||
/// ├── a
|
||||
/// │ └── a-1.0.0
|
||||
/// │ └── requires b>=2.0.0
|
||||
/// │ ├── satisfied by b-2.0.0+bar
|
||||
/// │ └── satisfied by b-2.0.0+foo
|
||||
/// └── b
|
||||
/// ├── b-2.0.0+bar
|
||||
/// └── b-2.0.0+foo
|
||||
/// ```
|
||||
#[test]
|
||||
|
@ -1784,6 +1813,7 @@ fn local_transitive_greater_than_or_equal() {
|
|||
/// │ └── requires b>2.0.0
|
||||
/// │ └── unsatisfied: no matching version
|
||||
/// └── b
|
||||
/// ├── b-2.0.0+bar
|
||||
/// └── b-2.0.0+foo
|
||||
/// ```
|
||||
#[test]
|
||||
|
@ -1834,8 +1864,10 @@ fn local_transitive_greater_than() {
|
|||
/// ├── a
|
||||
/// │ └── a-1.0.0
|
||||
/// │ └── requires b<=2.0.0
|
||||
/// │ ├── satisfied by b-2.0.0+bar
|
||||
/// │ └── satisfied by b-2.0.0+foo
|
||||
/// └── b
|
||||
/// ├── b-2.0.0+bar
|
||||
/// └── b-2.0.0+foo
|
||||
/// ```
|
||||
#[test]
|
||||
|
@ -1893,6 +1925,7 @@ fn local_transitive_less_than_or_equal() {
|
|||
/// │ └── requires b<2.0.0
|
||||
/// │ └── unsatisfied: no matching version
|
||||
/// └── b
|
||||
/// ├── b-2.0.0+bar
|
||||
/// └── b-2.0.0+foo
|
||||
/// ```
|
||||
#[test]
|
||||
|
@ -1943,9 +1976,11 @@ fn local_transitive_less_than() {
|
|||
/// ├── a
|
||||
/// │ └── a-1.0.0
|
||||
/// │ └── requires b==2.0.0
|
||||
/// │ └── satisfied by b-2.0.0+foo
|
||||
/// │ ├── satisfied by b-2.0.0+foo
|
||||
/// │ └── satisfied by b-2.0.0+bar
|
||||
/// └── b
|
||||
/// └── b-2.0.0+foo
|
||||
/// ├── b-2.0.0+foo
|
||||
/// └── b-2.0.0+bar
|
||||
/// ```
|
||||
#[test]
|
||||
fn local_transitive() {
|
||||
|
@ -2011,19 +2046,22 @@ fn local_used_without_sdist() {
|
|||
uv_snapshot!(filters, command(&context)
|
||||
.arg("local-used-without-sdist-a==1.2.3")
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-a==1.2.3 has no wheels with a matching Python ABI tag and you require package-a==1.2.3, we can conclude that your requirements are unsatisfiable.
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ package-a==1.2.3+foo
|
||||
"###);
|
||||
|
||||
// The version '1.2.3+foo' satisfies the constraint '==1.2.3'.
|
||||
assert_not_installed(
|
||||
assert_installed(
|
||||
&context.venv,
|
||||
"local_used_without_sdist_a",
|
||||
"1.2.3+foo",
|
||||
&context.temp_dir,
|
||||
);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
source: crates/uv/tests/ecosystem.rs
|
||||
source: crates/uv/tests/it/ecosystem.rs
|
||||
expression: snapshot
|
||||
---
|
||||
success: true
|
||||
|
@ -7,4 +7,4 @@ exit_code: 0
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 296 packages in [TIME]
|
||||
Resolved 281 packages in [TIME]
|
||||
|
|
|
@ -152,18 +152,6 @@ def main(scenarios: list[Path], snapshot_update: bool = True):
|
|||
else:
|
||||
scenario["python_patch"] = False
|
||||
|
||||
# We don't yet support local versions that aren't expressed as direct dependencies.
|
||||
for scenario in data["scenarios"]:
|
||||
expected = scenario["expected"]
|
||||
|
||||
if scenario["name"] in (
|
||||
"local-less-than-or-equal",
|
||||
"local-simple",
|
||||
"local-transitive-confounding",
|
||||
"local-used-without-sdist",
|
||||
):
|
||||
expected["satisfiable"] = False
|
||||
|
||||
# Split scenarios into `install`, `compile` and `lock` cases
|
||||
install_scenarios = []
|
||||
compile_scenarios = []
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue