MIXI DEVELOPERS NOTE
🥬

Rust と Bazel メモ

2023/01/26に公開

rules_rust を使って Rust プロジェクトを Bazel でビルドするためのメモ書きです。

普段 Rust 書かないので、Rust の基本的な話も含まれるやも。

https://github.com/bazelbuild/rules_rust

作ろうとしたツールは、静的解析して結果を reviewdog 用に出力するだけのものです。

依存パッケージ周りをいい感じに解決するには crate_universe を使うと良いです。

https://bazelbuild.github.io/rules_rust/crate_universe.html

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_vendorcargo を使って生成した Cargo.tomlCargo.lock ファイルを読んで、いい感じに依存パッケージの vendoring と BUILD ファイルの生成をしてくれます。mode には remotelocal があります。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_binaryrust_library で使える依存パッケージ用のメソッドが定義されています:

https://bazelbuild.github.io/rules_rust/crate_universe.html#dependencies-api

例えば、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 クレートを使うことにしました:

https://crates.io/crates/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 管理すると、とんでもないサイズになるので注意が必要です。

MIXI DEVELOPERS NOTE
MIXI DEVELOPERS NOTE

Discussion