🦀

Rustとgithub actionsでCI環境構築

2021/09/24に公開

モチベーション

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を作成しています。

.github/actions/cache_cargo/action.yml
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ファイルになります。
今回のワークフローは

  1. 事前にcargo buildを実行してキャッシュを作成する
  2. キャッシュしたデータを基にして
    2-a. rustfmtを利用したコードスタイルのチェック
    2-b. clippyを利用した静的解析
    2-c. 自動テストの実行(postgresを用いたdatabaseテストも可能)

という構成になっていて、最低限の構成になっています。

本当はtarpaulinを用いてcoverageの取得などもしたかったですが、cacheがうまく効かないので一旦offにしています。
(RUSTC_FORCE_INCREMENTAL: 1は意味がないかもしれないですが参考文献をならって一応入れてます。)

.github/workflows/rust_ci.yml
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が重たくなりそうだと思ったのと、プロジェクトサイズがそこまで大きくないので、外部クレートのキャッシュができればまずは十分だと判断しました。

Rust プロジェクトの GitHub Actions で incremental build をするためのテクニック

GitHubで編集を提案

Discussion