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 {
365365 inode: u64,
366366 /// Nanoseconds.
367367 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`.
368370 digest: BinDigest,
369371 /// Starting with this field and continuing into the path, excluding the null byte,
370372 /// is the string that is hashed for the manifest digest.
......@@ -440,6 +442,14 @@ pub const Manifest = struct {
440442 }
441443 };
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
443453 fn setStat(file: *File, m: *Manifest, stat: Stat) Io.Cancelable!void {
444454 file.size = stat.size;
445455 file.inode = stat.inode;
......@@ -660,16 +670,19 @@ pub const Manifest = struct {
660670 pub fn checkProgressless(man: *Manifest) Check.Error!Check.Status {
661671 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.
663675 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);
665677 }
666678
667679 man.diagnostic = .none;
668680
669 var bin_digest: BinDigest = undefined;
670 man.hash.hasher.final(&bin_digest);
671 const hex_digest = binToHex(bin_digest);
672 const manifest_file_path = &hex_digest;
681 var input_digest: BinDigest = undefined;
682 man.hash.hasher.final(&input_digest);
683 const input_hex_digest = binToHex(input_digest);
684
685 const manifest_file_path = &input_hex_digest;
673686 const io = man.cache.io;
674687
675688 // We'll try to open the cache with an exclusive lock, but if that would block
......@@ -744,22 +757,20 @@ pub const Manifest = struct {
744757
745758 man.want_refresh_timestamp = true;
746759
747 // We're going to construct a second hash. Its input will begin with the digest we've
748 // already computed (`bin_digest`), and then it'll have the digests of each input file,
749 // including "post" files (see `addDiscoveredPath`). If this is a hit, we learn the set of "post"
750 // files from the manifest on disk. If this is a miss, we'll learn those from future calls
751 // to `addDiscoveredPath` etc. As such, the state of `man.hash.hasher` after this function
752 // depends on whether this is a hit or a miss.
760 // We're going to construct a second hash. Its input will begin with the digest we've already computed
761 // (`input_digest`), and then it'll have the digests of each input file, including discovered files (see
762 // `addDiscoveredPath`). If this is a hit, we learn the set of discovered files from the manifest on disk. If
763 // this is a miss, we'll learn those from future calls to `addDiscoveredPath` etc. As such, the state of
764 // `man.hash.hasher` after this function depends on whether this is a hit or a miss.
753765 //
754 // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include
755 // the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache
756 // miss, `man.hash.hasher` will include the digests of all non-"post" files -- that is,
757 // the ones we've already been told about. The rest will be discovered through calls to
758 // `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user can
759 // use `final`, and will at some point `writeManifest` the file list to disk.
766 // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include the digests of the discovered
767 // files, so the caller can call `final`. Otherwise, on a cache miss, `man.hash.hasher` will include the digests
768 // of all non-discovered files -- that is, the ones we've already been told about. The rest will be discovered
769 // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user
770 // can use `final`, and will at some point `writeManifest` the file list to disk.
760771
761772 man.hash.hasher = hasher_init;
762 man.hash.hasher.update(&bin_digest);
773 man.hash.hasher.update(&input_digest);
763774
764775 hit: {
765776 digests: {
......@@ -772,7 +783,7 @@ pub const Manifest = struct {
772783 // Before trying again, we must reset `man.hash.hasher` and `man.files`.
773784 // This is basically just the first half of `unhit`.
774785 man.hash.hasher = hasher_init;
775 man.hash.hasher.update(&bin_digest);
786 man.hash.hasher.update(&input_digest);
776787 man.shrinkFilesToInput();
777788 switch (try man.checkLocked()) {
778789 .hit => break :hit,
......@@ -784,7 +795,7 @@ pub const Manifest = struct {
784795 // unless it returns an error.
785796 man.manifest_dirty = true;
786797 // 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);
788799 return .miss;
789800 }
790801
......@@ -1005,9 +1016,9 @@ pub const Manifest = struct {
10051016 const actual_is_directory = actual_stat.kind == .directory;
10061017 if (actual_is_directory != file.flags.is_directory) return .miss;
10071018
1008 if (try file.setStatChanged(m, .init(actual_stat))) return .miss;
1009
1010 return .hit;
1019 const changed = try file.setStatChanged(m, .init(actual_stat));
1020 file.hashFromMetadata();
1021 return if (changed) .miss else .hit;
10111022 }
10121023
10131024 if (file.flags.is_directory) {
......@@ -1082,15 +1093,15 @@ pub const Manifest = struct {
10821093 return .hit;
10831094 }
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`.
10861097 /// 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).
10881099 ///
1089 /// Assumes that `bin_digest` is populated for all input files.
1090 pub fn unhit(man: *Manifest, bin_digest: *const BinDigest) void {
1100 /// Assumes that `digest` is populated for all input files.
1101 pub fn unhit(man: *Manifest, input_digest: *const BinDigest) void {
10911102 // Reset the hash.
10921103 man.hash.hasher = hasher_init;
1093 man.hash.hasher.update(bin_digest);
1104 man.hash.hasher.update(input_digest);
10941105 man.shrinkFilesToInput();
10951106 const contents = man.contents.items;
10961107 for (man.files.keys()) |off| {
......@@ -1206,13 +1217,14 @@ pub const Manifest = struct {
12061217 if (options.stat) |stat| {
12071218 try header.setStat(m, stat);
12081219 if (header.flags.metadata_only) {
1209 return;
1220 header.hashFromMetadata();
12101221 } else if (options.contents) |contents| {
12111222 var hasher = hasher_init;
12121223 hasher.update(contents);
12131224 hasher.final(&header.digest);
1214 return;
12151225 }
1226 m.hash.hasher.update(&header.digest);
1227 return;
12161228 }
12171229
12181230 const need_stat = options.stat == null;
......@@ -1243,6 +1255,8 @@ pub const Manifest = struct {
12431255 try populateFile(m, header, need_stat, handle, options.contents, metadata_only);
12441256 },
12451257 }
1258
1259 m.hash.hasher.update(&header.digest);
12461260 }
12471261
12481262 fn populateFile(
......@@ -1259,7 +1273,10 @@ pub const Manifest = struct {
12591273 const stat = try handle.stat(io);
12601274 try file.setStat(m, .init(stat));
12611275 }
1262 if (metadata_only) return;
1276 if (metadata_only) {
1277 file.hashFromMetadata();
1278 return;
1279 }
12631280 if (contents) |bytes| {
12641281 var hasher = hasher_init;
12651282 hasher.update(bytes);
......@@ -1285,7 +1302,10 @@ pub const Manifest = struct {
12851302 const stat = try handle.stat(io);
12861303 try file.setStat(m, .init(stat));
12871304 }
1288 if (metadata_only) return;
1305 if (metadata_only) {
1306 file.hashFromMetadata();
1307 return;
1308 }
12891309 if (contents) |bytes| {
12901310 var hasher = hasher_init;
12911311 hasher.update(bytes);
......@@ -1611,7 +1631,7 @@ pub const Manifest = struct {
16111631 hasher.final(bin_digest);
16121632 }
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 {
16151635 const contents = m.contents.items;
16161636 const flags_off = @offsetOf(File, "flags");
16171637 comptime assert(@offsetOf(File, "path_start") - flags_off == 1);