はじめに
build.rs
とは、Rustのプロジェクトにてビルド前に実行したい処理を書いておくソースコードです。例えば関連するCのコードをコンパイルしたり、Rustのコードを生成したりできます。今回このbuild.rs
にてサブモジュールを宣言するmod ...
を生成しようとしたところ、素直にはできなかったのでやり方をまとめておきます。
やりたいこと
以下のようなディレクトリ構成を想定します。
|-- build.rs
|-- Cargo.toml
`-- src
|-- main.rs
|-- plugins
| |-- xxx.rs
| `-- yyy.rs
`-- plugins.rs
このような構成の場合、plugins.rs
は以下のようになります。
pub mod xxx;
pub mod yyy;
pub use xxx::*;
pub use yyy::*;
pub use
は必須ではありませんが、このようにエクスポートしておくことでuse plugins::*
のように一括でインポートできるようになります。
ここで、pluginsディレクトリの中身が大量にあり、頻繁に足したり消したりする場合を考えます。そのようなときに毎回plugins.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
を生成します。
pub mod xxx;
pub use xxx::*;
pub mod yyy;
pub use yyy::*;
これをinclude!
マクロを使って取り込みます。
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
からの相対で探してほしい)
成功例
「サブモジュールのパスが見つからないなら指定すればよい」ということで以下が成功例です。
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...]
の部分です。これを実行すると以下が生成されます。
#[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宣言を自動生成することができるようになりました。