LoginSignup
5
4

More than 3 years have passed since last update.

build.rsでmod宣言を自動生成する

Posted at

はじめに

build.rsとは、Rustのプロジェクトにてビルド前に実行したい処理を書いておくソースコードです。例えば関連するCのコードをコンパイルしたり、Rustのコードを生成したりできます。今回このbuild.rsにてサブモジュールを宣言するmod ...を生成しようとしたところ、素直にはできなかったのでやり方をまとめておきます。

やりたいこと

以下のようなディレクトリ構成を想定します。

|-- build.rs
|-- Cargo.toml
`-- src
    |-- main.rs
    |-- plugins
    |   |-- xxx.rs
    |   `-- yyy.rs
    `-- plugins.rs

このような構成の場合、plugins.rsは以下のようになります。

plugins.rs
pub mod xxx;
pub mod yyy;
pub use xxx::*;
pub use yyy::*;

pub useは必須ではありませんが、このようにエクスポートしておくことでuse plugins::*のように一括でインポートできるようになります。

ここで、pluginsディレクトリの中身が大量にあり、頻繁に足したり消したりする場合を考えます。そのようなときに毎回plugins.rsを書き換えるのは面倒なのでなんとか自動生成したい、というのが今回の目的です。

失敗例

最初に失敗例を載せておきます。

build.rs
fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();

    let mut plugins = Vec::new();
    for entry in WalkDir::new("src/plugins") {
        let entry = entry.unwrap();
        if entry.file_type().is_file() {
            let file_name = String::from(entry.path().file_stem().unwrap().to_string_lossy());
            plugins.push(file_name);
        }
    }

    plugins.sort();

    let out = Path::new(&out_dir).join("gen.rs");
    let mut out = File::create(&out).unwrap();

    for file_name in &plugins {
        let _ = write!(out, "pub mod {};\n", file_name);
        let _ = write!(out, "pub use {}::*;\n", file_name);
    }
}

build.rsではWalkDirを使ってsrc/plugins以下のファイル一覧を取得し、以下のようなgen.rsを生成します。

gen.rs
pub mod xxx;
pub use xxx::*;
pub mod yyy;
pub use yyy::*;

これをinclude!マクロを使って取り込みます。

plugins.rs
include!(concat!(env!("OUT_DIR"), "/gen.rs"));

これをコンパイルすると

error[E0583]: file not found for module `xxx`
 --> ..../target/debug/build/..../out/gen.rs:1:9
  |
1 | pub mod xxx;
  |         ^^^
  |
  = help: name the file either xxx.rs or xxx/mod.rs inside the directory "..../target/debug/build/..../out"

というエラーになります。これは「サブモジュールのファイルをgen.rsからの相対パスで探しにいって失敗している」ということです。(本当はplugins.rsからの相対で探してほしい)

成功例

「サブモジュールのパスが見つからないなら指定すればよい」ということで以下が成功例です。

build.rs
fn main() {
    // 追加
    let root_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let out_dir = env::var("OUT_DIR").unwrap();

    let mut plugins = Vec::new();
    for entry in WalkDir::new("src/plugins") {
        let entry = entry.unwrap();
        if entry.file_type().is_file() {
            let file_name = String::from(entry.path().file_stem().unwrap().to_string_lossy());
            plugins.push(file_name);
        }
    }

    plugins.sort();

    let out = Path::new(&out_dir).join("gen.rs");
    let mut out = File::create(&out).unwrap();

    for file_name in &plugins {
        // 追加
        let _ = write!(
            out,
            "#[path = \"{}/src/plugins/{}.rs\"]\n",
            root_dir, file_name
        );
        let _ = write!(out, "pub mod {};\n", file_name);
        let _ = write!(out, "pub use {}::*;\n", file_name);
    }
}

追加は#[path...]の部分です。これを実行すると以下が生成されます。

gen.rs
#[path = "..../src/plugins/xxx.rs"]
pub mod xxx;
pub use xxx::*;
#[path = "..../src/plugins/yyy.rs"]
pub mod yyy;
pub use yyy::*;

#[path]modに付けることのできるアトリビュートで、そのモジュールのファイルパスを指定できます。このようにすることで、build.rsからmod宣言を自動生成することができるようになりました。

5
4
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
5
4