cache アクション

CI ではプロジェクトの依存パッケージのインストールを走らせることが多いです。 1 からインストールを行うと、規模によっては数分掛かりることもあります。 数回であれば気になりませんが、pushの度複数ワークフローを実行とかになると、準備ステップに時間をあまり掛けたくありません。 (プライベートリポジトリの場合、 ワークフローの合計実行時間の制限もあるので余計に)

cache アクション

キャッシュには公式が管理している actions/cache という action を使います。これを使うと、以前と同じ状態、または近いものがあれば今に復元できます。
実行時間の掛かる処理によって作られたものを復元することで、 1 から再度作るよりも時間を短縮できるはずです。

2020-05-07 だとバージョン 1 が主流です。使うには以下のように記述します。

steps:
  - # ...略
  - uses: actions/cache@v1
    id: something-cache
    with:
      path: ...
      key: ...
      restore-keys: ...

入力値

3 つの値を渡して実行できます。pathkeyそしてrestore-keysです。この中でpathkeyの 2 つの値は必須です。

まず最初にpathにはキャッシュしたいディレクトリやファイルへの絶対パスを渡します。次にkeyにはキャッシュする際のキー( 512 文字以下)を指定します。 ワークフローの最後にそのキーによるキャッシュが行われ、次に実行した際にそのキーによるキャッシュが存在すればそれが復元されます。

restore-keysは指定したキーによるキャッシュが存在しなかった場合に使われるフォールバック的なキーです。キーによって前方一致した最新のキャッシュを復元できるようになります。不完全でも前の結果がある方が処理を早く終えられるようなものに設定するのが良いです。

出力値

cache-hitという値があります。これはキャッシュを復元できた場合のみ、steps.something-cache.outputs.cache-hittrueという文字列が格納されます。

ジョブの例

このジョブはパッケージマネージャ Yarn を用いた JavaScript プロジェクトを想定してます。プロジェクトルートにはpackage.jsonがあり、適用にパッケージが追加されてます。また依存はローカルでインストール済でその解決方法が記載されたyarn.lockもプロジェクトルートに置かれれる状態です。

キャッシュに関するステップは- id: yarn-cache-dirの次の次の行にあります。

jobs:
  build:
    # ubuntu 18.04 環境上で実行
    runs-on: ubuntu-18.04

    # それぞれ以下のNode.jsが使える環境にして実行
    # - node@^10
    # - node@^12
    # - node@^13
    strategy:
      matrix:
        node-version: [10.x, 12.x, 13.x]

    # 実行処理
    steps:
      # Node.js の指定バージョンで使えるようにする
      - uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}

      # コードを持ってくる
      - uses: actions/checkout@v2

      # キャッシュがあれば復元
      - id: yarn-cache-dir
        run: echo "::set-output name=value::$(yarn cache dir)"
      - id: restore-keys
        run: echo "::set-output name=value::node-dependencies-${{ matrix.node-version }}-v1"
      - uses: actions/cache@v1
        id: node-dependencies
        with:
          path: ${{ steps.yarn-cache-dir.outputs.value }}
          key: ${{ steps.restore-keys.outputs.value }}-${{ hashFiles('**/yarn.lock') }}
          restore-keys: ${{ steps.restore-keys.outputs.value }}

      # インストール
      - run: yarn

キャッシュアクション部分を見ていきます。まずpathですが${{ steps.yarn-cache-dir.outputs.value }}を指定してます、この中身は id がyarn-cache-dirのステップのアウトプット値value、つまりはyarn cache dirコマンドを実行時の結果となっています。
yarnはインストール時に各パッケージ結果をキャッシュディレクトリに保存していて、次回のインストール時には可能ならそこからパッケージを解決してくれる為高速化が望めます。

echo "::set-output name=NAME::VALUE"部分ですが、ここは特殊なechoコマンドで、 GitHub Actions ではこのように書くと、steps.step-id.outputs.NAMEVALUEを格納して、後のステップで変数のように使えるようになります。

次にkeyです。これには${{ steps.restore-keys.outputs.value }}-${{ hashFiles('**/yarn.lock') }}-を指定してます。steps.restore-keys.outputs.valueidrestore-keysなステップのアウトプット値valuehashFilesによるハッシュ値、それぞれの文字列を組み合わせたものを指定してます。

hashFilesは GitHub Actions で使える関数の 1 つで指定したファイルパターンからハッシュを計算します。例のように**/yarn.lockのように書くとプロジェクトに含まれるすべてのyarn.lockファイルを対象に計算してくれます。

最後のrestore-keysでは、keyからhashFiles部分を除いたものを指定してます。こうしておくことで、依存に変更が入りyarn.lockの中身が更新され、完全マッチなキャッシュが無くなったとしても、この最新のキャッシュを復元できるようになります。これは上記でも述べた前方一致すればいいキーだからです。

実行してみる

yarn.lockが同じ状態で 2 回ワークフローを走らせてみます。キャッシュがうまく効いていれば 2 回目はより早く完了するはずです。

1 回目

1 回目を実行しました。これにより 2 つの事が分かりました。

  1. yarnの実行時間は 17 秒
  2. 終わる前に実行されるキャッシュアクションによるステップのログCache saved successfullyからキャッシュがnode-dependencies-12.x-v1-a135e48fc66de6bb6175d4726583f1b07aefbaac334cc580f8f80e8b70532c3aキーで作られた

2 回目

上記が完了した後 2 回目を実行しました。

  1. yarnの実行時間は 5 秒
  2. Cache hit occurred on the primary key... not saving cache(プリマリキーでキャッシュヒットが発生しました、キャッシュを保存してません)から今度はキャッシュが作られなかった

上記の結果からキャッシュが正常に作られたことで、ワークフローに掛かる時間が短縮できることが分かりました。

1 回目2 回目はリンク先から結果を見ることができます。

キャッシュを復元できた時、作成コマンドの実行がいらない場合

トップの例でキャッシュが復元できた時steps.something-cache.outputs.cache-hittrueになると書きました。もし作成コマンドの実行がいらないなら、この値を使ってステップをスキップできます。
それにはstep.ifを設定します。以下ではsteps.something-cache.outputs.cache-hit != 'true'の時のみrun: ...部分のコマンドが実行されます。

steps:
  - # ...略
  - uses: actions/cache@v1
    id: something-cache
    with:
      path: ...
      key: ...
      restore-keys: ...
    - if: steps.something-cache.outputs.cache-hit != 'true'
      run: ...