zig のビルドシステム

2024/06/08に公開

zig-0.13.0

20240820 compile_commands.json
20240808 整理

zig は組み込みでタスクランナーを搭載しており、
build.zig に記述できます。

一次ドキュメント

https://ziglang.org/learn/build-system/

に主だった使い方が書いてあります。

最小限

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

pub fn build(b: *std.Build) void {
    const exe = b.addExecutable(.{
        .name = "hello",
        .root_source_file = b.path("hello.zig"),
        .target = b.host, // 👈 実行環境と同じ。クロスビルドしない
    });
    b.installArtifact(exe);
}

zig init で生成される build.zig を読む

$ mkdir zig_hello
$ cd zig_hello
$ zig init
$ fd
build.zig
build.zig.zon
src\
src\main.zig
src\root.zig

Step 確認

抜粋
> zig build --help
Usage: zig.exe build [steps] [options]

Steps:
  # 👇 zig build(step 引数無し) とするとデフォルトの `install` Step が実行される
  install (default)            Copy build artifacts to prefix path
  uninstall                    Remove build artifacts from prefix path
  # 👇 b.step("run", "Run the app") で登録
  run                          Run the app
  # 👇 b.step("test", "Run unit tests") で登録
  test                         Run unit tests

build.zig の内容

lib

src/root.zig を root とした lib をビルドする。

build.zig
    const lib = b.addStaticLibrary(.{
        .name = "hello_build",
        // In this case the main source file is merely a path, however, in more
        // complicated build scripts, this could be a generated file.
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });

    // This declares intent for the library to be installed into the standard
    // location when the user invokes the "install" step (the default step when
    // running `zig build`).
    b.installArtifact(lib); // 👈 install step に artifact を追加する。zig-out へ

exe

src/main.zigpub fn main をエントリーポイントとした exe をビルドする。

build.zig
    const exe = b.addExecutable(.{
        .name = "hello_build",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // This declares intent for the executable to be installed into the
    // standard location when the user invokes the "install" step (the default
    // step when running `zig build`).
    b.installArtifact(exe); // 👈 install step に artifact を追加する。zig-out へ

run

exe をビルドする。
exe を実行する。
zig build の引数を exe に渡す。

build.zig
    // This *creates* a Run step in the build graph, to be executed when another
    // step is evaluated that depends on it. The next line below will establish
    // such a dependency.
    const run_cmd = b.addRunArtifact(exe); // 👈 Run Step 追加

    // By making the run step depend on the install step, it will be run from the
    // installation directory rather than directly from within the cache directory.
    // This is not necessary, however, if the application depends on other installed
    // files, this ensures they will be present and in the expected location.
    run_cmd.step.dependOn(b.getInstallStep()); // 👈 Run Step は Install step に依存

    // This allows the user to pass arguments to the application in the build
    // command itself, like this: `zig build run -- arg1 arg2 etc`
    if (b.args) |args| {
        run_cmd.addArgs(args); // 👈Run Step にコマンドライン引数追加
    }

    // This creates a build step. It will be visible in the `zig build --help` menu,
    // and can be selected like this: `zig build run`
    // This will evaluate the `run` step rather than the default, which is "install".
    const run_step = b.step("run", "Run the app"); // 👈 zig build に `run` を登録
    run_step.dependOn(&run_cmd.step); // 👈 zig build run がRun Step に依存

test

lib と exe を両方ビルドしてテストする。

build.zig
    // Creates a step for unit testing. This only builds the test executable
    // but does not run it.
    const lib_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });

    const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);

    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);

    // Similar to creating the run step earlier, this exposes a `test` step to
    // the `zig build --help` menu, providing a way for the user to request
    // running the unit tests.
    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_lib_unit_tests.step);
    test_step.dependOn(&run_exe_unit_tests.step);

最小まで削る

exe のビルドに必要な最小まで削ってみる。

build.zig
src\main.zig
build.zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // *std.Build.Step.Compile を作る
    const exe = b.addExecutable(.{
        .name = "zig_hello",
        .target = target,
        .optimize = optimize,
        // std.Build.LazyPath
        .root_source_file = b.path("src/main.zig"), // zig のエントリーポイント
    });
    // ビルド対象に追加して zig-out に出てくるようにする
    b.installArtifact(exe);
}

build.zig.zon について

他のライブラリーへの依存情報の記述と、
自分のライブラリーに関する公開情報の2本立て。

dependencies

外部ライブラリへの参照情報を記述できる。
アーカイブの url や, git の repository を記述できる。

単なるダウンロードマシンとして使うことが可能です。
download される側には build.zig は無くても問題ありません。

  • exe を download してコード生成に使う
  • c のライブラリを download して zig cc でビルドして組み込む
  • zig のライブラリを download して import する

dependency を使う

const raylib_dep = b.dependency("raylib", .{});

のようにします。

dependency の中身にアクセスする

const raylib_src = raylib_dep.path("src");
// "" で root を得る
const raylib_root = raylib_dep.path("");

dependency の module を得る

const sokol_module = sokol_dep.module("sokol");

addModule で公開する

公開
const mod_sokol = b.addModule("sokol", .{ .root_source_file = b.path("src/sokol/sokol.zig") });

https://github.com/floooh/sokol-zig/blob/master/build.zig#L29

使う
const dep_sokol = b.dependency("sokol", .{});
// dependency から module を取り出す
const mod_sokol = dep_sokol.module("sokol")
// modle を import に登録
exe.root_module.addImport("sokol", mod_sokol);

dependency の artifact を得る

https://github.com/floooh/sokol-zig-imgui-sample/blob/main/build.zig

const sokol_artifact = sokol_dep.artifact("sokol_clib");

