Help us understand the problem. What is going on with this article?

Ruby 2.6でしか動かないGemを探す

More than 1 year has passed since last update.

この記事は、表参道.rb #42でLTをした内容をベースに、加筆修正を行ったものです。発表資料はこちらにあります。

モチベーション

 Ruby 2.6がリリースされたため、私のローカルにも導入されましたが、具体的にRuby 2.6の新機能を使ってどのような事ができるようになったのかを知りたくなりました。変更を追うのであればRuby2.6 Advent Calendarや、プロと読み解く Ruby2.6 NEWSファイルを読めばいいと思うのですが、まだリリースして2週間程度しか経っていない現時点で、Ruby 2.6でしか動かないGemはどのような使い方をしているのか、ということを調べてみると楽しそうだな、と思い、LTの発表テーマにすることにしました。

調べ方

 以下のようなスクリプトを用意します。
 事前に https://rubygems.org/latest_specs.4.8.gz をダウンロードして同一ディレクトリに置いておく必要があります。説明不要かと思いますが、latest_spec.4.8.gzはそのサイトに格納されているrubygemsのインデックス情報をすべて持っているファイルです。
 これを展開して、Marshal.loadしてあげることで、各Gemの情報が手に入るため、個別にgemspec情報が格納されている情報をダウンロードしてあげることで、Gem::Specificationクラスのインスタンスを参照することができます。

require 'rubygems'
require 'open-uri'
require 'ruby-progressbar'

specs = Marshal.load(Gem.gunzip(File.read("./latest_specs.4.8.gz")))
total = specs.length

pb = ProgressBar.create(
  :title => "Searching",
  :starting_at => 0,
  :progress_mark => '>',
  :remainder_mark => '#',
  :format => '%t(%c/%C): S%BE :%t',
  :total => total,
  :length => 50
)

specs.each do |gem_name, gem_version, _|
  pb.progress += 1
  begin
    compressed = open("https://rubygems.org/quick/Marshal.4.8/#{gem_name}-#{gem_version}.gemspec.rz").read
    inflated = Gem.inflate(compressed)
    spec = Marshal.load(inflated)
    ruby1_8 = Gem::Version.new("1.8.7")
    ruby1_9 = Gem::Version.new("1.9.3")
    ruby2_0 = Gem::Version.new("2.0.0")
    ruby2_1 = Gem::Version.new("2.1.0")
    ruby2_2 = Gem::Version.new("2.2.0")
    ruby2_3 = Gem::Version.new("2.3.0")
    ruby2_4 = Gem::Version.new("2.4.0")
    ruby2_5 = Gem::Version.new("2.5.0")
    ruby2_5_1 = Gem::Version.new("2.5.1")
    ruby2_5_2 = Gem::Version.new("2.5.2")
    ruby2_5_3 = Gem::Version.new("2.5.3")
    ruby2_6 = Gem::Version.new("2.6.0")
    old_versions = [ruby1_8, ruby1_9, ruby2_0, ruby2_1,
                    ruby2_2, ruby2_3, ruby2_4, ruby2_5,
                    ruby2_5_1, ruby2_5_2, ruby2_5_3]
    required = spec.required_ruby_version
    if old_versions.all?{|v| !required.satisfied_by?(v) } && required.satisfied_by?(ruby2_6)
      puts "#{gem_name}-#{gem_version}"
    end
  rescue => e
    puts "error: #{gem_name}: #{e.message}"
  end
end
pb.finish

 その中でrequired_ruby_versionというメソッドに、Rubyの要求バージョンがGem::Requirementの形式で格納されているため、この値を使って判定を行います。

 Gem::Requirement#satisfied_by?は、引数に指定した、バージョンが要求を満たしているかを判定するメソッドです。当初はrequired_ruby_version.satisfied_by?(Gem::Version.new(2.6.0))だけで大丈夫かと思ったのですが、それだと、>= 2.0のような指定をされているGemでも適合してしまうため、Ruby 1.8〜Ruby 2.5までのバージョンを用意し、それらの全てでsatisfied_by?の結果がfalseになり、Ruby 2.6でsatisfied_by?がtrueになるGemを抽出しています。

