LoginSignup
5
1

More than 1 year has passed since last update.

bundler 2.2.x でのエラーを乗り越えたい

Posted at

2020年12月に、 Rails アプリの Docker イメージをビルドする CI が突然エラーを起こしたことがありました。これは bundler 2.2.0 がリリースされたことに起因していたため、イメージに入れる bundler を一旦 2.1.4 に固定することで対処しました。

そのままずっと問題に蓋をしていたものの、 bundler は既に 2.2.33 や 2.3.15 が出ているため、きちんと当時のエラーについて調べてバージョンアップしようと思い立ちました。

エラーが何だったのか、 bundler の changelog などを見てもすぐにはわからなかったので、実際に様々なバージョンで実験して探りました。( x86-64 の Linux および macOS を対象としています)

TL;DR

bundler 2.2 からは、 Gemfile.lock に Ruby のプラットフォームの情報( x86_64-linuxx86_64-darwin-18 など)を記載して活用しようとします。特に初期の頃はこれがうまく働かず、 gem のバイナリ配布版を選ばずソース版からコンパイルしようとしてしまうことがありました。

対策として以下を実施すれば、多くの場合にうまく動くはずです。

  • bundler 2.2.3 以上を使う1
  • Gemfile.lock に適切なプラットフォームを書き込む
    • 開発環境だけでなくデプロイ環境の分も明示的に追加が必要
    • bundler 2.2.0 までにある ruby は不要なら抜いておく
gem install bundler -v '>= 2.2.3' --conservative

gem env platform | tr ':' '\n'  # 現在のプラットフォームを表示
bundle lock --add-platform x86_64-linux  # 必要なだけ追加
bundle lock --remove-platform ruby

実験

環境準備

実験は Ubuntu 18.04 上で、 Ruby 2.7.4 を用いて行いました。ただし後で、開発環境が macOS の場合を模倣します。

プラットフォーム毎にパッケージが用意されている gem でエラーが発生しうるので、今回は libv8 だけで試します。

Gemfile
source "https://rubygems.org"
gem "libv8"

また、様々なバージョンの bundler で試したいので、まとめてインストールしておきます。(結果的には 2.2.3 まであれば十分でした)

# bundler 2.1.4, 2.2.0〜33, 2.3.0〜15 をインストール
gem install --no-document bundler:{2.1.4,2.2.{0..33},2.3.{0..15}}

# 複数入れてある場合、バージョンを指定して実行できる
bundle _2.3.7_ -v

(1) Gemfile.lock が無い場合

新しく Rails アプリを作るときなどは、 Gemfile.lock はまだ無く Gemfile から生成することになります。この結果が bundler バージョンによって異なるか確認しておきます。

set -eu
versions=(2.1.4 2.2.{0..33} 2.3.{0..15})
gf0=Gemfile  # 準備で作ったファイル

rm -rf .bundle/
bundle config set --local path 'vendor/bundle'

for v in ${versions[@]}
do
    echo "#--- bundler ${v} ---#"
    gf=${gf0}-${v}
    cp ${gf0} ${gf}
    bundle _${v}_ install --gemfile=${gf} || true
    echo
done

生成された Gemfile-${v}.lock は以下のようになりました。この3パターンを以降の実験で使います。

version PLATFORMS
2.1.4 ruby のみ
2.2.0 rubyx86_64-linux
2.2.1 以上 x86_64-linux のみ
Gemfile-2.2.0.lock
GEM
  remote: https://rubygems.org/
  specs:
    libv8 (8.4.255.0)
    libv8 (8.4.255.0-x86_64-linux)

PLATFORMS
  ruby
  x86_64-linux

DEPENDENCIES
  libv8

BUNDLED WITH
   2.2.0

(2) Gemfile.lock がある場合

新しい開発環境に gem をインストールするような場合です。少しややこしい場合を想定し、「 Gemfile.lock は macOS 上で生成した」「新しい環境は Linux 」という状況を再現して実験します( Gemfile.lock 内のプラットフォーム情報を無理やり書き換えておきます)。

