zig のビルドシステム
zig-0.13.0
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);
}
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 をビルドする。
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);
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);
}
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") });
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");
dependency に引数を渡す
const raylib_dep = b.dependency("raylib", .{
.target = target, // 👈 -Dtarget を渡す意味になる
});
c/c++ のソースを download する
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...
- name
- version
- etc
各種 step
std.Build.default_step
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
で動いております。
手順参考 👇
@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