状況
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。
つまり、冒頭の 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 のキャッシュが保存され、復元され、更新して保存されます。
補足
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/bundle
に bundle 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
をエイリアスに登録して、それを叩いたりしました。