FlatBuffersの定義ファイルを指定するだけで生成されたRustコードを作ってみたので苦労した部分を共有しておきます。
FlatBuffersについてはそのうち記事を書きたいと思いますが、Protocol Buffersのようにスキーマから各言語用のコードを出力するタイプのシリアライズ形式です。
namespace addressbook;
table Person {
name:string;
age:int;
}
のように定義ファイルを書いてそれをflatc
というコンパイラで処理することでRustのコードを生成します。しかしそれは面倒なので以下のように書きたいと思うわけです:
use flatc_gen::flatc_gen;
flatc_gen!("../fbs/addressbook.fbs");
マクロflatc_gen!
はfunction-like procedual-macroを用いて実装しました:
# [proc_macro]
pub fn flatc_gen(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::LitStr); // 引数は文字列リテラルとしてパースします
// 長いので略
}
さて、手続きマクロはフル機能のRustが実行できるので、flatc
のコードをダウンロードしてcmake
でコンパイルして呼び出してRustのコードを生成して、そのファイルを読み出してマクロとして展開するわけですが、問題となるのが相対パスの扱いです。
手続きマクロのコード自体が実行されるときのパスはflatc_gen!
が呼び出されたファイルのディレクトリとは一般に異なります(おそらくrustcを起動したディレクトリになる)。すると上のコードの../fbs/addressbook.fbs
を解決するためにはflatc_gen!
が呼び出されているファイルのパスを特定する必要があります。
なお、file!()
より手続きマクロの方が先に展開されるのでmacro_rules!
を使って回避できません(多分)。
これが以外と大変な作業になります。この情報自体はrustcが持っているわけですが、それは現時点(2019/3)のstableではproc_macro
側には提供されていません。一応proc_macro_span featureとしてproc_macro::Span
にはsource_file
というAPIがnightlyには用意されており、これを使えば行けそうですが、TokenStream
をパースするsyn
crateのAPIは基本的にproc_macro2::Span
を返し、これをproc_macro::Span
に変換するAPIが無いので使えません。
そこでproc_macro2::Span
にあるsource_file関数を使おうとなるわけですが、これは普通にやるとexposeされていません。
This method is semver exempt and not exposed by default.
このメソッドを有効にするにはproc_macro2の方のunstable featureに説明があるように、rustcにフラグをつける必要があります。
RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo build
これはflatc-gen
を使うすべてのcrateのビルド時につける必要があります。.cargo/config
ファイルを用意して
[build]
rustflags = ["--cfg", "procmacro2_semver_exempt"]
とするのが楽でしょうが、実用するにはハードルが高すぎますね(´・ω・`)
まとめ
- proc_macro2のunstable featureを使うとマクロを起動したファイルのパスを取得できる
- でもAPIがまだ整備中でnightlyでもきつい