'zig build' is misinformed about emit-h output location
Zig Version
0.12.0-dev.2076+8fd15c6ca
Steps to Reproduce and Observed Behavior
With Compile.getEmittedH and Compile.addIncludePath, the expectation would be that you can do something like this:
exe.addIncludePath(lib.getEmittedH().dirname());
Unfortunately, that fails with:
[...]/emit-h-repro/src/use.c:2:10: error: 'repro.h' file not found
#include "repro.h"
^~~~~~~~~~
error: warning: add '[...]/emit-h-repro/zig-cache/o/06f02ee06b4da68b1f6bdfd73277edb8' to header searchlist '-I' conflicts with '-isystem'
error: the following command failed with 1 compilation errors:
[$HOME]/zig/0.12.0-dev.2076+8fd15c6ca/files/zig build-exe [..]/emit-h-repro/src/use.c [..]/emit-h-repro/zig-cache/o/06f02ee06b4da68b1f6bdfd73277edb8/librepro.a -ODebug -isystem [..]/emit-h-repro/zig-cache/o/06f02ee06b4da68b1f6bdfd73277edb8 -I [..]/emit-h-repro/zig-cache/o/06f02ee06b4da68b1f6bdfd73277edb8 -lc --cache-dir [..]/emit-h-repro/zig-cache --global-cache-dir [$HOME]/.cache/zig --name use --listen=-
The build system believes that the header file will be written to the same directory in zig-cache as the library, when in fact it is written to the root of the zig-cache:
❯ ls zig-cache
h o repro.h tmp z
One can currently work around this by doing something like the following to put zig-cache on the include path (which then hits the usual "zig.h" not found errors but that's its own problem: #13528).
const cache_root = b.cache_root.path orelse try b.cache_root.join(b.allocator, &.{"."});
exe.addIncludePath(.{ .path = cache_root });
lib.getEmittedH().addStepDependencies(&exe.step);
To reproduce locally, create a file structure like the following and run zig build.
-- build.zig --
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{
.name = "repro",
.root_source_file = .{ .path = "src/root.zig" },
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
const exe = b.addExecutable(.{
.name = "use",
.target = target,
.optimize = optimize,
.link_libc = true,
});
exe.addCSourceFile(.{
.file = .{ .path = "src/use.c" },
.flags = &.{},
});
exe.linkLibrary(lib);
exe.addIncludePath(lib.getEmittedH().dirname());
// WORKAROUND:
// const cache_root = b.cache_root.path orelse try b.cache_root.join(b.allocator, &.{"."});
// exe.addIncludePath(.{ .path = cache_root });
// lib.getEmittedH().addStepDependencies(&exe.step);
b.installArtifact(exe);
}
-- build.zig.zon --
.{
.name = "emit-h-repro",
.version = "0.0.0",
.dependencies = .{},
.paths = .{""},
}
-- src/root.zig --
const std = @import("std");
const testing = std.testing;
export fn add(a: i32, b: i32) i32 {
return a + b;
}
-- src/use.c --
#include <stdio.h>
#include "repro.h"
int main() {
printf("%d\n", add(1, 2));
}
Expected Behavior
zig build-lib and zig build should agree about the destination of the emitted header so that exe.addIncludePath(lib.getEmittedH().dirname()) behaves as expected.
I'm not super familiar with the underlying code, but from peeking around, I'm guessing our options are:
- Inform Step.Compile that
-femit-hwrites to the root of the zig-cache - Make Step.Compile use the
-femit-h=fooform to write to the correct directory - Change the default output path for emitted headers in
zig build-lib
Poking around this more, I think (3) makes the most sense:
other -emit flags write to the cache directory, but emit-h writes to the cache root.
emit-h should also write to the same cache output directory as other emits by default.
Users who want it in a different location should use -emit-h=path/to/dst.h, or use the build system to install the file where they want it.
I'm concerned by any solution that doesn't put an emitted header in a directory all on its own. That includes what's currently going on, but also what you seem to be suggesting "the same cache output directory as other emits". There's no way to add the header directly to an include path, one can only add the directory its in. In which case it's important that adding that include directory does not accidentally add other header files as well.
I tried to build the example in the 0.13 language docs for Mixing Object Files with zig-0.13. It doesn't build because the headers are not in the include path in the step that compiles the C file. In fact I can't find any generated headers anywhere in .zig-cache. I tried to generate headers manually with zig build-obj base64.zig -femit-h=base64.h, but then Zig just crashes:
Segmentation fault (core dumped). The crash is already reported in https://github.com/ziglang/zig/issues/18188.
A workaround is to write extern or extern "C" declarations in the calling C/C++ code, but it would be nicer to have working examples in the official documentation :-)