dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

GitHub ActionsでRustプロジェクトを複数ツールチェイン&複数OSでビルドする

背景

こちらの本を読んでRustを勉強しています。

「10-5 自動テストを行う」でTravis CIとAppVeyorを使ったCI環境の構築方法が紹介されていたのですが、せっかくなので同じことをGitHub Actionsで実現しようとして、調べたことをメモ。

やりたいこと

  1. Rustの Stable/Beta/Nightly チャネルそれぞれでビルド・テストを実行したい
  2. 加えて、Windows/macOS/Linux でビルド・テストを実行したい
  3. Nightly チャネルでのテスト失敗は無視したい

1 については、書籍によると

本章でも行っているとおり、beta チャネルでもテストするのがベストプラクティスとされています。

とあります。また 3 については

一方で nightly は正常に動くことは保証されていないので、テストをするとしても失敗を許可した方がよいでしょ う。

という記載があります。

最終的な設定ファイル

先に、できたもの。
以下を .github/workflows/test.yml などとしてプロジェクトに置きます。

name: test

on: [push, pull_request]

jobs:
  build:

    name: Rust ${{ matrix.os }} ${{ matrix.rust }}
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        rust:
          - stable
          - beta
          - nightly
        os: [ubuntu-latest, windows-latest, macos-latest]

    steps:
    - uses: actions/checkout@v2

    - name: Setup Rust
      uses: actions-rs/toolchain@v1
      with:
        profile: minimal
        toolchain: ${{ matrix.rust }}
        override: true
    - name: Build
      run: cargo build
    - name: Run tests
      run: cargo test
      continue-on-error: ${{ matrix.rust == 'nightly' }}

以下、設定ファイルの解説。

基本は actions-rs を使う

自分が調べた限りですが、Rust 向けの GitHub Actions としては actions-rs というアクション集があるようです。
Organization の説明を見ると

Unofficial GitHub Actions for Rust programming language

とあるので公式ではないようですが、ググった中ではこれが一般的に使われている印象を受けました。

一番よく使われるであろう actions-rs/rust-toolchainGitHub での Star 数は120程度ですが、
Marketplace にあるその他の Rust 向けアクションよりはだいぶ Star 数が多いようです。

https://github.com/marketplace?type=actions&query=rust

※ Organization の member が一人なのが若干気になりますが。。。

また、この Organization 内にある actions-rs/meta#Recipesactions-rs/example には
actions-rs 内のアクションを使ったサンプルがいくつか置かれており、参考になります。

今回はこのうちの Matrix CI Workflow というレシピをベースにしています。

複数ツールチェイン、複数プラットフォームは matrix ビルドで

Stable/Beta/Nightly といった複数のツールチェイン、ならびに Windows/macOS/Linux といった複数の OS 環境での実行は matrix パラメータを使います。

Rust ツールチェインのインストールは actions-rs/toolchain

rustup を使って各ツールチェインをインストールしてくれるようです。指定可能なオプションは #Inputs の項に書いてあります。

profile: minimal とは?

https://github.com/rust-lang/rustup#profiles
によれば、rustup には profile という概念があり、minimal, default, complete の順にインストールされるコンポーネントが増えるようです。

  • minimal: コンパイラを動かすための最低限 (rustc, rust-std, cargo) しかインストールしない
  • default: minimal に加え rust-docs, rustfmt, clippy
  • complete: すべて

今回は cargo build および cargo test だけできれば十分なので minimal を指定していますが、将来フォーマッターやリンターもかけたいとなった場合は default がいいのかもしれません。
(もしくは components パラメータで必要なコンポーネントを直接個別に指定する)

override: true とは?

#Inputs の項には

Set installed toolchain as an override for the current directory

と書かれており、rustup の Directory overrides に相当します。
このディレクトリで使うツールチェインをデフォルトから上書きしたい場合は指定します。
インストールしたチャネルのツールチェインを使いたいので、指定する必要があります。

Nightly チャネルではテストの失敗を許可する(TravisCI や AppVeyor の allow_failures 相当)

書籍の TravisCI や AppVeyor の設定例では、 allow_failures というオプションを使用しています。
これを GitHub Actions でどう実現するか調べた結果、以下の記事を参考に continue-on-error を Nightly チャネルのときだけ true にすることにしました。

