Link against static C library with zig 0.16

As I mentioned on another post I made, I am just starting to jump onboard zig 0.16. Can anybody point me to the current, recommended way to link against a simple C static library? I have files gonzo.h and libgonzo.a, and I’m looking for the correct incantation to place in my build.zig that will link the library into my executable, and make all of the library’s functions available to my zig code.

I would hope that the answer points me to the right, supported (let’s say, “future proof”) way of doing this – I remember doing it with @cImport / @cInclude in the past, but these are deprecated now, right?

I also would not mind if some generous soul explains how I might go about creating a “ziggier” wrapper for a C library. Perhaps pointing me to a nice (0.16) example?

Many thanks in advance!

1 Like

I believe the linking API is the same.

As for headers, you’ll want to replace @cImport and @cInclude with std.Build.addTranslateC. Make a small C header file (e.g. imports.h) that #includes everything you need, then translate it using the build system into a Zig module, e.g. like so.

To provide more context, addTranslateC is basically what @cImport and @cInclude did in prior versions, but is now removed from the language itself and was instead moved out into the build system. This allows Zig to not be so tightly coupled to LLVM to ease development[1].


  1. But LLVM should AFAIU still remain a very important part of the pipeline, it’s not getting removed, just less tightly coupled – people keep panicking about this every time it’s mentioned, so just to be clear ↩︎

Translate C is planned to be distributed as a package.

The most “future proof” solution is to depend on the translate C package:

https://codeberg.org/ziglang/translate-c

Here is an example of linking a precompiled library (I don’t personally have an example for compiling the c files)
https://codeberg.org/jeffective/zenoh-zig/src/branch/main/build.zig

And for a (maybe bad) example of wrapping the c API in a zig API:
https://codeberg.org/jeffective/zenoh-zig/src/commit/e7351b65a768127a1f46a6b3e1a20c31c58b93d5/src/Session.zig#L35

1 Like

I managed to link my library. In the end I did this:

    exe.root_module.linkSystemLibrary("gonzo", .{});
    exe.root_module.linkSystemLibrary("crypto", .{});

However, that’s not exactly what I think I need. libcrypto.so lives in /usr/lib, so it is a proper system library. But libgonzo.a lives on its own directory, a sibling of my zig project; if I were linking with cc, I would add ... -L../gonzo -lgonzo to the compilation line. To make this work with my build.zig, I had to hack it and copy libgonzo.a to /usr/local/lib.

What is the correct solution for this? If it is passing the correct -L flag, how would I do that in build.zig? Or should I do this differently?

Install the gonzo library using installArtifact for libgonzo.a and installHeader for gonzo.h.

    const lib = b.addLibrary(.{
        .name = "gonzo", // library artifact name
        ...
    });
    // copy from src/ to $install_prefix/include/
    lib.installHeader(b.path("src/gonzo.h"), "gonzo.h");
    // copy a library artifact to $install_prefix/lib, an executable artifact to $install_prefix/bin
    b.installArtifact(lib);

After running zig build install the files must be in $install_prefix/include/gonzo.h and $install_prefix/lib/libgonzo.a


To link with the artifact libgonzo.a, load it from the dependency with artifact and then use linkLibrary. To include gonzo.h use addIncludePath with the dependency include path.

    const gonzo = b.dependency("gonzo", .{ // name of the dependency in build.zig.zon
        ...
     });

    exe.root_module.addIncludePath(gonzo.path("include")); // directory in $install_prefix
    const gonzo_lib = gonzo.artifact("gonzo"); // artifact name used in addLibrary
    exe.root_module.linkLibrary(gonzo_lib);
2 Likes

Hi, here is another example, static linking to a (pre compliled) C library, works on 0.16.0 and master 0.17*-dev

Key points are

  1. use the addTranslateC() function to translate C library header(s) to zig
  2. from the translated header, create a module and add it as an import in the binary module, so from zig code, you can @import() it
  3. call addObjectFile() to “inject” into the binary module the C library (.a) (if you want static linking)

Thank you everybody for their suggestions. This is what worked for me in the end:

const std = @import("std");

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

    // we get this from our build.zig.zon file
    const httpz = b.dependency("httpz", .{
        .target = target,
        .optimize = optimize,
    });

    const gonzo = b.addTranslateC(.{
        .root_source_file = b.path("../gonzo/include/gonzo.h"),
        .target = target,
        .optimize = optimize,
        .link_libc = true, // Required for most headers
    });

    const exe = b.addExecutable(.{
        .name = "server",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/server.zig"),
            .target = target,
            .optimize = optimize,
            .link_libc = true, // due to using libgonzo.a
            .imports = &.{
                .{ .name = "httpz", .module = httpz.module("httpz") },
                .{ .name = "gonzo", .module = gonzo.createModule() },
            },
        }),
    });

    // add gonzo library
    exe.root_module.addObjectFile(b.path("../gonzo/lib/libgonzo.a"));

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}
1 Like