# Don't understand how to cache step output

**URL:** <https://ziggit.dev/t/dont-understand-how-to-cache-step-output/17574>\
**Category:** Help\
**Tags:** build-system\
**Created:** [September 11, 2026, 11:09pm UTC](https://ziggit.dev/t/dont-understand-how-to-cache-step-output/17574 "2026-09-11T23:09:37Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![cheriunderscore](https://ziggit.dev/letter_avatar_proxy/v4/letter/c/958977/32.png) [@cheriunderscore](https://ziggit.dev/u/cheriunderscore)\
**Post date:** [September 11, 2026, 11:09pm UTC](https://ziggit.dev/t/dont-understand-how-to-cache-step-output/17574/1 "2026-09-11T23:09:37Z")

</div>

I’m making a custom build step to execute `godot --headless --dump-gdextension-interface` and `godot --headless --dump-extension-api` which create files at the current working directory. They do not take an output file argument, nor will they output to stdout, so I can’t use std.Build.Step.Run. I made my own DumpGodotInterfaces step by referencing std/Build/Step/Run.zig and it does do what I want it to, and I can pass a LazyPath pointing to gdextension\_interface.h to a TranslateC step.  
Now my problem, I tried to make it cache, but it’s not, I guess. It outputs directly to a cache directory, and I can find it in there by running `find -name "gdextension_interface.h"` in the .zig-cache directory, but it still reruns the commands every build, so I can only assume I’m not using that manifest thing correctly.  
Here is the make function whom I put many comments in for the sake of this post:

```zig
pub fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) !void {
    const b = step.owner;
    const io = b.graph.io;
    const self: *@This() = @fieldParentPtr("step", step);
    _ = options;

    // this is the only part I'm sure I'm supposed to do
    var manifest = b.graph.cache.obtain();
    defer manifest.deinit();

    // version of godot being dumped, currently hard coded and not checked against :P
    manifest.hash.add(std.SemanticVersion{ .major = 4, .minor = 7, .patch = 0 });

    // not really sure what this does even after reading the code, but I get FileNotFound if this is not present or is anywhere else
    const cached = try step.cacheHitAndWatch(&manifest);

    // I already know where the files go at this point probably
    const output_path = try b.cache_root.join(b.allocator, &.{ "o", &manifest.final() });
    // the GeneratedFiles are just struct fields but theres also .interface_header: LazyPath and .api_schema: LazyPath whose .generated are these
    self.interface_header_file.path = b.pathJoin(&.{ output_path, "gdextension_interface.h" });
    self.api_schema_file.path = b.pathJoin(&.{ output_path, "extension_api.json" });

    // this is never true I guess
    if (cached) {
        return;
    }

    // surely the cache directory already exists if it's cached, so this is after
    b.cache_root.handle.createDirPath(io, output_path) catch |err| {
        // I think I just took this stuff out of Build.Step.Run
        return step.fail("unable to make path '{f}{s}': {s}", .{
            b.cache_root, output_path, @errorName(err),
        });
    };

    // these'll run at the same time
    var child1 = try std.process.spawn(io, .{ .cwd = .{ .path = output_path }, .argv = &.{ "godot", "--headless", "--dump-gdextension-interface" } }); // creates ./gdextension_interface.h file
    var child2 = try std.process.spawn(io, .{ .cwd = .{ .path = output_path }, .argv = &.{ "godot", "--headless", "--dump-extension-api" } }); // creates ./extension_api.json file
    _ = try child1.wait(io);
    _ = try child2.wait(io);
}

```

zig 0.16.0 btw. I would’ve just given this up and let it not cache if it weren’t for zls not giving me completions for it’s corresponding module.

---

<div class="post-metadata">

**Author:** ![squeek502](https://ziggit.dev/user_avatar/ziggit.dev/squeek502/32/409_2.png) [@squeek502](https://ziggit.dev/u/squeek502)\
**Post date:** [September 12, 2026, 5:20am UTC](https://ziggit.dev/t/dont-understand-how-to-cache-step-output/17574/2 "2026-09-12T05:20:53Z")

</div>

[FYI, custom build steps are going away.](https://codeberg.org/ziglang/zig/pulls/35428)

However, you may not need custom steps for this, even with 0.16.0. Here’s an example where the `godot` commands will still be run every time, but the output files will be cached so e.g. the translate c result will be cached.

```zig
// Directory to use as the CWD for the dump commands
const godot_temp_output = b.addWriteFiles();

// Directory to copy the output into, so that we get integration with the cache system.
const godot_output = b.addWriteFiles();
const generated_h = godot_output.addCopyFile(
    godot_temp_output.getDirectory().path(b, "gdextension_interface.h"),
    "gdextension_interface.h",
);

const run_godot_dump = b.addSystemCommand(&.{ "godot", "--dump-gdextension-interface" });
run_godot_dump.setCwd(godot_temp_output.getDirectory());
godot_output.step.dependOn(&run_godot_dump.step);

// generated_h can be used with addTranslateC, etc

```

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [September 12, 2026, 5:20am UTC](https://ziggit.dev/t/dont-understand-how-to-cache-step-output/17574/3 "2026-09-12T05:20:54Z")

</div>

dont make custom steps, the ability to do so has already been removed on master.

prefer run steps, they make caching much easier, something like

```zig
const cwd = b.addWriteFiles().getDirectory();
const foobar = b.addSystemCommand(&.{ "foo", "bar" });
foobar.setCwd(cwd);
const ma_file = cwd.path(b, "sub/path");

```
