use crate::artifact::ArtifactKey;
const STOW_HASH_VERSION: &[u8] = b"stow-v1";
#[must_use]
pub fn compute_artifact_hash(key: &ArtifactKey) -> String {
let mut hasher = blake3::Hasher::new();
hasher.update(STOW_HASH_VERSION);
hash_str(&mut hasher, &key.crate_id.name);
hash_str(&mut hasher, &key.crate_id.version.to_string());
let feature_count =
u32::try_from(key.features.0.len()).expect("feature count exceeds u32 range");
hasher.update(&feature_count.to_le_bytes());
for f in &key.features.0 {
hash_str(&mut hasher, f);
}
hash_str(&mut hasher, &key.target.0);
hash_str(&mut hasher, &key.rustc_version.version.to_string());
hash_str(&mut hasher, &key.rustc_version.commit_hash);
hash_str(&mut hasher, &key.profile.opt_level);
hasher.update(&key.profile.debuginfo.to_le_bytes());
hasher.update(&[u8::from(key.profile.debug_assertions)]);
hasher.update(&[u8::from(key.profile.overflow_checks)]);
hash_str(&mut hasher, key.profile.panic.as_str());
let crate_type_count =
u32::try_from(key.crate_types.len()).expect("crate type count exceeds u32 range");
hasher.update(&crate_type_count.to_le_bytes());
for crate_type in &key.crate_types {
hash_str(&mut hasher, crate_type.as_str());
}
hash_str(&mut hasher, key.kind.as_str());
hex::encode(hasher.finalize().as_bytes())
}
fn hash_str(h: &mut blake3::Hasher, s: &str) {
let len = u32::try_from(s.len()).expect("hash input string length exceeds u32 range");
h.update(&len.to_le_bytes());
h.update(s.as_bytes());
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::*;
use crate::artifact::{ArtifactKey, ArtifactKind, RustCrateType};
use crate::crate_info::{CrateId, FeatureSet};
use crate::platform::{PanicStrategy, Profile, RustcVersion, Target};
fn make_key() -> ArtifactKey {
ArtifactKey {
crate_id: CrateId {
name: "serde".into(),
version: semver::Version::new(1, 0, 210),
},
features: FeatureSet(BTreeSet::from(["derive".into(), "default".into()])),
crate_types: vec![RustCrateType::Rlib],
target: Target("x86_64-unknown-linux-gnu".into()),
rustc_version: RustcVersion {
version: semver::Version::new(1, 83, 0),
commit_hash: "90b35a623".into(),
llvm_version: "19.1.4".into(),
},
profile: Profile {
opt_level: "0".into(),
debuginfo: 2,
debug_assertions: true,
overflow_checks: true,
panic: PanicStrategy::Unwind,
strip: crate::platform::StripLevel::None,
},
kind: ArtifactKind::Rlib,
}
}
#[test]
fn hash_is_deterministic() {
let key = make_key();
let h1 = compute_artifact_hash(&key);
let h2 = compute_artifact_hash(&key);
assert_eq!(h1, h2);
}
#[test]
fn hash_is_64_hex_chars() {
let key = make_key();
let h = compute_artifact_hash(&key);
assert_eq!(h.len(), 64); assert!(h.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn different_features_produce_different_hash() {
let key1 = make_key();
let mut key2 = make_key();
key2.features = FeatureSet(BTreeSet::from(["std".into()]));
assert_ne!(compute_artifact_hash(&key1), compute_artifact_hash(&key2));
}
#[test]
fn different_kind_produces_different_hash() {
let key1 = make_key();
let mut key2 = make_key();
key2.kind = ArtifactKind::ProcMacro;
assert_ne!(compute_artifact_hash(&key1), compute_artifact_hash(&key2));
}
#[test]
fn different_crate_types_produce_different_hash() {
let key1 = make_key();
let mut key2 = make_key();
key2.crate_types = vec![RustCrateType::Dylib];
key2.kind = ArtifactKind::Dylib;
assert_ne!(compute_artifact_hash(&key1), compute_artifact_hash(&key2));
}
#[test]
fn different_target_produces_different_hash() {
let key1 = make_key();
let mut key2 = make_key();
key2.target = Target("aarch64-apple-darwin".into());
assert_ne!(compute_artifact_hash(&key1), compute_artifact_hash(&key2));
}
#[test]
fn different_rustc_version_produces_different_hash() {
let key1 = make_key();
let mut key2 = make_key();
key2.rustc_version.version = semver::Version::new(1, 84, 0);
assert_ne!(compute_artifact_hash(&key1), compute_artifact_hash(&key2));
}
#[test]
fn different_profile_produces_different_hash() {
let key1 = make_key();
let mut key2 = make_key();
key2.profile.opt_level = "3".into();
assert_ne!(compute_artifact_hash(&key1), compute_artifact_hash(&key2));
}
}