Build apparently succeeds with a failed command

Whilst trying to get a feel for the build system I came across some behavior that I’m struggling to understand. The setup is pretty simple, create an executable, run it, ‘install’ the file:

// build.zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const exe = b.addExecutable(.{
        .name = "uppercase",
        .root_module = b.createModule(.{
            .root_source_file = b.path("make_a_file.zig"),
            .target = b.graph.host,
        }),
    });

    const run = b.addRunArtifact(exe);

    const out = run.addOutputFileArg("filename");

    const install = b.addInstallFile(out, "output.txt");

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

with the module:

// make_a_file.zig
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    const io = init.io;
    var args = init.minimal.args.iterate();
    defer args.deinit();
    _ = args.skip();

    const out_path = args.next() orelse unreachable;

    std.debug.print("out_path: {s}\n", .{out_path});

    const out_dirname = std.fs.path.dirname(out_path) orelse unreachable;
    const out_dir = try std.Io.Dir.cwd().openDir(io, out_dirname, .{});
    defer out_dir.close(io);
    const out_basename = std.fs.path.basename(out_path);

    try std.Io.Dir.writeFile(out_dir, io, .{
        .sub_path = out_basename,
        .data = "file contents",
        .flags = .{},
    });
}

Running zig build install throws an error:

zig build install
install
└─ install generated to output.txt
   └─ run exe uppercase (filename) w
out_path: /Users/jonesd/code/zig_build_system_tutorial/lazy/.zig-cache/o/aaa57efef3d395e30276c62129524bb4/filename
failed command: ./.zig-cache/o/5ef789bb1e051a50c178a99f7d004fc4/uppercase /Users/jonesd/code/zig_build_system_tutorial/lazy/.zig-cache/o/aaa57efef3d395e30276c62129524bb4/filename

but the the expected output is right where it should be, at zig-out/output.txt. Running the install step again results in an instant build, and no error:

 zig build install
...

On top of that, I’m able to run the ‘failed’ command directly, and get a 0 exit code:

✦ ❯ ./.zig-cache/o/5ef789bb1e051a50c178a99f7d004fc4/uppercase /Users/jonesd/code/zig_build_system_tutorial/lazy/.zig-cache/o/aaa57efef3d395e30276c62129524bb4/filename
out_path: /Users/jonesd/code/zig_build_system_tutorial/lazy/.zig-cache/o/aaa57efef3d395e30276c62129524bb4/filename

✦ ❯ echo $?
0

I don’t understand what’s causing the error message. Is this even something I should worry about or am I just being pedantic?

The build system is invoking the executable, which prints to stderr and creates the file. When the executable returns, the build system checks stderr. If it’s not empty, it treats that as failure. But at this point, the program has already run and the file has been created.

1 Like

That fixed it, thanks.

Why would the mere presence of stderr output be considered enough to flag the invocation as failed though? I would have thought it’s a common practice for code to output useful information that way.

1 Like

It comes fron Linux’s tradition that silence means sucess.

1 Like

Figured I’d drop alternative solutions to the above, since I rely on stderr as a means of assessing the progress of programs that I’m using pretty regularly and would rather not disable it and perhaps others are in the same boat.

The build system can pipe stderr through to the user via:

run.stdio = .inherit;

and stderr can be matched against nothing to disable the check:

run.expectStdErrMatch("");

Not sure if that’s the right way to do it but it works.

1 Like