| //! 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 |
| } |