gemを簡単に作れるようにするgemであるrake-compilerをメンテナンスしている須藤です。gemの作り方をまとめたこともあります。gemをやめたらどうか、という話をします。gemとはビルド済みバイナリーが入ったgemのことです。Pythonで言えばwheelのようなものです。RubyはC言語でRuby用のライブラリーを実装することができます。これを拡張ライブラリーと呼びます。拡張ライブラリーの用途は主に高速化(Rubyで実装したよりCで実装した方が速い)とバインディング(C・C++言語で実装されたライブラリーをRubyから使えるようにするライブラリー)です。拡張ライブラリーをインストールするにはC言語のプログラムをビルドしなければいけません。ビルドするためにはインストール時にC言語のビルド環境が必要になります。ユーザーの環境にC言語のビルド環境がないとインストールできません。インストールの敷居が高くなります。gemにはビルド済みのバイナリーが含まれているためユーザーはC言語のビルド環境を用意しなくてもいいのです。gemのおかげでユーザーはハッピーになれそうですね。gemの問題点を説明します。新しいRubyを使えるまでにタイムラグがあるRubyは毎年クリスマスに新しいバージョンがリリースされます。(メンテナンスリリースは必要に応じて随時行われています。gemにはビルド済みのバイナリーが含まれています。これは、だれかが事前にビルドしてくれているということです。だれかというのはgemの開発者です。Rubyはバージョン間でのABIの互換性を保証していません。2.7では使えません。そのため、新しいバージョンのRubyがリリースされたらそのバージョンのRuby用にビルドしないといけません。2.6.5でも使えます。gemをビルドしてくれていなければ新しいRubyを使えません。gemをリリースすることも十分ありえます。リリースされるならまだいいですが、メンテナンスモードになっているgemは重大な問題がなければ数ヶ月経ってもリリースされないかもしれません。リリースされないgemが変更無しで新しいRubyでビルドできたとしてもユーザーは使えません。APIはそんなに劇的に変わらないのでだいたいビルドできます。gemが新しいRubyに対応しなければ新しいRubyを使えません。1つでも新しいRubyに対応していないとインストールできないからです。gem内に入れていることになります。バインディング対象のライブラリーに脆弱性があった場合は迅速に修正版をリリースするべきです。しないとユーザーが危険な状態が伸びてしまうからです。gem開発者がそんなにタイミングよく作業できるわけではありません。そのため、脆弱性対応リリースが遅くなりがちです。gemにしていない場合でも発生しえます。Nokogiriのようにデフォルトで特定バージョンの依存ライブラリーをビルドするタイプのgemでも発生しえます。指定したバージョンのライブラリーに脆弱性があったらバージョンを更新してリリースしないと脆弱なバージョンを使ったままのユーザーが増えてしまいます。明示的に--use-system-librariesを指定すればNokogiriのバージョンを上げなくても対応できるのですが、残念ながら多くのユーザーはそこまで頑張ってくれないでしょう。gemを更新しなければユーザーは新しいバージョンを使えません。gemに対応するには次のようなコードを入れる必要があります。2.6/io/console.so(ビルド済みバイナリー)があればそっちを優先し、なければio/console.so(自分でビルドしたバイナリー)を読み込むというロジックです。gemを使うなら↓だけで大丈夫です。"#{RUBY_VERSION[/\d+\.\d+/]}/io/console.so"しかし、それではユーザーが自分でビルドして使うという選択肢がなくなります。gemを用意する覚悟を決める必要があります。それは現実的ではないので、普通は前述のように自分でビルドしたバイナリーにフォールバックします。gem用のrequireが失敗します。この分requireが遅くなるということです。$LOAD_PATHにたくさんのパスが入っている環境では無視できないくらい遅くなります。gemをたくさんインストールしているとその分$LOAD_PATHも大きくなります。Railsアプリケーションではたくさんのgemを使うことになるので影響が大きいです。gemを提供している環境でだけフォールバックする対策をとっているgemもあります。releaseだけでgemをリリースできるようにしています。これはすぐに完了します。.gemファイルを作ってrubygems.orgにアップロードするだけだからです。tagをするとかちょろっとしたことをしています。gemをリリースするにはもう一手間必要です。gemを作り、それらをrubygems.orgにアップロードします。各環境用のバイナリーは大変です。rake-compiler-dockを使えば楽になりますが、それでも面倒です。gemのリリースを忘れたりします。Ruby-GNOMEはリリースが億劫だなと思っていました。gemのリリースを忘れていました。gemのリリースは大変なんです。gemのリリースは大変です。gemを作っていました。バインディング対象のGTKなどのライブラリーはLinux上でMinGWを使ってクロスコンパイルしていました。これがすごく大変です。いうのは、クロスコンパイルしている人がほとんどいないので、バインディング対象のライブラリーのバージョンを上げるとビルドエラーになることがよくあるからです。Ruby-GNOMEをリリースするたびにアップストリームにパッチを送っていたものです。gemをやめる決心をしました。gemは主にWindows向けに提供されていますが、Linux向けに提供している野心的なgemもあります。Windowsはバージョンが限られていますし、後方互換性があるので古いWindows向けにビルドしていればいろんなWindowsでもだいたい大丈夫です。Linuxディストリビューションはたくさんあり、使っているlibcも違います。スタティックリンクしたバイナリーを用意すれば大丈夫なのかというとそうでもない気がします。?Pythonのwheelではmanylinuxという(だいたいの)Linux環境で動く仕組み(?)を用意しているので、このくらい頑張れば大丈夫なのかもしれません。私としては、この方向で頑張っちゃうの。?という気持ちになります。RubyGemsはそうなって欲しくないなという気持ちです。gemの問題点をまとめました。gemの問題点を解決する方法を示します。gemをやめることです。gemが必要だったのはユーザーがビルド環境を持っていないことが多かったからです。今は状況が変わっています。Windowsがほぼ標準でDevKitを提供しています。2.3がEOLになったので、今はWindowsユーザーでもビルド環境があるのです。Linuxではパッケージをインストールすればすぐにビルド環境を整えられます。macOSでもXcodeをインストールすればビルド環境を整えられます。Homebrewを使っている人はすでに整っているはずです。他の環境(たとえば*BSD)でもビルド環境はすぐに整えられるでしょう。今はユーザーがビルド環境を持っていると仮定してもよい状態になっています。gemを提供しなくてもユーザーがインストールできる状態が整っています。gemをやめましたが、最近はWindowsでのインストールトラブルはほとんど報告されていません。gemをやめるとうれしいことをまとめます。APIが変わっている可能性があります。2.7ではrb_f_notimplement()が変わります。APIは互換性があるのでなにも変更しなくても新しいRubyで動くことが多いです。その場合は、特になにもしなくてもすぐに新しいRubyを使えます。ユーザーは単に新しいRubyを使ってgemをインストールすればよいだけだからです。2.7対応版をリリースしておくこともできます。すればクリスマス後にリリースしなくてもよくなるのでgem開発者に余裕があります。gemを使った場合でも、プレビュー版でバイナリーを作ってリリースしておくことができなくはありませんが、rake-compiler-dockなど各種ツールが事前に対応していないと難しいです。脆弱性対応をシステムに任せられるバインディングをシステムのライブラリーを使ってビルドするようにしていた場合、バインディング対象のライブラリーに脆弱性があってもシステムのライブラリーを更新すれば対策できます。gemの開発チームよりシステムのライブラリーをメンテナンスしている人たちの方が層が厚いいので迅速に脆弱性に対応してもらえます。gemでもそうでなくても関係ありません。新しい依存ライブラリーをすぐに使える。こともあるバインディングをシステムのライブラリーを使ってビルドするようにしていた場合、バインディング対象のライブラリーの更新はシステムのパッケージシステムが面倒をみてくれます。Rawhide、ArchLinux、Homebrew、MSYS2などのように最新のバージョンに随時アップデートされるシステムではgemの更新を待たずに新しいライブラリーを使えます。ライブラリーのバージョンアップでAPIが変わった場合はgemの更新が必要です。gem用のrequireがいらなくなるので失敗するrequireを実行しなくてもよくなります。これによりrequireが遅くなりません。gemのサポートをやめたのはこれが理由です。gemのリリースをしなくてよくなるので開発者は本来の開発にリソースを注力できます。gemは事前にビルドしたバイナリーをすべてのユーザーが共通で使うことになるので最大公約数の最適化しかできません。gemをやめて各ユーザーごとにインストールする場合はその環境毎に最適化できません。-march=nativeというオプションをつけてビルドするとその環境向けに最適化されます。CPUがSIMDをサポートしていればSIMDを使ったバイナリーを生成することもあります。gemをやめるとユーザーも開発者もハッピーになれそうですね。gemをやめたときの問題点とその解決策をまとめます。gemの場合はビルド済みのバイナリーをコピーするだけなのですぐにインストールは完了します。gemをやめるとインストールするたびにビルドすることになるので時間がかかります。