set -eu
versions=(2.1.4 2.2.{0..33} 2.3.{0..15})
v0=2.1.4
gf0=Gemfile-${v0}  # 実験(1)で作ったファイル

rm -rf .bundle/
bundle config set --local path 'vendor/bundle'

for v in ${versions[@]}
do
    echo "#--- bundler ${v0} --> ${v} ---#"
    gf=${gf0}-${v}
    cp ${gf0}      ${gf}
    cp ${gf0}.lock ${gf}.lock
    sed -i -e 's/x86_64-linux/x86_64-darwin-18/g' ${gf}.lock
    bundle _${v}_ install --gemfile=${gf} || true
    echo
done

初回が 2.1.4

2.1.4 で作った Gemfile.lock の PLATFORMS には ruby しか書かれていません。そのため開発環境を変えた影響はありません。

2.2.0 〜 2.2.2 では、 PLATFORMS に現在の環境 x86_64-linux が追加されました。 2.2.3 以上では何も変更されませんでした。

Gemfile-2.1.4-2.2.2.lock
GEM
  remote: https://rubygems.org/
  specs:
    libv8 (8.4.255.0)
    libv8 (8.4.255.0-x86_64-linux)

PLATFORMS
  ruby
  x86_64-linux

DEPENDENCIES
  libv8

BUNDLED WITH
   2.2.2

初回が 2.2.0

初期状態では PLATFORMS に rubyx86_64-darwin-18 が書かれています。

2.2.0 〜 2.2.2 では、 PLATFORMS に現在の環境 x86_64-linux が追加されました。 2.2.3 以上では gem をコンパイルしようとしました(手元ではコンパイル失敗しました)。

初回が 2.2.1

初期状態では PLATFORMS に x86_64-darwin-18 のみが書かれています。

どのバージョンでも PLATFORMS に情報が追加されました。バージョン毎の追加内容は実験(1)と同じでした。

Gemfile-2.2.1-2.2.0.lock
GEM
  remote: https://rubygems.org/
  specs:
    libv8 (8.4.255.0)
    libv8 (8.4.255.0-x86_64-darwin-18)
    libv8 (8.4.255.0-x86_64-linux)

PLATFORMS
  ruby
  x86_64-darwin-18
  x86_64-linux

DEPENDENCIES
  libv8

BUNDLED WITH
   2.2.1

(3) deploymentを指定した場合

デプロイ環境で gem をインストールする際は、開発時に想定しているものがそのまま使われるべきです。そのために Gemfile.lock を変更しないよう deployment または frozen を指定します。変更しないという制約により、実験(2)とは異なった結果になります。

set -eu
versions=(2.1.4 2.2.{0..33} 2.3.{0..15})
v0=2.1.4
gf0=Gemfile-${v0}  # 実験(1)で作ったファイル

rm -rf .bundle/
bundle config set --local deployment 'true'  # ここを追加
bundle config set --local path 'vendor/bundle'

for v in ${versions[@]}
do
    echo "#--- bundler ${v0} --> ${v} (deployment) ---#"
    gf=${gf0}-${v}-deployment
    cp ${gf0}      ${gf}
    cp ${gf0}.lock ${gf}.lock
    sed -i -e 's/x86_64-linux/x86_64-darwin-18/g' ${gf}.lock
    bundle _${v}_ install --gemfile=${gf} || true
    echo
done

初回が 2.1.4

PLATFORMS に ruby のみが書かれています。

2.2.0 では gem をコンパイルしようとしました(手元ではコンパイル失敗しました)。他では問題なくインストールされました。

初回が 2.2.0

PLATFORMS に rubyx86_64-darwin-18 が書かれています。

2.2.0 以上では gem をコンパイルしようとしました(手元ではコンパイル失敗しました)。したがって 2.1.4 のみで問題なくインストールされました。

初回が 2.2.1

