Railsアプリなど、Bundlerでgemの依存管理をしているプロジェクトでRubyのアップデートをしようとするとき、不幸なことにgemが全体的に古いことがある。
また、gemspecでrequired_ruby_version
というメタデータでRubyのバージョンの範囲を設定していると、その範囲のRubyの環境下でしかそのgemはインストールできない。よくある例として、次のようにRuby 2系でしか使えないようにしているものがある。
Gem::Specification.new do |spec| spec.required_ruby_version = "~> 2.0" end
このような状況下でRubyだけアップデートするとgemをインストールできないことがある。
こうなると、まずgemをアップデートしていく必要がある。この作業の範囲がどれぐらいか把握するために、Gemfile.lockに載っているgemのうち、特定のバージョンのRubyだとrequired_ruby_version
を満たせないgemの一覧を出せるようなスクリプトを書いた。
# unusable_gems.rb require 'bundler' def resolve_gemspec_path(spec) # nokogiriはarchとosをgemspecのファイル名に持つ場合がある if spec.name == 'nokogiri' native_gem_path = Bundler.specs_path.join("#{spec.name}-#{spec.version}-#{Gem::Platform.local.cpu}-#{Gem::Platform.local.os}.gemspec") return native_gem_path if native_gem_path.exist? end case spec.source when Bundler::Source::Rubygems # default gem default_gem_path = Pathname.new(Gem.default_specifications_dir).join("#{spec.name}-#{spec.version}.gemspec") return default_gem_path if default_gem_path.exist? # ふつうのgem Bundler.specs_path.join("#{spec.name}-#{spec.version}.gemspec") when Bundler::Source::Git # git sourceからインストールしたgem Bundler.bundle_path.join("#{spec.source.install_path}", "#{spec.name}.gemspec") else warn "failed to resolve: #{spec.inspect}" nil end end gemspec_paths = Bundler::LockfileParser.new(Bundler.default_lockfile.read).specs.map { |spec| path = resolve_gemspec_path(spec) unless path&.exist? warn "gemspec not found: #{path}" next nil end path }.compact gemspecs = gemspec_paths.map { |path| Gem::Specification.load(path.to_s) } checked_ruby_version = Gem::Version.create(ARGV[0]) puts gemspecs.reject { |gemspec| gemspec.required_ruby_version.satisfied_by?(checked_ruby_version) }.map { |gemspec| "#{gemspec.name} (#{gemspec.version})" }
例として、まず次のGemfileでbundle install
しておく。
source "https://rubygems.org" gem "activerecord", "7.0.4.2" # required_ruby_version = ">= 2.7.0"
Gemfile.lockが存在するディレクトリで次のように実行する。かなり古いバージョン2.6.0、2.7.0を例として使う。
$ ruby unusable_gems.rb 2.6.0 activemodel (7.0.4.2) activerecord (7.0.4.2) activesupport (7.0.4.2) nokogiri (1.14.1) oauth (1.1.0) oauth-tty (1.0.5) $ ruby unusable_gems.rb 2.7.0 # 2.7.0だと使えないgemがないので結果は空
たとえば間接依存のoauth 1.1.0も https://github.com/oauth-xx/oauth-ruby/blob/v1.1.0/oauth.gemspec#L36 を見るとわかるように2.7.0以上の制限がある。
注意点としては、今回必要だったユースケースではgemのソースの種類としてパス (Bundler::Source::Path
) などはなかったので入れていない。また、nokogiriのような特殊なパターンもまだあるかもしれない。