zig の Build.Step の詳細
0.15.1
zig の build でちょっと凝ったことをやろうとする場合、既存の Step を組み合わせるよりも自作した方が簡単に実現できる場合がある。
- LazyPath の中身にアクセスしたい場合
- cache の挙動を制御したい場合(cmake の再実行を抑止するとか)
など。
LazyPath の中身にアクセスする Step
Step を作るにはイディオムのようなものがあって、
おおむね以下のような感じになります。
pub const PrintLazyPathContent = struct {
step: std.Build.Step,
path: std.Build.LazyPath,
pub fn create(owner: *std.Build, path: std.Build.LazyPath) *@This() {
const this = owner.allocator.create(@This()) catch @panic("OOM");
this.* = .{
.step = std.Build.Step.init(.{
.id = .custom,
.name = "PrintLazyPath",
.owner = owner,
.makeFn = make,
}),
.path = path,
};
// 👇 この step が path を待つ
path.addStepDependencies(&this.step);
return this;
}
fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void {
// メンバーのポインタから持ち主を得る 👇
// これが Step の作法になっている。
// this は予約後ではなく普通の変数。
const this: *@This() = @fieldParentPtr("step", step);
const b = step.owner;
// path解決
const path3 = this.path.getPath3(b, step);
// b.cache_root.handle は Dir
const file = try b.cache_root.handle.openFile(path3.sub_path, .{});
defer file.close();
const content = try file.readToEndAlloc(b.allocator, std.math.maxInt(usize));
std.log.debug("content: {s}", .{content});
}
};
Step の依存関係
b.getInstallStep().dependOn(&install.step);
のように std.Build.Step で依存 graph を構築する。
明示的に dependOn する必要がある場合は限られていて、
std.Build.LazyPath などを介して裏で繋げてくれる関数がわりとある。
b.getInstallStep() や b.step() 関数で得られる step を root として木を構築して、
末端から逆順にタスクを実行する。
zig build に --summary all 引数をつけることで依存ツリーを表示するとわかりやすい。
> zig build --summary all
Build Summary: 17/17 steps succeeded
install cached
├─ install xrhandsfb cached
│ └─ compile exe xrhandsfb Debug native cached 73ms MaxRSS:34M
│ ├─ WriteFile cached
│ │ └─ WriteFile cached
│ │ ├─ CmakeStep cached
│ │ │ └─ GetVcEnv cached
│ │ │ └─ FindVcInstall success
│ │ ├─ CmakeStep cached
│ │ │ └─ GetVcEnv cached
│ │ │ └─ FindVcInstall success
│ │ └─ CmakeStep cached
│ │ └─ GetVcEnv cached
│ │ └─ FindVcInstall success
│ ├─ compile lib SampleXrFramework Debug native cached 50ms MaxRSS:34M
│ │ ├─ WriteFile (+3 more reused dependencies)
│ │ ├─ WriteFile (+3 more reused dependencies)
│ │ └─ WriteFile (+3 more reused dependencies)
│ ├─ WriteFile (+3 more reused dependencies)
│ └─ compile lib SampleXrFramework Debug native (+3 more reused dependencies)
├─ install generated/ cached
│ └─ WriteFile (+3 more reused dependencies)
└─ install assets/ cached
std.Build.LazyPath
step と連携する重要な型に LazyPath というのがある。
b.path("src/main.zig")
これは union になっていて、
- src_path
- generated
- cwd_relative
- dependency
となっている。
一番単純なのが cwd_relative で文字列から直接作る。
var path = std.Build.LazyPath{ .cwd_relative = "C:/path/to/abs/path/some.exe" };
Std.Build.Step.Run と LazyPath(generated)
generated な LazyPath は動的に path が決まる。
generatedLazyPath.addStepDependencies(step) のように待つことができる。
中で dependsOn を呼んでいる。
pub const GeneratedFile = struct {
/// The step that generates the file
step: *Step,
/// The path to the generated file. Must be either absolute or relative to the build runner cwd.
/// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.
// 👇
path: ?[]const u8 = null,
};
Std.Build.Step.Run で
名前に Out がついて、std.Build.LazyPath を返す関数は
generated な LazyPath を返すものが多い。
- addOutputFileArg
- captureStdOut
中で generatedLazyPath.addStepDependencies してくれそうなところは dependOn を
呼ぶ必要が無い、というか呼ばない方がよい。
もし呼ぶと、不要な dependency が増えたり、悪くするとエラーになる。
--summary all で確認するべし。
generated を makeFn でセットする例
manifest と cache
fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void {
const this: *@This() = @fieldParentPtr("step", step);
const b = step.owner;
// manifest
var man = step.owner.graph.cache.obtain();
defer man.deinit();
// man にstepの入力に応じた byte を追加して変化させる
if (try step.cacheHitAndWatch(&man)) {
// 前回の実行から manifest が変化していないので
// キャッシュを返すべし
const digest = man.final();
const prefix_dir = try b.cache_root.join(b.allocator, &.{ "o", &digest, "prefix" });
this.output.path = prefix_dir;
return;
}
// .zig-cache/o/XXXXXXXXXXXXXXXXX が決まる
const digest = man.final();
const cache_dir = b.pathJoin(&.{ "o", &digest });
// b.cache_root.handle が std.fs.Dir なので、ここから相対パスとして扱うのが基本
b.cache_root.handle.makePath(cache_dir) catch |err| {
return step.fail("unable to make path '{f}{s}': {s}", .{
b.cache_root, cache_dir, @errorName(err),
});
};
// fullpath の文字列を得る例
const cwd = b.fmt("{s}/o/{s}", .{ try b.cache_root.handle.realpathAlloc(b.allocator, ""), &digest });
// タスク
// キャッシュを登録する
try step.writeManifestAndWatch(&man);
}
参考
使用例。
Discussion