包管理
随着 0.11 的发布,zig 终于迎来了一个正式的官方包管理器,此前需要通过第三方包管理器下载并处理包。
zig 当前并没有一个中心化存储库,包可以来自任何来源,无论是本地还是网络上。
当前的包管理模式为,先在 build.zig.zon 添加包的元信息,然后在 build.zig 中引入包。
新的文件结构
build.zig.zon 这个文件存储了包的信息,它是 zig 新引入的一种简单数据交换格式,使用了 zig 的匿名结构和数组初始化语法。
.{
// 包名字
.name = .importer,
// 包版本
.version = "0.0.0",
.fingerprint = 0x64e883e88dde22e2,
// 包依赖
.dependencies = .{
// 包依赖项的名字
.@"tarball-exporter" = .{
.url = "https://github.com/zigcc/zig-msgpack/archive/refs/tags/0.0.17.tar.gz",
.hash = "zig_msgpack-0.0.14-evvueL5SBQACmim6j6klQ9wWIIG_UxGlPvVYdiNy0KT8",
},
.@"path-exporter" = .{
// path 为本地包的路径
.path = "../package_management_exporter",
},
},
// 包所包含的源文件,一般用于在对外提供包时才使用,还是建议养成写清楚paths的习惯
.paths = .{
"src",
"build.zig",
"build.zig.zon",
},
}以上字段含义为:
name:当前你所开发的包的名字,Zig 0.16 起应写成 enum literal,例如.importer。version:包的版本,使用 Semantic Version。fingerprint: 包的身份指纹,Zig 0.16 起为必填项;它不同于依赖的hash,会和name一起用于识别同一个项目的不同版本或本地 fork,缺失时zig build会报错并提示应填写的值。dependencies:依赖项,内部是一个匿名结构体;每个字段名就是依赖包名,字段值则是该依赖的配置。 常见配置项包括url、hash和path。 当使用远程源码包时填写url和hash;当使用本地包时填写path,两种方式不能混用。paths:显式声明会被打包和参与 hash 的文件或目录,例如src、build.zig和build.zig.zon;不在列表中的文件会被过滤掉,需要包含整个包时可写"."。
🅿️ 提示
小技巧:如何直接使用指定分支的源码?
如果代码托管平台提供分支源码打包直接返回功能,就支持,例如 github 的源码分支打包返回的 url 格式为:
https://github.com/username/repo-name/archive/branch.tar.gz
其中的 username 就是组织名或者用户名,repo-name 就是对应的仓库名,branch 就是分支名。
例如 https://github.com/limine-bootloader/limine-zig/archive/trunk.tar.gz 就是获取 limine-zig 这个包的主分支源码打包。
而若是想要离线使用本地包时则是先下载源码包并直接使用绝对或相对路径导入,例如在下载完包之后放在项目的 deps 目录下,那么使用本地包的格式为:
./deps/trunk.tar.gz
编写包
🅿️ 提示
zig 支持在一个 build.zig 中对外暴露出多个模块,也就是说一个包本身可以包含多个模块,并且 lib 和 executable 两种是完全可以共存的!
如何将模块对外暴露呢?
可以使用 build 函数传入的参数 b: *std.Build,它包含一个方法 addModule,它的原型如下:
pub fn addModule(
b: *Build,
name: []const u8,
options: Module.CreateOptions
) *Module使用起来也很简单,例如:
_ = b.addModule("exporter", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});这就是一个最基本的包暴露实现,指定了包名和包的入口源文件地址(b.path 会按当前包根目录解析并返回 LazyPath),通过 addModule 函数暴露的模块是完全公开的。
🅿️ 提示
如果需要使用私有的模块,请使用 std.Build.createModule,使用方式和 addModule 同理。
关于二进制构建结果(例如动态链接库和静态链接库),任何被执行 install 操作的构建结果均会被暴露出去(即引入该包的项目均可看到该包的构建结果,但需要手动 link)。
引入包
可以使用 build 函数传入的参数 b: *std.Build,它包含一个方法 dependency,它的原型如下:
fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency其中 name 是在在 .zon 中的包名字,它返回一个 *std.Build.Dependency,可以使用 artifact 和 module 方法来访问包的链接库和暴露的 module。
// 通过 dependency 函数获取到依赖
const pe = b.dependency("path-exporter", .{
.target = target,
.optimize = optimize,
});
const te = b.dependency("tarball-exporter", .{
.target = target,
.optimize = optimize,
});
// 将 module 添加到 exe 的 root module 中
exe.root_module.addImport("path_exporter", pe.module("exporter"));
exe.root_module.addImport("tarball_exporter", te.module("msgpack"));🅿️ 提示
dependency 包含一个额外的参数 args,这是传给对应的包构建的参数(类似在命令行构建时使用的 -D 参数,通常是我们使用 b.option 获取,通过 std.Build.option 实现),当前包的参数并不会向包传递,需要手动显式指定转发。