Skip to content
zig 版本:0.14.0

构建系统

Zig 除了是一门编程语言外,本身还是一套完整的工具链,例如:

  • zig cczig c++ C/C++ 编译器
  • zig build 适用于 Zig/C/C++ 的构建系统

本章节就来介绍 Zig 的构建系统。

理念

Zig 使用 build.zig 文件来描述一个项目的构建步骤。

如其名字所示,该文件本身就是一个 Zig 程序,而不是类似 Cargo.tomlCMakeLists.txt 这样的领域特定语言(DSL)。

这样的好处很明显,表达能力更强,开发者只需要使用同一门语言即可进行项目构建,减轻了用户心智。

一个典型的构建文件如下:

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 = "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 的构建分为两个阶段:

  1. 生成由 std.Build.Step 构成有向无环图(DAG)
  2. 执行真正的构建逻辑

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,大致的图如下:

sh
数据C
|
C --> B --> A
      |     |
      |     程序A
      |
      库B

例如我们可以在 build.zig 中添加一个运行程序的步骤:

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 模式构建。

以下讲述四种构建模式的区别:

DebugReleaseFastReleaseSafeReleaseSmall
构建速度很快构建速度慢构建速度慢构建速度慢
启用安全检查启用安全检查启用安全检查禁用安全检查
较差的运行效率很好的运行效率中等的运行效率中等的运行效率
二进制体积大二进制体积大二进制体积大二进制体积小
无复现构建可复现构建可复现构建可复现构建
关于 Debug 不可复现的原因

关于为什么 Debug 是不可复现的,zig 官方手册并未给出具体说明,以下内容为询问社区获得:

在 Debug 构建模式下,编译器会添加一些随机因素进入到程序中(例如内存结构不同),所以任何没有明确说明内存布局的容器在 Debug 构建下可能会有所不同,这便于我们在 Debug 模式下快速暴露某些错误。

有意思的是,这并不会影响程序正常运行,除非你的程序逻辑有问题。

这是 zig 加强安全性的一种方式(尽可能提高安全性但又不至于造成类似 Rust 开发时过重的心智负担)。

CLI 参数

通过 b.option 使构建脚本部分配置由用户决定(通过命令行参数传递),这也可用于依赖于当前包的其他包。

zig
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 可以看到输出多了一行:

sh
Project-Specific Options:
  -Dis_strip=[bool]            whether strip executable

Options 编译期配置

Options 允许我们将一些信息传递到项目中,例如我们可以以此实现让程序打印构建时的时间戳:

zig
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});
}
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 = "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 的方式如下:

zig
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 实现该功能。示例:

zig
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 本身提供了一个实验性的文档生成器,它支持搜索查询,操作如下:

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

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 = "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 的源代码部分:

zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    @import("pkg2").helperFunction(b);
}
zig
.{
    .name = .pkg1,
    .version = "0.0.0",
    .fingerprint = 0x759ba61b105d16f9,
    .dependencies = .{
        .pkg2 = .{
            // path 为本地包的路径
            .path = "../pkg2",
        },
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
    },
}

pkg2 的源代码部分:

zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    _ = b;
}

pub fn helperFunction(artifact: *std.Build) void {
    _ = artifact;
}
zig
.{
    .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 传递给构建(addExecutableaddStaticLibrary 等),如:

zig
// 构建一个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 queryresolved target 完全分开,如果要手动指定构建目标,需要先创建一个 Query,再使用 b.resolveTargetQuery 进行解析。

关于该部分的变动可以参考此处的 PR:Move many settings from being per-Compilation to being per-Module.

embedFile

@embedFile 是由 zig 提供的一个内嵌文件的方式,它的引入规则与 @import 相同。

build.zig 直接使用 addAnonymousImport 添加一个匿名模块即可,如:

zig
const std = @import("std");
const hello = @embedFile("hello");
// const hello = @embedFile("hello.txt"); 均可以

pub fn main() !void {
    std.debug.print("{s}\n", .{hello});
}
txt
Hello, World!
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 = "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 传递给包:

zig
const std = @import("std");
const hello = @embedFile("hello");

pub fn main() !void {
    std.debug.print("{s}", .{hello});
}
zig
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 进行编译!

zig
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);
}
cpp
#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

更多参考