GitHub Actionsでは公式が依存ライブラリやビルド成果物をキャッシュするためのaction, actions/cacheが提供されています。npmやpipなどの依存関係管理ツールがパッケージを保存するディレクトリをキャッシュすることで、各ビルド・テストの時間を大幅に削減することができます。ところが、公式のabout
Cache dependencies and build outputs in GitHub Actions
のbuild outputs
は例示がなく、これまで使ったことがありませんでした。
今回はこれをmakefileで管理するプロジェクトにあてはめ、いい感じにしてくれるようにしたので、ご紹介します。
そもそもmakeの結果をキャッシュする... ?
そもそも論、せっかくクリアーな環境を毎回用意してくれるんだから、makeの結果をキャッシュするなんてナンセンスだ!という主張があると思います。全くその通りです。ただ、
- プライベートリポジトリで時間を少しでも節約したい
- 比較的重い処理があり、vCPUが2個だけだとキャッシュがないと辛い。
- ほとんど変わらないのにブランチへのプッシュを全部作りなおすのはちょっと...
というケースはあると思います。僕の場合、研究の解析に使うスクリプトをプライベートリポジトリに生データごと入れておいて、毎プッシュごとに自動ビルドしています。
データは50本のクソデカサンプルで、これのアンサンブルをとる処理になっています。具体的には、データから距離データやボロノイ体積を計算したりする比較的重めの処理があり、これらが計算時間をだいぶ使ってしまっていました。そのままでは研究室のオーガナイゼーションのビルド時間がひっ迫してしまうため、makeの結果をキャッシュすることにしました。
解決法
GNU makeは、outputとinputのtimestampを比較して、inputの方が新しければ再ビルドする
という挙動をします。
actions/checkoutとactions/cacheを使うと、timestampは全部最新のものになってしまうため、キャッシュされていてもmakeはすべて再ビルドしてしまいます。また、workflowをtriggerしたcommitのすぐ直前のビルド結果のキャッシュを使ってほしいです。いろいろ複雑
これを解消するために cache-make というactionsを作成しました。
使い方
actions/checkout
でfetch-depth: 0
を付けていつも通りcacheを呼んだあとは、uses: flat35hd99/cache-make@v1.0.0
を宣言するだけで動くようにしてあります。Easy to use !
jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # 履歴を全部取得
- name: Setup cache
uses: actions/cache@v2
with:
path: dist/
key: ${{ runner.os }}-makefile-${{ github.sha }}-${{ hashFiles('**/makefile') }}
restore-keys: |
${{ runner.os }}-makefile-
- name: Change updated time
uses: flat35hd99/cache-make@v1.0.0 # 読み込むだけ!
- name: Build
run: make
仕組み
- checkoutする(actions/checkout)
- cacheを取得する(actions/cache)
- gitに記録されているファイルのtimestampを変更する(flat35hd99/cache-make)
- 前回のコミットなど(ref)との差分を取得
- 変更されていないファイルのtimestampを大昔のtimestampに変更する(1970/1/1)
こうすればmakeはgitのツリー上で新しくなったスクリプトの影響を受けるものだけmakeし直します。
終わりに
まだ微妙なところはたくさんある(ex: merge時の挙動、インターフェースの改善)ので、issueやpull requestいただけるととても嬉しいです。
そもそも論を繰り返させていただきますが、今回紹介したactionは大事なビルドなどでは使わないことを強く推奨します。せっかくクリーンな環境があるのに、それを無下にしてしまいかねません。実際、キャッシュの挙動が予測しきれておらず、ビルドが通らないということがありました。GitHub Actionsは自動で全部やってくれることが売りなのに、その挙動を開発者が考えなければならないのは愚かでしかないです。ビルド時間を早くする、他の解決法を見つけるなどして、できるだけこのツールは使わないようにすべきです。どうしてもというときに、リリース以外のPull requestのプッシュだけで有効にするなど、限られたケースでは有用ではないかと思います。
あと実はこれは「身の回りの困りごとを楽しく解決!【PR】Works Human Intelligence Advent Calendar 2021」の記事なのですが、楽しさポイントは最近読んでいるA Philosophy of Software Designのインターフェースを減らして複雑性を減らそうという考え方を全面的に体現できるように実装したことです。学んで実践するプロセスができて僕は大変満足です。