Rust と Bazel メモ
rules_rust を使って Rust プロジェクトを Bazel でビルドするためのメモ書きです。
普段 Rust 書かないので、Rust の基本的な話も含まれるやも。
作ろうとしたツールは、静的解析して結果を reviewdog 用に出力するだけのものです。
依存パッケージ周りをいい感じに解決するには crate_universe
を使うと良いです。
crate_universe
を使うには WORKSPACE ファイルで crate_universe_dependencies
を追記する必要があります:
http_archive(
name = "rules_rust",
sha256 = "aaaa4b9591a5dad8d8907ae2dbe6e0eb49e6314946ce4c7149241648e56a1277",
urls = ["https://github.com/bazelbuild/rules_rust/releases/download/0.16.1/rules_rust-v0.16.1.tar.gz"],
)
load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_register_toolchains")
rules_rust_dependencies()
rust_register_toolchains(
edition = "2021",
versions = ["1.66.1"],
)
load("@rules_rust//crate_universe:repositories.bzl", "crate_universe_dependencies")
crate_universe_dependencies()
Go ではよく依存パッケージを vendoring して、Gazelle で BUILD ファイルを生成するということをしていたので、それに近そうな crates_vendor
を今回は使ってみます。そのために、BUILD ファイルを次のように書きます:
load("@rules_rust//crate_universe:defs.bzl", "crates_vendor")
crates_vendor(
name = "crates_vendor",
mode = "local",
cargo_lockfile = ":Cargo.lock",
manifests = [":Cargo.toml"],
)
crates_vendor
は cargo
を使って生成した Cargo.toml
と Cargo.lock
ファイルを読んで、いい感じに依存パッケージの vendoring と BUILD ファイルの生成をしてくれます。mode
には remote
と local
があります。local
の場合は vendoring したファイル群と生成した BUILD ファイルをプロジェクトのローカルに保存しますが、remote
の場合は BUILD ファイルのみを保存し vendoring はビルド時に行います。
BUILD ファイルなどの生成は name
で指定したコマンドを実行することで生成されます:
$ bazelisk run //project:crates_vendor
INFO: Analyzed target //project:crates_vendor (1 packages loaded, 3 targets configured).
INFO: Found 1 target...
Target //project:crates_vendor up-to-date:
bazel-bin/project/crates_vendor.sh
INFO: Elapsed time: 2.679s, Critical Path: 1.93s
INFO: 4 processes: 4 internal.
INFO: Build completed successfully, 4 total actions
INFO: Build completed successfully, 4 total actions
デフォルトで crates
ディレクトリに vendoring したファイル群と生成した BUILD ファイルが生成されます。ディレクトリを変えるには vendor_path
を指定してください。
この他に crates/defs.bzl
が生成されます。ここには rust_binary
や rust_library
で使える依存パッケージ用のメソッドが定義されています:
例えば、all_crate_deps
を使うことで簡単に依存パッケージ(deps
)を指定できます:
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//crates:defs.bzl", "all_crate_deps")
rust_binary(
name = "project",
srcs = ["src/main.rs"],
deps = all_crate_deps(normal = True),
)
また、mode="remote"
の場合は crates/defs.bzl
にある crate_repositories
を WORKSPACE ファイルで呼んでおく必要があります。
rdfmt
reviewdog 用の出力をフォーマットするために、今回は rdfmt クレートを使うことにしました:
これをそのまま rules_rust でビルドすると次のようなエラーが出ます:
error: proc macro panicked
--> bazel-out/darwin-fastbuild/bin/crates/rdfmt-0.2.1/rdfmt_build_script.out_dir/generated/schema.rs:4:5
|
4 | schemafy::schemafy!(root: Diagnostic "/bazel-sandbox/project/bazel-out/darwin-fastbuild/bin/crates/rdfmt-0.2.1/rdfmt_build_script.out_dir/json_schema/Diagnostic.jsonschema...
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: Unable to read `/bazel-sandbox/project/bazel-out/darwin-fastbuild/bin/crates/rdfmt-0.2.1/rdfmt_build_script.out_dir/json_schema/Diagnostic.jsonschema`: No such file or directory (os error 2)
Rust のクレートでは Build Script というのがあって、クレート自体をコンパイルする前に実行するスクリプトを定義できます。
rdfmt の場合、reviewdog の出力フォーマットが定義されたスキーマを reviewdog のリポジトリから取ってきて、そのスキーマから型やメソッドを schemafy で定義するようなソースコードを生成しています。
このとき、コンパイル時にダウンロードされたスキーマのパスが Bazel のサンドボックスになってしまうため、コンパイルエラーになるようです。
rdfmt の Build Script を読んだところ、CARGO_FEATURE_BUILD_WITH_LOCAL_SCHEMA
環境変数を指定することで rdfmt リポジトリに置いてあるスキーマファイルを参照するようなので、これを rules_rust で指定してあげます:
load("@rules_rust//crate_universe:defs.bzl", "crate", "crates_vendor")
crates_vendor(
name = "crates_vendor",
mode = "local",
cargo_lockfile = ":Cargo.lock",
manifests = [":Cargo.toml"],
annotations = {
"rdfmt": [crate.annotation(build_script_env = {"CARGO_FEATURE_BUILD_WITH_LOCAL_SCHEMA": "true"})] # ここを追加
}
)
こうすることで、crate_vendor
で生成される rdfmt の BUILD ファイルの cargo_build_script
に設定した環境変数が渡されるようになります。
おしまい
ちなみに、mode="local"
の crates_vendor
の結果を git 管理すると、とんでもないサイズになるので注意が必要です。
Discussion