build.rsを活用する
Section: Technology
Tags: Rust

この記事は 広島大学ITエンジニア Advent Calendar 2020の20日目です。

今回の記事は、後で楽をするために結構苦労した話を書いています。

楽したくないですか?

自分は記事を書いた後、新しく記事のファイルの場所を記述したstrを定数で持たせるようなコードを毎回書いています。このような毎回同じようなコードを書くときは面倒であるため、自動化したいものです。

const ABOUT_CREATED_LOGO: &str = include_str!("./about_created_logo.ja.md");
const ABOUT_CREATED_SITE: &str = include_str!("./about_created_site.ja.md");
...
static ARTICLE_MAP: phf::Map<&str, &str> = phf_map! {
    "about_created_logo" => ABOUT_CREATED_LOGO,
    "about_created_site" => ABOUT_CREATED_SITE,
    // ...
};

自動化

自動化するにあたって、記事のあるディレクトリからすべてのファイル名をとりループさせればできるだろうという甘い考えを持っていました。

ディレクトリ内のファイル名取得はstd::fs::read_dir(SOURCE_DIR)を用いれば、簡単に実装できます。しかしその後の処理に少し問題がありました。

問題点

  • conststaticを使用したいため、ループでする方法がわからない
  • include_str!はコンパイル時に実行するため、引数に変数を用いれない

以上の問題点が浮かび、どうしたらいいか悩みました。macroを用いてもできそうにないので、調べていたところbuild.rsを見つけました。

build.rs

build.rsはクレートをビルドする前に実行するビルドスクリプトです。主にRust以外の言語をコンパイルしたいみたいなときに使用するらしいのですが、.rsであるためもちろんRustのコードを書いてもいいのです。

実装

ということでビルドスクリプトの部分でrustのコードをファイルに記述し、includeマクロでコードを導入します。クレートのビルド時には、include_strマクロの引数等はすでに文字列に代わっているという方法でコンパイルエラーを回避します。

pub static ARTICLE_MAP: phf::Map<&str, &str> = include!("./article_map.rs");

この後build.rsにコードを書いていったのですが、たくさんのエラーに悩まされました。

クレート内は読み込まない

ビルドスクリプトではクレートのビルド前に実行するため当たり前ですが、クレート内のコードを使用できません。

そのため外部クレートに必要な構造体を移行しました。

外部クレートを読み込んでくれない

外部クレートに構造体を移したためCargo.toml[dependencies]に追加したのですが、上手く外部クレートを読み込んでくれません。

error[E0433]: failed to resolve: use of undeclared crate or module `shared`
 --> client/build.rs:1:5
  |
1 | use shared::models::meta::Meta;
  |     ^^^^^^ use of undeclared crate or module `shared`

ビルドスクリプトの場合[build-dependencies]に記述する必要がありました。

[build-dependencies]
shared = { path = "../shared" }

constはconst関数以外許さない

現在の構造体の新規作成をStringに変えるtraitか何かが欲しいなと思いつつ地道にinclude!のファイルの部分を、時間をかけて書いて完成かと思ったら、問題が起こりました。

pub static ARTICLE_META_MAP: phf::Map<&str, &Meta> = include!("../../article_meta_map.rs");

記事のメタ情報の部分は構造体を用いていたのですが、どうやらconst等はconst関数しか使えないということでした。つまりString::from()すら使えません。となるとどうやってStringに変換すればいいのかわからず、Meta構造体の構造を変える必要がありました。

pub struct Meta {
    pub seed: String,
    pub title: Option<String>,
    // ...

Stringはすべて&strに変えるなどしました。結構フィールドの数が多かったり違う型とか使っていたため、これもまた時間がかかりました。

pub struct Meta<'a> {
    pub seed: &'a str,
    pub title: Option<&'a str>,
    // ...

完成

これらとちょっとしたエラーに対処し何とか自動化することができました。

終わりに

今後sitemap.xmlを追加する際にもbuild.rsを用いることができるため、Rustを書く時の柔軟性が向上しました。

しかしリアルタイムで記事を生成したり、記事を書く度に何行か追加するということを面倒に思わなければ、今回のコードは必要なかったのかもしれません。逆にとても時間を使ってしまいました。

最後までお読み下さり、ありがとうございました。