PLATFORMS に x86_64-darwin-18 のみが書かれています。

2.1.4 では普通に x86_64-linux がインストールされましたが、 2.2.0 〜 2.2.2 では x86_64-darwin-18誤インストールされました。 2.2.3 以上ではプラットフォーム不一致のエラーが出ました

#--- bundler 2.2.1 --> 2.2.0 (deployment) ---#
Warning: the running version of Bundler (2.2.0) is older than the version that created the lockfile (2.2.1). We suggest you to upgrade to the version that created the lockfile by running `gem install bundler:2.2.1`.
Fetching gem metadata from https://rubygems.org/.
Using bundler 2.2.0
Fetching libv8 8.4.255.0 (x86_64-darwin-18)
Installing libv8 8.4.255.0 (x86_64-darwin-18)
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Bundled gems are installed into `./vendor/bundle`

#--- bundler 2.2.1 --> 2.2.3 (deployment) ---#
Your bundle only supports platforms ["x86_64-darwin-18"] but your local platform is x86_64-linux.
Add the current platform to the lockfile with `bundle lock --add-platform x86_64-linux` and try again.

動作の背景

実験結果をもとに、 bundler の changelog から動作変更の内容を探しました。

2.2.0

2.2 系から、 Gemfile.lock にプラットフォームの情報を持つようになりました。

しかし、 2.1.4 までに作られた Gemfile.lock は ruby しか書いていません。これにより 2.2.0 は「厳密に ruby のパッケージを選ぶ」という動作をし、ソース版をダウンロードしてコンパイルしてしまいます。

2.2.1

2.2.0 で判明した問題への暫定対応として、「 2.2 以前に作られた Gemfile.lock は 2.2 以前の方法で解釈する」ようになりました。これにより deployment (frozen) であっても昔の Gemfile.lock は問題なく使えます。
https://github.com/rubygems/rubygems/pull/4127

一方で、 2.2 以降に作られた Gemfile.lock に対しては記載のプラットフォームを守ろうとします。 2.2.0 が書き込んだ ruby だけが一致すればそれを使おうとするため、やはりソース版をダウンロードしてコンパイルしてしまいます。

さらに 2.2.1 から PLATFORMS に ruby を書かなくなったため、一致するプラットフォームが無い場合が出てきます。このときに誤ったバージョンをインストールしてしまう問題が報告されました。
https://github.com/rubygems/rubygems/issues/4166

2.2.3

開発環境とデプロイ環境とでプラットフォームが異なる場合の問題が修正されました。一致するプラットフォームが無いときは明確にエラーを出します。
https://github.com/rubygems/rubygems/pull/4172

また、 2.2 以前の Gemfile.lock に対する互換性が上がったようです。(本記事では実験(1)の「初回が 2.1.4 」において、不要な更新が発生しなくなっています)

対処法

bundler 2.2.x に由来するエラーは、だいたい Gemfile.lock 生成時と参照時の環境の違いによって起きます。最も単純な対処法は両者で bundler バージョンもプラットフォームも一致させることです。

とはいえ開発環境が macOS 、デプロイ環境が Linux といったことは普通にあります。このような場合は、「 Gemfile.lock にプラットフォームの情報を持つようになった」ということを理解して対処法を考えます。

  • 使用する bundler は、動作の安定した 2.2.3 以降が良いです1
  • 開発環境のプラットフォームを追加します: bundle lock --add-platform <name>
    • 新しく Gemfile.lock を生成する場合と同じものを入れておくのが良いと思います
    • 特に 2.2.0 まであった ruby は、デプロイ環境でソース版を選ぶ原因になりうるので、必要ない限りは抜いておきます
  • デプロイ環境のプラットフォームも追加します
    • 2.2.3 以降なら、足りない場合はエラーの中で必要なコマンドを教えてくれます
  1. Ruby 3.0.0 に付属しているのが bundler 2.2.3 のため、デフォルトのものを使っているなら改めてインストールしなくても大丈夫です 2

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1