包管理
随着 0.11
的发布,zig 终于迎来了一个正式的官方包管理器,此前已知是通过第三方包管理器下载并处理包。
zig 当前并没有一个中心化存储库,包可以来自任何来源,无论是本地还是网络上。
当前的包管理模式为,先在 build.zig.zon
添加包的元信息,然后在 build.zig
中引入包。
新的文件结构
build.zig.zon
这个文件存储了包的信息,它是 zig 新引入的一种简单数据交换格式,使用了 zig 的匿名结构和数组初始化语法。
.{
// 包名字
.name = "importer",
// 包版本
.version = "0.0.0",
// 包依赖
.dependencies = .{
// 包依赖项的名字
.@"tarball-exporter" = .{
// url 为包的下载地址
.url = "https://github.com/zigcc/zig-msgpack/archive/refs/tags/0.0.5.tar.gz",
// hash 为包的哈希值,但不是对整个包的哈希值,而是对包中的所有文件的哈希值
.hash = "12207ec1296852c17c0424b5e650003bd9a3c4f35e9d0ab586d39ea0ab812c2d9f2c",
},
.@"path-exporter" = .{
// path 为本地包的路径
.path = "../package_management_exporter",
},
},
// 包所包含的源文件,一般用于在对外提供包时才使用,还是建议养成写清楚paths的习惯
.paths = .{
"src",
"build.zig",
"build.zig.on",
},
}
以上字段含义为:
name
:当前你所开发的包的名字version
:包的版本,使用 Semantic Version。dependencies
:依赖项,内部是一连串的匿名结构体,字段dep_name
是依赖包的名字,url
是源代码地址,hash
是对应的 hash(源文件内容的 hash),path
是不使用源码包而是本地目录时目录的路径。 当使用目录方法导入包时就不能使用url
和hash
,反之同理。paths
:显式声明包含的源文件,包含所有则指定为空。
🅿️ 提示
小技巧:如何直接使用指定分支的源码?
如果代码托管平台提供分支源码打包直接返回功能,就支持,例如 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/tunk.tar.gz
🅿️ 提示
目前 zig 已支持通过 zig fetch
来获取 hash 并写入到 .zon
中!
编写包
🅿️ 提示
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
是相对当前项目路径取 Path
),通过 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.options
获取,通过 std.Build.option
实现),当前包的参数并不会向包传递,需要手动显式指定转发。