dependency に引数を渡す

const raylib_dep = b.dependency("raylib", .{
  .target = target, // 👈 -Dtarget を渡す意味になる
});

c/c++ のソースを download する

https://github.com/floooh/sokol-zig-imgui-sample/blob/main/deps/cimgui/build.zig.zon

sokol で imgui と cimgui のソースを得る例。
ここで docking ブランチを指定することもできた。

build.zig に公開する

build.zig@import("dependency_name") とすることで dependency の build.zig を
取り込めるぽい。

// "sokol" は dependencies に記述されている
const sokol = @import("sokol");

pub fn build(b: *Build) !void {
   // sokol の build.zig の pub fn emLinkStep が見えている
   sokol.emLinkStep();
}

その他の情報

使ったことが無い。TODO...

https://github.com/ziglang/zig/blob/master/doc/build.zig.zon.md

  • name
  • version
  • etc

各種 step

std.Build.default_step

https://github.com/ziglang/zig/issues/5202

std.Build.Step.Compile

std.Build.Step.InstallArtifact

const run_cmd = b.addRunArtifact(exe);

// pub fn installArtifact(b: *Build, artifact: *Step.Compile) void {
//     b.getInstallStep().dependOn(&b.addInstallArtifact(artifact, .{}).step);
// }

b.installArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());

// ぜんぶビルドしてから run する
b.step("run", "Run the app").dependsOn(&run_cmd.step);
const run_cmd = b.addRunArtifact(exe);

const install = b.addInstallArtifact(exe, .{});
b.getInstallStep().dependOn(&install.step);
run_cmd.step.dependsOn(&install.step);

// exe ひとつを install してから run する
b.step("run", "Run the app").dependsOn(&run_cmd.step);

std.Build.Step.Compile.getEmittedDocs

document 生成ができる。

zig
のような reference ができる。
wasm で動いております。

手順参考 👇

https://sudw1n.gitlab.io/posts/zig-build-docs/

@import の取り扱い

@import の登録

zig の @import は2種類ある。

@import("path_to.zig")

@import("pkg_name")

@import の返り値は struct(type) であり、

const std = @import("std");

のように使う。

@import("relative_path_to.zig")

@import を記述する .zig からの相対パスで指定する。

@import("pkg_name") の登録

build.zig
    // module を作る
    const tinygizmo = b.createModule(.{
        .root_source_file = b.path("tinygizmo/main.zig"),
    });

    // module を @import できるように登録する
    exe.root_module.addImport("tinygizmo", tinygizmo);

target

ターゲットを変更することでクロスコンパイルできる。

target の指定方法

コマンドラインから

build.zig
    // Standard target options allows the person running `zig build` to choose
    // what target to build for. Here we do not override the defaults, which
    // means any target is allowed, and the default is native. Other options
    // for restricting supported target set are available.
    const target = b.standardTargetOptions(.{});

に対して、以下のようにする。

$ zig build -Dtarget=wasm32-emscripten

build.zig にハードコーディングする

        const target = b.resolveTargetQuery(.{
            .cpu_arch = .wasm32,
            .os_tag = .freestanding,
        }),

wasm32-freestanding

https://github.com/meheleventyone/zig-wasm-test

zig-0.13.0 向けにしてみた。

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

pub fn build(b: *std.Build) void {
    // addSharedLibrary ではなく
    // exe に entry = .disabled を指定する
    const exe = b.addExecutable(.{
        .name = "wasmtest",
        .root_source_file = b.path("src/main.zig"),
        // -target wasm32-freestanding
        .target = b.resolveTargetQuery(.{
            .cpu_arch = .wasm32,
            .os_tag = .freestanding,
        }),
        .optimize = b.standardOptimizeOption(.{}),
        .version = .{ .major = 0, .minor = 0, .patch = 1 },
    });
    exe.rdynamic = true;
    // 👇
    exe.entry = .disabled;
    b.installArtifact(exe);
}

wasm32-emscripten

https://github.com/floooh/sokol-zig

std.Build

中に allocator が入っていて便利関数がたくさん入っている。

std.Build.LazyPath

  • std.Build からの相対パス
  • std.Build.Dependency からの相対パス
  • cwd_relative

複数の形式を格納できる。

const lazy_path: std.Build.LazyPath = b.path("src/main.zig"); // build.zig のフォルダから相対

// 絶対パスに解決
const real_path: []const u8 = lazy_path.getPath(b);

// 絶対パスから std.Build.LazyPath 化
const abs = std.Build.LazyPath{.cwd_relative = "c:/ProgramFiles/abs/path.txt"};

fmt

雑に sprintf する感じで使える

const str = b.fmt("hoge {s}", .{exe.name});

findProgram

path の通ったところからファイルを検索

C / C++ との混合プロジェクト

clangd 向けに compile_commands.json を出力する

https://github.com/xxxbrian/zig-compile-commands

c のライブラリとリンクする

TODO: raylib の例

https://ziglang.org/ja/learn/samples/#c相互運用性

build.zigcmake と似ていて、
まずCのコンパイルを通す(@cImport できるようにする)。
次にリンクを解決するという2段階で考えます(cやcppをコンパイルしてリンクする)。

c のエントリーポイント

zig の .root_source_file を省いて
C の main addCSourceFile する。

main.cpp
#include <iostream>

int main(int argc, char **argv) {
  std::cout << "hello" << std::endl;
  return 0;
}
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 = "cpp_entry",
        // .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    exe.addCSourceFile(.{
        .file = b.path("main.cpp"),
    });
    exe.linkLibCpp(); // #include <iostream> に必要
    b.installArtifact(exe);
}

ライブラリに含まれる build.zig

Discussion