[zig] ビルドスクリプト内でパッケージのモジュールを直接使用する方法

## はじめに

zig バージョン0.11系でパッケージ管理がサポートされ、外部依存のモジュールをカジュアルに扱えるようになった。
しかし、0.11.0のリリースノート (https://ziglang.org/download/0.11.0/release-notes.html#Package-Management) を見ても、実行バイナリに対してパッケージを適用する方法しか書かれてなく、ビルドスクリプト内からパッケージのモジュールを直接使用する方法は明記されていない。

:::note info

`NodeJS`の`npm`における`dev dependency`を思い浮かべていただくと理解しやすいかも。

:::

ビルドスクリプト内で、パッケージとして公開された機能を使用する方法[^1] が見つかったため記録として残す。

[^1]: 経緯は、mitchellh/zig-objcのレポジトリで使ってて、ナンジャラホイかと調べてみた

## 方法

::: note warn

2023-11-2時点のnightly buildで確認しているため、安定版の0.11.xでは動かないかもしれない。

:::

方法は、いったって簡単で、`build.zig`の`build`関数内で`build.zig.zon`で指定した依存パッケージをインポートするだけ。
対象バッケージの`build.zig`がインポートされる。

`build.zig`で型やメソッドを公開(`pub const〜` や `pub fn〜`を記述)すれば、呼び出し元で使用できるようになる。

例えば`build.zig.zon`に`foo`と名付けたパッケージを依存として追加した場合

```zig
pub fn build(b: *std.Build) void {
   ....
   const foo = @import("foo");
   ....
}
```

## 具体例

一つのビルド環境で複数の実行バイナリがビルドでき、そのバイナリを選択して実行（再ビルド）できるビルドステップである`zig-example-step`を作成したので、これを用いてより具体的な手順の説明を行う。
`zig-example-step`は以下に公開している。

https://github.com/ritalin/zig-example-step

* アプリケーション作成のための環境構築

以下のコマンドを実行する
```
zig init-exe
```

* build.zig.zonファイルをフォルダルートに作成する

`build.zig.zon`の内容
```zig
.{
    .name = "sample",
    .version = "0.0.0",
    .paths = .{ // pathsは0.12系で追加予定。0.11系では不要（なはず
        "build.zig",
        "build.zig.zon",
        "src/",
    },
    .dependencies = .{
        .example_step = "https://github.com/ritalin/zig-example-step/archive/main.tar.gz",
        // hash値は2023-12-07時点のもの
        .hash = "122062deb341bae70b230bb447bdad522ff2e3538124153bcf72028935d392ef6496"
    }
}
```

作成後のフォルダ構成

```
/
  📁src
      🗒️main.zig
  🗒️build.zig
  🗒️build.zig.zon
```

* build.zigでパッケージを使用する

`build.zig`の内容

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "sample",
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });
    // zig buildで一括インポートできるよう以下の行は残す
    b.installArtifact(exe);

    // ↓↓↓↓以下を追加
    
    // パッケージをインポートする
    const example_package = @import("example_step");
    // インスタンス化
    const example_cmd = example_package.RunExample.create(b);
    // exampleとして登録する
    example_cmd.addExample(exe, .{});
    
    // ↑↑↑↑追加ここまで
    
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());

    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    // unit testは変更なしのため省略
}
```

* 以下のコマンドが実行可能となる

```
zig example -- sample
```

## パッケージ管理についてのおまけ

`zig`言語のバージョン0.12系で、`URL`でのアーカイブファイルの指定に加え、以下の指定が追加されている。

<dl>
<dt>path</dt>
<dd>
ローカルファイルに展開されたパッケージを直接使用する。

指定先は、対象パッケージの`build.zig`が存在するフォルダ

```zig
.dependencies = .{
   .path = "/path/to"
}
```

</dd>

<dt>githubなどからレポジトリを直接参照</dt>
<dd>
URLとして、git+httpもしくはgit+httpsでURLを記述する。

```zig
.dependencies = .{
   .url = "git+https://github.com/ritalin/zig-example-step#HEAD"
}
```

</dd>
</dl>

:::note info

レポジトリを直接参照は一見便利そうに見えるが、レポジトリをクローンしてくるのではなく、レポジトリのファイルをダウンロードしてくれるだけ。
しかも`.hash`も適切に指定しなければならない（間違ってたらビルド時に教えてはくれるが）

個人的には別途パッケージをクローンし、親レポジトリでサブモジュールとして追加した上で、`.path`でフォルダを指定するのが楽だと思う（.hashの指定も不要）。

:::