[GitHub Actions]actions/cache&pipenv&pytestの組み合わせを正常動作するまで検証&修正した記録

ActionsのWorkflow上でpipenvのインストールと時短目的のキャッシュにactions/cacheを利用していましたが、ログを見たところ全く想定した通りの動作になっていませんでした。正常な指定になっていなかった点や、訂正後の状態を合わせてまとめました。
2021.02.02

はじめに

GitHub ActionsのWorkflowにて、pipenvを使いつつcache化させて効率よく動かす、ということをされている方は多いと思われます。私自身、担当プロジェクトにて効率よく動かしている、と思っていました。一応キャッシュはしていたものの、キャッシュを無視して毎回インストールするフローになっていたのは不覚の極みでした。

正常にキャッシュされているかどうかの見極めと、pipenvとactions/cacheを併用したpytestの動作例をまとめました。

正常にキャッシュされているかどうかを見極める

セルフホストしていない限りは、ActionsのWorkflowログが確認の全てです。

キャッシュキーが意図した通りになっているか確認する

actions/cacheにて生成を想定するキーは恐らく次のような構成でしょう。

Linux-pipenv-8772fa24c3defb28f6eee443046fa57808f73961c10022ed9c7af06182663622

> ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}

ですが、指定を間違えている場合は以下のようになります。

Linux-pipenv-

> ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}

詳しくは公式ドキュメントの 「Context and expression syntax for GitHub Actions - GitHub Docs」を参照してみましょう。

actions/cacheでのサンプルがマッチ例と異なっているのも意図しない状況になる要因かもしれません。

- uses: actions/cache@v2
  with:
    path: ~/.local/share/virtualenvs
    key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
    restore-keys: |
      ${{ runner.os }}-pipenv-

なお、restore-keysの指定によっては復元するキャッシュ候補の範囲を広げることもできますが、厳密に復元候補をしばりたい場合は下のような完全マッチがおすすめです。

- uses: actions/cache@v2
  with:
    path: ~/.local/share/virtualenvs
    key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
    restore-keys: |
      ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}

キャッシュ保存時のフラグを利用する

action/cacheを使うことで、その後の処理が復元したキャッシュ前提で進むかというとそんなことはありません。ifによるチェックを挟む必要があります。

- uses: actions/cache@v2
  with:
    path: ~/.local/share/virtualenvs
    key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
    restore-keys: |
      ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}

- name: Install dependencies
  if: steps.pipenv-cache.outputs.cache-hit != 'true'
  run: |
    pipenv sync --dev

キャッシュが効いている場合はaction/cacheの実行ログに以下のような出力がなされています。

Cache restored from key: Linux-pipenv-8772fa24c3defb28f6eee443046fa57808f73961c10022ed9c7af06182663622

キャッシュ復元後のテスト実行

以下のようなログを見かけた場合はPipfileに追記が必要です。PATHに入れる手もありますが、ローカルとActionsの双方で同じコマンド実行ができる点でもPipfileがおすすめです。

Error: the command pytest could not be found within PATH or Pipfile's [scripts].

警告の通り、Pipfileのscriptsセクションにpytestを指定しましょう。セクションがない場合はセクション自体を追加してください。

[scripts]
test = "pytest --flake8 --cache-clear"

flake8によるチェックとキャッシュクリアも兼ねています。Workflow上には以下のように記載します。

- name: Test
  run: pipenv run test

実際のworkflow例

Workflowでpipenvをactions/cache併用時にはまった部分を挙げてきました。通しての設計は以下のようになります。

name: test

on:
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-18.04
    timeout-minutes: 30
    steps:
    - uses: actions/checkout@v2

    - name: Set up Python 3.7
      uses: actions/setup-python@v2
      with:
        python-version: 3.7

    - name: Install pipenv
      run: |
        python -m pip install --upgrade pip
        python -m pip install pipenv

    - id: install-aws-cli
      uses: unfor19/install-aws-cli-action@v1
      with:
        version: 2

    - name: Cache
      uses: actions/cache@v2
      id: pipenv-cache
      with:
        path: ~/.local/share/virtualenvs
        key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}

    - name: Install dependencies
      if: steps.pipenv-cache.outputs.cache-hit != 'true'
      run: pipenv sync --dev

    - name: Test
      run: pipenv run test

TestのステップはPipfileのscriptsセクション設定が前提なところだけ注意します。また、必要に応じてworking-directoryの指定も忘れずに。

あとがき

全くの想定外動作になっていることに気が付き、修正しては動作させることを繰り返していました。個々の修正点については検索してみると対処例が多く出てきますが、複合した例となると中々見当たらないものです。

Workflowの動作は前提として、各種Actionがどのように動いているのかも把握する必要があり、手間はかかりますが実現できれば楽になることも確かです。イマイチ上手くいかずに放置した方等の参考になれば幸いです。