构建系统
Zig 除了是一门编程语言外,本身还是一套完整的工具链,例如:
zig cc
、zig c++
C/C++ 编译器zig build
适用于 Zig/C/C++ 的构建系统
本章节就来介绍 Zig 的构建系统。
理念
Zig 使用 build.zig
文件来描述一个项目的构建步骤。
如其名字所示,该文件本身就是一个 Zig 程序,而不是类似 Cargo.toml
或 CMakeLists.txt
这样的领域特定语言(DSL)。
这样的好处很明显,表达能力更强,开发者只需要使用同一门语言即可进行项目构建,减轻了用户心智。
一个典型的构建文件如下:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
}
build
是构建的入口函数,而不是常见的 main
,真正的 main
函数定义在 build_runner.zig
中,这是由于 Zig 的构建分为两个阶段:
- 生成由
std.Build.Step
构成有向无环图(DAG) - 执行真正的构建逻辑
TIP
第一次接触 Zig 的构建流程,可能会觉得复杂,尤其是构建 Step 的依赖关系,但这是为了后续并发编译作基础。
如果没有 build_runner.zig
,让开发者自己去处理并发编译,将会是件繁琐且容易出错的事情。
Step
会在下一小节中会重点讲述,这里介绍一下上面这个构建文件的其他部分:
b.standardTargetOptions
: 允许构建器读取来自命令行参数的构建目标三元组。b.standardOptimizeOption
:允许构建器读取来自命令行参数的构建优化模式。b.addExecutable
:创建一个Build.Step.Compile
并返回对应的指针,其参数为std.Build.ExecutableOptions
。b.path
:该函数用于指定获取当前项目的源文件路径,请勿手动为root_source_file
赋值!
🅿️ 提示
标准构建会产生两个目录,一个是 zig-cache
、一个是 zig-out
,第一个是缓存目录(这有助于加快下次构建),第二个是安装目录,不是由项目决定,而是由用户决定(通过 zig build --prefix
参数),默认为 zig-out
。
Step
Step 可以称为构建时的步骤,它们将构成一个有向无环图。可以通过 Step 来指定构建过程之间的依赖管理,例如要构建的二进制程序 A 依赖一个库 B,那么我们可以在构建 A 前先构建出 B,而 B 的构建依赖于 另一个程序生成的数据 C,此时我们可以再指定构建库 B 前先构建出数据 C,大致的图如下:
数据C
|
C --> B --> A
| |
| 程序A
|
库B
例如我们可以在 build.zig
中添加一个运行程序的步骤:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// zig 提供了一个方便的函数允许我们直接运行构建结果
const run_exe = b.addRunArtifact(exe);
// 注意:该步骤可选,显式声明运行依赖于构建
// 这会使运行是从构建输出目录(默认为 zig-out/bin )运行而不是构建缓存中运行
// 不过,如果应用程序运行依赖于其他已存在的文件(例如某些 ini 配置文件)
// 这可以确保它们正确的运行
run_exe.step.dependOn(b.getInstallStep());
// 注意:此步骤可选
// 此操作允许用户通过构建系统的命令传递参数,例如 zig build -- arg1 arg2
// 当前是将参数传递给运行构建结果
if (b.args) |args| {
run_exe.addArgs(args);
}
// 指定一个 step 为 run
const run_step = b.step("run", "Run the application");
// 指定该 step 依赖于 run_exe,即实际的运行
run_step.dependOn(&run_exe.step);
}
以上代码中,我们可以使用 zig build run -- arg1
向构建产物传递参数!
🅿️ 提示
值得注意的是,b.installArtifact
是将构建放入 install
这一默认的 step 中。
如果我们想要重新创建一个全新的 install,可以使用 b.addInstallArtifact
。
它会返回一个新的 InstallArtifact
,让对应的 step 依赖它即可!
基本使用
构建模式
zig 提供了四种构建模式(Build Mode):
- Debug
- ReleaseFast
- ReleaseSafe
- ReleaseSmall
如果在 build.zig
中使用了 standardOptimizeOption
,则构建系统会接收命令行的参数来决定实际构建模式(缺省时为 Debug),参数类型为 -Doptimize
,例如 zig build -Doptimize=Debug
就是以 Debug 模式构建。
以下讲述四种构建模式的区别:
Debug | ReleaseFast | ReleaseSafe | ReleaseSmall |
---|---|---|---|
构建速度很快 | 构建速度慢 | 构建速度慢 | 构建速度慢 |
启用安全检查 | 启用安全检查 | 启用安全检查 | 禁用安全检查 |
较差的运行效率 | 很好的运行效率 | 中等的运行效率 | 中等的运行效率 |
二进制体积大 | 二进制体积大 | 二进制体积大 | 二进制体积小 |
无复现构建 | 可复现构建 | 可复现构建 | 可复现构建 |
关于 Debug 不可复现的原因
关于为什么 Debug 是不可复现的,zig 官方手册并未给出具体说明,以下内容为询问社区获得:
在 Debug 构建模式下,编译器会添加一些随机因素进入到程序中(例如内存结构不同),所以任何没有明确说明内存布局的容器在 Debug 构建下可能会有所不同,这便于我们在 Debug 模式下快速暴露某些错误。
有意思的是,这并不会影响程序正常运行,除非你的程序逻辑有问题。
这是 zig 加强安全性的一种方式(尽可能提高安全性但又不至于造成类似 Rust 开发时过重的心智负担)。
CLI 参数
通过 b.option
使构建脚本部分配置由用户决定(通过命令行参数传递),这也可用于依赖于当前包的其他包。
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 使用 option 来获取命令参数决定是否剥离调试信息
const is_strip =
b.option(bool, "is_strip", "whether strip executable") orelse
false;
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
// 设置 exe 的 strip
.strip = is_strip,
});
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
}
以上,我们通过使用 b.option
来实现从命令行读取一个参数决定是否剥离二进制程序的调试信息,使用 zig build --help
可以看到输出多了一行:
Project-Specific Options:
-Dis_strip=[bool] whether strip executable
Options 编译期配置
Options 允许我们将一些信息传递到项目中,例如我们可以以此实现让程序打印构建时的时间戳:
const std = @import("std");
// timestamp 这个包是通过 build.zig 添加的
const timestamp = @import("timestamp");
pub fn main() !void {
std.debug.print("build time stamp is {}\n", .{timestamp.time_stamp});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 通过标准库获取时间戳
const timestamp = std.time.timestamp();
// 创建一个 options
const options = b.addOptions();
// 向 options 添加 option, 变量名是time_stamp
options.addOption(i64, "time_stamp", timestamp);
// 向 exe 中添加 options
exe.root_module.addOptions("timestamp", options);
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
}
可以发现,我们使用 b.addOptions
创建了一个 options,并且向其中添加了 option,最后将整个 options 塞入二进制程序的构建中,这会允许我们通过 @import
来将 options 作为包导入。
🅿️ 提示
事实上,在 build.zig
中的 options,会在编译时转为一个规范的 zig 包传递给程序,这就是我们为何能够像普通包一样 import
它们的原因。
构建静/动态链接库
通常我们定义一个 lib
的方式如下:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 使用默认提供的构建目标,支持我们从命令行构建时指定构建目标(架构、系统、abi等等)
const target = b.standardTargetOptions(.{});
// 使用默认提供的优化方案,支持我们从命令行构建时指定构建模式
const optimize = b.standardOptimizeOption(.{});
// 尝试添加一个静态库
const lib = b.addStaticLibrary(.{
// 库的名字
.name = "example",
// 源文件地址
.root_source_file = b.path("src/root.zig"),
// 构建目标
.target = target,
// 构建模式
.optimize = optimize,
});
// 这代替原本的 lib.install,在构建时自动构建 lib
// 但其实这是不必要的,因为如果有可执行二进制程序构建使用了 lib,那么它会自动被构建
b.installArtifact(lib);
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 链接 lib
exe.linkLibrary(lib);
// 添加到顶级 install step 中作为依赖,构建 exe
b.installArtifact(exe);
}
对应地,如果要构建动态库可以使用 b.addSharedLibrary
。
通常,二进制可执行程序的构建结果会输出在 zig-out/bin
下,而链接库的构建结果会输出在 zig-out/lib
下。
如果要连接到系统的库,则使用 exe.linkSystemLibrary
,Zig 内部借助 pkg-config 实现该功能。示例:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 使用默认提供的构建目标,支持我们从命令行构建时指定构建目标(架构、系统、abi等等)
const target = b.standardTargetOptions(.{});
// 使用默认提供的优化方案,支持我们从命令行构建时指定构建模式
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "zip",
.root_source_file = b.path("src/main.zig"),
// 构建目标
.target = target,
// 构建模式
.optimize = optimize,
});
if (target.result.os.tag == .windows)
// 连接到系统的 ole32
exe.linkSystemLibrary("ole32")
else
// 链接到系统的 libz
exe.linkSystemLibrary("z");
// 链接到 libc
exe.linkLibC();
b.installArtifact(exe);
}
这会链接一个名为 libz 的库,约定库的名字不包含“lib”。
生成文档
zig 本身提供了一个实验性的文档生成器,它支持搜索查询,操作如下:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 构建一个 object,用于生成文档
const object = b.addObject(.{
.name = "object",
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
// 创建一个 step
const docs_step = b.step("docs", "Generate docs");
// 生成文档
const docs_install = b.addInstallDirectory(.{
// 指定文档来源
.source_dir = object.getEmittedDocs(),
// 指定安装目录
.install_dir = .prefix,
// 指定文档子文件夹
.install_subdir = "docs",
});
docs_step.dependOn(&docs_install.step);
}
以上代码定义了一个名为 docs
的 Step,并将 addInstallDirectory
操作作为依赖添加到 docs
Step 上。
单元测试
每个文件可以使用 zig test
命令来执行测试,但实际开发中这样很不方便,zig 的构建系统提供了另外一种方式来处理当项目变得复杂时的测试。
使用构建系统执行单元测试时,构建器和测试器会通过 stdin 和 stdout 进行通信,以便同时运行多个测试,并且可以有效地报告错误(不会将错误混到一起),但这导致了无法 在单元测试中写入 stdin,这会扰乱测试器的正常工作。另外,zig 将引入一个额外的机制,允许 预测 panic
。
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// 此处开始构建单元测试
// 构建一个单元测试的 Compile
const exe_unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 执行单元测试
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
// 如果想要跳过外部来自于其他包的单元测试(例如依赖中的包)
// 可以使用 skip_foreign_checks
run_exe_unit_tests.skip_foreign_checks = true;
// 构建一个 step,用于执行测试
const test_step = b.step("test", "Run unit tests");
// 测试 step 依赖上方构建的 run_exe_unit_tests
test_step.dependOn(&run_exe_unit_tests.step);
}
以上代码中,先通过 b.addTest
构建一个单元测试的 Compile
,随后进行执行并将其绑定到 test
Step 上。
高级功能
引用依赖中的 build.zig
该特性自 0.11
引入,允许包引用依赖的 build.zig
中提供的一些函数,示例如下:
pkg1
的源代码部分:
const std = @import("std");
pub fn build(b: *std.Build) void {
@import("pkg2").helperFunction(b);
}
.{
.name = .pkg1,
.version = "0.0.0",
.fingerprint = 0x759ba61b105d16f9,
.dependencies = .{
.pkg2 = .{
// path 为本地包的路径
.path = "../pkg2",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
},
}
pkg2
的源代码部分:
const std = @import("std");
pub fn build(b: *std.Build) void {
_ = b;
}
pub fn helperFunction(artifact: *std.Build) void {
_ = artifact;
}
.{
.name = .pkg2,
.version = "0.0.0",
.fingerprint = 0xec92f7a1a7362798,
.dependencies = .{},
.paths = .{
"build.zig",
"build.zig.zon",
},
}
交叉编译
得益于 LLVM 的存在,zig 支持交叉编译到任何 LLVM 的目标代码,zig 可以很方便的处理交叉编译,只需要指定好恰当的 target 即可。
关于所有的 target,可以使用 zig targets
查看。
最常用的一个 target 设置可能是 b.standardTargetOptions
,它会允许读取命令行输入来决定构建目标 target,它返回一个 ResolvedTarget
。
如果需要手动指定一个 target,可以手动构建一个 std.Target.Query
传递给构建(addExecutable
和 addStaticLibrary
等),如:
// 构建一个target
const target_query = std.Target.Query{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .gnu,
};
const ResolvedTarget = std.Build.ResolvedTarget;
// 解析的target
const resolved_target: ResolvedTarget = b.resolveTargetQuery(target_query);
// 解析结果
const target: std.Target = resolved_target.result;
_ = target;
// 构建 exe
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("main.zig"),
// 实际使用的是resolved_target
.target = resolved_target,
.optimize = optimize,
});
值得注意的是,目前 zig 已经将 target query
和 resolved target
完全分开,如果要手动指定构建目标,需要先创建一个 Query
,再使用 b.resolveTargetQuery
进行解析。
关于该部分的变动可以参考此处的 PR:Move many settings from being per-Compilation to being per-Module.
embedFile
@embedFile
是由 zig 提供的一个内嵌文件的方式,它的引入规则与 @import
相同。
在 build.zig
直接使用 addAnonymousImport
添加一个匿名模块即可,如:
const std = @import("std");
const hello = @embedFile("hello");
// const hello = @embedFile("hello.txt"); 均可以
pub fn main() !void {
std.debug.print("{s}\n", .{hello});
}
Hello, World!
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addAnonymousImport(
"hello",
.{ .root_source_file = b.path("src/hello.txt") },
);
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// zig 提供了一个方便的函数允许我们直接运行构建结果
const run_cmd = b.addRunArtifact(exe);
// 指定依赖
run_cmd.step.dependOn(b.getInstallStep());
// 传递参数
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 指定一个 step 为 run
const run_step = b.step("run", "Run the app");
// 指定该 step 依赖于 run_exe,即实际的运行
run_step.dependOn(&run_cmd.step);
}
不仅仅是以上两种方式,匿名模块还支持直接使用其他程序输出,具体可参考下面一小节。
执行外部命令
zig 的构建系统还允许我们执行一些额外的命令,录入根据 json 生成某些特定的文件(例如 zig 源代码),构建其他的编程语言(不只是 C / C++),如 Golang、Rust、前端项目构建等等!
例如我们可以让 zig 在构建时调用系统的 sh 来输出 hello 并使用 @embedFile
传递给包:
const std = @import("std");
const hello = @embedFile("hello");
pub fn main() !void {
std.debug.print("{s}", .{hello});
}
const std = @import("std");
pub fn build(b: *std.Build) !void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 在 windows 平台无法使用 bash,故我们直接返回
if (target.result.os.tag == .windows) {
return;
}
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 构建一个运行命令
const run_sys_cmd = b.addSystemCommand(&.{
"/bin/sh",
"-c",
});
// 添加参数,此方法允许添加多个参数
// 也可以使用 addArg 来添加单个参数
run_sys_cmd.addArgs(&.{
"echo hello",
});
// 尝试运行命令并捕获标准输出
// 也可以使用 captureStdErr 来捕获标准错误输出
const output = run_sys_cmd.captureStdOut();
// 添加一个匿名的依赖
exe.root_module.addAnonymousImport("hello", .{ .root_source_file = output });
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// zig 提供了一个方便的函数允许我们直接运行构建结果
const run_cmd = b.addRunArtifact(exe);
// 指定依赖
run_cmd.step.dependOn(b.getInstallStep());
// 传递参数
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 指定一个 step 为 run
const run_step = b.step("run", "Run the app");
// 指定该 step 依赖于 run_exe,即实际的运行
run_step.dependOn(&run_cmd.step);
}
构建纯 C++ 项目
由于 GTK 的 C++ 构建过于复杂(需要手动编译 gtkmm),故我们这里选择构建一个 tinytetris:
WARNING
注意:由于依赖了 curses 库,故只能在 linux 进行编译!
const std = @import("std");
pub fn build(b: *std.Build) void {
// 构建目标
const target = b.standardTargetOptions(.{});
// 构建优化模式
const optimize = b.standardOptimizeOption(.{});
if (target.result.os.tag == .windows) {
return;
}
// 添加一个二进制可执行程序构建
// 注意:我们在这里并没有使用 root_source_file 字段
// 该字段是为 zig 源文件准备的
const exe = b.addExecutable(.{
.name = "zig",
.target = target,
.optimize = optimize,
});
// 添加 C 源代码文件,两个参数:
// 源代码路径(相对于build.zig)
// 传递的 flags
// 多个 C 源代码文件可以使用 addCSourceFiles
exe.addCSourceFile(.{
.file = b.path("src/main.cc"),
.flags = &.{},
});
// 链接C++ 标准库
// 同理对于 C 标准库可以使用 linkLibC
exe.linkLibCpp();
// 链接系统库 ncurses
exe.linkSystemLibrary("ncurses");
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// 创建一个运行
const run_cmd = b.addRunArtifact(exe);
// 依赖于构建
run_cmd.step.dependOn(b.getInstallStep());
// 运行时参数传递
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 运行的 step
const run_step = b.step("run", "Run the app");
// 依赖于前面的运行
run_step.dependOn(&run_cmd.step);
}
#include <ctime>
#include <curses.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
// block layout is: {w-1,h-1}{x0,y0}{x1,y1}{x2,y2}{x3,y3} (two bits each)
int x = 431424, y = 598356, r = 427089, px = 247872, py = 799248, pr,
c = 348480, p = 615696, tick, board[20][10],
block[7][4] = {{x, y, x, y},
{r, p, r, p},
{c, c, c, c},
{599636, 431376, 598336, 432192},
{411985, 610832, 415808, 595540},
{px, py, px, py},
{614928, 399424, 615744, 428369}},
score = 0;
// extract a 2-bit number from a block entry
int NUM(int x, int y) { return 3 & block[p][x] >> y; }
// create a new piece, don't remove old one (it has landed and should stick)
void new_piece() {
y = py = 0;
p = rand() % 7;
r = pr = rand() % 4;
x = px = rand() % (10 - NUM(r, 16));
}
// draw the board and score
void frame() {
for (int i = 0; i < 20; i++) {
move(1 + i, 1); // otherwise the box won't draw
for (int j = 0; j < 10; j++) {
board[i][j] && attron(262176 | board[i][j] << 8);
printw(" ");
attroff(262176 | board[i][j] << 8);
}
}
move(21, 1);
printw("Score: %d", score);
refresh();
}
// set the value of the board for a particular (x,y,r) piece
void set_piece(int x, int y, int r, int v) {
for (int i = 0; i < 8; i += 2) {
board[NUM(r, i * 2) + y][NUM(r, (i * 2) + 2) + x] = v;
}
}
// move a piece from old (p*) coords to new
void update_piece() {
set_piece(px, py, pr, 0);
set_piece(px = x, py = y, pr = r, p + 1);
}
// remove line(s) from the board if they're full
void remove_line() {
for (int row = y; row <= y + NUM(r, 18); row++) {
c = 1;
for (int i = 0; i < 10; i++) {
c *= board[row][i];
}
if (!c) {
continue;
}
for (int i = row - 1; i > 0; i--) {
memcpy(&board[i + 1][0], &board[i][0], 40);
}
memset(&board[0][0], 0, 10);
score++;
}
}
// check if placing p at (x,y,r) will be a collision
int check_hit(int x, int y, int r) {
if (y + NUM(r, 18) > 19) {
return 1;
}
set_piece(px, py, pr, 0);
c = 0;
for (int i = 0; i < 8; i += 2) {
board[y + NUM(r, i * 2)][x + NUM(r, (i * 2) + 2)] && c++;
}
set_piece(px, py, pr, p + 1);
return c;
}
// slowly tick the piece y position down so the piece falls
int do_tick() {
if (++tick > 30) {
tick = 0;
if (check_hit(x, y + 1, r)) {
if (!y) {
return 0;
}
remove_line();
new_piece();
} else {
y++;
update_piece();
}
}
return 1;
}
// main game loop with wasd input checking
void runloop() {
while (do_tick()) {
usleep(10000);
if ((c = getch()) == 'a' && x > 0 && !check_hit(x - 1, y, r)) {
x--;
}
if (c == 'd' && x + NUM(r, 16) < 9 && !check_hit(x + 1, y, r)) {
x++;
}
if (c == 's') {
while (!check_hit(x, y + 1, r)) {
y++;
update_piece();
}
remove_line();
new_piece();
}
if (c == 'w') {
++r %= 4;
while (x + NUM(r, 16) > 9) {
x--;
}
if (check_hit(x, y, r)) {
x = px;
r = pr;
}
}
if (c == 'q') {
return;
}
update_piece();
frame();
}
}
// init curses and start runloop
int main() {
srand(time(0));
initscr();
start_color();
// colours indexed by their position in the block
for (int i = 1; i < 8; i++) {
init_pair(i, i, 0);
}
new_piece();
resizeterm(22, 22);
noecho();
timeout(0);
curs_set(0);
box(stdscr, 0, 0);
runloop();
endwin();
}
🅿️ 提示
关于头文件的引入,可以使用 addIncludePath
针对多个 C 源代码文件,zig 提供了函数 addCSourceFiles
用于便捷地添加多个源文件。
关于 libc++
的问题
zig 的工具链使用的是 libc++
(LLVM ABI),而 GNU 的则是 libstdc++
,两者的标准库实现略有不同,这会导致混用可能出现问题!
正确的做法是,手动编译依赖的源代码(一般是出现问题的),或者使用 -nostdinc++ -nostdlib++
指示不使用默认标准库,并链接 GNU 的标准库,具体可以参考该 issue。