blob: d9f7766305d97baa1fa625078cd214184f9bc85f [file] [log] [blame]
//! A single hash over input attributes is recorded and used to determine when
//! those inputs change.
//!
//! See "Manifests instead of mtime order" in
//! https://neugierig.org/software/blog/2022/03/n2.html
use crate::graph::{self, Build, FileState, MTime, RspFile};
use std::{
collections::hash_map::DefaultHasher,
fmt::Write,
hash::{Hash, Hasher},
sync::Arc,
time::SystemTime,
};
/// Hash value used to identify a given instance of a Build's execution;
/// compared to verify whether a Build is up to date.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct BuildHash(pub u64);
/// A trait for computing a build's manifest. Indirected as a trait so we can
/// implement it a second time for "-d explain" debug purposes.
trait Manifest {
/// Write a list of files+mtimes. desc is used only for "-d explain" output.
fn write_files(&mut self, desc: &str, file_state: &FileState, ids: &[Arc<graph::File>]);
fn write_rsp(&mut self, rspfile: &RspFile);
fn write_cmdline(&mut self, cmdline: &str);
}
fn get_fileid_status<'a>(file_state: &FileState, id: &'a graph::File) -> (&'a str, SystemTime) {
let name = &id.name;
let mtime = file_state
.get(id)
.unwrap_or_else(|| panic!("no state for {:?}", name));
let mtime = match mtime {
MTime::Stamp(mtime) => mtime,
MTime::Missing => panic!("missing file: {:?}", name),
};
(name.as_str(), mtime)
}
/// The BuildHasher used during normal builds, designed to not serialize too much.
#[derive(Default)]
struct TerseHash(DefaultHasher);
const UNIT_SEPARATOR: u8 = 0x1F;
impl TerseHash {
fn write_string(&mut self, string: &str) {
string.hash(&mut self.0);
}
fn write_separator(&mut self) {
self.0.write_u8(UNIT_SEPARATOR);
}
fn finish(&mut self) -> BuildHash {
BuildHash(self.0.finish())
}
}
impl Manifest for TerseHash {
fn write_files<'a>(&mut self, _desc: &str, file_state: &FileState, ids: &[Arc<graph::File>]) {
for id in ids {
let (name, mtime) = get_fileid_status(file_state, &id);
self.write_string(name);
mtime.hash(&mut self.0);
}
self.write_separator();
}
fn write_cmdline(&mut self, cmdline: &str) {
self.write_string(cmdline);
self.write_separator();
}
fn write_rsp(&mut self, rspfile: &RspFile) {
rspfile.hash(&mut self.0);
}
}
fn build_manifest<M: Manifest>(
manifest: &mut M,
file_state: &FileState,
build: &Build,
) -> anyhow::Result<()> {
let cmdline = build.get_cmdline();
manifest.write_files("in", file_state, build.dirtying_ins());
manifest.write_files("discovered", file_state, build.discovered_ins());
manifest.write_cmdline(cmdline.as_deref().unwrap_or(""));
if let Some(rspfile) = &build.get_rspfile()? {
manifest.write_rsp(rspfile);
}
if !build.phony_output && cmdline.is_some() {
manifest.write_files("out", file_state, build.outs());
}
Ok(())
}
// Hashes the inputs of a build to compute a signature.
// Prerequisite: all referenced files have already been stat()ed and are present.
// (It doesn't make sense to hash a build with missing files, because it's out
// of date regardless of the state of the other files.)
pub fn hash_build(file_state: &FileState, build: &Build) -> anyhow::Result<BuildHash> {
let mut hasher = TerseHash::default();
build_manifest(&mut hasher, file_state, build)?;
Ok(hasher.finish())
}
/// A BuildHasher that records human-readable text for "-d explain" debugging.
#[derive(Default)]
struct ExplainHash {
text: String,
}
impl Manifest for ExplainHash {
fn write_files<'a>(&mut self, desc: &str, file_state: &FileState, ids: &[Arc<graph::File>]) {
writeln!(&mut self.text, "{desc}:").unwrap();
for id in ids {
let (name, mtime) = get_fileid_status(file_state, &id);
let millis = mtime
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis();
writeln!(&mut self.text, " {millis} {name}").unwrap();
}
}
fn write_rsp(&mut self, rspfile: &RspFile) {
writeln!(&mut self.text, "rspfile path: {}", rspfile.path.display()).unwrap();
let mut h = DefaultHasher::new();
h.write(rspfile.content.as_bytes());
writeln!(&mut self.text, "rspfile hash: {:x}", h.finish()).unwrap();
}
fn write_cmdline(&mut self, cmdline: &str) {
writeln!(&mut self.text, "cmdline: {}", cmdline).unwrap();
}
}
/// Logs human-readable state of all the inputs used for hashing a given build.
/// Used for "-d explain" debugging output.
pub fn explain_hash_build(file_state: &FileState, build: &Build) -> anyhow::Result<String> {
let mut explainer = ExplainHash::default();
build_manifest(&mut explainer, file_state, build)?;
Ok(explainer.text)
}