zig のビルドシステム

2024/06/08に公開

zig-0.13.0

https://ziglang.org/ja/learn/overview/#zigビルドシステム

zig build とは何か

コマンドラインから zig build するとビルドシステムが実行されます。
Step 引数を指定しない場合は、デフォルトの Step が発動します。
Step の内容は build.zig により記述されます。

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

zig build-system

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

https://www.youtube.com/watch?v=wFlyUzUVFhw&t=3s

Step.Compile 詳細

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

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

TODO: raylib の例

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

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

c のエントリーポイント

普通に使える。

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

zon 経由の外部依存

build.zig.zon の dependencies を使う

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

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

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

sokol-zig を使う例

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

zon の dependency を追加

build.zig.zon
    .dependencies = .{
        .sokol = .{
            .url = "https://github.com/floooh/sokol-zig/archive/refs/heads/master.zip",
        },

hash

> zig build
C:\sokol_sample\build.zig.zon:27:20: error: dependency is missing hash field
            .url = "https://github.com/floooh/sokol-zig/archive/refs/heads/master.zip",
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: expected .hash = "1220ce851df99fd0ddeb6f20e75cdd35dd4e24404f7de7a2aa4cc4f37de58f039cab",

とりあえずビルドすると hash 値が表示されるので、これを zon に追記します。

build.zig.zon
    .dependencies = .{
        .sokol = .{
            .url = "https://github.com/floooh/sokol-zig/archive/refs/heads/master.zip",
            .hash = "1220ce851df99fd0ddeb6f20e75cdd35dd4e24404f7de7a2aa4cc4f37de58f039cab",
        },
   }

import の追加

build.zig
    // zon の dependency sokol
    const sokol_dep = b.dependency("sokol", .{
        .target = target,
        .optimize = optimize,
    });
    // zon の dependency から "sokol" import を取り出して、 exe に追加する。
    exe.root_module.addImport("sokol", sokol_dep.module("sokol"));

import できるようになります。

const sokol = @import("sokol");

https://github.com/floooh/sokol-zig/blob/master/src/examples/clear.zig

を src/main.zig に上書きしたら動きました。

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

Discussion