//! The recipe's content hash — sha256 over every `src/**/*.rs`, in sorted path
//! order, `path \0 len \0 bytes` per file — exported as
//! `DRAUPNIR_BUILD_THING_CONTENT_HASH` for `recipe::CONTENT_HASH`. The same shape
//! tunnr's, gunnar's and korp's recipe crates use: a `Coordinate` is
//! `krate:version:hash`, and the hash is what tells two builds of the same
//! version apart.
use std::path::{Path, PathBuf};
use sha2::{Digest, Sha256};
const HASH_VAR: &str = "DRAUPNIR_BUILD_THING_CONTENT_HASH";
fn main() {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR");
let src = Path::new(&manifest_dir).join("src");
let mut files = Vec::new();
collect_rs(&src, &mut files);
files.sort();
assert!(
!files.is_empty(),
"no *.rs found under {} — the recipe would hash the empty string and every \
version of it would carry the same content hash",
src.display()
);
let mut hasher = Sha256::new();
for path in &files {
let rel = path
.strip_prefix(&src)
.expect("collected under src/")
.to_string_lossy()
.replace('\\', "/");
let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
hasher.update(rel.as_bytes());
hasher.update(b"\0");
hasher.update(bytes.len().to_le_bytes());
hasher.update(b"\0");
hasher.update(&bytes);
println!("cargo:rerun-if-changed={}", path.display());
}
let digest = hasher.finalize();
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
println!("cargo:rustc-env={HASH_VAR}={hex}");
}
fn collect_rs(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_rs(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}