いくつかの default gem を Bundler を利用してバージョンを固定して使おうとした場合、現在の Rubygems/Bundler/Ruby の仕様ではユーザーが指定したバージョンでは利用できないケースがあります。
例えば
$ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin17]
$ gem list | grep openssl
openssl (2.0.5, 2.0.4, default: 2.0.3)
というような環境の場合に require 'openssl'
を実行したときに activate されるバージョンは openssl で探索して最初に見つかったバージョン、すなわち 2.0.5 が使用されます。
ruby -ropenssl -e 'p OpenSSL::VERSION'
"2.0.5"
この時、例えばユーザーがどうしても openssl 2.0.4 を使いたいと思って以下のような Gemfile を書いたとします。
> cat Gemfile
# frozen_string_literal: true
source "https://rubygems.org"
gem 'openssl', '2.0.4'
しかし残念ながら、bundler が動く前に rubygems が openssl を require してしまっているため、以下のように activated エラーとなります。
> bundle exec ruby -ropenssl -e 'p OpenSSL::VERSION'
/path/to/2.4.1/lib/ruby/gems/2.4.0/gems/bundler-1.15.4/lib/bundler/runtime.rb:317:in `check_for_activated_spec!': You have already activated openssl 2.0.5, but your Gemfile requires openssl 2.0.4. Prepending `bundle exec` to your command may solve this. (Gem::LoadError)
この問題は pure ruby で実装されている default gem であれば bundler のリポジトリ配下に vendoring library として同梱することで回避できます。
https://github.com/bundler/bundler/blob/master/lib/bundler/vendor/fileutils/lib/fileutils.rb
bundler の場合、Bundler::FileUtils として名前空間を分けることでユーザーが指定したバージョンでも activate 時にコンフリクトしないで利用可能としています。しかし、この方法は C extention なライブラリでは利用することができません。
今回、bundler チームより json/psych をデータのシリアライズを行うために rubygems/bundler で利用したいので、C extension について Ruby 本体に何かしら回避する方法を実装できないかと相談を受けています。
@hsbt と @indirect で相談して、例えば
require_for_bundler 'json', '2.0.2'
したら、json-2.0.2(これは ruby 2.4.1 に同梱されているバージョン)が Bundler::JSON
というような名前空間に置かれたら回避できそうという話はしました。なお、過去にも似たような issue はありました。
https://bugs.ruby-lang.org/issues/10320
この issue にあるような方法を拡張した require 'json', version: '2.0.2', into: :Bundler
という書き方はそれっぽいと思います。また今回のユースケースでは default gem の使用を強制するような require 'json', version: :default, into: :Bundler
という機能で事足りそうです。
このような feature について松本さんはどうお考えでしょうか
English version: https://gist.github.com/hsbt/b09c7c7b4d09d7f8f7684359354f4818