blob: 23ea8d5265d9357bd69285f9773a57279b1ca728 [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::{Build, FileId, FileState, GraphFiles, MTime, RspFile};
use std::{
collections::hash_map::DefaultHasher,
fmt::Write,
hash::{Hash, Hasher},
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,
files: &GraphFiles,
file_state: &FileState,
ids: &[FileId],
);
fn write_rsp(&mut self, rspfile: &RspFile);
fn write_cmdline(&mut self, cmdline: &str);
}
fn get_fileid_status<'a>(
files: &'a GraphFiles,
file_state: &FileState,
id: FileId,
) -> (&'a str, SystemTime) {
let name = &files.by_id[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,
files: &GraphFiles,
file_state: &FileState,
ids: &[FileId],
) {
for &id in ids {
let (name, mtime) = get_fileid_status(files, 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,
files: &GraphFiles,
file_state: &FileState,
build: &Build,
) {
manifest.write_files("in", files, file_state, build.dirtying_ins());
manifest.write_files("discovered", files, file_state, build.discovered_ins());
manifest.write_cmdline(build.cmdline.as_deref().unwrap_or(""));
if let Some(rspfile) = &build.rspfile {
manifest.write_rsp(rspfile);
}
manifest.write_files("out", files, file_state, build.outs());
}
// 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(files: &GraphFiles, file_state: &FileState, build: &Build) -> BuildHash {
let mut hasher = TerseHash::default();
build_manifest(&mut hasher, files, file_state, build);
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,
files: &GraphFiles,
file_state: &FileState,
ids: &[FileId],
) {
writeln!(&mut self.text, "{desc}:").unwrap();
for &id in ids {
let (name, mtime) = get_fileid_status(files, 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(files: &GraphFiles, file_state: &FileState, build: &Build) -> String {
let mut explainer = ExplainHash::default();
build_manifest(&mut explainer, files, file_state, build);
explainer.text
}