use std::io::Cursor;
use std::path::Path;
use sha2::{Digest, Sha256};
use crate::hash::{base64_encode, HashAlgorithm, NixHash};
use crate::nar::{NarError, NarWriter};
use crate::store_path::compute_fixed_output_hash;
#[derive(Debug, Clone)]
pub struct SourceHash {
pub store_path: String,
pub nar_hash_sri: String,
pub nar_bytes: Vec<u8>,
}
pub fn nar_hash_source_tree(dir: &Path, name: &str) -> Result<SourceHash, NarError> {
let mut nar_bytes = Vec::new();
{
let mut cursor = Cursor::new(&mut nar_bytes);
NarWriter::write_path(&mut cursor, dir)?;
}
let digest = Sha256::digest(&nar_bytes);
let digest_bytes = digest.to_vec();
let hex: String = digest_bytes.iter().map(|b| format!("{b:02x}")).collect();
let store_path = compute_fixed_output_hash("sha256", &hex, true, name);
let nar_hash = NixHash::new(HashAlgorithm::Sha256, digest_bytes.clone());
let nar_hash_sri = nar_hash.to_sri();
Ok(SourceHash {
store_path,
nar_hash_sri,
nar_bytes,
})
}
#[must_use]
pub fn base64_sha256(bytes: &[u8]) -> String {
base64_encode(&Sha256::digest(bytes))
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn mk_flake_dir() -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
let flake_nix = dir.path().join("flake.nix");
let mut f = std::fs::File::create(&flake_nix).unwrap();
write!(f, "{{ outputs = {{ self }}: {{ value = 42; }}; }}\n").unwrap();
dir
}
#[test]
fn source_tree_produces_a_store_path_and_an_sri_hash() {
let dir = mk_flake_dir();
let sh = nar_hash_source_tree(dir.path(), "source").expect("nar hash");
assert!(sh.store_path.starts_with("/nix/store/"));
assert!(sh.store_path.ends_with("-source"));
assert!(sh.nar_hash_sri.starts_with("sha256-"));
assert!(!sh.nar_bytes.is_empty());
assert!(sh.nar_bytes.starts_with(b"\r\x00\x00\x00\x00\x00\x00\x00nix-archive-1"),
"NAR must begin with the magic header — got {:?}",
&sh.nar_bytes[..16]);
}
}