GitHub ActionsでTravis CIのallow_failures的なやつをやりたい - くりにっき

リンク先では

strategy:
  matrix:
    rust:
      - stable
      - beta
      - nightly
    os: [ubuntu-latest, windows-latest, macos-latest]
    include:
    - rust: nightly
      allow_failures: "true"

steps:
  ...
- name: Run tests
  run: cargo test
  continue-on-error: ${{ matrix.allow_failures == 'true' }}

のように専用の変数を設けていましたが、今回は allow_failures: true にしたい条件が単純で、かつ continue-on-error を指定してる箇所も1箇所なので
単純にツールチェインのチャネルの値 (matrix.rust) によって判断しています。

問題点としては、(参考リンク先でも指摘されていますが)現状GitHub Actionsの continue-on-error ではエラーが出ていてもパスしたときと同じ緑色で表示されるため、エラーに気づけないという点があります。

actions-rs/cargo は必要?

レシピやブログ記事を見ていると、cargo buildcargo test しているところに actions-rs/cargo というアクションを指定しているものがありました。

# たとえば、cargo build と cargo test の代わりに以下のように指定する
- uses: actions-rs/cargo@v1
  with:
    command: build
- uses: actions-rs/cargo@v1
  with:
    command: test

こちらをわざわざ導入すべきか?というところは少し迷ったんですが、 #Use Cases の項には

Note that this Action is not required usually and you can just use run step instead as in example below:

(コード略)

Why would you want to use this Action instead:

  1. Transparent cross installation and execution with use-cross: true input enabled
  2. Warnings and errors issued by cargo will be displayed in GitHub UI

というわけで、単に cargo buildcargo test を実行したいだけなら使う必要はないが、↑に書いたようなメリットがあるとのことでした。

1点めの cross とは https://github.com/rust-embedded/cross のことで、試したことはありませんがcargoと同じように扱えてクロスコンパイルを簡単に実現するためのツールのようです。

2点めの GitHub UI については、たとえばアクション実行結果のAnnotationsの部分にwarningやerrorが表示されるようになることは確認できました。

f:id:dackdive:20200726000304p:plain
https://github.com/zaki-yama-labs/rust-bicycle-book-wordcount/actions/runs/182343379

余談: actions-rs/toolchain の override: true は必要?

最初、actions-rs/toolchain の override: true オプションおよびそこから rustup の Directory overrides について調べたとき、
このオプションは必ず指定した方がいいのかどうか疑問でした。
rustup の挙動はよく理解していませんが、インストールされるツールチェインが1つであれば自動的にそれがデフォルトになって指定しなくても挙動は変わらないはず...と。

で、確認してみたところ、GitHub Actions で使われる VM にプリインストールされているツールの一覧が
https://github.com/actions/virtual-environments
というリポジトリにあるのですが、これによると Rust および rustup は最初からインストールされています。

そのため、actions-rs/toolchain での指定に関わらず Stable チャネルはインストール済みかつデフォルトに指定されている、と推測しました。

念のため、 override: true なしだと期待したツールチェインを使ってくれないことは
GitHub Actions 内で rustup showrustup toolchain list を実行するとわかります。

# beta チャネルを指定したビルドでの結果
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/runner/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu
beta-x86_64-unknown-linux-gnu

active toolchain
----------------

stable-x86_64-unknown-linux-gnu (default)
rustc 1.45.0 (5c1f21c3b 2020-07-13)

また、 actions-rs/toolchain 側の挙動を追っていったときのメモは以下。

  • actions-rs/toolchain は ここ で @actions-rs/core の getOrInstall() を呼んでおり、ここで rustup をインストールしていそう
  • getOrInstall() は、rustup がすでにあればそれを使うし(このへん)、インストール時も --default-toolchain none で実行する(このへん
  • rustup がインストールされてなければ ここ にかかれているデバッグログを出力するはずだが、アクションの実行結果を見る限り出力されてなかった
    • ちなみに core.debug を出力するには ACTIONS_STEP_DEBUG = true というシークレットを登録する必要がある(参考

その他の参考リンク