How to make a wrapper around a C library?

Hi,
I’d like to make a thin wrapper around a C library (freetype) that I’m planning on using in a bunch of projects. I’d like to make it into a module, so that the various projects that I have don’t have to use @cImport and can just @import the module that I made.

I’m just not sure what to put in my build.zig. Ultimately, I just want a Zig module, so I don’t need a library or an executable. I’ve looked around the codebase of various similar projects on github, but I’m not sure I understand everything they’re doing.

Do I need to create a library from my module? I can just link the my future projects’ executables against the system library anyway, right?

Thanks.

I suggest creating a package since you want to share it with other projects. A minimal package would need

  • A file that declares a variable storing the result of the cImport
  • A build.zig and build.zig.zon
// root.zig
pub const c = @cImport({
// @cInclude anything you need
})
// build.zig
const std = @import("std");

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

    const module = b.addModule("freetype", .{
        .root_source_file = b.path("root.zig"),
        .target = target,
        .optimize = optimize,
    });

    // not needed in your case but for completion sake
    // allow choosing to link against system library or not using -fsys=freetype
    if (b.systemIntegrationOption("freetype", .{})) {
        module.linkSystemLibrary("freetype2", .{});
    } else {
        const lib = b.addLibrary(.{
            .name = "freetype",
            .root_module = module,
        });
        // do all the work needed to compile from source
    }
}

For build.zig.zon better let zig init do that

Lastly you only need to create a library if you’re compiling from source

3 Likes

You can have a look at the dcimgui repository which I’m maintaining here (it’s essentially a source distribution for the Dear ImGui C bindings):

…build.zig:

This uses the build system translateC feature to build the C => Zig bindings, builds the C/C++ code into a static library, and exposes a Zig module which ‘links’ the C library so that projects only need to import the Zig module.

(technically everything exists twice, once for the regular Dear ImGui version, and once for the ‘Docking Branch’)

There’s some stuff in there for WASM support via Emscripten which you can probably ignore for your use case.

…and there’s also separate cmake build files which you can also ignore, this is because the same repository can also be used as a dependency in regular C/C++ projects…

4 Likes

Thank you both for your answers.
From what I’ve understood, for my specific use case, I just need this in my build.zig:

const std = @import("std");

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

    const module = b.addModule("freetype", .{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
        .link_libc = true,
    });
    module.addIncludePath(.{ .cwd_relative = "/usr/include/freetype2" });
}

And this works, I’ve been able to use this in another zig project. My issue is that when I do zig build on my bindings, it doesn’t actually do anything.

I get that this is because I’m not technically asking it to compile anything, since I’m just declaring a module, but this means that if I have any errors in my bindings, I get the compile error when I try to use the module in another project.

What do I need to add to get the compile errors when I zig build my bindings?

Just add a test executable/library, this can also serve as an example of how to use it.