zig のビルドシステム
zig-0.13.0
20241017 整理
20240820 compile_commands.json
20240808 整理
zig は組み込みでタスクランナーを搭載しており、
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);
}
std.Build.Step
step が zig build system のタスクです。
step を一覧することができます。
> zig build -l
# 👇 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
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
build.zig の内容
option
b.option
でコマンドライン引数を追加できます。
> zig build --help
Project-Specific Options:
-Dtarget=[string] The CPU architecture, OS, and ABI to build for
-Dcpu=[string] Target CPU features to add or subtract
-Ddynamic-linker=[string] Path to interpreter on the target system
-Doptimize=[enum] Prioritize performance, safety, or binary size
Supported Values:
Debug
ReleaseSafe
ReleaseFast
ReleaseSmall
build.zig
の冒頭の target と optimize はデフォルトで定義済みのオプションです。
// 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(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
lib
src/root.zig
を root とした staticlib をビルドする。
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.zig
の pub fn main
をエントリーポイントとした exe をビルドする。
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 に渡す。
// 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 を両方ビルドしてテストする。
// 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);
// test が有効になった exe を作成する
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
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);
}
dependencies
build.zig.zon
subfolder を dependency に分割する
zig init
の lib を dependency に分割する例。
src/root.zig
を deps/lib/src/root.zig
に移動。
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{
.name = "zig_hello",
// 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);
}
.{
.name = "lib",
.version = "0.0.0",
.paths = .{""},
}
相対パスの参照追加。
.dependencies = .{
.lib = .{
.path = "deps/lib",
},
}
build.zig の dependency を追加。以下のように書き換えます。
const lib = b.addStaticLibrary(.{
.name = "zig_hello",
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
// 👇 書き換え
const lib_dep = b.dependency("lib", .{ // 👈 build.zig.zon に書いた名前
.target = target,
.optimize = optimize,
});
const lib = lib_dep.artifact("zig_hello"); // b.installArtifact した名前
// 👇 重要。これがないと step が繋がらない
b.installArtifact(lib);
target, optimize を伝搬させる
const raylib_dep = b.dependency("raylib", .{
.target = target, // 👈 コマンドラインの -Dtarget=xxx を渡す意味になる
.optimize = optimize,
});
対象の dependency
に build.zig
と build.zig.zon
が含まれている場合は、
伝搬させることでビルドを制御できます。
外部dependency を使う
.dependencies = .{
.lib = .{
.url = "xxx",
.hash = "xxx",
},
}
外部ライブラリへの参照情報を記述できる。
アーカイブの url や, git の repository を記述できる。
単なるダウンロードマシンとして使うことが可能です。
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
で module を公開する
const mod_sokol = b.addModule("sokol", .{
.root_source_file = b.path("src/sokol/sokol.zig"),
});
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 を得る
const sokol_artifact = sokol_dep.artifact("sokol_clib");
// c の library
exe.linkLibrary(sokol_artifact);
// zig の library
exe.root_module.add_import("module_name", &sokol_artifact.root_module);
// 足りない include を足してやる
sokol_artifact.addIncludePath(b.path("include_path"));
c/c++ のソースを download する
sokol で imgui と cimgui のソースを得る例。
ここで docking
ブランチを指定することもできた。
build.zig の関数や変数を公開する
build.zig
で @import("dependency_name")
とすることで dependency の build.zig を
import できます。
pub
の変数や関数にアクセスできます。
// "sokol" は dependencies に記述されている
const sokol_build = @import("sokol");
pub fn build(b: *Build) !void {
// sokol の build.zig の pub fn emLinkStep が見えている
sokol_build.emLinkStep();
}
各種 step
std.Build.default_step
std.Build.Step.Compile
コンパイル生成物。
中に module が入っている。
std.Build.Step.WriteFile
複雑なファイル操作に必須
std.Build.Step.Run
コマンド呼び出し
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
で動いております。
手順参考 👇
@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") の登録
// module を作る
const tinygizmo = b.createModule(.{
.root_source_file = b.path("tinygizmo/main.zig"),
});
// module を @import できるように登録する
exe.root_module.addImport("tinygizmo", tinygizmo);
target
ターゲットを変更することでクロスコンパイルできる。
target の指定方法
コマンドラインから
// 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
を zig-0.13.0
向けにしてみた。
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
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 を出力する
c のライブラリとリンクする
TODO: raylib の例
build.zig
は cmake
と似ていて、
まずCのコンパイルを通す(@cImport できるようにする)。
次にリンクを解決するという2段階で考えます(cやcppをコンパイルしてリンクする)。
c のエントリーポイント
zig の .root_source_file
を省いて
C の main
addCSourceFile する。
#include <iostream>
int main(int argc, char **argv) {
std::cout << "hello" << std::endl;
return 0;
}
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);
}
Discussion