Filter out invalid wheels based on requires-python (#5084)

## Summary

The example in the linked issue doesn't quite work, but I think it has
to do with the existing filtering logic. Will follow-up separately.

Closes https://github.com/astral-sh/uv/issues/5012.
This commit is contained in:
Charlie Marsh 2024-07-15 21:01:38 -04:00 committed by GitHub
parent 14a1ea460d
commit 9a44bc1d35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 26 additions and 3 deletions

View file

@ -154,6 +154,9 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
database, database,
flat_index, flat_index,
tags, tags,
python_requirement
.target()
.and_then(|target| target.as_requires_python()),
AllowedYanks::from_manifest(&manifest, markers, options.dependency_mode), AllowedYanks::from_manifest(&manifest, markers, options.dependency_mode),
hasher, hasher,
options.exclude_newer, options.exclude_newer,

View file

@ -10,7 +10,7 @@ use uv_types::{BuildContext, HashStrategy};
use crate::flat_index::FlatIndex; use crate::flat_index::FlatIndex;
use crate::version_map::VersionMap; use crate::version_map::VersionMap;
use crate::yanks::AllowedYanks; use crate::yanks::AllowedYanks;
use crate::ExcludeNewer; use crate::{ExcludeNewer, RequiresPython};
pub type PackageVersionsResult = Result<VersionsResponse, uv_client::Error>; pub type PackageVersionsResult = Result<VersionsResponse, uv_client::Error>;
pub type WheelMetadataResult = Result<MetadataResponse, uv_distribution::Error>; pub type WheelMetadataResult = Result<MetadataResponse, uv_distribution::Error>;
@ -76,6 +76,7 @@ pub struct DefaultResolverProvider<'a, Context: BuildContext> {
/// These are the entries from `--find-links` that act as overrides for index responses. /// These are the entries from `--find-links` that act as overrides for index responses.
flat_index: FlatIndex, flat_index: FlatIndex,
tags: Option<Tags>, tags: Option<Tags>,
requires_python: Option<RequiresPython>,
allowed_yanks: AllowedYanks, allowed_yanks: AllowedYanks,
hasher: HashStrategy, hasher: HashStrategy,
exclude_newer: Option<ExcludeNewer>, exclude_newer: Option<ExcludeNewer>,
@ -88,6 +89,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
fetcher: DistributionDatabase<'a, Context>, fetcher: DistributionDatabase<'a, Context>,
flat_index: &'a FlatIndex, flat_index: &'a FlatIndex,
tags: Option<&'a Tags>, tags: Option<&'a Tags>,
requires_python: Option<&'a RequiresPython>,
allowed_yanks: AllowedYanks, allowed_yanks: AllowedYanks,
hasher: &'a HashStrategy, hasher: &'a HashStrategy,
exclude_newer: Option<ExcludeNewer>, exclude_newer: Option<ExcludeNewer>,
@ -97,6 +99,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
fetcher, fetcher,
flat_index: flat_index.clone(), flat_index: flat_index.clone(),
tags: tags.cloned(), tags: tags.cloned(),
requires_python: requires_python.cloned(),
allowed_yanks, allowed_yanks,
hasher: hasher.clone(), hasher: hasher.clone(),
exclude_newer, exclude_newer,
@ -127,6 +130,7 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a,
package_name, package_name,
&index, &index,
self.tags.as_ref(), self.tags.as_ref(),
self.requires_python.as_ref(),
&self.allowed_yanks, &self.allowed_yanks,
&self.hasher, &self.hasher,
self.exclude_newer.as_ref(), self.exclude_newer.as_ref(),

View file

@ -8,7 +8,8 @@ use tracing::instrument;
use distribution_filename::{DistFilename, WheelFilename}; use distribution_filename::{DistFilename, WheelFilename};
use distribution_types::{ use distribution_types::{
HashComparison, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist, HashComparison, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist,
RegistryBuiltWheel, RegistrySourceDist, SourceDistCompatibility, WheelCompatibility, PythonRequirementKind, RegistryBuiltWheel, RegistrySourceDist, SourceDistCompatibility,
WheelCompatibility,
}; };
use pep440_rs::Version; use pep440_rs::Version;
use platform_tags::{TagCompatibility, Tags}; use platform_tags::{TagCompatibility, Tags};
@ -20,7 +21,7 @@ use uv_types::HashStrategy;
use uv_warnings::warn_user_once; use uv_warnings::warn_user_once;
use crate::flat_index::FlatDistributions; use crate::flat_index::FlatDistributions;
use crate::{yanks::AllowedYanks, ExcludeNewer}; use crate::{yanks::AllowedYanks, ExcludeNewer, RequiresPython};
/// A map from versions to distributions. /// A map from versions to distributions.
#[derive(Debug)] #[derive(Debug)]
@ -44,6 +45,7 @@ impl VersionMap {
package_name: &PackageName, package_name: &PackageName,
index: &IndexUrl, index: &IndexUrl,
tags: Option<&Tags>, tags: Option<&Tags>,
requires_python: Option<&RequiresPython>,
allowed_yanks: &AllowedYanks, allowed_yanks: &AllowedYanks,
hasher: &HashStrategy, hasher: &HashStrategy,
exclude_newer: Option<&ExcludeNewer>, exclude_newer: Option<&ExcludeNewer>,
@ -105,6 +107,7 @@ impl VersionMap {
no_build: build_options.no_build_package(package_name), no_build: build_options.no_build_package(package_name),
index: index.clone(), index: index.clone(),
tags: tags.cloned(), tags: tags.cloned(),
requires_python: requires_python.cloned(),
exclude_newer: exclude_newer.copied(), exclude_newer: exclude_newer.copied(),
allowed_yanks, allowed_yanks,
required_hashes, required_hashes,
@ -288,6 +291,8 @@ struct VersionMapLazy {
allowed_yanks: FxHashSet<Version>, allowed_yanks: FxHashSet<Version>,
/// The hashes of allowed distributions. /// The hashes of allowed distributions.
required_hashes: Vec<HashDigest>, required_hashes: Vec<HashDigest>,
/// The `requires-python` constraint for the resolution.
requires_python: Option<RequiresPython>,
} }
impl VersionMapLazy { impl VersionMapLazy {
@ -508,6 +513,17 @@ impl VersionMapLazy {
} }
}; };
// Check if the wheel is compatible with the `requires-python` (i.e., the Python ABI tag
// is not less than the `requires-python` minimum version).
if let Some(requires_python) = self.requires_python.as_ref() {
if !filename.matches_requires_python(requires_python.specifiers()) {
return WheelCompatibility::Incompatible(IncompatibleWheel::RequiresPython(
requires_python.specifiers().clone(),
PythonRequirementKind::Target,
));
}
}
// Break ties with the build tag. // Break ties with the build tag.
let build_tag = filename.build_tag.clone(); let build_tag = filename.build_tag.clone();