Skip a step if the step it depends on is cached or not

Hi everyone!
I’m brand new to the world of zig! :slight_smile:

I’m currently trying to figure out the build system, I’m using zig 0.11.0, and would like to know if there’s a way to skip a step if the step it depends on is cached or not.

Assuming I have this simple build script:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const exe = b.addExecutable(.{
        .name = "test.bin",
        .root_source_file = .{ .path = "test.zig" },
    });

    const install = b.addInstallArtifact(exe, .{});

    const run_echo = b.addSystemCommand(&.{
        "echo",
        "Hello word... Need to rebuild! :)",
    });
    run_echo.step.dependOn(&install.step);

    b.getInstallStep().dependOn(&run_echo.step);
}

When I run zig build --summary all twice, this is what I get:

riblanc :: ~/test » zig build --summary all
Hello word... Need to rebuild! :)
Build Summary: 4/4 steps succeeded
install success
└─ run echo success 869us MaxRSS:2M
   └─ install test.bin success
      └─ zig build-exe test.bin Debug native success 743ms MaxRSS:164M
riblanc :: ~/test » zig build --summary all
Hello word... Need to rebuild! :)
Build Summary: 4/4 steps succeeded
install success
└─ run echo success 897us MaxRSS:2M
   └─ install test.bin cached
      └─ zig build-exe test.bin Debug native cached 3ms MaxRSS:36M

Even if the install test.bin step is cached, the run echo step runs.
What I’d like to do is run the echo step if and only if the install test.bin isn’t cached.

Does anyone know how to create such a dependency?

Hi @riblanc, welcome to ziggit! :slight_smile:

b.getInstallStep().dependOn(&run_echo.step); means that install depends on run_echo.step and it will run this step every time you run zig build install or zig build, because install is the default build step.
Note that install and uninstall are standard steps that are not related to the install declared constant.

You can use zig build --verbose to instruct zig to display the build commands before executing them. You will see that the builder runs zig build-exe, passing the cache directories as arguments. That means that the process zig build-exe handles caching and the process running build.zig is not aware about caching, so there is no easy way to do that within build.zig :frowning:

A Step.Run will always run anew when invoked unless it has output arguments (added with addOutputFileArg() and similar methods) or checks (added with expectExitCode()/expectStdOutEqual()/expectStdErrEqual()), in which case it will participate in the caching system and only be rerun when any of its input arguments change.

So in your case, you need to add the artifact as an input argument to your run step, and either an output argument or a check.

const run_echo = b.addSystemCommand(&.{
    "echo",
    "Hello word... Need to rebuild! :)",
});
run_echo.addArtifactArg(exe);
run_echo.expectExitCode(0);
$ zig build --summary all

Build Summary: 3/3 steps succeeded
install success
└─ run echo success 11ms MaxRSS:6M
   └─ zig build-exe main Debug native success 899ms MaxRSS:131M

$ zig build --summary all

Build Summary: 3/3 steps succeeded
install cached
└─ run echo cached
   └─ zig build-exe main Debug native cached 18ms MaxRSS:15M

# after making a change to main.zig

$ zig build --summary all

Build Summary: 3/3 steps succeeded
install success
└─ run echo success 10ms MaxRSS:6M
   └─ zig build-exe main Debug native success 936ms MaxRSS:131M

However, there are currently two caveats to this:

  • The path to the artifact (in zig-cache/) will obviously be passed as an argument to the command, so if you want the spawned child process to ignore this argument you need to handle it there somehow.
  • No output will be printed to the zig build process unless the command fails, so in this case you won’t ever see the “hello world” message.

Regarding the first point, Step.Run actually has a extra_file_dependencies which could in theory be used to add a file that should be checked to determine whether to re-run the command without explicitly passing it as an argument, however, it hasn’t received much love and is of the type []const []const u8, so you can’t check generated LazyPaths this way.

6 Likes

Hi @castholm, thank you very much for your clear explanations!
It’s exactly what I was searching for :slight_smile:

The goal here, as you can imagine, is not to make a simple echo, I just simplified my problem as much as possible so as not to add unnecessary details.
I’m developing a toy kernel and wanted to create a disk image with grub if and only if the binary needed to be rebuilt.

I tried your method and it works perfectly
Thank you very much again! :grin:

1 Like