authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-04 20:08:35-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-04 20:42:57-07:00
log96adf07fdeb0c10fa829ab4e645ee91d4022f900
treec7bb45e8f912511b94c0425d44806c1bed1c2daa
parent582fb4adc10dc10a25800ac483b2c5f76a67d838

std.Build.Cache: add missing hash-computing logic


1 files changed, 53 insertions(+), 33 deletions(-)

lib/std/Build/Cache.zig+53-33
...@@ -365,6 +365,8 @@ pub const Manifest = struct {...@@ -365,6 +365,8 @@ pub const Manifest = struct {
365 inode: u64,365 inode: u64,
366 /// Nanoseconds.366 /// Nanoseconds.
367 mtime: i64,367 mtime: i64,
368 /// To simplify the hashing logic, this value is computed from size, inode, and mtime
369 /// in `hashFromMetadata` in the case that `Flags.metadata_only` is `true`.
368 digest: BinDigest,370 digest: BinDigest,
369 /// Starting with this field and continuing into the path, excluding the null byte,371 /// Starting with this field and continuing into the path, excluding the null byte,
370 /// is the string that is hashed for the manifest digest.372 /// is the string that is hashed for the manifest digest.
...@@ -440,6 +442,14 @@ pub const Manifest = struct {...@@ -440,6 +442,14 @@ pub const Manifest = struct {
440 }442 }
441 };443 };
442444
445 fn hashFromMetadata(file: *File) void {
446 var hasher = hasher_init;
447 hasher.update(mem.asBytes(&file.size));
448 hasher.update(mem.asBytes(&file.inode));
449 hasher.update(mem.asBytes(&file.mtime));
450 hasher.final(&file.digest);
451 }
452
443 fn setStat(file: *File, m: *Manifest, stat: Stat) Io.Cancelable!void {453 fn setStat(file: *File, m: *Manifest, stat: Stat) Io.Cancelable!void {
444 file.size = stat.size;454 file.size = stat.size;
445 file.inode = stat.inode;455 file.inode = stat.inode;
...@@ -660,16 +670,19 @@ pub const Manifest = struct {...@@ -660,16 +670,19 @@ pub const Manifest = struct {
660 pub fn checkProgressless(man: *Manifest) Check.Error!Check.Status {670 pub fn checkProgressless(man: *Manifest) Check.Error!Check.Status {
661 assert(man.manifest_file == null);671 assert(man.manifest_file == null);
662672
673 // This is *not* hashing the contents of the input files. It is the
674 // flags (including prefix) and path only.
663 for (man.files.keys()[0..man.input_paths.items.len]) |file_off| {675 for (man.files.keys()[0..man.input_paths.items.len]) |file_off| {
664 man.digestHash(file_off, &man.hash.hasher);676 man.hashFlagsAndPath(file_off, &man.hash.hasher);
665 }677 }
666678
667 man.diagnostic = .none;679 man.diagnostic = .none;
668680
669 var bin_digest: BinDigest = undefined;681 var input_digest: BinDigest = undefined;
670 man.hash.hasher.final(&bin_digest);682 man.hash.hasher.final(&input_digest);
671 const hex_digest = binToHex(bin_digest);683 const input_hex_digest = binToHex(input_digest);
672 const manifest_file_path = &hex_digest;684
685 const manifest_file_path = &input_hex_digest;
673 const io = man.cache.io;686 const io = man.cache.io;
674687
675 // We'll try to open the cache with an exclusive lock, but if that would block688 // We'll try to open the cache with an exclusive lock, but if that would block
...@@ -744,22 +757,20 @@ pub const Manifest = struct {...@@ -744,22 +757,20 @@ pub const Manifest = struct {
744757
745 man.want_refresh_timestamp = true;758 man.want_refresh_timestamp = true;
746759
747 // We're going to construct a second hash. Its input will begin with the digest we've760 // We're going to construct a second hash. Its input will begin with the digest we've already computed
748 // already computed (`bin_digest`), and then it'll have the digests of each input file,761 // (`input_digest`), and then it'll have the digests of each input file, including discovered files (see
749 // including "post" files (see `addDiscoveredPath`). If this is a hit, we learn the set of "post"762 // `addDiscoveredPath`). If this is a hit, we learn the set of discovered files from the manifest on disk. If
750 // files from the manifest on disk. If this is a miss, we'll learn those from future calls763 // this is a miss, we'll learn those from future calls to `addDiscoveredPath` etc. As such, the state of
751 // to `addDiscoveredPath` etc. As such, the state of `man.hash.hasher` after this function764 // `man.hash.hasher` after this function depends on whether this is a hit or a miss.
752 // depends on whether this is a hit or a miss.
753 //765 //
754 // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include766 // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include the digests of the discovered
755 // the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache767 // files, so the caller can call `final`. Otherwise, on a cache miss, `man.hash.hasher` will include the digests
756 // miss, `man.hash.hasher` will include the digests of all non-"post" files -- that is,768 // of all non-discovered files -- that is, the ones we've already been told about. The rest will be discovered
757 // the ones we've already been told about. The rest will be discovered through calls to769 // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user
758 // `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user can770 // can use `final`, and will at some point `writeManifest` the file list to disk.
759 // use `final`, and will at some point `writeManifest` the file list to disk.
760771
761 man.hash.hasher = hasher_init;772 man.hash.hasher = hasher_init;
762 man.hash.hasher.update(&bin_digest);773 man.hash.hasher.update(&input_digest);
763774
764 hit: {775 hit: {
765 digests: {776 digests: {
...@@ -772,7 +783,7 @@ pub const Manifest = struct {...@@ -772,7 +783,7 @@ pub const Manifest = struct {
772 // Before trying again, we must reset `man.hash.hasher` and `man.files`.783 // Before trying again, we must reset `man.hash.hasher` and `man.files`.
773 // This is basically just the first half of `unhit`.784 // This is basically just the first half of `unhit`.
774 man.hash.hasher = hasher_init;785 man.hash.hasher = hasher_init;
775 man.hash.hasher.update(&bin_digest);786 man.hash.hasher.update(&input_digest);
776 man.shrinkFilesToInput();787 man.shrinkFilesToInput();
777 switch (try man.checkLocked()) {788 switch (try man.checkLocked()) {
778 .hit => break :hit,789 .hit => break :hit,
...@@ -784,7 +795,7 @@ pub const Manifest = struct {...@@ -784,7 +795,7 @@ pub const Manifest = struct {
784 // unless it returns an error.795 // unless it returns an error.
785 man.manifest_dirty = true;796 man.manifest_dirty = true;
786 // All input file digests are already populated by `checkLocked`, so we can call `unhit` directly.797 // All input file digests are already populated by `checkLocked`, so we can call `unhit` directly.
787 unhit(man, &bin_digest);798 unhit(man, &input_digest);
788 return .miss;799 return .miss;
789 }800 }
790801
...@@ -1005,9 +1016,9 @@ pub const Manifest = struct {...@@ -1005,9 +1016,9 @@ pub const Manifest = struct {
1005 const actual_is_directory = actual_stat.kind == .directory;1016 const actual_is_directory = actual_stat.kind == .directory;
1006 if (actual_is_directory != file.flags.is_directory) return .miss;1017 if (actual_is_directory != file.flags.is_directory) return .miss;
10071018
1008 if (try file.setStatChanged(m, .init(actual_stat))) return .miss;1019 const changed = try file.setStatChanged(m, .init(actual_stat));
10091020 file.hashFromMetadata();
1010 return .hit;1021 return if (changed) .miss else .hit;
1011 }1022 }
10121023
1013 if (file.flags.is_directory) {1024 if (file.flags.is_directory) {
...@@ -1082,15 +1093,15 @@ pub const Manifest = struct {...@@ -1082,15 +1093,15 @@ pub const Manifest = struct {
1082 return .hit;1093 return .hit;
1083 }1094 }
10841095
1085 /// Reset `man.hash.hasher` to the state it should be in after `hit` returns `Check.Status.miss`.1096 /// Reset `man.hash.hasher` to the state it should be in after `check` returns `Check.Status.miss`.
1086 /// The hasher contains the original input digest, and all original input file digests (i.e.1097 /// The hasher contains the original input digest, and all original input file digests (i.e.
1087 /// not including post files).1098 /// not including discovered files).
1088 ///1099 ///
1089 /// Assumes that `bin_digest` is populated for all input files.1100 /// Assumes that `digest` is populated for all input files.
1090 pub fn unhit(man: *Manifest, bin_digest: *const BinDigest) void {1101 pub fn unhit(man: *Manifest, input_digest: *const BinDigest) void {
1091 // Reset the hash.1102 // Reset the hash.
1092 man.hash.hasher = hasher_init;1103 man.hash.hasher = hasher_init;
1093 man.hash.hasher.update(bin_digest);1104 man.hash.hasher.update(input_digest);
1094 man.shrinkFilesToInput();1105 man.shrinkFilesToInput();
1095 const contents = man.contents.items;1106 const contents = man.contents.items;
1096 for (man.files.keys()) |off| {1107 for (man.files.keys()) |off| {
...@@ -1206,13 +1217,14 @@ pub const Manifest = struct {...@@ -1206,13 +1217,14 @@ pub const Manifest = struct {
1206 if (options.stat) |stat| {1217 if (options.stat) |stat| {
1207 try header.setStat(m, stat);1218 try header.setStat(m, stat);
1208 if (header.flags.metadata_only) {1219 if (header.flags.metadata_only) {
1209 return;1220 header.hashFromMetadata();
1210 } else if (options.contents) |contents| {1221 } else if (options.contents) |contents| {
1211 var hasher = hasher_init;1222 var hasher = hasher_init;
1212 hasher.update(contents);1223 hasher.update(contents);
1213 hasher.final(&header.digest);1224 hasher.final(&header.digest);
1214 return;
1215 }1225 }
1226 m.hash.hasher.update(&header.digest);
1227 return;
1216 }1228 }
12171229
1218 const need_stat = options.stat == null;1230 const need_stat = options.stat == null;
...@@ -1243,6 +1255,8 @@ pub const Manifest = struct {...@@ -1243,6 +1255,8 @@ pub const Manifest = struct {
1243 try populateFile(m, header, need_stat, handle, options.contents, metadata_only);1255 try populateFile(m, header, need_stat, handle, options.contents, metadata_only);
1244 },1256 },
1245 }1257 }
1258
1259 m.hash.hasher.update(&header.digest);
1246 }1260 }
12471261
1248 fn populateFile(1262 fn populateFile(
...@@ -1259,7 +1273,10 @@ pub const Manifest = struct {...@@ -1259,7 +1273,10 @@ pub const Manifest = struct {
1259 const stat = try handle.stat(io);1273 const stat = try handle.stat(io);
1260 try file.setStat(m, .init(stat));1274 try file.setStat(m, .init(stat));
1261 }1275 }
1262 if (metadata_only) return;1276 if (metadata_only) {
1277 file.hashFromMetadata();
1278 return;
1279 }
1263 if (contents) |bytes| {1280 if (contents) |bytes| {
1264 var hasher = hasher_init;1281 var hasher = hasher_init;
1265 hasher.update(bytes);1282 hasher.update(bytes);
...@@ -1285,7 +1302,10 @@ pub const Manifest = struct {...@@ -1285,7 +1302,10 @@ pub const Manifest = struct {
1285 const stat = try handle.stat(io);1302 const stat = try handle.stat(io);
1286 try file.setStat(m, .init(stat));1303 try file.setStat(m, .init(stat));
1287 }1304 }
1288 if (metadata_only) return;1305 if (metadata_only) {
1306 file.hashFromMetadata();
1307 return;
1308 }
1289 if (contents) |bytes| {1309 if (contents) |bytes| {
1290 var hasher = hasher_init;1310 var hasher = hasher_init;
1291 hasher.update(bytes);1311 hasher.update(bytes);
...@@ -1611,7 +1631,7 @@ pub const Manifest = struct {...@@ -1611,7 +1631,7 @@ pub const Manifest = struct {
1611 hasher.final(bin_digest);1631 hasher.final(bin_digest);
1612 }1632 }
16131633
1614 fn digestHash(m: *const Manifest, off: File.Offset, hasher: *Hasher) void {1634 fn hashFlagsAndPath(m: *const Manifest, off: File.Offset, hasher: *Hasher) void {
1615 const contents = m.contents.items;1635 const contents = m.contents.items;
1616 const flags_off = @offsetOf(File, "flags");1636 const flags_off = @offsetOf(File, "flags");
1617 comptime assert(@offsetOf(File, "path_start") - flags_off == 1);1637 comptime assert(@offsetOf(File, "path_start") - flags_off == 1);