RustプロジェクトでCircleCIを設定する
RustプロジェクトについにCircleCIを導入する余裕が生まれてきたので, 設定メモを書きます.
ググったのですが全網羅して書いてる所が見つからなかったので仕方なく自分で調べて書いてます.
CircleCIのimageを利用する
最初はRust公式のDockerイメージ rust - Docker Hub を使おうと思ったのですが,
CircleCI的にはCircleCIの作ってる circleci/rust - Docker Hub を推奨してるらしいですね.
それでタグは何を選択すれば良いんでしょう…
Tags (344)
とか探す気がしない.
少なくとも今回はJavaScriptは一切関係してこないので,
nodeだのbrowserだのは要らないのですが…
デフォルトのタグ無しを選択することにしました.
コンポーネントをインストールする
CircleCIのrustイメージにはclippyとrustfmtが含まれていないので
command: rustup component add clippy rustfmt
する必要があります.
rustfmtでフォーマットチェックする
ビルドには時間がかかるのでこちらを先に行います.
command: cargo fmt -- --check
で失敗した時返り値をエラーにしてdiffも出せます.
Building a Rust project on CircleCI
にはcargo fmt -- --write-mode=diff
とか書かれてますが多分古いバージョンですね.
ビルドする
普段のチェックならcargo check
でビルドせずにチェックだけ行いたいのですが,
今回はtestもちゃんと行うので結局ビルドするしか無さそうです.
またclippyも表層的な部分だけではなく深い所まで見るので結局ビルドを必要としています.
依存ライブラリが壊れている場合をステップ分けしたいので, 依存ライブラリだけ先にビルドしたいのですが, やっぱりそのオプションはまだ無いみたいですね…
素直にcommand: cargo build
するしか無さそうです.
clippyでチェックする
clippyはwarn程度のものだと返り値がエラーにならないので
command: cargo clippy -- -D warnings
とする必要があります.
testする
普通にcargo test
が動きます.
やっぱりローカルもstableにする
CIのデフォルトツールチェインはstableなのでローカル開発環境もstableに合わせてしまいましょう. CIをnightlyにする勇気はありませんでした.
キャッシュする
そのままだとビルドするたびに全てのコンポーネントをビルドするので, ビルドが毎回遅くなります.
キャッシュを使いましょう.
依存関係のキャッシュ - CircleCI を読んで考えます.
キャッシュするべきなのは
- rustupのコンポーネント(rustupの短縮)
- プロジェクトの依存(cargo buildの短縮)
の2つですね.
キャッシュされてさえいれば, 自動でキャッシュが有効化されて特別な操作無しにキャッシュを利用してくれるはずです.
rustupのコンポーネントがキャッシュ有効かはrustupのバージョンを見れば良いでしょう.
コマンド実行の結果をキーにする簡単な方法がわからない… ので一度ファイルに書き出してチェックサムを見るという非効率っぽい方法を取らざるを得ませんでした.
差分ビルドはまあ今回はそこまで大きいプロジェクトではないのでやらなくて良いですかね… ファイルのチェックサムを全て取るとかやればtargetを保存するだけで良いのですが, それはそれで面倒なので今回キャッシュするのは依存ライブラリだけです.
注意が必要なのは,
普通cargoで入れたファイルのキャッシュは
~/.cargo
に入りますが,
CircleCI環境,
というかRustのDocker環境の場合
/usr/local/cargo/registry
に入るそうです.
参考:
- CircleCI 2.0 configuration for Rust library crate project
- docker-rust/Dockerfile at 97a72441aad0ec26edf5175382c3d0022c84ba7d · rust-lang/docker-rust
rustupは/usr/local/rustup
ですね.
と思ってrustup component add
もキャッシュしようとしていたのですが,
/usr/local/rustup/
全体をcacheしてしまうとOperation not permitted
になってしまいますが,
全体をcacheしないとcacheの意味がないこと,
rustup component add
の実行に1秒ぐらいしかかからないことからrustup component add
はキャッシュしないことにしました.
そして出来たのが以下です.
version: 2
jobs:
build:
docker:
- image: circleci/rust
steps:
- checkout
- run:
name: rustup version
command: rustup --version
- run:
name: rustup component add
command: rustup component add clippy rustfmt
- run:
name: fmt
command: cargo fmt -- --check
- restore_cache:
keys:
- v1-cargo-lock-{{ checksum "Cargo.lock" }}
- run:
name: build
command: cargo build
- run:
name: lint
command: cargo clippy -- -D warnings
- save_cache:
key: v1-cargo-lock-{{ checksum "Cargo.lock" }}
paths:
- "/usr/local/cargo/registry"
- "target"
- run:
name: test
command: cargo test
ワークフローを使って分割する(失敗)
これまで全て同じbuildジョブに書いていきましたが, fmtは無条件で実行可能で, clippy, testはbuildさえ完了していれば可能なのですよね.
これからこのプロジェクトがそんなに大きく膨らんで,
分割しないとCI時間がやばいことになるプロジェクトになることはあまり想定されませんが,
今後他のRustプロジェクトにも使い回せるconfig.yml
を目指して分割してみることにしました.
Using Workflows to Schedule Jobs - CircleCI CircleCI2.0のWorkflowを試してみる - Qiita を参考に分割しました.
しかし
- 1つずつDockerイメージを起動するのでそのオーバーヘッドがバカにならないこと
- 分割できてもfmtとbuildが同時に出来てclippyとtestが同時に出来るぐらいで大したメリットがないこと
- なんかキャッシュの復元が出来なかった(解決済み?)
- 今はこんなことをやっている場合ではない
ことから没になりました.
巨大なRustプロジェクトをCircleCIに突っ込む時は思い出して分割しようと思います.