mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-27 12:39:09 +00:00
Track yanked versions as incompatibilities (#1290)
Moves yanked version filtering from `VersionMap::from_metadata` to the resolver and tracks it as a PubGrub unavailable incompatibility so yanked versions are reflected in error messages. e.g. before ``` ╰─▶ Because only albatross<=0.1.0 is available and you require albatross>0.1.0, we can conclude that the requirements are unsatisfiable. ``` after ``` ╰─▶ Because only the following versions of albatross are available: albatross<=0.1.0 albatross==1.0.0 and albatross==1.0.0 is unusable because it was yanked, we can conclude that albatross>0.1.0 cannot be used. And because you require albatross>0.1.0, we can conclude that the requirements are unsatisfiable. ```
This commit is contained in:
parent
d8619f668a
commit
b5dd8b7de2
12 changed files with 263 additions and 138 deletions
34
Cargo.lock
generated
34
Cargo.lock
generated
|
@ -633,9 +633,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.3.2"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
@ -1263,7 +1263,7 @@ dependencies = [
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"indexmap 2.2.2",
|
"indexmap 2.2.3",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1485,9 +1485,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.2"
|
version = "2.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
|
@ -2050,9 +2050,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-src"
|
name = "openssl-src"
|
||||||
version = "300.2.2+3.2.1"
|
version = "300.2.3+3.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8bbfad0063610ac26ee79f7484739e2b07555a75c42453b89263830b5c8103bc"
|
checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
@ -2218,7 +2218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"indexmap 2.2.2",
|
"indexmap 2.2.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2313,7 +2313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef"
|
checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"indexmap 2.2.2",
|
"indexmap 2.2.3",
|
||||||
"line-wrap",
|
"line-wrap",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2434,7 +2434,7 @@ name = "pubgrub"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "git+https://github.com/zanieb/pubgrub?rev=1b150cdbd1e6f93b1f465de9d08f499660d7f708#1b150cdbd1e6f93b1f465de9d08f499660d7f708"
|
source = "git+https://github.com/zanieb/pubgrub?rev=1b150cdbd1e6f93b1f465de9d08f499660d7f708#1b150cdbd1e6f93b1f465de9d08f499660d7f708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.2",
|
"indexmap 2.2.3",
|
||||||
"log",
|
"log",
|
||||||
"priority-queue",
|
"priority-queue",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
@ -2859,7 +2859,7 @@ dependencies = [
|
||||||
"fs-err",
|
"fs-err",
|
||||||
"futures",
|
"futures",
|
||||||
"gourgeist",
|
"gourgeist",
|
||||||
"indexmap 2.2.2",
|
"indexmap 2.2.3",
|
||||||
"insta",
|
"insta",
|
||||||
"install-wheel-rs",
|
"install-wheel-rs",
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
|
@ -3020,7 +3020,7 @@ version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46d4a5e69187f23a29f8aa0ea57491d104ba541bc55f76552c2a74962aa20e04"
|
checksum = "46d4a5e69187f23a29f8aa0ea57491d104ba541bc55f76552c2a74962aa20e04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.2",
|
"indexmap 2.2.3",
|
||||||
"pep440_rs 0.3.12",
|
"pep440_rs 0.3.12",
|
||||||
"pep508_rs",
|
"pep508_rs",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -4007,18 +4007,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.56"
|
version = "1.0.57"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.56"
|
version = "1.0.57"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -4250,7 +4250,7 @@ version = "0.22.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951"
|
checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.2",
|
"indexmap 2.2.3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use pep440_rs::VersionSpecifiers;
|
use pep440_rs::VersionSpecifiers;
|
||||||
use platform_tags::TagPriority;
|
use platform_tags::TagPriority;
|
||||||
use pypi_types::Hashes;
|
use pypi_types::{Hashes, Yanked};
|
||||||
|
|
||||||
use crate::Dist;
|
use crate::Dist;
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ struct PrioritizedDistributionInner {
|
||||||
compatible_wheel: Option<(DistRequiresPython, TagPriority)>,
|
compatible_wheel: Option<(DistRequiresPython, TagPriority)>,
|
||||||
/// An arbitrary, platform-incompatible wheel for the package version.
|
/// An arbitrary, platform-incompatible wheel for the package version.
|
||||||
incompatible_wheel: Option<DistRequiresPython>,
|
incompatible_wheel: Option<DistRequiresPython>,
|
||||||
|
/// Is the distribution yanked
|
||||||
|
yanked: Yanked,
|
||||||
/// The hashes for each distribution.
|
/// The hashes for each distribution.
|
||||||
hashes: Vec<Hashes>,
|
hashes: Vec<Hashes>,
|
||||||
}
|
}
|
||||||
|
@ -35,6 +37,7 @@ impl PrioritizedDistribution {
|
||||||
requires_python: Option<VersionSpecifiers>,
|
requires_python: Option<VersionSpecifiers>,
|
||||||
hash: Option<Hashes>,
|
hash: Option<Hashes>,
|
||||||
priority: Option<TagPriority>,
|
priority: Option<TagPriority>,
|
||||||
|
yanked: Yanked,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if let Some(priority) = priority {
|
if let Some(priority) = priority {
|
||||||
Self(Box::new(PrioritizedDistributionInner {
|
Self(Box::new(PrioritizedDistributionInner {
|
||||||
|
@ -42,13 +45,13 @@ impl PrioritizedDistribution {
|
||||||
compatible_wheel: Some((
|
compatible_wheel: Some((
|
||||||
DistRequiresPython {
|
DistRequiresPython {
|
||||||
dist,
|
dist,
|
||||||
|
|
||||||
requires_python,
|
requires_python,
|
||||||
},
|
},
|
||||||
priority,
|
priority,
|
||||||
)),
|
)),
|
||||||
incompatible_wheel: None,
|
incompatible_wheel: None,
|
||||||
hashes: hash.map(|hash| vec![hash]).unwrap_or_default(),
|
hashes: hash.map(|hash| vec![hash]).unwrap_or_default(),
|
||||||
|
yanked,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Self(Box::new(PrioritizedDistributionInner {
|
Self(Box::new(PrioritizedDistributionInner {
|
||||||
|
@ -59,6 +62,7 @@ impl PrioritizedDistribution {
|
||||||
requires_python,
|
requires_python,
|
||||||
}),
|
}),
|
||||||
hashes: hash.map(|hash| vec![hash]).unwrap_or_default(),
|
hashes: hash.map(|hash| vec![hash]).unwrap_or_default(),
|
||||||
|
yanked,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +72,7 @@ impl PrioritizedDistribution {
|
||||||
dist: Dist,
|
dist: Dist,
|
||||||
requires_python: Option<VersionSpecifiers>,
|
requires_python: Option<VersionSpecifiers>,
|
||||||
hash: Option<Hashes>,
|
hash: Option<Hashes>,
|
||||||
|
yanked: Yanked,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self(Box::new(PrioritizedDistributionInner {
|
Self(Box::new(PrioritizedDistributionInner {
|
||||||
source: Some(DistRequiresPython {
|
source: Some(DistRequiresPython {
|
||||||
|
@ -77,6 +82,7 @@ impl PrioritizedDistribution {
|
||||||
compatible_wheel: None,
|
compatible_wheel: None,
|
||||||
incompatible_wheel: None,
|
incompatible_wheel: None,
|
||||||
hashes: hash.map(|hash| vec![hash]).unwrap_or_default(),
|
hashes: hash.map(|hash| vec![hash]).unwrap_or_default(),
|
||||||
|
yanked,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +93,12 @@ impl PrioritizedDistribution {
|
||||||
requires_python: Option<VersionSpecifiers>,
|
requires_python: Option<VersionSpecifiers>,
|
||||||
hash: Option<Hashes>,
|
hash: Option<Hashes>,
|
||||||
priority: Option<TagPriority>,
|
priority: Option<TagPriority>,
|
||||||
|
yanked: Yanked,
|
||||||
) {
|
) {
|
||||||
|
if yanked.is_yanked() {
|
||||||
|
self.0.yanked = yanked;
|
||||||
|
}
|
||||||
|
|
||||||
// Prefer the highest-priority, platform-compatible wheel.
|
// Prefer the highest-priority, platform-compatible wheel.
|
||||||
if let Some(priority) = priority {
|
if let Some(priority) = priority {
|
||||||
if let Some((.., existing_priority)) = &self.0.compatible_wheel {
|
if let Some((.., existing_priority)) = &self.0.compatible_wheel {
|
||||||
|
@ -127,7 +138,12 @@ impl PrioritizedDistribution {
|
||||||
dist: Dist,
|
dist: Dist,
|
||||||
requires_python: Option<VersionSpecifiers>,
|
requires_python: Option<VersionSpecifiers>,
|
||||||
hash: Option<Hashes>,
|
hash: Option<Hashes>,
|
||||||
|
yanked: Yanked,
|
||||||
) {
|
) {
|
||||||
|
if yanked.is_yanked() {
|
||||||
|
self.0.yanked = yanked;
|
||||||
|
}
|
||||||
|
|
||||||
if self.0.source.is_none() {
|
if self.0.source.is_none() {
|
||||||
self.0.source = Some(DistRequiresPython {
|
self.0.source = Some(DistRequiresPython {
|
||||||
dist,
|
dist,
|
||||||
|
@ -148,18 +164,24 @@ impl PrioritizedDistribution {
|
||||||
&self.0.incompatible_wheel,
|
&self.0.incompatible_wheel,
|
||||||
) {
|
) {
|
||||||
// Prefer the highest-priority, platform-compatible wheel.
|
// Prefer the highest-priority, platform-compatible wheel.
|
||||||
(Some((wheel, tag_priority)), _, _) => {
|
(Some((wheel, tag_priority)), _, _) => Some(ResolvableDist::CompatibleWheel(
|
||||||
Some(ResolvableDist::CompatibleWheel(wheel, *tag_priority))
|
wheel,
|
||||||
}
|
&self.0.yanked,
|
||||||
|
*tag_priority,
|
||||||
|
)),
|
||||||
// If we have a compatible source distribution and an incompatible wheel, return the
|
// If we have a compatible source distribution and an incompatible wheel, return the
|
||||||
// wheel. We assume that all distributions have the same metadata for a given package
|
// wheel. We assume that all distributions have the same metadata for a given package
|
||||||
// version. If a compatible source distribution exists, we assume we can build it, but
|
// version. If a compatible source distribution exists, we assume we can build it, but
|
||||||
// using the wheel is faster.
|
// using the wheel is faster.
|
||||||
(_, Some(source_dist), Some(wheel)) => {
|
(_, Some(source_dist), Some(wheel)) => Some(ResolvableDist::IncompatibleWheel {
|
||||||
Some(ResolvableDist::IncompatibleWheel { source_dist, wheel })
|
source_dist,
|
||||||
}
|
yanked: &self.0.yanked,
|
||||||
|
wheel,
|
||||||
|
}),
|
||||||
// Otherwise, if we have a source distribution, return it.
|
// Otherwise, if we have a source distribution, return it.
|
||||||
(_, Some(source_dist), _) => Some(ResolvableDist::SourceDist(source_dist)),
|
(_, Some(source_dist), _) => {
|
||||||
|
Some(ResolvableDist::SourceDist(source_dist, &self.0.yanked))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,39 +213,54 @@ impl PrioritizedDistribution {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ResolvableDist<'a> {
|
pub enum ResolvableDist<'a> {
|
||||||
/// The distribution should be resolved and installed using a source distribution.
|
/// The distribution should be resolved and installed using a source distribution.
|
||||||
SourceDist(&'a DistRequiresPython),
|
SourceDist(&'a DistRequiresPython, &'a Yanked),
|
||||||
/// The distribution should be resolved and installed using a wheel distribution.
|
/// The distribution should be resolved and installed using a wheel distribution.
|
||||||
CompatibleWheel(&'a DistRequiresPython, TagPriority),
|
CompatibleWheel(&'a DistRequiresPython, &'a Yanked, TagPriority),
|
||||||
/// The distribution should be resolved using an incompatible wheel distribution, but
|
/// The distribution should be resolved using an incompatible wheel distribution, but
|
||||||
/// installed using a source distribution.
|
/// installed using a source distribution.
|
||||||
IncompatibleWheel {
|
IncompatibleWheel {
|
||||||
source_dist: &'a DistRequiresPython,
|
source_dist: &'a DistRequiresPython,
|
||||||
|
yanked: &'a Yanked,
|
||||||
wheel: &'a DistRequiresPython,
|
wheel: &'a DistRequiresPython,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ResolvableDist<'a> {
|
impl<'a> ResolvableDist<'a> {
|
||||||
/// Return the [`DistRequiresPython`] to use during resolution.
|
/// Return the [`DistRequiresPython`] to use during resolution.
|
||||||
pub fn resolve(&self) -> &DistRequiresPython {
|
pub fn for_resolution(&self) -> &DistRequiresPython {
|
||||||
match *self {
|
match *self {
|
||||||
ResolvableDist::SourceDist(sdist) => sdist,
|
ResolvableDist::SourceDist(sdist, _) => sdist,
|
||||||
ResolvableDist::CompatibleWheel(wheel, _) => wheel,
|
ResolvableDist::CompatibleWheel(wheel, _, _) => wheel,
|
||||||
ResolvableDist::IncompatibleWheel {
|
ResolvableDist::IncompatibleWheel {
|
||||||
source_dist: _,
|
source_dist: _,
|
||||||
|
yanked: _,
|
||||||
wheel,
|
wheel,
|
||||||
} => wheel,
|
} => wheel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`DistRequiresPython`] to use during installation.
|
/// Return the [`DistRequiresPython`] to use during installation.
|
||||||
pub fn install(&self) -> &DistRequiresPython {
|
pub fn for_installation(&self) -> &DistRequiresPython {
|
||||||
match *self {
|
match *self {
|
||||||
ResolvableDist::SourceDist(sdist) => sdist,
|
ResolvableDist::SourceDist(sdist, _) => sdist,
|
||||||
ResolvableDist::CompatibleWheel(wheel, _) => wheel,
|
ResolvableDist::CompatibleWheel(wheel, _, _) => wheel,
|
||||||
ResolvableDist::IncompatibleWheel {
|
ResolvableDist::IncompatibleWheel {
|
||||||
source_dist,
|
source_dist,
|
||||||
|
yanked: _,
|
||||||
wheel: _,
|
wheel: _,
|
||||||
} => source_dist,
|
} => source_dist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn yanked(&self) -> &Yanked {
|
||||||
|
match *self {
|
||||||
|
ResolvableDist::SourceDist(_, yanked) => yanked,
|
||||||
|
ResolvableDist::CompatibleWheel(_, yanked, _) => yanked,
|
||||||
|
ResolvableDist::IncompatibleWheel {
|
||||||
|
source_dist: _,
|
||||||
|
yanked,
|
||||||
|
wheel: _,
|
||||||
|
} => yanked,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use pep440_rs::Version;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use puffin_cache::{Cache, CacheBucket};
|
use puffin_cache::{Cache, CacheBucket};
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
use pypi_types::Hashes;
|
use pypi_types::{Hashes, Yanked};
|
||||||
|
|
||||||
use crate::cached_client::{CacheControl, CachedClientError};
|
use crate::cached_client::{CacheControl, CachedClientError};
|
||||||
use crate::html::SimpleHtml;
|
use crate::html::SimpleHtml;
|
||||||
|
@ -298,11 +298,17 @@ impl FlatIndex {
|
||||||
}));
|
}));
|
||||||
match distributions.0.entry(version) {
|
match distributions.0.entry(version) {
|
||||||
Entry::Occupied(mut entry) => {
|
Entry::Occupied(mut entry) => {
|
||||||
entry.get_mut().insert_built(dist, None, None, priority);
|
entry
|
||||||
|
.get_mut()
|
||||||
|
.insert_built(dist, None, None, priority, Yanked::default());
|
||||||
}
|
}
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
entry.insert(PrioritizedDistribution::from_built(
|
entry.insert(PrioritizedDistribution::from_built(
|
||||||
dist, None, None, priority,
|
dist,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
priority,
|
||||||
|
Yanked::default(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,10 +321,17 @@ impl FlatIndex {
|
||||||
}));
|
}));
|
||||||
match distributions.0.entry(filename.version.clone()) {
|
match distributions.0.entry(filename.version.clone()) {
|
||||||
Entry::Occupied(mut entry) => {
|
Entry::Occupied(mut entry) => {
|
||||||
entry.get_mut().insert_source(dist, None, None);
|
entry
|
||||||
|
.get_mut()
|
||||||
|
.insert_source(dist, None, None, Yanked::default());
|
||||||
}
|
}
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
entry.insert(PrioritizedDistribution::from_source(dist, None, None));
|
entry.insert(PrioritizedDistribution::from_source(
|
||||||
|
dist,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Yanked::default(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
|
use pypi_types::Yanked;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use distribution_types::{Dist, DistributionMetadata, Name};
|
use distribution_types::{Dist, DistributionMetadata, Name};
|
||||||
|
@ -247,19 +248,22 @@ impl<'a> Candidate<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`DistFile`] to use when resolving the package.
|
/// Return the [`DistFile`] to use when resolving the package.
|
||||||
pub(crate) fn resolve(&self) -> &DistRequiresPython {
|
pub(crate) fn resolution_dist(&self) -> &DistRequiresPython {
|
||||||
self.dist.resolve()
|
self.dist.for_resolution()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`DistFile`] to use when installing the package.
|
/// Return the [`DistFile`] to use when installing the package.
|
||||||
pub(crate) fn install(&self) -> &DistRequiresPython {
|
pub(crate) fn installation_dist(&self) -> &DistRequiresPython {
|
||||||
self.dist.install()
|
self.dist.for_installation()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the candidate doesn't match the given requirement, return the version specifiers.
|
/// If the candidate doesn't match the given Python requirement, return the version specifiers.
|
||||||
pub(crate) fn validate(&self, requirement: &PythonRequirement) -> Option<&VersionSpecifiers> {
|
pub(crate) fn validate_python(
|
||||||
|
&self,
|
||||||
|
requirement: &PythonRequirement,
|
||||||
|
) -> Option<&VersionSpecifiers> {
|
||||||
// Validate the _installed_ file.
|
// Validate the _installed_ file.
|
||||||
let requires_python = self.install().requires_python.as_ref()?;
|
let requires_python = self.installation_dist().requires_python.as_ref()?;
|
||||||
|
|
||||||
// If the candidate doesn't support the target Python version, return the failing version
|
// If the candidate doesn't support the target Python version, return the failing version
|
||||||
// specifiers.
|
// specifiers.
|
||||||
|
@ -269,20 +273,20 @@ impl<'a> Candidate<'a> {
|
||||||
|
|
||||||
// If the candidate is a source distribution, and doesn't support the installed Python
|
// If the candidate is a source distribution, and doesn't support the installed Python
|
||||||
// version, return the failing version specifiers, since we won't be able to build it.
|
// version, return the failing version specifiers, since we won't be able to build it.
|
||||||
if matches!(self.install().dist, Dist::Source(_)) {
|
if matches!(self.installation_dist().dist, Dist::Source(_)) {
|
||||||
if !requires_python.contains(requirement.installed()) {
|
if !requires_python.contains(requirement.installed()) {
|
||||||
return Some(requires_python);
|
return Some(requires_python);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the resolved file.
|
// Validate the resolved file.
|
||||||
let requires_python = self.resolve().requires_python.as_ref()?;
|
let requires_python = self.resolution_dist().requires_python.as_ref()?;
|
||||||
|
|
||||||
// If the candidate is a source distribution, and doesn't support the installed Python
|
// If the candidate is a source distribution, and doesn't support the installed Python
|
||||||
// version, return the failing version specifiers, since we won't be able to build it.
|
// version, return the failing version specifiers, since we won't be able to build it.
|
||||||
// This isn't strictly necessary, since if `self.resolve()` is a source distribution, it
|
// This isn't strictly necessary, since if `self.resolve()` is a source distribution, it
|
||||||
// should be the same file as `self.install()` (validated above).
|
// should be the same file as `self.install()` (validated above).
|
||||||
if matches!(self.resolve().dist, Dist::Source(_)) {
|
if matches!(self.resolution_dist().dist, Dist::Source(_)) {
|
||||||
if !requires_python.contains(requirement.installed()) {
|
if !requires_python.contains(requirement.installed()) {
|
||||||
return Some(requires_python);
|
return Some(requires_python);
|
||||||
}
|
}
|
||||||
|
@ -290,6 +294,10 @@ impl<'a> Candidate<'a> {
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn yanked(&self) -> &Yanked {
|
||||||
|
return self.dist.yanked();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Name for Candidate<'_> {
|
impl Name for Candidate<'_> {
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl FilePins {
|
||||||
pub(crate) fn insert(&mut self, candidate: &Candidate) {
|
pub(crate) fn insert(&mut self, candidate: &Candidate) {
|
||||||
self.0.entry(candidate.name().clone()).or_default().insert(
|
self.0.entry(candidate.name().clone()).or_default().insert(
|
||||||
candidate.version().clone(),
|
candidate.version().clone(),
|
||||||
candidate.install().dist.clone(),
|
candidate.installation_dist().dist.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,13 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tracing::{debug, info_span, instrument, trace, Instrument};
|
use tracing::{debug, info_span, instrument, trace, warn, Instrument};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuiltDist, Dist, DistributionMetadata, LocalEditable, Name, PackageId, RemoteSource,
|
BuiltDist, Dist, DistributionMetadata, LocalEditable, Name, RemoteSource, SourceDist,
|
||||||
SourceDist, VersionOrUrl,
|
VersionOrUrl,
|
||||||
};
|
};
|
||||||
use pep440_rs::{Version, VersionSpecifiers, MIN_VERSION};
|
use pep440_rs::{Version, VersionSpecifiers, MIN_VERSION};
|
||||||
use pep508_rs::{MarkerEnvironment, Requirement};
|
use pep508_rs::{MarkerEnvironment, Requirement};
|
||||||
|
@ -31,7 +31,7 @@ use puffin_distribution::DistributionDatabase;
|
||||||
use puffin_interpreter::Interpreter;
|
use puffin_interpreter::Interpreter;
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
use puffin_traits::BuildContext;
|
use puffin_traits::BuildContext;
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::{Metadata21, Yanked};
|
||||||
|
|
||||||
use crate::candidate_selector::CandidateSelector;
|
use crate::candidate_selector::CandidateSelector;
|
||||||
use crate::error::ResolveError;
|
use crate::error::ResolveError;
|
||||||
|
@ -51,6 +51,7 @@ pub use crate::resolver::provider::ResolverProvider;
|
||||||
pub(crate) use crate::resolver::provider::VersionsResponse;
|
pub(crate) use crate::resolver::provider::VersionsResponse;
|
||||||
use crate::resolver::reporter::Facade;
|
use crate::resolver::reporter::Facade;
|
||||||
pub use crate::resolver::reporter::{BuildId, Reporter};
|
pub use crate::resolver::reporter::{BuildId, Reporter};
|
||||||
|
use crate::yanks::AllowedYanks;
|
||||||
use crate::{DependencyMode, Options};
|
use crate::{DependencyMode, Options};
|
||||||
|
|
||||||
mod allowed_urls;
|
mod allowed_urls;
|
||||||
|
@ -64,6 +65,8 @@ mod reporter;
|
||||||
pub(crate) enum UnavailableVersion {
|
pub(crate) enum UnavailableVersion {
|
||||||
/// Version is incompatible due to the `Requires-Python` version specifiers for that package.
|
/// Version is incompatible due to the `Requires-Python` version specifiers for that package.
|
||||||
RequiresPython(VersionSpecifiers),
|
RequiresPython(VersionSpecifiers),
|
||||||
|
/// Version is incompatible because it is yanked
|
||||||
|
Yanked(Yanked),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The package is unavailable and cannot be used
|
/// The package is unavailable and cannot be used
|
||||||
|
@ -77,19 +80,25 @@ pub(crate) enum UnavailablePackage {
|
||||||
NotFound,
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ResolverVersion {
|
||||||
|
/// A usable version
|
||||||
|
Available(Version),
|
||||||
|
/// A version that is not usable for some reaosn
|
||||||
|
Unavailable(Version, UnavailableVersion),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Resolver<'a, Provider: ResolverProvider> {
|
pub struct Resolver<'a, Provider: ResolverProvider> {
|
||||||
project: Option<PackageName>,
|
project: Option<PackageName>,
|
||||||
requirements: Vec<Requirement>,
|
requirements: Vec<Requirement>,
|
||||||
constraints: Vec<Requirement>,
|
constraints: Vec<Requirement>,
|
||||||
overrides: Overrides,
|
overrides: Overrides,
|
||||||
|
allowed_yanks: AllowedYanks,
|
||||||
allowed_urls: AllowedUrls,
|
allowed_urls: AllowedUrls,
|
||||||
dependency_mode: DependencyMode,
|
dependency_mode: DependencyMode,
|
||||||
markers: &'a MarkerEnvironment,
|
markers: &'a MarkerEnvironment,
|
||||||
python_requirement: PythonRequirement,
|
python_requirement: PythonRequirement,
|
||||||
selector: CandidateSelector,
|
selector: CandidateSelector,
|
||||||
index: &'a InMemoryIndex,
|
index: &'a InMemoryIndex,
|
||||||
/// Incompatibilities for specific package versions
|
|
||||||
unavailable_versions: DashMap<PackageId, UnavailableVersion>,
|
|
||||||
/// Incompatibilities for packages that are entirely unavailable
|
/// Incompatibilities for packages that are entirely unavailable
|
||||||
unavailable_packages: DashMap<PackageName, UnavailablePackage>,
|
unavailable_packages: DashMap<PackageName, UnavailablePackage>,
|
||||||
/// The set of all registry-based packages visited during resolution.
|
/// The set of all registry-based packages visited during resolution.
|
||||||
|
@ -122,11 +131,6 @@ impl<'a, Context: BuildContext + Send + Sync> Resolver<'a, DefaultResolverProvid
|
||||||
tags,
|
tags,
|
||||||
PythonRequirement::new(interpreter, markers),
|
PythonRequirement::new(interpreter, markers),
|
||||||
options.exclude_newer,
|
options.exclude_newer,
|
||||||
manifest
|
|
||||||
.requirements
|
|
||||||
.iter()
|
|
||||||
.chain(manifest.constraints.iter())
|
|
||||||
.collect(),
|
|
||||||
build_context.no_binary(),
|
build_context.no_binary(),
|
||||||
);
|
);
|
||||||
Self::new_custom_io(
|
Self::new_custom_io(
|
||||||
|
@ -190,13 +194,20 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
)
|
)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Determine the allowed yanked package versions
|
||||||
|
let allowed_yanks = manifest
|
||||||
|
.requirements
|
||||||
|
.iter()
|
||||||
|
.chain(manifest.constraints.iter())
|
||||||
|
.collect();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
index,
|
index,
|
||||||
unavailable_versions: DashMap::default(),
|
|
||||||
unavailable_packages: DashMap::default(),
|
unavailable_packages: DashMap::default(),
|
||||||
visited: DashSet::default(),
|
visited: DashSet::default(),
|
||||||
selector,
|
selector,
|
||||||
allowed_urls,
|
allowed_urls,
|
||||||
|
allowed_yanks,
|
||||||
dependency_mode: options.dependency_mode,
|
dependency_mode: options.dependency_mode,
|
||||||
project: manifest.project,
|
project: manifest.project,
|
||||||
requirements: manifest.requirements,
|
requirements: manifest.requirements,
|
||||||
|
@ -369,6 +380,48 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
}
|
}
|
||||||
Some(version) => version,
|
Some(version) => version,
|
||||||
};
|
};
|
||||||
|
let version = match version {
|
||||||
|
ResolverVersion::Available(version) => version,
|
||||||
|
ResolverVersion::Unavailable(version, unavailable) => {
|
||||||
|
let reason = match unavailable {
|
||||||
|
UnavailableVersion::RequiresPython(requires_python) => {
|
||||||
|
// Incompatible requires-python versions are special in that we track
|
||||||
|
// them as incompatible dependencies instead of marking the package version
|
||||||
|
// as unavailable directly
|
||||||
|
let python_version = requires_python
|
||||||
|
.iter()
|
||||||
|
.map(PubGrubSpecifier::try_from)
|
||||||
|
.fold_ok(Range::full(), |range, specifier| {
|
||||||
|
range.intersection(&specifier.into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let package = &next;
|
||||||
|
for kind in [PubGrubPython::Installed, PubGrubPython::Target] {
|
||||||
|
state.add_incompatibility(Incompatibility::from_dependency(
|
||||||
|
package.clone(),
|
||||||
|
Range::singleton(version.clone()),
|
||||||
|
(&PubGrubPackage::Python(kind), &python_version),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
state.partial_solution.add_decision(next.clone(), version);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
UnavailableVersion::Yanked(yanked) => match yanked {
|
||||||
|
Yanked::Bool(_) => "it was yanked".to_string(),
|
||||||
|
Yanked::Reason(reason) => format!(
|
||||||
|
"it was yanked (reason: {})",
|
||||||
|
reason.trim().trim_end_matches('.')
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
state.add_incompatibility(Incompatibility::unavailable(
|
||||||
|
next.clone(),
|
||||||
|
version.clone(),
|
||||||
|
reason,
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.on_progress(&next, &version);
|
self.on_progress(&next, &version);
|
||||||
|
|
||||||
|
@ -483,6 +536,8 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
|
|
||||||
/// Given a set of candidate packages, choose the next package (and version) to add to the
|
/// Given a set of candidate packages, choose the next package (and version) to add to the
|
||||||
/// partial solution.
|
/// partial solution.
|
||||||
|
///
|
||||||
|
/// Returns [None] when there are no versions in the given range.
|
||||||
#[instrument(skip_all, fields(%package))]
|
#[instrument(skip_all, fields(%package))]
|
||||||
async fn choose_version(
|
async fn choose_version(
|
||||||
&self,
|
&self,
|
||||||
|
@ -490,14 +545,14 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
range: &Range<Version>,
|
range: &Range<Version>,
|
||||||
pins: &mut FilePins,
|
pins: &mut FilePins,
|
||||||
request_sink: &tokio::sync::mpsc::Sender<Request>,
|
request_sink: &tokio::sync::mpsc::Sender<Request>,
|
||||||
) -> Result<Option<Version>, ResolveError> {
|
) -> Result<Option<ResolverVersion>, ResolveError> {
|
||||||
match package {
|
match package {
|
||||||
PubGrubPackage::Root(_) => Ok(Some(MIN_VERSION.clone())),
|
PubGrubPackage::Root(_) => Ok(Some(ResolverVersion::Available(MIN_VERSION.clone()))),
|
||||||
|
|
||||||
PubGrubPackage::Python(PubGrubPython::Installed) => {
|
PubGrubPackage::Python(PubGrubPython::Installed) => {
|
||||||
let version = self.python_requirement.installed();
|
let version = self.python_requirement.installed();
|
||||||
if range.contains(version) {
|
if range.contains(version) {
|
||||||
Ok(Some(version.clone()))
|
Ok(Some(ResolverVersion::Available(version.clone())))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
@ -506,7 +561,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
PubGrubPackage::Python(PubGrubPython::Target) => {
|
PubGrubPackage::Python(PubGrubPython::Target) => {
|
||||||
let version = self.python_requirement.target();
|
let version = self.python_requirement.target();
|
||||||
if range.contains(version) {
|
if range.contains(version) {
|
||||||
Ok(Some(version.clone()))
|
Ok(Some(ResolverVersion::Available(version.clone())))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
@ -535,7 +590,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
// If the URL is that of a wheel, extract the version.
|
// If the URL is that of a wheel, extract the version.
|
||||||
let version = wheel_filename.version;
|
let version = wheel_filename.version;
|
||||||
if range.contains(&version) {
|
if range.contains(&version) {
|
||||||
Ok(Some(version))
|
Ok(Some(ResolverVersion::Available(version)))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
@ -550,7 +605,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
.ok_or(ResolveError::Unregistered)?;
|
.ok_or(ResolveError::Unregistered)?;
|
||||||
let version = &metadata.version;
|
let version = &metadata.version;
|
||||||
if range.contains(version) {
|
if range.contains(version) {
|
||||||
Ok(Some(version.clone()))
|
Ok(Some(ResolverVersion::Available(version.clone())))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
@ -605,13 +660,27 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the version is incompatible, short-circuit.
|
// If the version is incompatible because it was yanked
|
||||||
if let Some(requires_python) = candidate.validate(&self.python_requirement) {
|
if candidate.yanked().is_yanked() {
|
||||||
self.unavailable_versions.insert(
|
if self
|
||||||
candidate.package_id(),
|
.allowed_yanks
|
||||||
|
.allowed(package_name, candidate.version())
|
||||||
|
{
|
||||||
|
warn!("Allowing yanked version: {}", candidate.package_id());
|
||||||
|
} else {
|
||||||
|
return Ok(Some(ResolverVersion::Unavailable(
|
||||||
|
candidate.version().clone(),
|
||||||
|
UnavailableVersion::Yanked(candidate.yanked().clone()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the version is incompatible because of its Python requirement
|
||||||
|
if let Some(requires_python) = candidate.validate_python(&self.python_requirement) {
|
||||||
|
return Ok(Some(ResolverVersion::Unavailable(
|
||||||
|
candidate.version().clone(),
|
||||||
UnavailableVersion::RequiresPython(requires_python.clone()),
|
UnavailableVersion::RequiresPython(requires_python.clone()),
|
||||||
);
|
)));
|
||||||
return Ok(Some(candidate.version().clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(extra) = extra {
|
if let Some(extra) = extra {
|
||||||
|
@ -621,7 +690,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
extra,
|
extra,
|
||||||
candidate.version(),
|
candidate.version(),
|
||||||
candidate
|
candidate
|
||||||
.resolve()
|
.resolution_dist()
|
||||||
.dist
|
.dist
|
||||||
.filename()
|
.filename()
|
||||||
.unwrap_or("unknown filename")
|
.unwrap_or("unknown filename")
|
||||||
|
@ -632,7 +701,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
candidate.name(),
|
candidate.name(),
|
||||||
candidate.version(),
|
candidate.version(),
|
||||||
candidate
|
candidate
|
||||||
.resolve()
|
.resolution_dist()
|
||||||
.dist
|
.dist
|
||||||
.filename()
|
.filename()
|
||||||
.unwrap_or("unknown filename")
|
.unwrap_or("unknown filename")
|
||||||
|
@ -647,11 +716,11 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
|
|
||||||
// Emit a request to fetch the metadata for this version.
|
// Emit a request to fetch the metadata for this version.
|
||||||
if self.index.distributions.register(candidate.package_id()) {
|
if self.index.distributions.register(candidate.package_id()) {
|
||||||
let dist = candidate.resolve().dist.clone();
|
let dist = candidate.resolution_dist().dist.clone();
|
||||||
request_sink.send(Request::Dist(dist)).await?;
|
request_sink.send(Request::Dist(dist)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(version))
|
Ok(Some(ResolverVersion::Available(version)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -746,27 +815,6 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the package is known to be incompatible, return the Python version as an
|
|
||||||
// incompatibility, and skip fetching the metadata.
|
|
||||||
if let Some(entry) = self.unavailable_versions.get(&package_id) {
|
|
||||||
// TODO(zanieb): Handle additional variants here
|
|
||||||
let UnavailableVersion::RequiresPython(requires_python) = entry.value();
|
|
||||||
let version = requires_python
|
|
||||||
.iter()
|
|
||||||
.map(PubGrubSpecifier::try_from)
|
|
||||||
.fold_ok(Range::full(), |range, specifier| {
|
|
||||||
range.intersection(&specifier.into())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut constraints = DependencyConstraints::default();
|
|
||||||
constraints.insert(
|
|
||||||
PubGrubPackage::Python(PubGrubPython::Installed),
|
|
||||||
version.clone(),
|
|
||||||
);
|
|
||||||
constraints.insert(PubGrubPackage::Python(PubGrubPython::Target), version);
|
|
||||||
return Ok(Dependencies::Available(constraints));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the metadata to be available.
|
// Wait for the metadata to be available.
|
||||||
let metadata = self
|
let metadata = self
|
||||||
.index
|
.index
|
||||||
|
@ -942,17 +990,16 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the version is incompatible, short-circuit.
|
// If the version is incompatible, short-circuit.
|
||||||
if let Some(requires_python) = candidate.validate(&self.python_requirement) {
|
if candidate
|
||||||
self.unavailable_versions.insert(
|
.validate_python(&self.python_requirement)
|
||||||
candidate.package_id(),
|
.is_some()
|
||||||
UnavailableVersion::RequiresPython(requires_python.clone()),
|
{
|
||||||
);
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit a request to fetch the metadata for this version.
|
// Emit a request to fetch the metadata for this version.
|
||||||
if self.index.distributions.register(candidate.package_id()) {
|
if self.index.distributions.register(candidate.package_id()) {
|
||||||
let dist = candidate.resolve().dist.clone();
|
let dist = candidate.resolution_dist().dist.clone();
|
||||||
|
|
||||||
let (metadata, precise) = self
|
let (metadata, precise) = self
|
||||||
.provider
|
.provider
|
||||||
|
|
|
@ -16,7 +16,6 @@ use pypi_types::Metadata21;
|
||||||
|
|
||||||
use crate::python_requirement::PythonRequirement;
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::version_map::VersionMap;
|
use crate::version_map::VersionMap;
|
||||||
use crate::yanks::AllowedYanks;
|
|
||||||
|
|
||||||
type PackageVersionsResult = Result<VersionsResponse, puffin_client::Error>;
|
type PackageVersionsResult = Result<VersionsResponse, puffin_client::Error>;
|
||||||
type WheelMetadataResult = Result<(Metadata21, Option<Url>), puffin_distribution::Error>;
|
type WheelMetadataResult = Result<(Metadata21, Option<Url>), puffin_distribution::Error>;
|
||||||
|
@ -75,7 +74,6 @@ pub struct DefaultResolverProviderInner {
|
||||||
tags: Tags,
|
tags: Tags,
|
||||||
python_requirement: PythonRequirement,
|
python_requirement: PythonRequirement,
|
||||||
exclude_newer: Option<DateTime<Utc>>,
|
exclude_newer: Option<DateTime<Utc>>,
|
||||||
allowed_yanks: AllowedYanks,
|
|
||||||
no_binary: NoBinary,
|
no_binary: NoBinary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +95,6 @@ impl<'a, Context: BuildContext + Send + Sync> DefaultResolverProvider<'a, Contex
|
||||||
tags: &'a Tags,
|
tags: &'a Tags,
|
||||||
python_requirement: PythonRequirement,
|
python_requirement: PythonRequirement,
|
||||||
exclude_newer: Option<DateTime<Utc>>,
|
exclude_newer: Option<DateTime<Utc>>,
|
||||||
allowed_yanks: AllowedYanks,
|
|
||||||
no_binary: &'a NoBinary,
|
no_binary: &'a NoBinary,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -108,7 +105,6 @@ impl<'a, Context: BuildContext + Send + Sync> DefaultResolverProvider<'a, Contex
|
||||||
tags: tags.clone(),
|
tags: tags.clone(),
|
||||||
python_requirement,
|
python_requirement,
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
allowed_yanks,
|
|
||||||
no_binary: no_binary.clone(),
|
no_binary: no_binary.clone(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -138,7 +134,6 @@ impl<'a, Context: BuildContext + Send + Sync> ResolverProvider
|
||||||
&index,
|
&index,
|
||||||
&self_send.tags,
|
&self_send.tags,
|
||||||
&self_send.python_requirement,
|
&self_send.python_requirement,
|
||||||
&self_send.allowed_yanks,
|
|
||||||
self_send.exclude_newer.as_ref(),
|
self_send.exclude_newer.as_ref(),
|
||||||
self_send.flat_index.get(&package_name_owned).cloned(),
|
self_send.flat_index.get(&package_name_owned).cloned(),
|
||||||
&self_send.no_binary,
|
&self_send.no_binary,
|
||||||
|
|
|
@ -14,7 +14,6 @@ use puffin_warnings::warn_user_once;
|
||||||
use pypi_types::{Hashes, Yanked};
|
use pypi_types::{Hashes, Yanked};
|
||||||
|
|
||||||
use crate::python_requirement::PythonRequirement;
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::yanks::AllowedYanks;
|
|
||||||
|
|
||||||
/// A map from versions to distributions.
|
/// A map from versions to distributions.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
|
@ -30,7 +29,6 @@ impl VersionMap {
|
||||||
index: &IndexUrl,
|
index: &IndexUrl,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
python_requirement: &PythonRequirement,
|
python_requirement: &PythonRequirement,
|
||||||
allowed_yanks: &AllowedYanks,
|
|
||||||
exclude_newer: Option<&DateTime<Utc>>,
|
exclude_newer: Option<&DateTime<Utc>>,
|
||||||
mut flat_index: Option<FlatDistributions>,
|
mut flat_index: Option<FlatDistributions>,
|
||||||
no_binary: &NoBinary,
|
no_binary: &NoBinary,
|
||||||
|
@ -80,14 +78,15 @@ impl VersionMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When resolving, exclude yanked files.
|
// It is possible for files to have a different yank status per PEP 592 but in the official
|
||||||
if file.yanked.as_ref().is_some_and(Yanked::is_yanked) {
|
// PyPI warehouse this cannot happen.
|
||||||
if allowed_yanks.allowed(package_name, &version) {
|
// If any file is yanked, the version will be marked as yanked.
|
||||||
warn!("Allowing yanked version: {}", file.filename);
|
// <https://peps.python.org/pep-0592/#warehouse-pypi-implementation-notes>
|
||||||
|
let yanked = if let Some(ref yanked) = file.yanked {
|
||||||
|
yanked.clone()
|
||||||
} else {
|
} else {
|
||||||
continue;
|
Yanked::default()
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Prioritize amongst all available files.
|
// Prioritize amongst all available files.
|
||||||
let requires_python = file.requires_python.clone();
|
let requires_python = file.requires_python.clone();
|
||||||
|
@ -113,7 +112,13 @@ impl VersionMap {
|
||||||
file,
|
file,
|
||||||
index.clone(),
|
index.clone(),
|
||||||
);
|
);
|
||||||
priority_dist.insert_built(dist, requires_python, Some(hash), priority);
|
priority_dist.insert_built(
|
||||||
|
dist,
|
||||||
|
requires_python,
|
||||||
|
Some(hash),
|
||||||
|
priority,
|
||||||
|
yanked,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
DistFilename::SourceDistFilename(filename) => {
|
DistFilename::SourceDistFilename(filename) => {
|
||||||
let dist = Dist::from_registry(
|
let dist = Dist::from_registry(
|
||||||
|
@ -121,7 +126,7 @@ impl VersionMap {
|
||||||
file,
|
file,
|
||||||
index.clone(),
|
index.clone(),
|
||||||
);
|
);
|
||||||
priority_dist.insert_source(dist, requires_python, Some(hash));
|
priority_dist.insert_source(dist, requires_python, Some(hash), yanked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use puffin_normalize::PackageName;
|
||||||
/// A set of package versions that are permitted, even if they're marked as yanked by the
|
/// A set of package versions that are permitted, even if they're marked as yanked by the
|
||||||
/// relevant index.
|
/// relevant index.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct AllowedYanks(FxHashMap<PackageName, FxHashSet<Version>>);
|
pub(crate) struct AllowedYanks(FxHashMap<PackageName, FxHashSet<Version>>);
|
||||||
|
|
||||||
impl AllowedYanks {
|
impl AllowedYanks {
|
||||||
/// Returns `true` if the given package version is allowed, even if it's marked as yanked by
|
/// Returns `true` if the given package version is allowed, even if it's marked as yanked by
|
||||||
|
|
|
@ -1751,8 +1751,12 @@ fn compile_yanked_version_indirect() -> Result<()> {
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because only the following versions of attrs are available:
|
╰─▶ Because only the following versions of attrs are available:
|
||||||
attrs<=20.3.0
|
attrs<=20.3.0
|
||||||
|
attrs==21.1.0
|
||||||
attrs>=21.2.0
|
attrs>=21.2.0
|
||||||
and you require attrs>20.3.0,<21.2.0, we can conclude that the
|
and attrs==21.1.0 is unusable because it was yanked (reason:
|
||||||
|
Installable but not importable on Python 3.4), we can conclude that
|
||||||
|
attrs>20.3.0,<21.2.0 cannot be used.
|
||||||
|
And because you require attrs>20.3.0,<21.2.0, we can conclude that the
|
||||||
requirements are unsatisfiable.
|
requirements are unsatisfiable.
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
|
@ -2694,7 +2694,8 @@ fn package_only_yanked() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because there are no versions of albatross and you require albatross, we can conclude that the requirements are unsatisfiable.
|
╰─▶ Because only albatross==1.0.0 is available and albatross==1.0.0 is unusable because it was yanked, we can conclude that all versions of albatross cannot be used.
|
||||||
|
And because you require albatross, we can conclude that the requirements are unsatisfiable.
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Yanked versions should not be installed, even if they are the only one
|
// Yanked versions should not be installed, even if they are the only one
|
||||||
|
@ -2735,7 +2736,11 @@ fn package_only_yanked_in_range() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because only albatross<=0.1.0 is available and you require albatross>0.1.0, we can conclude that the requirements are unsatisfiable.
|
╰─▶ Because only the following versions of albatross are available:
|
||||||
|
albatross<=0.1.0
|
||||||
|
albatross==1.0.0
|
||||||
|
and albatross==1.0.0 is unusable because it was yanked, we can conclude that albatross>0.1.0 cannot be used.
|
||||||
|
And because you require albatross>0.1.0, we can conclude that the requirements are unsatisfiable.
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Since there are other versions of `a` available, yanked versions should not be
|
// Since there are other versions of `a` available, yanked versions should not be
|
||||||
|
@ -2870,7 +2875,8 @@ fn transitive_package_only_yanked() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because there are no versions of bluebird and albatross==0.1.0 depends on bluebird, we can conclude that albatross==0.1.0 cannot be used.
|
╰─▶ Because only bluebird==1.0.0 is available and bluebird==1.0.0 is unusable because it was yanked, we can conclude that all versions of bluebird cannot be used.
|
||||||
|
And because albatross==0.1.0 depends on bluebird, we can conclude that albatross==0.1.0 cannot be used.
|
||||||
And because only albatross==0.1.0 is available and you require albatross, we can conclude that the requirements are unsatisfiable.
|
And because only albatross==0.1.0 is available and you require albatross, we can conclude that the requirements are unsatisfiable.
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -2918,7 +2924,11 @@ fn transitive_package_only_yanked_in_range() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because only bluebird<=0.1 is available and albatross==0.1.0 depends on bluebird>0.1, we can conclude that albatross==0.1.0 cannot be used.
|
╰─▶ Because only the following versions of bluebird are available:
|
||||||
|
bluebird<=0.1
|
||||||
|
bluebird==1.0.0
|
||||||
|
and bluebird==1.0.0 is unusable because it was yanked, we can conclude that bluebird>0.1 cannot be used.
|
||||||
|
And because albatross==0.1.0 depends on bluebird>0.1, we can conclude that albatross==0.1.0 cannot be used.
|
||||||
And because only albatross==0.1.0 is available and you require albatross, we can conclude that the requirements are unsatisfiable.
|
And because only albatross==0.1.0 is available and you require albatross, we can conclude that the requirements are unsatisfiable.
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -3030,7 +3040,7 @@ fn transitive_yanked_and_unyanked_dependency() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because there is no version of crow==2.0.0 and albatross==1.0.0 depends on crow==2.0.0, we can conclude that albatross==1.0.0 cannot be used.
|
╰─▶ Because crow==2.0.0 is unusable because it was yanked and albatross==1.0.0 depends on crow==2.0.0, we can conclude that albatross==1.0.0 cannot be used.
|
||||||
And because only albatross==1.0.0 is available and you require albatross, we can conclude that the requirements are unsatisfiable.
|
And because only albatross==1.0.0 is available and you require albatross, we can conclude that the requirements are unsatisfiable.
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,12 @@ impl Yanked {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Yanked {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Bool(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A dictionary mapping a hash name to a hex encoded digest of the file.
|
/// A dictionary mapping a hash name to a hex encoded digest of the file.
|
||||||
///
|
///
|
||||||
/// PEP 691 says multiple hashes can be included and the interpretation is left to the client, we
|
/// PEP 691 says multiple hashes can be included and the interpretation is left to the client, we
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue