Rustとgithub actionsでCI環境構築
モチベーション
RustのprivateプロジェクトでCIを運用する上で、今までCircleCIを利用していましたが、github actionsに集約した方が色々と捗りそうだったので移行しようと思いました。
この記事ではその際に検討したことや、参考にしたものを共有をしようと思います。
想定読者
- RustでCIを運用したいと思っている人
- github actionsを使ってCIを運用したいと思っている人
注意事項
今回CIを構築したプロジェクトでは、サーバをRust、フロントをNext.jsで構築していて両者を同じリポジトリ(モノリポ)で管理しています。
gitリポジトリのルートディレクトリではなくserverというディレクトリ内部がRustのプロジェクトルートになっているため、今回のworkflowファイルもその影響を受けています。
具体的にはworking-directory: serverとかcacheディレクトリのパスがserver/target/になっているのがそれで、ルートにCargo.tomlを配置する場合は、working-directoryの指定は不要になったり、target/をキャッシュすれば良いということになります。
以下が今回のプロジェクトのディレクトリ構成図です。
. # ← gitリポジトリのルートはここ
├── .git
├── .github
│ ├── actions
│ │ └── cache_cargo
│ │ └── action.yml
│ └── workflows
│ └── rust_ci.yml
├── ...
└── server # ← Rustプロジェクトのルートはここ
├── ...
└── Cargo.toml
actionの作成
actionは共通処理を抽出するための機能で、いわゆる関数定義と同じようなことができます。
CircleCIではjobと呼ばれている機能とほぼ同じだと思われますが、CircleCIのjobとは異なり、workflowのyamlに直接actionを記述することはできず、1アクションにつき1ファイルを作る必要があります。
下記のファイルが今回作成したyamlファイルで、buildに有用なデータをcacheするactionを作成しています。
name: cache_cargo
description: caching .cargo directory
runs:
using: composite
steps:
- name: Cache cargo registry
uses: actions/cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
server/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
このyamlファイルは以下のような書き方でworkflowから呼び出すことができます。
- uses: ./.github/actions/cache_cargo
workflowの作成
github actionsを利用してCIを実際に動かすにはworkflowを作成する必要があります。
下記のファイルが今回作成したyamlファイルになります。
今回のワークフローは
- 事前にcargo buildを実行してキャッシュを作成する
- キャッシュしたデータを基にして
2-a. rustfmtを利用したコードスタイルのチェック
2-b. clippyを利用した静的解析
2-c. 自動テストの実行(postgresを用いたdatabaseテストも可能)
という構成になっていて、最低限の構成になっています。
本当はtarpaulinを用いてcoverageの取得などもしたかったですが、cacheがうまく効かないので一旦offにしています。
(RUSTC_FORCE_INCREMENTAL: 1は意味がないかもしれないですが参考文献をならって一応入れてます。)
on: push
jobs:
build_cache:
runs-on: ubuntu-latest
env:
RUSTC_FORCE_INCREMENTAL: 1
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/cache_cargo
- name: build
run: cargo build
working-directory: server
fmt:
runs-on: ubuntu-latest
needs: build_cache
steps:
- uses: actions/checkout@v2
- run: rustup component add rustfmt
- uses: ./.github/actions/cache_cargo
- name: fmt
run: cargo fmt --all -- --check
working-directory: server
clippy:
runs-on: ubuntu-latest
env:
RUSTC_FORCE_INCREMENTAL: 1
needs: build_cache
steps:
- uses: actions/checkout@v2
- run: rustup component add clippy
- uses: ./.github/actions/cache_cargo
- name: clippy
run: cargo clippy --all-targets --all-features -- -D warnings
working-directory: server
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:12
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
env:
RUSTC_FORCE_INCREMENTAL: 1
needs: build_cache
steps:
- name: create database for test
run: PGPASSWORD=postgres psql -h localhost -U postgres -c "CREATE DATABASE test"
- uses: actions/checkout@v2
- uses: ./.github/actions/cache_cargo
- name: test
run: cargo test --all -- --nocapture
working-directory: server
参考文献
workflowのサンプル
これらは非常に参考になりました。
denoのworkflow
actix-webのworkflow
actions/cache@v2のRustでのサンプル
sccacheの検討
sccacheというプログラム利用で、ビルドを高速化できるという話で試験導入してみましたが、あまり効果を実感できるようなプロジェクトサイズではないので見送りました。
sccacheでGitHub Actions上のRustビルドを高速化するやつ試した
GitHub Actions best practices for Rust projects
incremental buildの検討
mtimeを復元することでincremental buildができるらしいとのことですが、checkoutが重たくなりそうだと思ったのと、プロジェクトサイズがそこまで大きくないので、外部クレートのキャッシュができればまずは十分だと判断しました。
Discussion