It is quite often to include C library in Zig project. If it is a shared library, just link it in build.zig
.
But if it is not, it can be included as a dependency. Here I use tiny-regex-c
as an example.
First, add the c libaray as a dependency.
$ zig fetch --save=tiny_regex_c https://github.com/shahar99s/tiny-regex-c/archive/refs/heads/master.zip
It will show up in the build.zig.zon
file. Then include it in build.zig
...
const tiny_regex_c = b.dependency("tiny_regex_c", .{
.target = target,
.optimize = optimize,
});
exe.addCSourceFiles(.{
.root = tiny_regex_c.path(""),
.files = &.{"re.c"},
});
exe.addIncludePath(tiny_regex_c.path(""));
exe.linkLibC();
...
And use it in zig program:
const std = @import("std");
const tiny_regex = @cImport({
@cInclude("re.h");
});
pub fn main() !void {
var match_len:c_int = 0;
const re_t = tiny_regex.re_compile("[a-zA-Z]*");
if (re_t) |regex| {
const result = tiny_regex.re_matchp(regex, "ABCD0123", &match_len);
std.debug.print("result {d}, {d}\n", .{result, match_len});
}
}
This is a very standard way to import C library.
What if I want to pack this C library as a zig package independent from the main program ? Some C libraries are wrapped in Zig as modules. But since Zig supports C quite well, Zig wrap may not be necessary for some simple C libraries. In such case, I can pack C library as a static library and included it in the main program.
$ mkdir tiny-regex-c
$ cd tiny-regex-c
$ zig init
$ rm src/*.zig # I am not making zig library or executable here
$ zig fetch --save=tiny_regex_c https://github.com/shahar99s/tiny-regex-c/archive/refs/heads/master.zip
Now add build.zig
for C library like this
....
const lib = b.addStaticLibrary(.{
.name = "tiny_regex_lib",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
// .root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
const tiny_regex_c = b.dependency("tiny_regex_c", .{
.target = target,
.optimize = optimize, });
lib.addCSourceFiles(.{
.root = tiny_regex_c.path(""),
.files = &.{"re.c"},
});
lib.addIncludePath(tiny_regex_c.path(""));
lib.installHeadersDirectory(tiny_regex_c.path(""), "", .{
.include_extensions = &.{"re.h"},
});
lib.linkLibC();
// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
b.installArtifact(lib);
...
After zig build
, there will be re.h
and libtiny_regex_lib.a
under zig-out
directory.
Go back to the main program, fetch this new package like this:
$ zig fetch --save=tiny-regex ../tiny-regex-c
Now the build.zig
in the main program looks like this:
....
const tiny_regex = b.dependency("tiny-regex", .{
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.linkLibrary(tiny_regex.artifact("tiny_regex_lib"));
....
A good thing about this approach is that we can add unit test for this C library.
We can test the library independently from the main program to avoid some issues.
Now go back to the library. Add file src/test.zig
:
const std = @import("std");
const tiny_regex = @cImport({
@cInclude("re.h");
});
test "regex" {
var re_t: ?*tiny_regex.regex_t = undefined;
var match_len:c_int = 0;
re_t = tiny_regex.re_compile("[a-zA-Z]*");
if (re_t) |regex| {
const result = tiny_regex.re_matchp(regex, "ABCD0123", &match_len);
std.debug.print("result {d}, {d}\n", .{result, match_len});
try std.testing.expectEqual(result, @as(c_int, 0));
try std.testing.expectEqual(match_len, @as(c_int, 4));
}
}
test "true" {
try std.testing.expect(41 == 41);
}
test {
std.testing.refAllDecls(@This());
}
It is quite simiar to the main program above. Add this as unit test in build.zig
....
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/test.zig"),
.target = target,
.optimize = optimize,
});
unit_tests.linkLibrary(lib);
const run_unit_tests = b.addRunArtifact(unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
Now run the unit test:
$ zig build test
This may be an easier way to maintain some external libaries.