1const std = @import("std");
  2
  3pub fn build(b: *std.Build) void {
  4    const test_step = b.step("test", "Test it");
  5    b.default_step = test_step;
  6
  7    const target = b.standardTargetOptions(.{});
  8    const optimize = b.standardOptimizeOption(.{});
  9
 10    const exe = b.addExecutable(.{
 11        .name = "create-file",
 12        .root_module = b.createModule(.{
 13            .root_source_file = b.path("main.zig"),
 14            .target = target,
 15            .optimize = optimize,
 16        }),
 17    });
 18
 19    {
 20        const run_random_with_sideeffects_first = b.addRunArtifact(exe);
 21        run_random_with_sideeffects_first.setName("run with side-effects (first)");
 22        run_random_with_sideeffects_first.has_side_effects = true;
 23
 24        const run_random_with_sideeffects_second = b.addRunArtifact(exe);
 25        run_random_with_sideeffects_second.setName("run with side-effects (second)");
 26        run_random_with_sideeffects_second.has_side_effects = true;
 27
 28        // ensure that "second" runs after "first"
 29        run_random_with_sideeffects_second.step.dependOn(&run_random_with_sideeffects_first.step);
 30
 31        const first_output = run_random_with_sideeffects_first.addOutputFileArg("a.txt");
 32        const second_output = run_random_with_sideeffects_second.addOutputFileArg("a.txt");
 33
 34        const expect_uncached_dependencies = CheckOutputCaching.init(b, false, &.{ first_output, second_output });
 35        test_step.dependOn(&expect_uncached_dependencies.step);
 36
 37        const expect_unequal_output = CheckPathEquality.init(b, true, &.{ first_output, second_output });
 38        test_step.dependOn(&expect_unequal_output.step);
 39
 40        const check_first_output = b.addCheckFile(first_output, .{ .expected_matches = &.{"a.txt"} });
 41        test_step.dependOn(&check_first_output.step);
 42        const check_second_output = b.addCheckFile(second_output, .{ .expected_matches = &.{"a.txt"} });
 43        test_step.dependOn(&check_second_output.step);
 44    }
 45
 46    {
 47        const run_random_without_sideeffects_1 = b.addRunArtifact(exe);
 48        run_random_without_sideeffects_1.setName("run without side-effects (A)");
 49
 50        const run_random_without_sideeffects_2 = b.addRunArtifact(exe);
 51        run_random_without_sideeffects_2.setName("run without side-effects (B)");
 52
 53        run_random_without_sideeffects_2.step.dependOn(&run_random_without_sideeffects_1.step);
 54
 55        const first_output = run_random_without_sideeffects_1.addOutputFileArg("a.txt");
 56        const second_output = run_random_without_sideeffects_2.addOutputFileArg("a.txt");
 57
 58        const expect_cached_dependencies = CheckOutputCaching.init(b, true, &.{second_output});
 59        test_step.dependOn(&expect_cached_dependencies.step);
 60
 61        const expect_equal_output = CheckPathEquality.init(b, true, &.{ first_output, second_output });
 62        test_step.dependOn(&expect_equal_output.step);
 63
 64        const check_first_output = b.addCheckFile(first_output, .{ .expected_matches = &.{"a.txt"} });
 65        test_step.dependOn(&check_first_output.step);
 66        const check_second_output = b.addCheckFile(second_output, .{ .expected_matches = &.{"a.txt"} });
 67        test_step.dependOn(&check_second_output.step);
 68    }
 69}
 70
 71const CheckOutputCaching = struct {
 72    step: std.Build.Step,
 73    expect_caching: bool,
 74
 75    pub fn init(owner: *std.Build, expect_caching: bool, output_paths: []const std.Build.LazyPath) *CheckOutputCaching {
 76        const check = owner.allocator.create(CheckOutputCaching) catch @panic("OOM");
 77        check.* = .{
 78            .step = std.Build.Step.init(.{
 79                .id = .custom,
 80                .name = "check output caching",
 81                .owner = owner,
 82                .makeFn = make,
 83            }),
 84            .expect_caching = expect_caching,
 85        };
 86        for (output_paths) |output_path| {
 87            output_path.addStepDependencies(&check.step);
 88        }
 89        return check;
 90    }
 91
 92    fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
 93        const check: *CheckOutputCaching = @fieldParentPtr("step", step);
 94
 95        for (step.dependencies.items) |dependency| {
 96            if (check.expect_caching) {
 97                if (dependency.result_cached) continue;
 98                return step.fail("expected '{s}' step to be cached, but it was not", .{dependency.name});
 99            } else {
100                if (!dependency.result_cached) continue;
101                return step.fail("expected '{s}' step to not be cached, but it was", .{dependency.name});
102            }
103        }
104    }
105};
106
107const CheckPathEquality = struct {
108    step: std.Build.Step,
109    expected_equality: bool,
110    output_paths: []const std.Build.LazyPath,
111
112    pub fn init(owner: *std.Build, expected_equality: bool, output_paths: []const std.Build.LazyPath) *CheckPathEquality {
113        const check = owner.allocator.create(CheckPathEquality) catch @panic("OOM");
114        check.* = .{
115            .step = std.Build.Step.init(.{
116                .id = .custom,
117                .name = "check output path equality",
118                .owner = owner,
119                .makeFn = make,
120            }),
121            .expected_equality = expected_equality,
122            .output_paths = owner.allocator.dupe(std.Build.LazyPath, output_paths) catch @panic("OOM"),
123        };
124        for (output_paths) |output_path| {
125            output_path.addStepDependencies(&check.step);
126        }
127        return check;
128    }
129
130    fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
131        const check: *CheckPathEquality = @fieldParentPtr("step", step);
132        std.debug.assert(check.output_paths.len != 0);
133        for (check.output_paths[0 .. check.output_paths.len - 1], check.output_paths[1..]) |a, b| {
134            try std.testing.expectEqual(check.expected_equality, std.mem.eql(u8, a.getPath(step.owner), b.getPath(step.owner)));
135        }
136    }
137};