Build.zig: depending on a file generated by system command

I’m trying to port a makefile to build.zig and having trouble getting an intermediate step to generate a necessary file so that the main executable can be built successfully:

bin/shaders.h: src/shaders.glsl
	mkdir -p bin
	./scripts/sokol-shdc --ifdef -l metal_macos -i src/shaders.glsl -o bin/shaders.h

.PHONY: bin/ui
bin/ui: bin/shaders.h
	mkdir -p bin
	$(CC) -o bin/ui src/sokol_main.m

this is what I have so far in build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    b.exe_dir = "bin";
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const shaders = b.addSystemCommand(&.{
        "./scripts/sokol-shdc",
        "--ifdef",
        "-l",
        "metal_macos",
        });
    shaders.addArg("-i");
    shaders.addFileArg(b.path("src/shaders.glsl"));
    shaders.addArg("-o");
    _=shaders.addOutputFileArg("bin/shaders.h");

    const exe = b.addExecutable(.{
        .name = "sokol_test",
        .target = target,
        .optimize = optimize,
        .strip = false,
        });
    exe.linkLibC();
    exe.addCSourceFile(.{
        .file = b.path("src/sokol_main.m"),
        .flags = &.{},
        });
    exe.step.dependOn(&shaders.step);
    b.installArtifact(exe);
}

running that with zig build gives the below error

~/Developer/sokol_test (wip*) » zig build 
install
└─ install sokol_test
   └─ zig build-exe dbg Debug native 1 errors
/Users/daniel/Developer/sokol_test/src/sokol_main.m:37:10: error: '../bin/shaders.h' file not found
#include "../bin/shaders.h"
         ^~~~~~~~~~~~~~~~~~~

Why isn’t shaders command running even with exe.step.dependOn(&shaders.step);?

You’re on the right path but there are some problems with your build script:

_=shaders.addOutputFileArg("bin/shaders.h");

std.Build.Step.Run outputs are written to .zig-cache, with unique subpaths based on the hash of the command inputs. Unlike some other build systems, output files are not written to paths relative to the build root (and having your build.zig explicitly try to hard-code relative output paths is discouraged because it interferes with caching). Your shaders.addOutputFile("bin/shaders.h") line will result in the file being written to an unpredictable path like .zig-cache/o/df55a21e586b7fc74f7f30bad8e644fa/bin/shaders.h.

Because of this, this also means that you need to add the generated directory to exe’s include search path. This can be done by taking LazyPath output return returned by shaders.addOutputFile (which you are currently discarding) and passing it to exe.root_module.addIncludePath, e.g.

const shaders_h = shaders.addOutputFileArg("bin/shaders.h");

// ...

exe.root_module.addIncludePath(shaders_h.dirname());

(If you do this, you can also remove the exe.step.dependOn(&shaders.step) line, because the above already sets up the step dependencies automatically.)

Because the include directive in your error message is #include "../bin/shaders.h", I believe this should already work so long as that is the only file using this header. But since this means you’re no longer including a file relative to the sokol_main.m source file, it might make more sense to change the include directive in sokol_main.m to #include "shaders.h" and drop the bin/ prefix from the subpath passed to addOutputFileArg.

Thank you for the background info and solution, that got it working!

why do you recommend exe.root_module.addIncludePath and not just exe.addIncludePath? It seems to work either way

Also, I’m noticing that zig build is about two times slower than make after modifying whitespace of sokol_main.m (2.865s vs 1.407s, best of 3 runs). Is that expected? Or is it a sign that my build.zig is not setting up an equivalent build job to the makefile I’m trying to copy? Are zig nightly builds not optimized builds? I’m using 0.14.0-dev.2198+e5f5229fd

Both work but the exe Step.Compile API is a bit older and might get removed in the future, so I prefer always using the “modern” exe.root_module std.Build.Module API for the sake of future-proofing.

I don’t know why it’s slower, maybe someone else who knows the details could fill you out. The build.zig you posted looks like it does virtually the same amount of work as the makefile. Make sure you’ve set CC=zig cc when testing the makefile so you’re testing the same compiler.

ah, yes, CC=zig cc makes them take the same amount of time, so the issue is with zig cc and not the zig build system / build.zig file.