mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 02:48:17 +00:00
Allow yanked versions when specified via ==
(#561)
## Summary This enables users to rely on yanked versions via explicit `==` markers, which is necessary in some projects (and, in my opinion, reasonable). Closes #551.
This commit is contained in:
parent
c3a917bbf6
commit
2d1e19e474
8 changed files with 178 additions and 8 deletions
|
@ -56,7 +56,7 @@ impl From<&[Requirement]> for Preferences {
|
|||
else {
|
||||
return None;
|
||||
};
|
||||
let [version_specifier] = &**version_specifiers else {
|
||||
let [version_specifier] = version_specifiers.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let version = PubGrubVersion::from(version_specifier.version().clone());
|
||||
|
|
|
@ -20,3 +20,4 @@ mod resolution_mode;
|
|||
mod resolution_options;
|
||||
mod resolver;
|
||||
mod version_map;
|
||||
mod yanks;
|
||||
|
|
|
@ -36,6 +36,7 @@ use crate::pubgrub::{
|
|||
};
|
||||
use crate::resolution::Graph;
|
||||
use crate::version_map::VersionMap;
|
||||
use crate::yanks::AllowedYanks;
|
||||
use crate::ResolutionOptions;
|
||||
|
||||
pub struct Resolver<'a, Context: BuildContext + Send + Sync> {
|
||||
|
@ -43,6 +44,7 @@ pub struct Resolver<'a, Context: BuildContext + Send + Sync> {
|
|||
requirements: Vec<Requirement>,
|
||||
constraints: Vec<Requirement>,
|
||||
allowed_urls: AllowedUrls,
|
||||
allowed_yanks: AllowedYanks,
|
||||
markers: &'a MarkerEnvironment,
|
||||
tags: &'a Tags,
|
||||
client: &'a RegistryClient,
|
||||
|
@ -79,6 +81,11 @@ impl<'a, Context: BuildContext + Send + Sync> Resolver<'a, Context> {
|
|||
}
|
||||
})
|
||||
.collect(),
|
||||
allowed_yanks: manifest
|
||||
.requirements
|
||||
.iter()
|
||||
.chain(manifest.constraints.iter())
|
||||
.collect(),
|
||||
project: manifest.project,
|
||||
requirements: manifest.requirements,
|
||||
constraints: manifest.constraints,
|
||||
|
@ -553,6 +560,7 @@ impl<'a, Context: BuildContext + Send + Sync> Resolver<'a, Context> {
|
|||
self.tags,
|
||||
self.markers,
|
||||
self.build_context.interpreter(),
|
||||
&self.allowed_yanks,
|
||||
self.exclude_newer.as_ref(),
|
||||
);
|
||||
self.index
|
||||
|
|
|
@ -15,6 +15,7 @@ use pypi_types::{SimpleJson, Yanked};
|
|||
|
||||
use crate::file::{DistFile, SdistFile, WheelFile};
|
||||
use crate::pubgrub::PubGrubVersion;
|
||||
use crate::yanks::AllowedYanks;
|
||||
|
||||
/// A map from versions to distributions.
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -28,6 +29,7 @@ impl VersionMap {
|
|||
tags: &Tags,
|
||||
markers: &MarkerEnvironment,
|
||||
interpreter: &Interpreter,
|
||||
allowed_yanks: &AllowedYanks,
|
||||
exclude_newer: Option<&DateTime<Utc>>,
|
||||
) -> Self {
|
||||
let mut version_map: BTreeMap<PubGrubVersion, PrioritizedDistribution> =
|
||||
|
@ -72,14 +74,16 @@ impl VersionMap {
|
|||
}
|
||||
}
|
||||
|
||||
// When resolving, exclude yanked files.
|
||||
// TODO(konstin): When we fail resolving due to a dependency locked to yanked version,
|
||||
// we should tell the user.
|
||||
if file.yanked.as_ref().is_some_and(Yanked::is_yanked) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(filename) = WheelFilename::from_str(file.filename.as_str()) {
|
||||
// When resolving, exclude yanked files.
|
||||
if file.yanked.as_ref().is_some_and(Yanked::is_yanked) {
|
||||
if allowed_yanks.allowed(package_name, &filename.version) {
|
||||
warn!("Allowing yanked version: {}", file.filename);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let priority = filename.compatibility(tags);
|
||||
|
||||
match version_map.entry(filename.version.into()) {
|
||||
|
@ -96,6 +100,15 @@ impl VersionMap {
|
|||
} else if let Ok(filename) =
|
||||
SourceDistFilename::parse(file.filename.as_str(), package_name)
|
||||
{
|
||||
// When resolving, exclude yanked files.
|
||||
if file.yanked.as_ref().is_some_and(Yanked::is_yanked) {
|
||||
if allowed_yanks.allowed(package_name, &filename.version) {
|
||||
warn!("Allowing yanked version: {}", file.filename);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
match version_map.entry(filename.version.into()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().insert_source(SdistFile(file));
|
||||
|
|
47
crates/puffin-resolver/src/yanks.rs
Normal file
47
crates/puffin-resolver/src/yanks.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use fxhash::{FxHashMap, FxHashSet};
|
||||
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::Requirement;
|
||||
use puffin_normalize::PackageName;
|
||||
|
||||
/// A set of package versions that are permitted, even if they're marked as yanked by the
|
||||
/// relevant index.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct AllowedYanks(FxHashMap<PackageName, FxHashSet<Version>>);
|
||||
|
||||
impl AllowedYanks {
|
||||
/// Returns `true` if the given package version is allowed, even if it's marked as yanked by
|
||||
/// the relevant index.
|
||||
pub(crate) fn allowed(&self, package_name: &PackageName, version: &Version) -> bool {
|
||||
self.0
|
||||
.get(package_name)
|
||||
.map(|allowed_yanks| allowed_yanks.contains(version))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<&'a Requirement> for AllowedYanks {
|
||||
fn from_iter<T: IntoIterator<Item = &'a Requirement>>(iter: T) -> Self {
|
||||
let mut allowed_yanks = FxHashMap::<PackageName, FxHashSet<Version>>::default();
|
||||
for requirement in iter {
|
||||
let Some(pep508_rs::VersionOrUrl::VersionSpecifier(specifiers)) =
|
||||
&requirement.version_or_url
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let [specifier] = specifiers.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
if matches!(
|
||||
specifier.operator(),
|
||||
pep440_rs::Operator::Equal | pep440_rs::Operator::ExactEqual
|
||||
) {
|
||||
allowed_yanks
|
||||
.entry(requirement.name.clone())
|
||||
.or_default()
|
||||
.insert(specifier.version().clone());
|
||||
}
|
||||
}
|
||||
Self(allowed_yanks)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue