Rust プログラムのビルド情報を表示するためのちょっとした小技系ライブラリの紹介です。
ビルド情報
デプロイされてるバイナリの動きが怪しいが、どのコミットで壊れたか調べたい。そもそも誰がどこでビルドしたのか?どのコミットでつくられたの?この辺がわからなくて混乱や余計な手間がかかる時があります。CI/CD 等が確立されてしっかり管理されてれば避けられる問題かもですが。それでも往々にしてプログラムでビルド情報を表示できるようしたくなります。
# こんなやつ
$ ./my_program -version
v0.1.24
commit: abcdef89726d
date: 2020-10-31
Build Script
Rust においてビルド情報を集めてプログラムで表示できるようにする正攻法では Build Script (build.rs
) を使うことになるかと思います。Build Script とはビルド時に実行される Rust のコードです。主に、依存する C 言語のライブラリを事前にコンパイルしたり、Rust のコード自体を生成したりといった目的のためにあります。
ビルド情報を表示できるようにするためには、この Build Script を使って、ビルド時のコミットハッシュや場所等のビルド情報を集めて、プログラム本体に組み込みます。
.
├── Cargo.lock
├── Cargo.toml
├── build.rs ... ビルド時にこれが実行されるのでここでビルド情報を集める
└── src
└── main.rs
Rust でビルド情報を表示する既存の crates たちも基本的に、ユーザが自分で Build Script を書く必要があります。
• build_info
• built
例えば、 build_info
という crate では、このように、build.rs
を用意します。
fn main() {
// ここでビルド情報を収集する。
build_info_build::build_script();
}
そして、このようにすることで、 main.rs
からビルド情報が取れるようになります。
build_info::build_info!(fn build_info);
fn main() {
println!("{:#?}", build_info());
}
これでも十分シンプルではあります。ただ、たくさんの Rust アプリを開発するにあたって build.rs
すら省いて、もっと貪欲に少ない手数でビルド情報を付けられないかなと思いました。
ever
このライブラリは main
の先頭でマクロをひとつ呼んでおけばビルド情報を表示できるようになります。build.rs
は不要です。
use ever::ever;
fn main() {
ever!();
println!("Hello, world!");
}
ビルド情報を表示したいときだけ、プログラム実行時に環境変数 EVER
をセットします。これでプログラムはビルド情報をはいて停止します。
$ EVER=1 ./my_program
my_program 0.1.0 (debug):
date: Sat Dec 5 11:17:09 2020 +0900
commit: 49fec228607448df6fcb8950171441a1f56c2e7b-dirty
user: me
host: my_host
builddir: /home/me/my_program
rustc: 1.48.0 (7eac88abb 2020-11-16)
環境変数 EVER
をセットしなければ通常の処理が走ります。
$ ./my_program
Hello, world!
基本機能はこれだけです。
補助機能として、各種情報を個別に取れるような API もあります。
コミットハッシュを表示する API です。
// "49fec228607448df6fcb8950171441a1f56c2e7b-dirty" のように出力されます。
println!("{}", ever::build_commit_hash!());
コミットハッシュ末尾の -dirty
はコミットされてないファイルがあるときにビルドした場合に付けられます。
また、以下はビルドに使用したコンパイラ rustc
のバージョンを表示する API です。
// "1.48.0 (7eac88abb 2020-11-16)" のように出力されます。
println!("{}", ever::rustc_version!());
API 一覧は こちら。
仕組み
build.rs
のほかにビルド時に実行される Rust コードがあります。proc-macro
です。 proc-macro
とは、 macro
なのですが、 Rust のコードを Rust のコードでパース&生成するタイプの macro
です。
そこで ever
は proc_macro
内ででビルド情報を収集しています。
// これが proc-macro の定義です。 `ever!()` というマクロが呼ばれると、ビルド時にこの関数が評価されます。
pub fn ever(input: TokenStream) -> TokenStream {
// ...
// ここはビルド時に走るロジックです。それぞれで一生懸命ビルド情報を収集しています。
let name = vars::package_name();
let version = vars::package_version();
let about = vars::package_description();
let date = vars::build_date();
let commit = vars::build_commit_hash();
let user = vars::build_username();
let host = vars::build_hostname();
let builddir = vars::build_dir();
let rustc = vars::rustc_version();
let mode = vars::build_mode();
let lock = vars::lock_file();
// 以下 `quote!` で囲まれた部分が
// `ever!()` マクロが展開されたときに出来上がる Rust コードです。
// `main` の先頭にこのコードが挿入されます。
(quote! {
match std::env::var("EVER").as_deref() {
Ok("1") | Ok("true") => {
// 環境変数 `EVER` がセットされていればビルド情報を表示。
println!(
r#"{name} {version} ({mode}): {about}
date: {date}
commit: {commit}
user: {user}
host: {host}
builddir: {builddir}
rustc: {rustc}"#,
name = #name,
version = #version,
about = #about,
date = #date,
commit = #commit,
user = #user,
host = #host,
builddir = #builddir,
rustc = #rustc,
mode = #mode,
);
std::process::exit(1);
}
// ...
_ => {}
}
}).into()
}
ビルド情報を回収する際に以下の crates を利用しています。
- whoami: ユーザ名やホスト名を取得しています。
- rustc_version: コンパイラバージョンを取得しています。
- git2: コミットハッシュなどを取得しています。
proc-macro-hack
について
ever
は proc-macro-hack
というちょっととがった名前の crate を利用しています。この理由は Rust 1.45 までは proc-macro
で作られたマクロが式の位置に配置できない問題がありました。
// これができなかった。
println!("{}", ever::rustc_version!());
それを解決するのが proc-macro-hack
でした。proc-macro-hack
でラップされた proc-macro
は式の位置に配置できるようになります。そこで、ever
ではこれをつかっていました。今でも、1.45 以前の Rust でも利用できるように使い続けています。
おまけ機能: Cargo.lock の吐き出し
おまけ機能として Cargo.lock が吐き出せます。環境変数 EVER
に dump_lock
を指定します。
ever
コンパイラのバージョンも吐き出せますので、仮に Cargo.lock や rust-toolchain で依存関係やコンパイラが固定できていなくても、Cargo.lock とコンパイラバージョンでざっくりとビルド時の状態を再現できるようになります。
$ EVER=dump_lock ./your_program
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
...
まとめ
ever
というビルド情報を表示するちょっとしたライブラリの紹介でした。