Fall back to distributions without hashes in resolver (#2949)

## Summary

This represents a change to `--require-hashes` in the event that we
don't find a matching hash from the registry. The behavior in this PR is
closer to pip's.

Prior to this PR, if a distribution had no reported hash, or only
mismatched hashes, we would mark it as incompatible. Now, we mark it as
compatible, but we use the hash-agreement as part of the ordering, such
that we prefer any distribution with a matching hash, then any
distribution with no hash, then any distribution with a mismatched hash.

As a result, if an index reports incorrect hashes, but the user provides
the correct one, resolution now succeeds, where it would've failed.

Similarly, if an index omits hashes altogether, but the user provides
the correct one, resolution now succeeds, where it would've failed.

If we end up picking a distribution whose hash ultimately doesn't match,
we'll reject it later, after resolution.
This commit is contained in:
Charlie Marsh 2024-04-10 15:19:47 -04:00 committed by GitHub
parent 1f3b5bb093
commit c18551fd3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 321 additions and 185 deletions

View file

@ -8,11 +8,11 @@ use tracing::instrument;
use distribution_filename::{DistFilename, WheelFilename};
use distribution_types::{
Dist, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist,
Dist, Hash, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist,
SourceDistCompatibility, WheelCompatibility,
};
use pep440_rs::{Version, VersionSpecifiers};
use platform_tags::Tags;
use platform_tags::{TagCompatibility, Tags};
use pypi_types::{HashDigest, Yanked};
use uv_client::{OwnedArchive, SimpleMetadata, VersionFiles};
use uv_configuration::{NoBinary, NoBuild};
@ -456,19 +456,6 @@ impl VersionMapLazy {
));
}
// Check if hashes line up
if !self.required_hashes.is_empty() {
if hashes.is_empty() {
return SourceDistCompatibility::Incompatible(IncompatibleSource::MissingHash);
}
if !hashes
.iter()
.any(|hash| self.required_hashes.contains(hash))
{
return SourceDistCompatibility::Incompatible(IncompatibleSource::MismatchedHash);
}
}
// Check if yanked
if let Some(yanked) = yanked {
if yanked.is_yanked() && !self.allowed_yanks.contains(version) {
@ -489,7 +476,23 @@ impl VersionMapLazy {
}
}
SourceDistCompatibility::Compatible
// Check if hashes line up. If hashes aren't required, they're considered matching.
let hash = if self.required_hashes.is_empty() {
Hash::Matched
} else {
if hashes.is_empty() {
Hash::Missing
} else if hashes
.iter()
.any(|hash| self.required_hashes.contains(hash))
{
Hash::Matched
} else {
Hash::Mismatched
}
};
SourceDistCompatibility::Compatible(hash)
}
#[allow(clippy::too_many_arguments)]
@ -513,19 +516,6 @@ impl VersionMapLazy {
return WheelCompatibility::Incompatible(IncompatibleWheel::ExcludeNewer(upload_time));
}
// Check if hashes line up
if !self.required_hashes.is_empty() {
if hashes.is_empty() {
return WheelCompatibility::Incompatible(IncompatibleWheel::MissingHash);
}
if !hashes
.iter()
.any(|hash| self.required_hashes.contains(hash))
{
return WheelCompatibility::Incompatible(IncompatibleWheel::MismatchedHash);
}
}
// Check if yanked
if let Some(yanked) = yanked {
if yanked.is_yanked() && !self.allowed_yanks.contains(version) {
@ -542,8 +532,31 @@ impl VersionMapLazy {
}
}
// Determine a compatibility for the wheel based on tags
WheelCompatibility::from(filename.compatibility(&self.tags))
// Determine a compatibility for the wheel based on tags.
let priority = match filename.compatibility(&self.tags) {
TagCompatibility::Incompatible(tag) => {
return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag))
}
TagCompatibility::Compatible(priority) => priority,
};
// Check if hashes line up. If hashes aren't required, they're considered matching.
let hash = if self.required_hashes.is_empty() {
Hash::Matched
} else {
if hashes.is_empty() {
Hash::Missing
} else if hashes
.iter()
.any(|hash| self.required_hashes.contains(hash))
{
Hash::Matched
} else {
Hash::Mismatched
}
};
WheelCompatibility::Compatible(hash, priority)
}
}