GitHub Actions にて container に 生の Ruby イメージ を指定すると gem のキャッシュが保存されない場合の対処方法

状況

GitHub Actions の YAML が以下のような場合を想定しています。これは 公式ドキュメント に沿った書き方です。

on: [push]
name: hogehoge
jobs:
  fugafuga:
    name: foobar
    runs-on: ubuntu-latest
    container:
      image: ruby:2.7.0
(中略)
    steps:
    - uses: actions/checkout@v2
    - name: restore gems cache
      uses: actions/cache@v1
      with:
        path: vendor/bundle
        key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-gem-
    - name: bundle install
      run: |
        bundle config set path 'vendor/bundle'
        bundle config set deployment 'true'
        bundle install --jobs 4 --retry 3
(以下省略)

上記の YAML では、キャッシュの保存に失敗します

キャッシュの保存に失敗している状況をログから調べる

キャッシュの保存に失敗している部分のログを見ると、以下のように書いてあります。

 /bin/tar -cz -f /__w/_temp/71ea7712-9523-4dd6-bda3-34caa6cbdb84/cache.tgz -C /__w/FOO/FOO/vendor/bundle .
 /bin/tar: /__w/FOO/FOO/vendor/bundle: Cannot open: No such file or directory

No such file or directory と書いてあり、キャッシュとして保存したいディレクトリ(ファイル群)が存在していないことが書かれています。

プロジェクト直下に vendor/bundle が存在しない理由を調べる

冒頭の YAML では - name: bundle install の箇所で bundle config set path 'vendor/bundle' と指定しています。それなのに、なぜ プロジェクト直下に vendor/bundle が存在していないのでしょう。bundle install自体は成功しているので、vendor/bundle 以外のどこかに gem がインストールされているはずなのですが、それはどこなのでしょうか。

結論としては、container: で指定している Ruby のイメージである ruby:2.7.0 に原因があります*1。詳細は以下の記事にまとめられております(感謝)*2

blog.freedom-man.com

つまり、冒頭の YAML の状態で bundle install を行うと、vendor/bundle 配下ではなく、/usr/local/bundle 配下に gem がインストールされます。

これを確認するには YAML 内のどこかで bundle config コマンドを実行します*3。そうすると以下のような出力が得られるのでそれにより分かります。

 Settings are listed in order of priority. The top value will be used.
 path
 Set via BUNDLE_PATH: "/usr/local/bundle"
 Set for the current user (/github/home/.bundle/config): "vendor/bundle"
 
 deployment
 Set for the current user (/github/home/.bundle/config): true
 
 retry
 Set for your local app (/usr/local/bundle/config): 3
 
 jobs
 Set for your local app (/usr/local/bundle/config): 4
 
 app_config
 Set via BUNDLE_APP_CONFIG: "/usr/local/bundle"
 
 silence_root_warning
 Set via BUNDLE_SILENCE_ROOT_WARNING: true

Set for the current user (/github/home/.bundle/config): "vendor/bundle" という表示があるので、bundle config set path 'vendor/bundle' というコマンドは確かに実行されています。しかし、出力中にある The top value will be used. のメッセージ通り、path の項目においては、より上の行に書かれている Set via BUNDLE_PATH: "/usr/local/bundle" が使われます*4

対処方法(結論)

したがって今回の問題に対処する方法は、「環境変数である BUNDLE_PATH の値を vendor/bundle と指定する」ことです。冒頭の YAML をそのように書き換えると次のようになります。

on: [push]
name: hogehoge
jobs:
  fugafuga:
    name: foobar
    runs-on: ubuntu-latest
    container:
      image: ruby:2.7.0
      env:
        BUNDLE_PATH: vendor/bundle
(中略)
    steps:
    - uses: actions/checkout@v2
    - name: restore gems cache
      uses: actions/cache@v1
      with:
        path: vendor/bundle
        key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-gem-
    - name: bundle install
      run: |
        bundle config set deployment 'true'
        bundle install --jobs 4 --retry 3
(以下省略)

container: の部分で環境変数を指定しています。また、name: bundle install の部分で bundle config set path 'vendor/bundle' の行を削除しています*5

この YAML を用いて GitHub Actions を実行することで、bundle install した gem のキャッシュが保存され、復元され、更新して保存されます。

gyazo.com

補足

BUNDLE_PATH を指定するのではなく、bundle install --path オプションでも大丈夫か

冒頭の YAML にて、bundle config set path 'vendor/bundle' ではなく、bundle install --path vendor/bundle を行うと、BUNDLE_PATH の設定は関係なく --path の指定が有効になります(※検証が十分でないので、もしかしたら有効にならない可能性があります)。

しかし、--path は deprecated ですので、使わないという方針でこの記事を書いています。

CircleCI ではなぜ BUNDLE_PATH を指定しなくても大丈夫なのか

.circleci/config.yml の Docker Image の名称は circleci/ruby:2.7.0 となっていませんか。これは 生の Ruby のイメージではないので、中ではよしなにやってくれています。

BUNDLE_PATH を vendor/bundle 以外にしても大丈夫か

大丈夫です。BUNDLE_PATH で指定する場所と、name: restore gems cache 内の with: path: で指定する場所が一致していれば、キャッシュは期待通りに扱われます。

しかしながら、Rails では vendor/bundlebundle install することが一般的ですので、あえてそこから外れる必要はないかと思います。

もっとも、bundle config set deployment 'true' のオプション指定をしているので、実質的には vendor/bundle でないとダメですね。

BUNDLE_PATH を指定せずに name: restore gems cache 内の with: path: の値を /usr/local/bundle にしても大丈夫か

大丈夫です。しかし前項と同じ問題を抱えますので、現実にはダメでしょう。

感想

CI の設定のデバッグは、特に GitHub Actions では、空コミットを使ったりして毎回 push しないといけないので、今回の問題を解決するために凄まじい時間を溶かしました*6

少しでも時間を短縮するため、たとえば git commit --allow-empty -m "空コミット" && git push をエイリアスに登録して、それを叩いたりしました。

参考

github.com

*1:バージョンは問題ではありません

*2:厳密には今回の問題に関しての詳細ではありませんが、本質は同じです

*3:さらに ls コマンドを併せて実行するとよいです

*4:「Set for the current user (/github/home/.bundle/config): "vendor/bundle"」の方は使われません

*5:残していても無視されるだけなので、削除しなくてもいいといえばいいです

*6:丸一日

Powered by はてなブログ