2023年1月1日日曜日

GitHub Actionsのcomposite actionでNPMをキャッシュする方法

えー、あけましておめでとうございます。

これは2023年最初の記事なんですが、実は2022年最後の記事のつもりでした。なぜこうなったかというと、単に2022年の日曜がもう1回あると思ってただけです。

というわけで、新年一発目はタイトルどおりGitHub ActionsのTipsです。季節一切関係ないから2022年最後だろうが2023年最初だろうがどうでもいいんですが。

キャッシュについて

GitHub Actionsには便利なキャッシュ機能があります。これを使うと別のアクションとか以前に実行したアクションで生成したファイルを再度生成しなくて済むため、実行時間の節約につながります。

たとえばnpm ciでホームディレクトリーに生成される.npmをキャッシュして、次回以降のnpm ciを高速化するというのがよくある使い方です。

これのやりかたは検索すると色々出てきますが、典型的にはこんなワークフローでしょうか。

- uses: actions/cache@v3
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      npm-

  • package-lock.jsonの内容が全く同じ以前のキャッシュ」があればそれを最優先で使う
  • 次点で「全く同じでなくてもいいので以前のキャッシュ」があればそれを使う
  • キャッシュ対象(および復元場所)は~/.npm

という意味です。

これは大体の場合はうまく動きます。「大体の場合」というのは、

  • 1つの環境(OSやNode.jsバージョン)のみでアクションを実行している
  • 動作OSはPOSIX(Linux/macOS)

の両方を満たしている場合です。ウェブアプリケーションであればLinuxの特定のNode.jsバージョンだけで動けばいい場合が大半なので「大体の場合は」と書きました。しかし、フレームワークやライブラリーなどはなるべく多くの環境で動かしたい場合があります。

前者(複数環境対応)はキャッシュキーを工夫することで割と簡単に改善できますが、後者(Windows対応)はさらにひと工夫必要です。

また、他のアクションからも再利用しやすいようにcomposite actionとして作りたい場合もあります。

これらの場合でもうまく動く、キャッシュ用composite actionを作ってみました。

解答例

まず実際に作ったcomposite actionをごらんいただきましょう。

https://github.com/shimataro/value-schema/blob/7d85e269573282f255f131e78d59f611001c1808/.github/actions/cache-npm/action.yml

このように使います。

https://github.com/shimataro/value-schema/blob/7d85e269573282f255f131e78d59f611001c1808/.github/actions/build-package/action.yml#L26-L30

osにはこのアクションで動かしているOS情報(ubuntu-22.04windows-2022など)、node-versionにはNode.jsのバージョン情報(16.0.018.0.0など)を指定します。

どちらも動作環境が複数ある場合のキャッシュのキー判別に使っているだけなので、1つの環境でしか使わない場合は適当な値("default"等)でかまいません。

ポイント

以下は、なぜ最初に書いたワークフローでは動かないのかの説明です。ポイントが3つほどありますが、とりあえず答えだけわかればいいというせっかちさんは読み飛ばしてもらって構いません。

1. WindowsではNPMのキャッシュディレクトリーが違う

POSIX(LinuxとmacOS)ではキャッシュディレクトリーは~/.npmですが、Windowsでは~\AppData\npm-cacheです。そのためpath部分を決め打ちにはできません。

この情報と対策はCacheアクションの公式ドキュメントに記載されています。簡単に言うと、「キャッシュディレクトリー名をnpm config get cacheで取得」→「GitHub Actionsの出力結果取得機能を使って次のアクションにディレクトリー名を渡す」という方法です。

ただ、この公式ドキュメントではPOSIX版とWindows版で別々のシェルコマンドを実行するように書かれています。できることならOSに関係なく同一コマンドで実行したいですよね。

2. BashはWindowsでは動作しない

・・・というのはちょっと誤解を生む表現かもしれません。GitHub ActionsのOSにWindowsを指定してもBash(Git Bash)は普通に使えます。

しかし、Git BashではWindows形式のファイル名を扱えないようで、shell: bashと指定すると実行時にエラーが発生します。

- run: echo "NPM_CACHE_DIRECTORY=$(npm config get cache)" >> ${{ github.env }}
  shell: bash # エラー: Windowsでは ${{ github.env }} は"D:\a\_temp\_runner_file_commands\set_env_XXX" という形式なので、Bashでは書き込めない

そのためshell: pwshと指定する必要があります。PowerShellはすべてのプラットフォームでサポートされていて、Linux/macOSで動かしたときにはPOSIX形式のファイル名を扱えます。

3. キャッシュ情報の取得には環境変数を使う

上で、公式ドキュメントには「GitHub Actionsの出力結果取得機能を使って次のアクションにディレクトリー名を渡す」という方法が説明されている、と書きました。

この説明自体に間違いはありません。出力結果の取得機能というのは、まさにそのためにある機能です。

しかし、composite actionの中でこの方法を使うと、ワークフローの終了時(Post処理時)に以下のようなメッセージが出て対象ディレクトリーがキャッシュされないという問題があります。

Warning: Input required and not supplied: path

内部処理の問題なので細かな条件まではわかりませんが、composite action内のステップの出力情報は該当のアクションが終わるとクリアされてしまい、終了時のPost処理には引き継がれないのでしょうか。

そこで、出力結果取得機能ではなく環境変数を使ってディレクトリー名を渡すことにしました。${{ github.output }}ではなく${{ github.env }}を使っているのがその理由です。

ただしこれも万能というわけではなく、

  • 他のステップによって該当の環境変数(NPM_CACHE_DIRECTORY)が書き換えられる恐れがある
  • 他のステップにも常に環境変数情報がついてまわるので少々うざい

という問題があります。前者は誤って書き換えられないような名前にするとか、後者は実害はないので我慢してもらうしかないかもしれません。


0 件のコメント:

コメントを投稿