I’m using a custom build step called “precompile” to generate some Zig source files with Zig 0.16.
The precompile step has 2 parts: One part uses a Python script to generate a Zig file, the other part uses a Zig program to generate two other Zig files.
This is an excerpt of the build.zig file:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const precompile_step = b.step("precompile", "Generate Zig sources from Python.");
const precompile_cmd = b.addSystemCommand(&.{ "cmd.exe", "/C", "precompile.cmd", "x" });
precompile_cmd.addFileInput(b.path("precompile.cmd"));
precompile_cmd.addFileInput(b.path("src/builtins/autogen/errors.py"));
precompile_cmd.addFileInput(b.path("src/builtins/autogen/errors_input.py"));
precompile_step.dependOn(&precompile_cmd.step);
const wrap_raylib = b.addExecutable(.{ .name = "wrap_raylib", .root_module = b.createModule(.{
.root_source_file = b.path("tools/wrap_raylib.zig"),
.target = b.graph.host,
.optimize = .Debug,
}) });
const wrap_raylib_step = b.addRunArtifact(wrap_raylib);
const generated_raymath_zig = wrap_raylib_step.addOutputFileArg("raymath.zig");
const generated_raylib_zig = wrap_raylib_step.addOutputFileArg("raylib.zig");
const wf = b.addUpdateSourceFiles();
wf.addCopyFileToSource(generated_raymath_zig, "src/std/raymath.zig");
wf.addCopyFileToSource(generated_raylib_zig, "src/std/raylib.zig");
wf.step.dependOn(&wrap_raylib_step.step);
precompile_step.dependOn(&wf.step);
I can see the output of the Python script when I call zig build precompile.
The Zig part of the precompile step also works as expected and I can see the output of the Zig program, but then in the end the zig build precompile says:
failed command: ".\\.zig-cache\\o\\…\\wrap_raylib.exe" ...
Ok, I thought, so maybe the build system doesn’t like writing to stderr, just as if my custom build step was a test, and I know the opinionated Zig build system does not allow any stderr output in tests because it interpretes this as an error symtom (which causes some confusion for users every now and then).
So I tried to write to switch to std.log instead (which is cleaner anyway for my purpose) and write my own little logFn which should write to stdout instead of stderr, like this:
pub const std_options: std.Options = .{
.logFn = myLog,
};
pub fn myLog(
comptime level: std.log.Level,
comptime scope: @EnumLiteral(),
comptime format: []const u8,
args: anytype,
) void {
const io = Io.Threaded.global_single_threaded.io();
// const prev = io.swapCancelProtection(.blocked);
// defer _ = io.swapCancelProtection(prev);
var buffer: [64]u8 = undefined;
var stdout = Io.File.stdout().writer(io, &buffer);
const terminal: Io.Terminal = .{
.writer = &stdout.interface,
.mode = Io.Terminal.Mode.escape_codes,
};
return std.log.defaultLogFileTerminal(level, scope, format, args, terminal) catch @panic("IO");
}
Using this approach avoids the “failed command: …” error message, but it doesn’t show any output.
It seems weird that it is either impossible or unreasonable complicated to show some output in a custom build step.