From b68effe86f91250cdf3e4a8a53293e822c6bfca5 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 16 Dec 2025 21:02:37 -0500 Subject: [PATCH] Avoid enforcing incorrect hash --- crates/uv-resolver/src/lock/installable.rs | 14 ++-- crates/uv-resolver/src/lock/mod.rs | 84 +++++++++++++++------- crates/uv/tests/it/lock.rs | 1 - 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/crates/uv-resolver/src/lock/installable.rs b/crates/uv-resolver/src/lock/installable.rs index b6d93904f..9c112a38b 100644 --- a/crates/uv-resolver/src/lock/installable.rs +++ b/crates/uv-resolver/src/lock/installable.rs @@ -16,7 +16,7 @@ use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_platform_tags::Tags; use uv_pypi_types::ResolverMarkerEnvironment; -use crate::lock::{LockErrorKind, Package, TagPolicy}; +use crate::lock::{HashedDist, LockErrorKind, Package, TagPolicy}; use crate::{Lock, LockError}; pub trait Installable<'lock> { @@ -527,18 +527,14 @@ pub trait Installable<'lock> { marker_env: &ResolverMarkerEnvironment, build_options: &BuildOptions, ) -> Result { - let dist = package.to_dist( - self.install_path(), - TagPolicy::Required(tags), - build_options, - marker_env, - )?; + let tag_policy = TagPolicy::Required(tags); + let HashedDist { dist, hashes } = + package.to_dist(self.install_path(), tag_policy, build_options, marker_env)?; let version = package.version().cloned(); let dist = ResolvedDist::Installable { dist: Arc::new(dist), version, }; - let hashes = package.hashes(); Ok(Node::Dist { dist, hashes, @@ -553,7 +549,7 @@ pub trait Installable<'lock> { tags: &Tags, marker_env: &ResolverMarkerEnvironment, ) -> Result { - let dist = package.to_dist( + let HashedDist { dist, .. } = package.to_dist( self.install_path(), TagPolicy::Preferred(tags), &BuildOptions::default(), diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 3ef583155..370d037f5 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -171,6 +171,15 @@ static ANDROID_X86_MARKERS: LazyLock = LazyLock::new(|| { marker }); +/// A distribution with its associated hash. +/// +/// This pairs a [`Dist`] with the [`HashDigests`] for the specific wheel or +/// sdist that would be installed. +pub(crate) struct HashedDist { + pub(crate) dist: Dist, + pub(crate) hashes: HashDigests, +} + #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] #[serde(try_from = "LockWire")] pub struct Lock { @@ -1761,7 +1770,7 @@ impl Lock { if let Some(version) = package.id.version.as_ref() { // For a non-dynamic package, fetch the metadata from the distribution database. - let dist = package.to_dist( + let HashedDist { dist, .. } = package.to_dist( root, TagPolicy::Preferred(tags), &BuildOptions::default(), @@ -1912,7 +1921,7 @@ impl Lock { // exactly. For example, `hatchling` will flatten any recursive (or self-referential) // extras, while `setuptools` will not. if !satisfied { - let dist = package.to_dist( + let HashedDist { dist, .. } = package.to_dist( root, TagPolicy::Preferred(tags), &BuildOptions::default(), @@ -2579,20 +2588,26 @@ impl Package { Ok(()) } - /// Convert the [`Package`] to a [`Dist`] that can be used in installation. + /// Convert the [`Package`] to a [`Dist`] that can be used in installation, along with its hash. fn to_dist( &self, workspace_root: &Path, tag_policy: TagPolicy<'_>, build_options: &BuildOptions, markers: &MarkerEnvironment, - ) -> Result { + ) -> Result { let no_binary = build_options.no_binary_package(&self.id.name); let no_build = build_options.no_build_package(&self.id.name); if !no_binary { if let Some(best_wheel_index) = self.find_best_wheel(tag_policy) { - return match &self.id.source { + let hashes = self.wheels[best_wheel_index] + .hash + .as_ref() + .map(|hash| HashDigests::from(vec![hash.0.clone()])) + .unwrap_or_else(|| HashDigests::from(vec![])); + + let dist = match &self.id.source { Source::Registry(source) => { let wheels = self .wheels @@ -2604,7 +2619,7 @@ impl Package { best_wheel_index, sdist: None, }; - Ok(Dist::Built(BuiltDist::Registry(reg_built_dist))) + Dist::Built(BuiltDist::Registry(reg_built_dist)) } Source::Path(path) => { let filename: WheelFilename = @@ -2616,7 +2631,7 @@ impl Package { install_path: absolute_path(workspace_root, path)?.into_boxed_path(), }; let built_dist = BuiltDist::Path(path_dist); - Ok(Dist::Built(built_dist)) + Dist::Built(built_dist) } Source::Direct(url, direct) => { let filename: WheelFilename = @@ -2632,29 +2647,39 @@ impl Package { url: VerbatimUrl::from_url(url), }; let built_dist = BuiltDist::DirectUrl(direct_dist); - Ok(Dist::Built(built_dist)) + Dist::Built(built_dist) } - Source::Git(_, _) => Err(LockErrorKind::InvalidWheelSource { - id: self.id.clone(), - source_type: "Git", + Source::Git(_, _) => { + return Err(LockErrorKind::InvalidWheelSource { + id: self.id.clone(), + source_type: "Git", + } + .into()); } - .into()), - Source::Directory(_) => Err(LockErrorKind::InvalidWheelSource { - id: self.id.clone(), - source_type: "directory", + Source::Directory(_) => { + return Err(LockErrorKind::InvalidWheelSource { + id: self.id.clone(), + source_type: "directory", + } + .into()); } - .into()), - Source::Editable(_) => Err(LockErrorKind::InvalidWheelSource { - id: self.id.clone(), - source_type: "editable", + Source::Editable(_) => { + return Err(LockErrorKind::InvalidWheelSource { + id: self.id.clone(), + source_type: "editable", + } + .into()); } - .into()), - Source::Virtual(_) => Err(LockErrorKind::InvalidWheelSource { - id: self.id.clone(), - source_type: "virtual", + Source::Virtual(_) => { + return Err(LockErrorKind::InvalidWheelSource { + id: self.id.clone(), + source_type: "virtual", + } + .into()); } - .into()), }; + + return Ok(HashedDist { dist, hashes }); } } @@ -2663,7 +2688,16 @@ impl Package { // any local source tree, or at least editable source trees, which we allow in // `uv pip`.) if !no_build || sdist.is_virtual() { - return Ok(Dist::Source(sdist)); + let hashes = self + .sdist + .as_ref() + .and_then(|s| s.hash()) + .map(|hash| HashDigests::from(vec![hash.0.clone()])) + .unwrap_or_else(|| HashDigests::from(vec![])); + return Ok(HashedDist { + dist: Dist::Source(sdist), + hashes, + }); } } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 6c9bd7858..fba5865d5 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -8231,7 +8231,6 @@ fn lock_invalid_hash() -> Result<()> { ╰─▶ Hash mismatch for `idna==3.6` Expected: - sha256:aecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca sha256:d05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f Computed: