Gemfile.lockをもとに特定のバージョンのRubyで利用できないgemの一覧を出す
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のような特殊なパターンもまだあるかもしれない。