Skip to content

Commit a1f7726

Browse files
committed
Avoid generating unused hashes during uv lock
1 parent a056cb2 commit a1f7726

File tree

6 files changed

+63
-38
lines changed

6 files changed

+63
-38
lines changed

crates/uv-distribution-types/src/hash.rs

+28-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
use crate::BuiltDist;
12
use uv_pypi_types::{HashAlgorithm, HashDigest};
23

34
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45
pub enum HashPolicy<'a> {
56
/// No hash policy is specified.
67
None,
78
/// Hashes should be generated (specifically, a SHA-256 hash), but not validated.
8-
Generate,
9+
Generate(HashGeneration),
910
/// Hashes should be validated against a pre-defined list of hashes. If necessary, hashes should
1011
/// be generated so as to ensure that the archive is valid.
1112
Validate(&'a [HashDigest]),
@@ -17,21 +18,28 @@ impl HashPolicy<'_> {
1718
matches!(self, Self::None)
1819
}
1920

20-
/// Returns `true` if the hash policy is `Generate`.
21-
pub fn is_generate(&self) -> bool {
22-
matches!(self, Self::Generate)
23-
}
24-
2521
/// Returns `true` if the hash policy is `Validate`.
2622
pub fn is_validate(&self) -> bool {
2723
matches!(self, Self::Validate(_))
2824
}
2925

