I’m adding a build.zig
for the nodejs N-API. To build a Node addon, you compile against a .h
and tell the linker to leave those symbols undefined. On Windows, I’ve learned that you need to link against an import library, which you generate from a .def
file, which is included in the repo above.
I’m having trouble integrating this step into the build.
I can generate .lib
s just fine, using b.addSystemCommand(&.{b.graph.zig_exe, "dlltool", ... })
. Beyond that, I can’t find anything that works. If I add them (N-API comes with 2 .def
s) with addObjFile
, the addon crashes when linking against that library, and there are compiler warnings. Zig’s output with --verbose
is this (shortened for brevity):
ar rcs node_api.lib obj_container.lib
That seems to be putting the .lib
files in an archive rather than their object file contents, and zig ar t
seems to confirm that theory. I don’t think that’s right, but I’m not sure. The same thing happens if I use addCSourceFile
. Linking the addon against both of these .lib
s directly from the cache folder does make the addon work, so I know those alone are correct.
Does Zig or LLVM not understand .lib
? Are .lib
files not able to be “combined”? Or is there no way to addStaticLibrary
, so it’s treated as an archive? If there was some way to expose the .lib
paths as artifacts, that would be an acceptable work-around too.
build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const node_api_headers = b.dependency("node_api_headers", .{});
const node_api = b.addLibrary(.{
.name = "node_api",
.linkage = .static,
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
})
});
// so that upstream people can call linkLibrary on non-Windows
node_api.addCSourceFile(.{.file = b.path("empty.c"), .flags = &.{}});
node_api.installHeadersDirectory(node_api_headers.path("include"), "", .{});
if (target.result.os.tag == .windows) {
const node_api_lib = dlltool(b, target, node_api_headers.path("def/node_api.def"), "node_api.lib");
const js_native_api_lib = dlltool(b, target, node_api_headers.path("def/js_native_api.def"), "js_native_api.lib");
node_api.addObjectFile(node_api_lib);
node_api.addObjectFile(js_native_api_lib);
}
b.installArtifact(node_api);
}
pub fn dlltool(
b: *std.Build,
target: std.Build.ResolvedTarget,
def_file: std.Build.LazyPath,
lib_file_basename: []const u8
) std.Build.LazyPath {
const exe = b.addSystemCommand(&.{b.graph.zig_exe, "dlltool", "-d", def_file.getPath(b), "-l"});
const lib_file = exe.addOutputFileArg(lib_file_basename);
exe.addArgs(&.{
"-m", switch (target.result.cpu.arch) {
.x86 => "i386",
.x86_64 => "i386:x86-64",
.arm => "arm",
.aarch64 => "arm64",
else => "i386:x86-64",
},
});
return lib_file;
}
build.zig.zon
.{
.name = .node_api,
.version = "0.0.0",
.dependencies = .{
.node_api_headers = .{
.url = "https://github.com/nodejs/node-api-headers/archive/refs/tags/v1.5.0.tar.gz",
.hash = "N-V-__8AAASNAQA0UI0mvQk4LyFEhdFuFBPgH8HSokdFFpnz"
}
},
}
Background: I’m hoping to get a static, cross-compile of node-canvas
working with Zig. node-canvas
has wasted lot of people’s collective time over the years due to being hard to install.