MacでRubyを使った開発していると、プロジェクトごとに使われているBundlerのバージョン自体が違うことがあります。その際の対策をお伝えします。
もくじ
プロジェクトごとに使われるBundlerのバージョンが違う
プロジェクトごとに生成されるGemfile.lockに使われたBundlerのバージョンが記載されます。
1 2 |
BUNDLED WITH 2.1.4 |
Gemfile.lockで指定されたBundlerのバージョンと違うBundlerで bundle install を実行するとエラーとなりGemがインストールできません。
1 2 3 4 5 6 |
$ bundle install /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (2.1.4) required by your /Users/xxxx/Library/Mobile Documents/iCloud~net~xmind~brownieapp/Documents/testrb/search_xmind/Gemfile.lock. (Gem::GemNotFoundException) To update to the latest version installed on your system, run `bundle update --bundler`. To install the missing version, run `gem install bundler:2.1.4` from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path' from /usr/bin/bundle:23:in `<main>' |
なので、プロジェクトごとに使われているBundlerのバージョンが異なっていれば、Macに複数のBundlerをインストールし、都度使い分ける必要が出てきます。
同じような悩みは以下のページでも記載されています。
MacでのRuby開発環境の考え方。rbenvを使おう。
答えの前に、まずMacでRubyを使った開発を行う環境構築について簡単にご説明します。
当方はMacに何かしらソフトウェアをインストールする場合は、Homebrewを使うことを推奨してきました。
しかし、RubyをMacにインストールする場合は違います。RubyをMacに入れる場合、rbenvというRubyを管理するツールをHomebrewでインストールし、rbenvを使ってRubyをインストールするのがおすすめです。
複数のRubyの開発プロジェクトに関わっていると、それぞれのプロジェクトで使われているRubyのバージョンが違うということはザラにあります。HomeBrewだと、以下で説明したように、過去のバージョンをインストールすることはできなくはないですが、一手間かかりますし、気軽に切り替えることができません。
rbenvならバージョン指定してRubyをワンコマンドでインストール、切り替えもできますし、ディレクトリごとに別のバージョンのRubyを使うことも簡単にできます。
BundlerはRubyのバージョンごとにグローバルにインストールされる
次に、Rubyのプロジェクトごとに使われる多くのGemと言われるライブラリの管理についてご説明します。
通常GemはRubyのバージョンごとにインストールされます。例えば、MacにrbenvによってRubyの2.4.1、2.4.2の二つのバージョンがインストールされているとします。すると、2.4.1と2.4.2のそれぞれがGemをインストールするディレクトリをグローバルに持ち、そこにインストールされるようになります。
これだと問題が起こります。同じRubyのバージョンを使っているプロジェクトは全て同じGemのバージョンを使わないといけなくなります。プロジェクトごとにGemのバージョンを変えられないということになります。
それを解決するのがBundlerです。BundlerはGemの管理のスタンダードとなっています。Bundlerを使うと、プロジェクトごとのディレクトリ配下にGemをローカルにインストールすることが可能となります。
なので、Gemは基本的にグローバルにはインストールせず、全てプロジェクトごとにローカルにインストールする開発スタイルがおすすめです。重複するGemもあるかもしれませんが、こちらの方がマシンに依存せずプロジェクト内に閉じて管理できるのでわかりやすいです。
実はBudler自体もGemです。しかし、BundlerはBundler自身によって管理することができないので、Budlerだけはグローバルにインストールするしかありません。つまり、Rubyのバージョンごとにインストールします。
ここで出てくるのが冒頭で紹介した問題です。先のページの質問者さんが考えるようにBundler自体もローカルにインストールできればいいのですがそれはできません。
Bundlerを複数インストールし実行時に指定する
解決策は以下となります。
Rubyのバージョンごとに複数のGemをインストールすることは可能です。そして、Gemは実行時にバージョンを指定することが可能です。
なので、一つのRubyのバージョンに複数のBundlerをインストールします。以下のようにバージョンを指定して複数インストールしてみます。
1 |
$ gem install bundler -v 2.1.4 |
1 |
$ gem install bundler -v 2.2.15 |
すると、以下のように複数のBundlerのバージョンがインストールされたことが確認できます。
1 2 |
$ gem list | grep bundler bundler (2.2.15, 2.1.4) |
バージョンを指定する方法は次のように _でバージョン番号を挟んで指定できます。
1 2 |
$ bundle _2.1.4_ -v Bundler version 2.1.4 |
対象のRubyのプロジェクトのディレクトリに移動し、
1 |
$ bundle _2.1.4_ install |
とGemfile.lockで指定されたバージョンを指定すると無事にGemをインストールできます。
上位のバージョンを使っておけばOKなのかも
冒頭のエラーは2.1.4のBundlerが求められているプロジェクトで1.17.2で実行した場合です。実は、この状況で2.2.15で実行した際は、正常に実行できました。
つまり、もしかしたら求められているものより上位のバージョンで実行すれば問題ないのかもしれません。
ここまで書いて今更気づいたのですが、そうするとBundlerに関しては、バージョンの使い分けなどを考える必要はなく、常に最新のバージョンを入れておけば良いのかもしれません。Homebrew、rbenvもそうですが、このような管理ツールはなるべく互換性を維持しながらアップデートされていくからです。しかし、どこかのタイミングでツールの構成がガラッと変わって互換性が完全には維持されない可能性も無くはないので、上述した対処策は知っておいて損はないでしょう。
また、Bundlerは bundle installだけではなく開発しているプログラム自体を実行する際の、 bundle execでも使われます。プログラムの実行環境がちょっとでも変わると気持ち悪い気もするので、以下のようにBundler自体のバージョンもしっかり指定しておくというのはアリだと思います。
1 |
$ bundle _2.1.4_ exec ruby example.rb |
ちなみに、 bundle _2.1.4_ exec の部分が長くて毎回入力するのが面倒!って思う方はお使いのシェル(zsh)などのアライアスを使って短くしちゃうのもありですね。
.zshrcにコマンドエイリアスを定義してbundle execをbeと省略可能にする方法
1 |
alias be='bundle _2.1.4_ exec' |
ただ、これだと、どのプロジェクトでも2.1.4固定になってしまうので、direnvなどのツールと組み合わせてプロジェクトごとに自動で切り替えてくれるようにしてみてもいいかもしれません。