26+
/// Returns `true` if the hash policy indicates that hashes should be generated.
27+
pub fn is_generate(&self, dist: &BuiltDist) -> bool {
28+
match self {
29+
HashPolicy::Generate(HashGeneration::Url) => dist.file().is_none(),
30+
HashPolicy::Generate(HashGeneration::All) => {
31+
dist.file().map_or(true, |file| file.hashes.is_empty())
32+
}
33+
HashPolicy::Validate(_) => false,
34+
HashPolicy::None => false,
35+
}
36+
}
37+
3038
/// Return the algorithms used in the hash policy.
3139
pub fn algorithms(&self) -> Vec<HashAlgorithm> {
3240
match self {
3341
Self::None => vec![],
34-
Self::Generate => vec![HashAlgorithm::Sha256],
42+
Self::Generate(_) => vec![HashAlgorithm::Sha256],
3543
Self::Validate(hashes) => {
3644
let mut algorithms = hashes.iter().map(HashDigest::algorithm).collect::<Vec<_>>();
3745
algorithms.sort();
@@ -45,12 +53,22 @@ impl HashPolicy<'_> {
4553
pub fn digests(&self) -> &[HashDigest] {
4654
match self {
4755
Self::None => &[],
48-
Self::Generate => &[],
56+
Self::Generate(_) => &[],
4957
Self::Validate(hashes) => hashes,
5058
}
5159
}
5260
}
5361

62+
/// The context in which hashes should be generated.
63+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64+
pub enum HashGeneration {
65+
/// Generate hashes for direct URL distributions.
66+
Url,
67+
/// Generate hashes for direct URL distributions, along with any distributions that are hosted
68+
/// on a registry that does _not_ provide hashes.
69+
All,
70+
}
71+
5472
pub trait Hashed {
5573
/// Return the [`HashDigest`]s for the archive.
5674
fn hashes(&self) -> &[HashDigest];
@@ -59,7 +77,7 @@ pub trait Hashed {
5977
fn satisfies(&self, hashes: HashPolicy) -> bool {
6078
match hashes {
6179
HashPolicy::None => true,
62-
HashPolicy::Generate => self
80+
HashPolicy::Generate(_) => self
6381
.hashes()
6482
.iter()
6583
.any(|hash| hash.algorithm == HashAlgorithm::Sha256),
@@ -71,7 +89,7 @@ pub trait Hashed {
7189
fn has_digests(&self, hashes: HashPolicy) -> bool {
7290
match hashes {
7391
HashPolicy::None => true,
74-
HashPolicy::Generate => self
92+
HashPolicy::Generate(_) => self
7593
.hashes()
7694
.iter()
7795
.any(|hash| hash.algorithm == HashAlgorithm::Sha256),

crates/uv-distribution/src/distribution_database.rs

+19-13
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ use uv_client::{
2121
};
2222
use uv_distribution_filename::WheelFilename;
2323
use uv_distribution_types::{
24-
BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, InstalledDist, Name,
25-
SourceDist,
24+
BuildableSource, BuiltDist, Dist, FileLocation, HashGeneration, HashPolicy, Hashed,
25+
InstalledDist, Name, SourceDist,
2626
};
2727
use uv_extract::hash::Hasher;
2828
use uv_fs::write_atomic;
@@ -405,22 +405,28 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
405405
return Ok(ArchiveMetadata::from_metadata23(metadata.clone()));
406406
}
407407

408-
// If hash generation is enabled, and the distribution isn't hosted on an index, get the
408+
// If hash generation is enabled, and the distribution isn't hosted on a registry, get the
409409
// entire wheel to ensure that the hashes are included in the response. If the distribution
410410
// is hosted on an index, the hashes will be included in the simple metadata response.
411411
// For hash _validation_, callers are expected to enforce the policy when retrieving the
412412
// wheel.
413+
//
414+
// Historically, for `uv pip compile --universal`, we also generate hashes for
415+
// registry-based distributions when the relevant registry doesn't provide them. This was
416+
// motivated by `--find-links`. We continue that behavior (under `HashGeneration::All`) for
417+
// backwards compatibility, but it's a little dubious, since we're only hashing _one_
418+
// distribution here (as opposed to hashing all distributions for the version), and it may
419+
// not even be a compatible distribution!
420+
//
413421
// TODO(charlie): Request the hashes via a separate method, to reduce the coupling in this API.
414-
if hashes.is_generate() {
415-
if dist.file().map_or(true, |file| file.hashes.is_empty()) {
416-
let wheel = self.get_wheel(dist, hashes).await?;
417-
let metadata = wheel.metadata()?;
418-
let hashes = wheel.hashes;
419-
return Ok(ArchiveMetadata {
420-
metadata: Metadata::from_metadata23(metadata),
421-
hashes,
422-
});
423-
}
422+
if hashes.is_generate(dist) {
423+
let wheel = self.get_wheel(dist, hashes).await?;
424+
let metadata = wheel.metadata()?;
425+
let hashes = wheel.hashes;
426+
return Ok(ArchiveMetadata {
427+
metadata: Metadata::from_metadata23(metadata),
428+
hashes,
429+
});
424430
}
425431

426432
let result = self

crates/uv-requirements/src/source_tree.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use url::Url;
1313
use uv_configuration::ExtrasSpecification;
1414
use uv_distribution::{DistributionDatabase, Reporter, RequiresDist};
1515
use uv_distribution_types::{
16-
BuildableSource, DirectorySourceUrl, HashPolicy, SourceUrl, VersionId,
16+
BuildableSource, DirectorySourceUrl, HashGeneration, HashPolicy, SourceUrl, VersionId,
1717
};
1818
use uv_fs::Simplified;
1919
use uv_normalize::{ExtraName, PackageName};
@@ -213,8 +213,8 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
213213
// manual match.
214214
let hashes = match self.hasher {
215215
HashStrategy::None => HashPolicy::None,
216-
HashStrategy::Generate => HashPolicy::Generate,
217-
HashStrategy::Verify(_) => HashPolicy::Generate,
216+
HashStrategy::Generate(mode) => HashPolicy::Generate(*mode),
217+
HashStrategy::Verify(_) => HashPolicy::Generate(HashGeneration::All),
218218
HashStrategy::Require(_) => {
219219
return Err(anyhow::anyhow!(
220220
"Hash-checking is not supported for local directories: {}",

crates/uv-types/src/hash.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use url::Url;
55

66
use uv_configuration::HashCheckingMode;
77
use uv_distribution_types::{
8-
DistributionMetadata, HashPolicy, Name, Resolution, UnresolvedRequirement, VersionId,
8+
DistributionMetadata, HashGeneration, HashPolicy, Name, Resolution, UnresolvedRequirement,
9+
VersionId,
910
};
1011
use uv_normalize::PackageName;
1112
use uv_pep440::Version;
@@ -19,7 +20,7 @@ pub enum HashStrategy {
1920
#[default]
2021
None,
2122
/// Hashes should be generated (specifically, a SHA-256 hash), but not validated.
22-
Generate,
23+
Generate(HashGeneration),
2324
/// Hashes should be validated, if present, but ignored if absent.
2425
///
2526
/// If necessary, hashes should be generated to ensure that the archive is valid.
@@ -35,7 +36,7 @@ impl HashStrategy {
3536
pub fn get<T: DistributionMetadata>(&self, distribution: &T) -> HashPolicy {
3637
match self {
3738
Self::None => HashPolicy::None,
38-
Self::Generate => HashPolicy::Generate,
39+
Self::Generate(mode) => HashPolicy::Generate(*mode),
3940
Self::Verify(hashes) => {
4041
if let Some(hashes) = hashes.get(&distribution.version_id()) {
4142
HashPolicy::Validate(hashes.as_slice())
@@ -56,7 +57,7 @@ impl HashStrategy {
5657
pub fn get_package(&self, name: &PackageName, version: &Version) -> HashPolicy {
5758
match self {
5859
Self::None => HashPolicy::None,
59-
Self::Generate => HashPolicy::Generate,
60+
Self::Generate(mode) => HashPolicy::Generate(*mode),
6061
Self::Verify(hashes) => {
6162
if let Some(hashes) =
6263
hashes.get(&VersionId::from_registry(name.clone(), version.clone()))
@@ -79,7 +80,7 @@ impl HashStrategy {
7980
pub fn get_url(&self, url: &Url) -> HashPolicy {
8081
match self {
8182
Self::None => HashPolicy::None,
82-
Self::Generate => HashPolicy::Generate,
83+
Self::Generate(mode) => HashPolicy::Generate(*mode),
8384
Self::Verify(hashes) => {
8485
if let Some(hashes) = hashes.get(&VersionId::from_url(url)) {
8586
HashPolicy::Validate(hashes.as_slice())
@@ -100,7 +101,7 @@ impl HashStrategy {
100101
pub fn allows_package(&self, name: &PackageName, version: &Version) -> bool {
101102
match self {
102103
Self::None => true,
103-
Self::Generate => true,
104+
Self::Generate(_) => true,
104105
Self::Verify(_) => true,
105106
Self::Require(hashes) => {
106107
hashes.contains_key(&VersionId::from_registry(name.clone(), version.clone()))
@@ -112,7 +113,7 @@ impl HashStrategy {
112113
pub fn allows_url(&self, url: &Url) -> bool {
113114
match self {
114115
Self::None => true,
115-
Self::Generate => true,
116+
Self::Generate(_) => true,
116117
Self::Verify(_) => true,
117118
Self::Require(hashes) => hashes.contains_key(&VersionId::from_url(url)),
118119
}

crates/uv/src/commands/pip/compile.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ use uv_configuration::{
1717
use uv_configuration::{KeyringProviderType, TargetTriple};
1818
use uv_dispatch::{BuildDispatch, SharedState};
1919
use uv_distribution_types::{
20-
DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, Origin,
21-
UnresolvedRequirementSpecification, Verbatim,
20+
DependencyMetadata, HashGeneration, Index, IndexLocations, NameRequirementSpecification,
21+
Origin, UnresolvedRequirementSpecification, Verbatim,
2222
};
2323
use uv_fs::Simplified;
2424
use uv_install_wheel::linker::LinkMode;
@@ -266,7 +266,7 @@ pub(crate) async fn pip_compile(
266266

267267
// Generate, but don't enforce hashes for the requirements.
268268
let hasher = if generate_hashes {
269-
HashStrategy::Generate
269+
HashStrategy::Generate(HashGeneration::All)
270270
} else {
271271
HashStrategy::None
272272
};

crates/uv/src/commands/project/lock.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use uv_configuration::{
1717
use uv_dispatch::{BuildDispatch, SharedState};
1818
use uv_distribution::DistributionDatabase;
1919
use uv_distribution_types::{
20-
DependencyMetadata, Index, IndexLocations, NameRequirementSpecification,
20+
DependencyMetadata, HashGeneration, Index, IndexLocations, NameRequirementSpecification,
2121
UnresolvedRequirementSpecification,
2222
};
2323
use uv_git::ResolvedRepositoryReference;
@@ -472,7 +472,7 @@ async fn do_lock(
472472
.index_strategy(index_strategy)
473473
.build_options(build_options.clone())
474474
.build();
475-
let hasher = HashStrategy::Generate;
475+
let hasher = HashStrategy::Generate(HashGeneration::Url);
476476

477477
// TODO(charlie): These are all default values. We should consider whether we want to make them
478478
// optional on the downstream APIs.

0 commit comments

Comments
 (0)