🕰️

Zigでビルド時にZig依存を作る

2024/04/03に公開

はじめに

きっかけ

アイヌ語のIMEを作る時に、COMは自動的にアンロードされないので、いまどのバージョンを実行しているんだっけということを知らせるために、versionという定数にバージョンを入れたいが、手書きで書き換えるの流石にめんどくさいので、ビルド時今の日付・時間を自動的に書き込んでインポートしたいという次第です。

TL;DR

  1. pub const VERSION = "04/03 12:34";というZIGファイル生成するスクリプトを作る。
  2. build.zigでそれをビルドし、匿名インポートとして追加する。
  3. 本来のプロジェクトファイルでconst VERSION = @import("version").VERSION;

インストール・開発環境整備

以前の文章参照。

実装

以下の資料を参照して、時間を書き込む。
https://ziglang.org/learn/build-system/
https://zenn.dev/catallaxy_dev/articles/ziglang-how-to-get-date

時間付きバージョンファイルの生成

tools\generate_time.zig
const std = @import("std");
pub fn main() !void {
    var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena_state.deinit();
    const arena = arena_state.allocator();

    const args = try std.process.argsAlloc(arena);
    if (args.len != 2) return error.@"Usage: version.zig <version_file_path>\n";

    const output_file_path = args[1];

    const jst_offset = 60 * 60 * 9;

    const now = std.time.epoch.EpochSeconds{ .secs = @intCast(std.time.timestamp() + jst_offset) };
    const month_day = now.getEpochDay().calculateYearDay().calculateMonthDay();
    const day_seconds = now.getDaySeconds();
    const month = month_day.month.numeric();
    const day = month_day.day_index + 1;
    const hour = day_seconds.getHoursIntoDay();
    const minute = day_seconds.getMinutesIntoHour();

    var version_file = try std.fs.cwd().createFile(output_file_path, .{});
    defer version_file.close();

    const version = try std.fmt.allocPrint(std.heap.page_allocator, "{d:0>2}/{d:0>2} {d:0>2}:{d:0>2}", .{ month, day, hour, minute });
    const module = try std.fmt.allocPrint(std.heap.page_allocator, "pub const VERSION = \"{s}\";", .{version});
    try version_file.writeAll(module);

    return std.process.cleanExit();
}

ビルドステップの追加

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

pub fn build(b: *std.Build) !void {
    const tool = b.addExecutable(.{ .name = "generate_time", .root_source_file = .{ .path = "tools/generate_time.zig" }, .target = b.host });
    const tool_step = b.addRunArtifact(tool);
    tool_step.has_side_effects = true;
    const output = tool_step.addOutputFileArg("version.zig");

    const artifact = // exeなり、dllなり、libなり...
    artifact.root_module.addAnonymousImport("version", .{ .root_source_file = output });
    b.installArtifact(artifact);
}

キャッシュについて

現時点では、以下のIssueやPull Requestがある通り、もしhas_side_effectstrueになっている場合、addOutputFileArgファイルパスが伝わらないという問題があるので、以上の方法だとエラーが出てしまうため、以下のように時間をパスすることで一時的な解決を図れる。
https://github.com/ziglang/zig/issues/18281
https://github.com/ziglang/zig/pull/19194

pub fn build(b: *std.Build) !void {
    const tool = b.addExecutable(.{ .name = "generate_time", .root_source_file = .{ .path = "tools/generate_time.zig" }, .target = b.host });

    const tool_step = b.addRunArtifact(tool);
    const output = tool_step.addOutputFileArg("version.zig");
    tool_step.addArg(try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{std.time.timestamp()}));

    // ...
}
const std = @import("std");
pub fn main() !void {
    // ...
    const args = try std.process.argsAlloc(arena);
    defer std.process.argsFree(arena, args);

    if (args.len < 2) return error.@"Usage: version.zig <version_file_path>\n";

    const output_file_path = args[1];
    // ...
}

Discussion