在zig项目中通过zig构建系统添加另外一个zig工程当然比较容易,添加其依赖即可。

但是zig项目中如何使用zig构建系统来接入一个纯c的库该如何处理呢?毕竟很多流行的c库还是并没有支持zig作为构建系统的。

同样的,还是以ghostty为例来看其是如何实践的,在项目根目录的build.zig.zon中可以找到依赖的c库,这里以比较简单的zlib库来看啊

.zlib = .{ .path = "./pkg/zlib", .lazy = true },

由于官方的zlib库并没有支持zig构建系统,所以在本地创建了一层zig化的工程,这个依赖指向的也是本地路径下的zlib工程,进入pkg/zlib目录,内容也非常简单,就是一个zig项目结构的工程。自己实现了一下zlib库的zig工程化。如果后续官方支持了,也可以考虑切换到远端仓库依赖,而不是使用自己的zig工程包装了。

zlib
├── build.zig
└── build.zig.zon

c项目zig工程化

zlib zig工程化的思路为,添加zlib的官方c库作为依赖,然后将其编译为一个静态库,作为zlib zig工程的一个制品。一步一步了解整个过程。

  1. 添加zlib c库依赖
.{
    .name = .zlib,
    .version = "1.3.1",
    .fingerprint = 0x73887d3aef823f9e,
    .paths = .{""},
    .dependencies = .{
        // madler/zlib
        .zlib = .{
            .url = "https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz",
            .hash = "N-V-__8AAB0eQwD-0MdOEBmz7intriBReIsIDNlukNVoNu6o",
            .lazy = true,
        },
    },
}
  1. 将zlib c库编译为静态库,这是核心步骤。这个过程要求对c库的编译流程比较清楚,完成编译过程的处理
// 1. 创建一个静态库对象
const lib = b.addStaticLibrary(.{
        .name = "z",
        .target = target,
        .optimize = optimize,
    });
// 2. 这个库需要连接libc库
lib.linkLibC();
// 3. 这个静态库是基于依赖的zlib c库制作的。按照c工程进行编译,添加源代码,设置头文件搜索路径,设置编译选项
if (b.lazyDependency("zlib", .{})) |upstream| {
		// 3.1 添加头文件搜索路径
        lib.addIncludePath(upstream.path(""));
        // 3.2 这个库安装的时候头文件的目录,这里是将zlib c目录下的h后缀的头文件安装到安装目录(zig-out)下的根目录中
        lib.installHeadersDirectory(
            upstream.path(""),
            "",
            .{ .include_extensions = &.{".h"} },
        );
		// 3.3 设置编译选项
        var flags = std.ArrayList([]const u8).init(b.allocator);
        defer flags.deinit();
        try flags.appendSlice(&.{
            "-DHAVE_SYS_TYPES_H",
            "-DHAVE_STDINT_H",
            "-DHAVE_STDDEF_H",
            "-DZ_HAVE_UNISTD_H",
        });
        // 3.4 添加源码文件
        lib.addCSourceFiles(.{
	        // 源码文件相对路径对于的根目录
            .root = upstream.path(""),
            .files = srcs, // 下面的源码文件列表
            .flags = flags.items, // 编译选项
        });
    }
 
// 源码文件,这里是个相对路径
const srcs: []const []const u8 = &.{
    "adler32.c",
    "compress.c",
    "crc32.c",
    "deflate.c",
    "gzclose.c",
    "gzlib.c",
    "gzread.c",
    "gzwrite.c",
    "inflate.c",
    "infback.c",
    "inftrees.c",
    "inffast.c",
    "trees.c",
    "uncompr.c",
    "zutil.c",
};
 
  1. 将静态库作为安装步骤的一个依赖,执行安装步骤的时候就会安装这个静态库。方便以标准布局查看制品,实际上这个对其他zig模块依赖该静态库没有影响
b.installArtifact(lib);

依赖zig工程化的c项目

上面的zlib库已经zig工程化了,在这个工程中添加了一个制品,那就是名为z的这个静态库。

那么现在别的工程要使用zlib库的话,就可以添加这个zlib库依赖,然后链接这个制品

// 1. 添加zlib的zig工程以来
const zlib_dep = b.dependency("zlib", .{ .target = target, .optimize = optimize });
// 2. zlib的zig工程提供了名为z的静态库制品,进行连接。
lib.linkLibrary(zlib_dep.artifact("z"));

带zig绑定代码的zig工程化c项目库

由于这里举例的zlib库只是简单的提供的zig作为构建系统的编译方式,没有提供zig包装的代码,所以比较简单。

不过很多时候引入c库除了编译之外,还需要添加zig包装代码。c项目库的编译过程还是如上,zig代码部分就可以再额外添加一个module制品,然后外部添加以来的时候同时导致module,链接库即可。比如cimgui这个库

const module = b.addModule("cimgui", .{
	.root_source_file = b.path("main.zig"),
	.target = target,
	.optimize = optimize,
});
// zig代码部分只会调用c项目库的头文件,添加进来就好
module.addIncludePath(b.path("vendor"));

除此之外还添加了一个名为cimgui的静态库制品,思路和上面的zlib库是一样的。然后依赖这个库的时候就zig代码部分添加module依赖,然后链接上静态库即可

// cimgui
    if (b.lazyDependency("cimgui", .{
        .target = target,
        .optimize = optimize,
    })) |cimgui_dep| {
        step.root_module.addImport("cimgui", cimgui_dep.module("cimgui"));
        step.linkLibrary(cimgui_dep.artifact("cimgui"));
        try static_libs.append(cimgui_dep.artifact("cimgui").getEmittedBin());
    }