実行結果

対象Gem数: 148736個(2018/1/10時点)
バッチ実行時間: 13時間
該当Gem数: 35個

 1個も見つからなかったら企画終了だったのですが、まずは見つかって一安心という結果です。

除外Gem

 見つかった35個のGemのうち、今回の調査対象から外したGemがあります。
 例えばautherです。年1回required_ruby_versionをアップデートしているようで、最新のGemバージョンにおいては、最新のRubyバージョンしかサポートしないポリシーのようです。この作者のGemは全てこのポリシーになっており、困る人いないのかな…とも思いますが、今回は気にしないことにしました。

 これらのGemを除外することで、有意な結果になりました。

Ruby2.6でしか動かないGem

covered

rubygems: https://rubygems.org/gems/covered
github: https://github.com/ioquatix/covered

RubyVM::AbstractSyntaxTreeを使ったカバレッジツール。通常のアプローチと違って、eval内のコードもカバレッジを取得できるのが特徴のようです。

function-composite

rubygems: https://rubygems.org/gems/function-composite
github: https://github.com/nobu/function-composite

Ruby2.6から導入されたProc#<<Proc#>>の引数にSymbolも渡せるようにした拡張。作者はnobuさんでした。

require 'function-composite'
using Function::Composite

p %w{72 101 108 108 111}.map(&:to_i >> :chr) #=> ["H", "e", "l", "l", "o"]

p %w{72 101 108 108 111}.map(&proc { |s| s.to_i } >> :chr) #=> ["H", "e", "l", "l", "o"]

h = { Alice: 30, Bob: 60, Cris: 90 }
p %w{Alice Bob Cris}.map(&(:to_sym >> h)) #=> [30, 60, 90]

import_as

rubygems: https://rubygems.org/gems/import_as
github: https://github.com/hanachin/import_as

TypeScriptライクなimport文をRubyに取り込む拡張。

# c.rb
class C
  def hi
    puts :hi
  end
end

# main.rb
require "import_as/core_ext"

import { C as C2 }.from "./c.rb"

C2.new.hi
# hi

RubyVM::AbstractSyntaxTreeを使って実現しているようです。

pdfh

rubygems: https://rubygems.org/gems/pdfh
github: https://github.com/iax7/pdfh

 PDFのスクレイピングライブラリ。Initial Commitが2019/1/6で、そのためrequired_ruby_versionが2.6になったものと思われます。

rvnc

rubygems: https://rubygems.org/gems/rvnc
github: https://github.com/siman-man/rvnc

Rubyの変数一覧を出力してくれるツール。RubyVM::AbstractSyntaxTreeを使って実現しているようです。

$global = 'global'
foo = 1
bar = 'hi'
BAZ = :baz
a, *b = [1, 2, 3]

class A
  @@test = 'test'

  def initialize
    @name = 'test'
  end
end
+---------+---------------+
| Name    | Location      |
+---------+---------------+
| $global | example.rb:1  |
| foo     | example.rb:2  |
| bar     | example.rb:3  |
| BAZ     | example.rb:4  |
| a       | example.rb:5  |
| b       | example.rb:5  |
| @@test  | example.rb:8  |
| @name   | example.rb:11 |
+---------+---------------+

まとめ

 まだ公開されて2週間程度なので、Ruby2.6でしか動かないGemは少ないようですが、RubyVM::AbstractSyntaxTreeを使った黒魔術的なGemが人気のようです。これまで公開されていたGemがRuby2.6に対応するコードを追加したケースも入れると、もう少し引っかかりそうなのですが、今回のスクリプトでは検出できなかったので、何かいいアイデアがあれば是非教えてください。

bbank
クラウド型経営管理システム《ALL-IN》の開発を行なっております。開発者募集中!
https://bbank.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away