diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index bbf9044814e18fff0bb51c4710ffbfa9ba34b32b..66ba521c040d736e679af5bf58773bfcecfc55d2 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -2618,7 +2618,7 @@ fn markFailedStepsDirty(maker: *Maker) void { for (all_steps) |step_index| { const step = maker.stepByIndex(step_index); switch (step.state) { - .success => step.result_cached = true, + .success => step.result_cache_status = .hit, else => continue, } } @@ -3004,7 +3004,7 @@ fn makeSteps( const found = switch (maker.summary) { .all, .line, .none => unreachable, .failures => step.state != .success, - .new => !step.result_cached, + .new => step.result_cache_status != .hit, }; if (found) break :blk i; } @@ -3242,7 +3242,7 @@ fn printTreeStep( const skip = switch (summary) { .none, .line => unreachable, .all => false, - .new => make_step.result_cached, + .new => make_step.result_cache_status == .hit, .failures => make_step.state == .success, }; if (skip) return; @@ -3268,7 +3268,7 @@ fn printTreeStep( const found = switch (summary) { .all, .line, .none => unreachable, .failures => dep.state != .success, - .new => !dep.result_cached, + .new => dep.result_cache_status != .hit, }; if (found) break :blk i; } @@ -3312,24 +3312,45 @@ fn printStepStatus(maker: *Maker, step_index: Configuration.Step.Index, stderr: }, .success => { - try stderr.setColor(.green); - if (s.result_cached) { - try writer.writeAll(" cached"); - } else if (s.test_results.test_count > 0) { - const pass_count = s.test_results.passCount(); - assert(s.test_results.test_count == pass_count + s.test_results.skip_count); - try writer.print(" {d} pass", .{pass_count}); - if (s.test_results.skip_count > 0) { + cs: { + switch (s.result_cache_status) { + .hit => { + try stderr.setColor(.green); + try writer.writeAll(" cached"); + break :cs; + }, + .incomplete_manifest => { + // Common case: first-time cache miss, print nothing. + try stderr.setColor(.green); + }, + .invalid_manifest => { + try stderr.setColor(.blue); + try writer.writeAll(" invalid_manifest"); + try stderr.setColor(.green); + }, + .path_deleted, .directory_status_changed, .metadata_changed, .contents_changed => |path, tag| { + try stderr.setColor(.blue); + try writer.print(" {t}: {s}", .{ tag, path }); + try stderr.setColor(.green); + }, + } + if (s.test_results.test_count > 0) { + const pass_count = s.test_results.passCount(); + assert(s.test_results.test_count == pass_count + s.test_results.skip_count); + try writer.print(" {d} pass", .{pass_count}); + if (s.test_results.skip_count > 0) { + try stderr.setColor(.reset); + try writer.writeAll(", "); + try stderr.setColor(.yellow); + try writer.print("{d} skip", .{s.test_results.skip_count}); + } try stderr.setColor(.reset); - try writer.writeAll(", "); - try stderr.setColor(.yellow); - try writer.print("{d} skip", .{s.test_results.skip_count}); + try writer.print(" ({d} total)", .{s.test_results.test_count}); + } else { + try writer.writeAll(" success"); } - try stderr.setColor(.reset); - try writer.print(" ({d} total)", .{s.test_results.test_count}); - } else { - try writer.writeAll(" success"); } + try stderr.setColor(.reset); if (s.result_duration_ns) |ns| { try stderr.setColor(.dim); diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig index 58bf7c332447a2600e43284e68a6a642eda3586b..791d5b402cb21a526a46bca54eeadcc8b79c7e9d 100644 --- a/lib/compiler/Maker/Step.zig +++ b/lib/compiler/Maker/Step.zig @@ -59,7 +59,8 @@ result_error_msgs: std.ArrayList([]const u8) = .empty, result_error_bundle: std.zig.ErrorBundle = .empty, /// Owned by `Maker.gpa`. result_stderr: []const u8 = "", -result_cached: bool = false, +/// Owned by `Maker.gpa`. +result_cache_status: ResultCacheStatus = .incomplete_manifest, /// Indicates error information is missing due to allocation failure. result_oom: bool = false, result_duration_ns: ?u64 = null, @@ -78,6 +79,16 @@ comptime { if (std.atomic.cache_line <= 128) assert(@sizeOf(@This()) <= 128 * 3); } +const ResultCacheStatus = union(enum) { + hit, + incomplete_manifest, + invalid_manifest, + path_deleted: [*:0]const u8, + directory_status_changed: [*:0]const u8, + metadata_changed: [*:0]const u8, + contents_changed: [*:0]const u8, +}; + pub const Extended = union(enum) { check_file: CheckFile, compile: Compile, @@ -314,6 +325,7 @@ pub fn make( pub fn deinit(step: *Step, gpa: Allocator, io: Io) void { step.clearResultStderr(gpa); + step.clearResultCacheStatus(gpa); step.clearFailedCommand(gpa); step.clearErrorBundle(gpa); step.inputs.deinit(gpa); @@ -344,8 +356,8 @@ pub fn reset(step: *Step, maker: *Maker) void { clearFailedCommand(step, gpa); clearResultStderr(step, gpa); + clearResultCacheStatus(step, gpa); step.result_error_msgs.clearRetainingCapacity(); - step.result_cached = false; step.result_duration_ns = null; step.result_peak_rss = 0; step.test_results = .{}; @@ -650,7 +662,7 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi .emit_digest => { const emit_digest = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; const digest = body_r.takeArray(Cache.bin_digest_len) catch unreachable; - s.result_cached = emit_digest.flags.cache_hit; + s.result_cache_status = if (emit_digest.flags.cache_hit) .hit else .incomplete_manifest; result = .{ .bin = digest.* }; }, .file_system_inputs => { @@ -779,9 +791,7 @@ pub fn cacheHit(s: *Step, maker: *Maker, man: *Cache.Manifest, parent_progress_n var diag: Cache.Manifest.CheckDiagnostic = undefined; const status = man.check(&diag, parent_progress_node) catch |err| return failWithCacheError(s, maker, man, &diag, err); - const hit = .hit == status; // TODO cache miss reason in the build summary - s.result_cached = hit; - return hit; + return setResultCacheStatus(s, man, status); } /// Clears previous watch inputs, if any, and then populates watch inputs from @@ -792,12 +802,36 @@ pub fn cacheHitWatched(s: *Step, maker: *Maker, man: *Cache.Manifest, parent_pro var diag: Cache.Manifest.CheckDiagnostic = undefined; const status = man.check(&diag, parent_progress_node) catch |err| return failWithCacheError(s, maker, man, &diag, err); - const hit = .hit == status; // TODO cache miss reason in the build summary - s.result_cached = hit; - // The above call to hit() populates the manifest with files, so in case of - // a hit, we need to populate watch inputs. - if (hit) try setWatchInputsFromManifest(s, maker, man); - return hit; + // The above call to check() populates the manifest with files, so in case of a hit, we need to + // populate watch inputs. + if (status == .hit) try setWatchInputsFromManifest(s, maker, man); + return setResultCacheStatus(s, man, status); +} + +fn setResultCacheStatus(s: *Step, man: *const Cache.Manifest, status: Cache.Manifest.CheckResult) Allocator.Error!bool { + const gpa = man.cache.gpa; + clearResultCacheStatus(s, gpa); + s.result_cache_status = switch (status) { + .hit => .hit, + .incomplete_manifest => .incomplete_manifest, + .invalid_manifest => .invalid_manifest, + inline .path_deleted, .directory_status_changed, .metadata_changed, .contents_changed => |off, tag| @unionInit( + ResultCacheStatus, + @tagName(tag), + try gpa.dupeSentinel(u8, off.path(man.contents.items), 0), + ), + }; + return status == .hit; +} + +fn clearResultCacheStatus(s: *Step, gpa: Allocator) void { + switch (s.result_cache_status) { + .hit, .incomplete_manifest, .invalid_manifest => {}, + .path_deleted, .directory_status_changed, .metadata_changed, .contents_changed => |path| { + gpa.free(std.mem.sliceTo(path, 0)); + }, + } + s.result_cache_status = .incomplete_manifest; } fn failWithCacheError( diff --git a/lib/compiler/Maker/Step/InstallArtifact.zig b/lib/compiler/Maker/Step/InstallArtifact.zig index 5caa3a92711c4d868bcac046d71b8df81b704a9e..25a997e1bc1ae92aaa8ff2f82deec7dede2e0b51 100644 --- a/lib/compiler/Maker/Step/InstallArtifact.zig +++ b/lib/compiler/Maker/Step/InstallArtifact.zig @@ -137,5 +137,7 @@ pub fn make( }; } - step.result_cached = all_cached; + // TODO instead of a bool, make this a proper Step.ResultCacheStatus that tracks the file path that caused + // a miss for better reporting. + step.result_cache_status = if (all_cached) .hit else .incomplete_manifest; } diff --git a/lib/compiler/Maker/Step/InstallDir.zig b/lib/compiler/Maker/Step/InstallDir.zig index d8b7b7ed1881f23ed502f85009a3cec428ff43b2..a82073db7aef6bc80767e6fc95a07c3a31f22653 100644 --- a/lib/compiler/Maker/Step/InstallDir.zig +++ b/lib/compiler/Maker/Step/InstallDir.zig @@ -95,5 +95,7 @@ pub fn make( } } - step.result_cached = all_cached; + // TODO instead of a bool, make this a proper Step.ResultCacheStatus that tracks the file path that caused + // a miss for better reporting. + step.result_cache_status = if (all_cached) .hit else .incomplete_manifest; } diff --git a/lib/compiler/Maker/Step/InstallFile.zig b/lib/compiler/Maker/Step/InstallFile.zig index eb9547f44ed7149b728e409580be1002329bbfe8..895304e4a0bf15936a6298addb55d0e680e413d1 100644 --- a/lib/compiler/Maker/Step/InstallFile.zig +++ b/lib/compiler/Maker/Step/InstallFile.zig @@ -22,5 +22,5 @@ pub fn make( try step.singleUnchangingWatchInput(maker, arena, conf_if.source.get(conf)); const p = try maker.installLazyPathSub(arena, conf_if.source, conf_if.dest_dir, conf_if.dest_sub_path.slice(conf), step_index); - step.result_cached = p == .fresh; + step.result_cache_status = if (p == .fresh) .hit else .incomplete_manifest; } diff --git a/lib/compiler/Maker/Step/Options.zig b/lib/compiler/Maker/Step/Options.zig index 1b71707bef4bdd9e4cad0baaf992cb6137e6191a..968966bf183f9112344c33986584c4433c25f598 100644 --- a/lib/compiler/Maker/Step/Options.zig +++ b/lib/compiler/Maker/Step/Options.zig @@ -80,7 +80,7 @@ pub fn make( if (try step.cacheHitWatched(maker, &man, progress_node)) { const digest = man.hitDigestHex(); _ = try maker.setGeneratedPath(conf_options.generated_file, .local_cache, &.{ "o", &digest, basename }); - step.result_cached = true; + step.result_cache_status = .hit; return; } diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig index ecea450dc3727db1fe4aa5606f8aaf2e6c23a82f..73b36d1ec7ad1b592cce4ac4716193c5ac5249b5 100644 --- a/lib/compiler/Maker/Step/Run.zig +++ b/lib/compiler/Maker/Step/Run.zig @@ -573,7 +573,7 @@ pub fn make( const digest = man.hitDigestHex(); try populateGeneratedStdIo(maker, &conf_run, &digest); try populateGeneratedPaths(maker, output_placeholders.items, &digest); - step.result_cached = true; + step.result_cache_status = .hit; return; } diff --git a/lib/compiler/Maker/Step/UpdateSourceFiles.zig b/lib/compiler/Maker/Step/UpdateSourceFiles.zig index dde19e0ce865930dba1972a5a8e5670665efb8f9..bb89d30c68bc264e650c322c7f0bb6ad5a75e0e2 100644 --- a/lib/compiler/Maker/Step/UpdateSourceFiles.zig +++ b/lib/compiler/Maker/Step/UpdateSourceFiles.zig @@ -84,5 +84,7 @@ pub fn make( progress_node.completeOne(); } - step.result_cached = !any_miss; + // TODO instead of a bool, make this a proper Step.ResultCacheStatus that tracks the file path that caused + // a miss for better reporting. + step.result_cache_status = if (!any_miss) .hit else .incomplete_manifest; } diff --git a/lib/compiler/Maker/Step/WriteFile.zig b/lib/compiler/Maker/Step/WriteFile.zig index 7701e71ac6f01e8385fbd232a0c546c09a62b45a..ff43dddc9876f8a13bf5fa45d5a785bcc054efcd 100644 --- a/lib/compiler/Maker/Step/WriteFile.zig +++ b/lib/compiler/Maker/Step/WriteFile.zig @@ -110,7 +110,7 @@ pub fn make( if (try step.cacheHit(maker, &man, progress_node)) { const digest = man.hitDigestHex(); _ = try maker.setGeneratedPath(conf_wf.generated_directory, .local_cache, &.{ "o", &digest }); - assert(step.result_cached); + assert(step.result_cache_status == .hit); return; } @@ -122,7 +122,7 @@ pub fn make( try step.finalizeManifest(maker, &man); }, .tmp => { - step.result_cached = false; + step.result_cache_status = .incomplete_manifest; var rand_int: u64 = undefined; io.random(@ptrCast(&rand_int)); @@ -135,7 +135,7 @@ pub fn make( try operate(maker, step_index, open_dir_cache, out_path, progress_node); }, .mutate => { - step.result_cached = false; + step.result_cache_status = .incomplete_manifest; const root_path = try maker.resolveLazyPathIndex(arena, conf_wf.mutate_path.value.?, step_index); try operate(maker, step_index, open_dir_cache, root_path, progress_node); _ = try maker.setGeneratedPathPath(conf_wf.generated_directory, root_path); diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index cbb5c413c35fe17a00a8fbac36019720b9e04441..c4d1c106022d436b7b32295c627c0898797b8906 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -978,7 +978,7 @@ pub const Manifest = struct { // this is a miss, we'll learn those from future calls to `addDiscoveredPath` etc. As such, the state of // `man.hash.hasher` after this function depends on whether this is a hit or a miss. // - // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include the digests of the discovered + // If we return `CheckResult.hit`, then `man.hash.hasher` must already include the digests of the discovered // files, so the caller can call `final`. Otherwise, on a cache miss, `man.hash.hasher` will include the digests // of all non-discovered files -- that is, the ones we've already been told about. The rest will be discovered // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user