3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Zig言語で自己依存モジュールを作成する

Last updated at Posted at 2023-04-22

TL;DR

以下のIssueにやり方書かれてるから読むよろし。

はじめに

まず自己依存モジュールとは、モジュールの内側から自身が属するモジュールをインポートすることを指している。

具体的には、x1.zigx2.zigがモジュールxに属している時

// x1.zig

const x = @import("x");
// ....
// x2.zig

const x = @import("x");
// ....

のように記述すること。

stdモジュール内で割とよく見かけると思う。

小さなモジュールであれば、各ソースユニットを相対パスでインポートすればよいが、規模が大きくなると自己依存モジュールが効果を発揮する。

ところでこの自己依存モジュール、バージョン0.10.x系では、何も考えなくても自己依存モジュールを作ることができた(らしい)。
しかし、バージョン0.11.x以降1、ちょっと意識して実装する必要がある模様。

方法については、以下のIssueに記載されている。

この記事は、Issueの内容を頑張って噛み砕いてみた記録。

環境

  • zig 0.11系のmaster

Zig言語がまだバージョン1.0に到達していないということもあり、将来的にちゃぶ台返されるかもしれないことに注意。

準備

ここでは説明のために、自身のパッケージの中にモジュールを作って直接利用する構成とする。2

├── build.zig
└── src
    ├── main.zig
    ├── mod_a
    │   ├── a.zig
    │   ├── bar.zig
    │   └── foo.zig
    └── mod_a.zig

それぞれの内容は、以下の通り。

  • mod_a/a.zig
const mod_a = @import("mod_a");

pub fn a1() void {
    mod_a.foo("foo_1");
}

pub fn a2() void {
    mod_a.bar();
}
  • mod_a/bar.zig
const std = @import("std");

pub fn bar() void {
    std.debug.print("bar called\n", .{});
}
  • mod_a/foo.zig
const std = @import("std");

pub fn foo(name: []const u8) void {
    std.debug.print("{s} called\n", .{ name });
}
  • mod_a.zig
pub const foo = @import("./mod_a/foo.zig").foo;
pub const bar = @import("./mod_a/bar.zig").bar;

const a = @import("./mod_a/a.zig");
pub const a1 = a.a1;
pub const a2 = a.a2;
  • main.zig
const mod_a = @import("mod_a");

pub fn main() void {
    mod_a.a1();
    mod_a.a2();
}

実装方法

build.zigでモジュールが利用できるよう宣言する

1. build関数内で、std.Build.createModule関数を使用してモジュールを定義する。

// createModule に渡す引数の型はCreateModuleOptions
// CreateModuleOptions 型は以下のような定義
//
//   pub const CreateModuleOptions = struct {
//       source_file: FileSource,
//       dependencies: []const ModuleDependency = &.{},
//   };
//
// FileSource 型は以下のようなunion型
// 通常はpathフィールドを指定することになるだろう
//
//   pub const FileSource = union(enum) {
//    /// A plain file path, relative to build root or absolute.
//    path: []const u8,
//
//    /// A file that is generated by an interface. Those files usually are
//    /// not available until built by a build step.
//    generated: *const GeneratedFile,
//    ....
const mod_a = b.createModule(.{ .source_file = .{ .path = "./src/mod_a.zig" } });

2. 作成したモジュール自身を自身の依存リストに加える。

// dependencies の型は、std.StringArrayHashMap(*Module)
// キーに自身のモジュール内でインポートの際に指定する名前
// 値に作成したモジュールを渡す
try mod_a.dependencies.put("mod_a", mod_a);

3. CompileStep.addModuleメソッドを使用して、実行モジュールに作成したモジュールを追加する

// 第一引数は、モジュール内でインポートの際に指定する名前
// 第二引数は、作成したモジュール
exe.addModule("mod_a", mod_a);

4. tryを使うため、build関数の戻り値を!voidにする。

全体をまとめると、build.zigは以下のようになる。

const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) !void {
    // 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(.{});

    const mod_a = b.createModule(.{ .source_file = .{ .path = "./src/mod_a.zig" } });
    try mod_a.dependencies.put("mod_a", mod_a);

    const exe = b.addExecutable(.{
        .name = "self_depend_module_app",
        // 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 = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });
    exe.addModule("mod_a", mod_a);

    // 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`).
    exe.install();

    // This *creates* a RunStep 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 = exe.run();

    // 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());

    // 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);
    }

    // テストの構成は同じなので省略

2023-6-3 追記

時期までは詳細に追いかけていないですが、少なくとも 0.11.0-dev.2969+855493bb8 のバージョンでstd.BuildのAPIが変更されています。
具体的には、exe.install()の箇所。

0.11.0-dev.2969+855493bb8のバージョンでは、以下のように変更する必要があります。3

b.installArtifact(exe);

注意点

以下のように、モジュール内のユニットに直接触るとコンパイルエラー(imported from module root)になるので注意。

const mod_a = @import("mod_a");

// 直接モジュール内のユニットに触ってる
const bar = @import("./mod_a/bar.zig").bar;

pub fn main() !void {
    mod_a.a1();
    mod_a.a2();

    bar();
}
Build Summary: 0/3 steps succeeded; 1 failed (disable with -fno-summary)
install transitive failure
└─ install self_depend_module transitive failure
   └─ zig build-exe self_depend_module Debug native 1 errors
src/mod_a/bar.zig:1:1: error: file exists in multiple modules
src/main.zig:3:21: note: imported from module root
const bar = @import("./mod_a/bar.zig").bar;
                    ^~~~~~~~~~~~~~~~~
src/mod_a.zig:2:25: note: imported from module root.mod_a
pub const bar = @import("./mod_a/bar.zig").bar;
                        ^~~~~~~~~~~~~~~~~
  1. 正確には、 https://github.com/ziglang/zig/issues/14307 でモジュール管理周りが再構成されたあたり以降

  2. パッケージを分け、別パッケージから利用するためには新しくなったパッケージ管理に従う必要があり説明するのが厄介なため

  3. 元のbuild.zigのバックアップをとっておき、zig init-exebuild.zigを再作成したのち、バックアップから必要な箇所を置き換える方法でもOK

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?