Goodbye fat gem

: author

Sutou Kouhei

: institution

ClearCode Inc.

: content-source

RubyKaigi Takeout 2020

: date

2020-09-04

: start-time

2020-09-04T10:00:00+09:00

: end-time

2020-09-04T10:25:00+09:00

: theme

.

Sutou Kouhei

The president of ClearCode Inc.n (('note:クリアコードの社長'))

# img
# src = images/clear-code-rubykaigi-2020-silver-sponsor.png
# relative_height = 100
# reflect_ratio = 0.1

Sutou Kouhei

* The maintainer of (({rake-compiler}))\n
  (('note:(({rake-compiler}))のメンテナー'))
  * A gem for generating fat gem\n
    (('note:fat gemを作るためのgem'))
* The maintainer of (({Ruby-GNOME}))\n
  (('note:(({Ruby-GNOME}))のメンテナー'))
  * A project that used fat gem\n
    (('note:fat gemを使っていたプロジェクト'))

Fat gem

Gem that includes pre-built binariesn (('note:ビルド済みバイナリー入りのgem'))

Why is fat gem needed?n(('note:どうしてfat gemが必要なのか'))

* Difficult to build extension library\n
  (('note:拡張ライブラリーのビルドが難しい'))
  * Extension library:\n
    A Ruby library implemented with C API\n
    (('note:拡張ライブラリー:C APIを使って実装されたRubyライブラリー'))
* Fat gem just copies pre-built binaries\n
  (('note:fat gemは単にビルド済みバイナリーをコピーするだけ(ビルドしない)'))

Why is building it difficult?n(('note:どうして拡張ライブラリーのビルドは難しいのか'))

* Users need build environment\n
  (('note:ユーザーはビルド環境を用意しないといけない'))
  * e.g.: C compiler, (({make})) and so on\n
    (('note:例:Cコンパイラーや(({make}))など'))
* Users need build dependencies\n
  (('note:ユーザーが依存するライブラリーを用意しないといけない'))
  * e.g.: GTK+ 3 for gtk3 gem\n
    (('note:例:gtk3 gemはGTK+ 3が必要'))

With fat gemn(('note:fat gemを使うと'))

* Users don't need build environment!\n
  (('note:ユーザーはビルド環境を用意しなくてもよい'))
  * No C compiler\n
    (('note:ユーザーはビルド環境がなくてもよい'))
* Users don't fail installation!\n
  (('note:ユーザーはインストールに失敗しない'))
  * Fat gem just copies pre-built binaries\n
    (('note:fat gemは単にビルド済みバイナリーをコピーするだけ'))

With fat gemn(('note:fat gemを使うと'))

(('tag:center')) (('tag:xx-large')) Happy!n 🙌

(('tag:right')) (('wait'))…Really?n (('note:…ほんとに?'))

A fat gem maintainer says…n(('note:あるfat gemメンテナー曰く…'))

Thanks fat gem!n Goodbye fat gem!n (('note:ありがとうfat gem!'))n (('note:さよならfat gem!'))

Fat gem problem 1n(('note:fat gemの問題1'))

Can't usen the latest Rubyn immediatelyn (('note:いち早く最新のRubyを使えない'))

Detailsn(('note:詳細'))

* Ruby is released every Christmas\n
  (('note:Rubyは毎年クリスマスにリリースされる'))
* To use fat gems with the latest Ruby:\n
  (('note:最新のRubyでfat gemを使うには'))
  * Need to release fat gems for it\n
    (('note:最新のRuby用のfat gemがリリースされていないといけない'))
* Users can't use the latest Ruby while:\n
  (('note:ユーザーは↓の間は最新のRubyを使えないまま'))
  * Any of fat gems doesn't support it\n
    (('note:使っているfat gemのどれか1つでも最新のRubyをサポートしていない'))

From fat gem maintainer viewn(('note:fat gemメンテナー視点'))

Not all fat gem maintainersn can do itn ((immediately))n (('note:すべてのfat gemメンテナーが((*すぐに*))対応できるわけじゃない'))

Fat gem problem 2n(('note:fat gemの問題2'))

Vulnerability responsen may get delayedn (('note:脆弱性対応が遅れがち'))

Detailsn(('note:詳細'))

* Only bindings fat gem case\n
  (('note:バインディングのfat gemだけのケース'))
  * (('note:Bindings: A extension library to use an external library'))\n
    (('note:バインディング:外部のライブラリーを使う拡張ライブラリー'))
* Includes the external library binary\n
  (('note:fat gem内に外部ライブラリーのバイナリーも含む'))
* When a vulnerability of the external library is found:\n
  (('note:外部ライブラリーに脆弱性が見つかった場合:'))
  * Should be released immediately with fix\n
    (('note:すぐに修正を含んだバージョンをリリースするべき'))

From fat gem maintainer viewn(('note:fat gemメンテナー視点'))

Not all fat gem maintainersn can do itn ((immediately))n (('note:すべてのfat gemメンテナーが((*すぐに*))対応できるわけじゃない'))

Fat gem problem 3n(('note:fat gemの問題3'))

Can't controln the external library versionn (('note:外部ライブラリーのバージョンをコントロールできない'))

Detailsn(('note:詳細'))

* Only bindings fat gem case\n
  (('note:バインディングのfat gemだけのケース'))
* Maintainers choose one external library version\n
  (('note:メンテナーが外部ライブラリーのどのバージョンを使うかを選ぶ'))
* If old external library is chosen, users can't use the latest version\n
  (('note:古い外部ライブラリーを選んだら、ユーザーは最新バージョンを使えない'))

Fat gem problem 4n(('note:fat gemの問題4'))

(({require})) is slowern (('note:(({require}))が遅くなる'))

(({require})) for fat gemn(('note:fat gem用の(({require}))'))

Fat gem needs the following code:n (('note:fat gemでは次のようなコードが必要:'))

# rouge ruby
begin
  # Try the bundled binary in fat gem
  require "#{RUBY_VERSION[/\d+\.\d+/]}/io/console.so"
rescue LoadError
  # Use the local built binary
  require 'io/console.so'
end

Why slower?n(('note:なぜ遅くなるか'))

* For not fat gem install:\n
  (('note:fat gemを使わない環境:'))
  * Such as on GNU/Linux and macOS\n
    (('note:たとえばGNU/LinuxやmacOS'))
  * (({require})) for fat gem is always failed\n
    (('note:fat gem用の(({require}))は必ず失敗'))
* Can't ignore with large (({$LOAD_PATH})):\n
  (('note:(({$LOAD_PATH}))が大きい場合は無視できない'))
  * e.g.: +0.1s with Ruby on Rails application\n
    (('note:例:Ruby on Railsアプリケーションでは0.1秒遅くなる'))\n
    (('note:((<URL:https://github.com/ruby/io-console/pull/4>))'))

Fat gem problem 5n(('note:fat gemの問題5'))

Fat gem releasen may be forgottenn (('note:fat gemのリリースを忘れられる'))

Detailsn(('note:詳細'))

* Normal release is easy\n
  (('note:通常のリリースは簡単'))
  * (({rake release})) is done in a few seconds\n
    (('note:(({rake release}))は数秒で終わる'))
* Fat gem release isn't easy\n
  (('note:fat gemのリリースは簡単じゃない'))
  * rake-compiler-dock help maintainers but...\n
    (('note:rake-compiler-dockを使えばマシになるけど…'))
  * It will take at least a few minutes...\n
    (('note:少なくとも数分かかる…'))

From fat gem maintainer viewn(('note:fat gemメンテナー視点'))

* Don't want to release a new version...\n
  (('note:新しいバージョンをリリースしたくないな…'))
  * Ruby-GNOME case: 10+ related gems\n
    (('note:Ruby-GNOMEの場合:10以上の関連gemがある'))
  * It takes at least 30 minutes\n
    (('note:順調にいった場合でも少なくとも30分はかかる'))
* Forget to release a fat gem\n
  (('note:fat gemをリリースするのを忘れる'))
  * io-console 0.4.8 case\n
    (('note:((<URL:https://github.com/ruby/bigdecimal/pull/148#issuecomment-512075494>))'))

Fat gem problem 6n(('note:fat gemの問題6'))

Highn maintenancen costn (('note:メンテナンスが大変'))

Detailsn(('note:詳細'))

* Especially binding fat gem case\n
  (('note:特にバインディングのfat gemのケース'))
* Normally, cross-compiling is used\n
  (('note:通常、クロスコンパイルしてfat gemを作る'))
  * Most external libraries don't do it\n
    (('note:多くの外部ライブラリーはクロスコンパイルなんてしない'))
  * There are some problems on upgrading\n
    (('note:外部ライブラリーのバージョンをあげるたびになにかしら問題が見つかる'))
  * Ruby-GNOME has 10+ related external libraries\n
    (('note:Ruby-GNOMEは10以上の関連外部ライブラリーがあって大変だった'))

How to solve these problems?n(('note:これらの問題を解決するには?'))

Thanks fat gem!n Goodbye fat gem!n (('note:ありがとうfat gem!'))n (('note:さよならfat gem!'))

Why is fat gem neededn(('note:fat gemが必要だった理由'))

* Users don't have build environment\n
  (('note:ユーザーがビルド環境を持っていない'))
  * Especially, Windows users\n
    (('note:特にWindowsユーザー'))

Windows users and build envn(('note:Windowsユーザーとビルド環境'))

* ((<RubyInstaller for Windows|URL:https://rubyinstaller.org/>)) provides build env by default since 2.4\n
  (('note:RubyInstaller for Windows 2.4以降はデフォルトでビルド環境を提供'))
  * Ruby 2.3 reached EOL\n
    (('note:Ruby 2.3はEOLになっている'))
  * Most Windows users must use 2.4 or later\n
    (('note:I know some Windows users don't use RubyInstaller for Windows'))\n
    (('note:ほとんどのWindowsユーザーは2.4以降を使っているはず'))
* Most Ruby users have build env now!\n
  (('note:ほとんどのユーザーはビルド環境を持っている!'))

Ruby-GNOME said goodbye fat gem!n(('note:Ruby-GNOMEはfat gemにさようならをした!'))

* Since 2018-10-31\n
  (('note:2018-10-31から'))
* Install related issues isn't increased\n
  (('note:インストール関連のissueは増えていない'))

Resolve fat gem problem 1n(('note:fat gem関連の問題の解決1'))

Can usen the latest Rubyn immediatelyn (('note:いち早く最新のRubyを使える'))

Detailsn(('note:詳細'))

* Don't need a new release for new Ruby\n
  (('note:新しいRuby用にgemをリリースする必要がない'))
  * Until new Ruby doesn't change C API\n
    (('note:新しいRubyでC APIが変わっていない限り'))
* Can release a new version\n
  before new Ruby release\n
  (('note:新しいRubyがリリースされる前に新しいバージョンのgemをリリースできる'))
  * Can test with preview release Ruby on CI\n
    (('note:CIでpreviewリリースのRubyをテストできる'))

Resolve fat gem problem 2n(('note:fat gem関連の問題の解決2'))

Vulnerability responsen can be done byn each systemn (('note:脆弱性は各システムが対応してくれる'))

Detailsn(('note:詳細'))

* Especially, binding fat gem case\n
  (('note:特にバインディングfat gemの場合'))
* Packaging system will update soon than binding fat gem maintainers\n
  (('note:バインディングfat gemのメンテナーよりもパッケージングシステムの方がすぐに更新することが多い'))
  * The # of packaging system maintainers\n
    is larger than\n
    the # of maintainers of each fat gem\n
    (('note:各fat gemのメンテナーの数よりもパッケージングシステムのメンテナーの数の方が多いから'))

Resolve fat gem problem 3n(('note:fat gem関連の問題の解決3'))

Can controln the external library versionn (('note:外部ライブラリーのバージョンをコントロールできる'))

Detailsn(('note:詳細'))

* Packaging system may provide the latest external library\n
  (('note:パッケージングシステムは最新の外部ライブラリーを提供してくれるかもしれない'))
* Users can build suitable version of the external library\n
  (('note:ユーザーは適切なバージョンの外部ライブラリーをビルドできる'))

Resolve fat gem problem 4n(('note:fat gem関連の問題の解決4'))

(({require})) isn't slowern (('note:(({require}))が遅くならない'))

Detailsn(('note:詳細'))

No fallback (({require})):n (('note:フォールバック用の(({require}))がいらない'))

# rouge ruby

# Always use the local built binary
require 'io/console.so'

bigdecimal said goodbye fat gem!n(('note:bigedimalはfat gemにさようならをした!'))

* Because of this\n
  (('note:これが理由'))
* ((<"ruby/bigdecimal#149"|URL:https://github.com/ruby/bigdecimal/pull/149>))

Resolve fat gem problem 5, 6n(('note:fat gem関連の問題の解決5,6'))

Easy to maintainn (('note:メンテナンスしやすくなる'))

Detailsn(('note:詳細'))

* No fat gem release process\n
  (('note:fat gemをリリースする作業がなくなる'))
  * Don't forget fat gem release😀\n
    (('note:fat gemのリリースも忘れない😀'))
* No cross compiling\n
  (('note:クロスコンパイルしなくてもよい'))
  * No rake-compiler\n
    (('note:rake-compilerもいらない'))
  * Maintainers can use their time for others\n
    (('note:メンテナーは別のことに時間を使える'))

Stop fat gemn(('note:fat gemをやめると'))

* All problems can be solved!\n
  (('note:すべての問題を解決できる!'))
* Additional good points\n
  (('note:さらにいいことも'))
  * Enable optimization for each user's env\n
    (('note:各ユーザーの環境ごとに最適化できる'))
  * e.g.: (({-O3 -march=native})) GCC options\n
    (('note:例:GCCの(({-O3 -march=native}))オプションを使う'))

Stop fat gemn(('note:fat gemをやめると'))

(('tag:center')) (('tag:xx-large')) Happy!n 🙌

(('tag:right')) (('wait'))…Really?n (('note:…ほんとに?'))

Stop fat gem problem 1n(('note:fat gemをやめたときの問題1'))

Long install timen (('note:インストール時間が長くなる'))

Detailsn(('note:詳細'))

* Fat gem:
  * Just copy pre-built binaries\n
    (('note:単にビルド済みバイナリーをコピー'))
* Gem:
  * Build and install binaries\n
    (('note:バイナリーをビルドしてインストール'))

How to resolve this?n(('note:解決法は?'))

No idea…n (('note:なんかある?'))

Stop fat gem problem 2n(('note:fat gemをやめたときの問題2'))

Fail to installn (('note:インストールに失敗する'))

Detailsn(('note:詳細'))

* Only bindings gem case\n
  (('note:バインディングのgemだけのケース'))
* Needs the external library to build\n
  (('note:ビルドするために外部ライブラリーが必要'))
  * If it doesn't exist, (({gem install})) is failed\n
    (('note:外部ライブラリーがないと(({gem install}))が失敗'))
  * e.g.: (({gem install rmagick})) is failed without ImageMagick\n
    (('note:例:ImageMagickがないと(({gem install rmagick}))が失敗'))

How to resolve this?n(('note:解決法は?'))

Installn the external libraryn automaticallyn (('note:自動で外部ライブラリーをインストール'))

RubyInstaller for Windows

Put the external library package name to the gem's metadata:n (('note:gemのメタデータに外部ライブラリーのパッケージ名を設定'))

# rouge ruby

gemspec.metadata["msys2_mingw_dependencies"] = "cairo"

(('note:((<“MSYS2 library dependencies - For gem developers - onclick/rubyinstaller2 Wiki”|URL:github.com/oneclick/rubyinstaller2/wiki/For-gem-developers#-msys2-library-dependency>))'))

native-package-installer

rcairo case:

# rouge ruby

require "pkg-config"
require "native-package-installer"
unless PKGConfig.have_package("cairo")
  # Install cairo from packaging system automatically
  unless NativePackageInstaller.install(:arch_linux => "cairo",
                                        :debian => "libcairo2-dev",
                                        :homebrew => "cairo",
                                        :macports => "cairo",
                                        :redhat => "cairo-devel")
    exit(false)
  end
  exit(false) unless PKGConfig.have_package("cairo")
end

Stop fat gem problem 3n(('note:fat gemをやめたときの問題3'))

Bundler'sn dependency resolutionn may failn (('note:Bundlerの依存関係解決が失敗するかもしれない'))

Bundler may use wrong dependencyn(('note:Bundlerは間違った依存情報を使ってしまうかもしれない'))

# rouge bash

git clone https://github.com/rails/rails.git
cd rails
bundle install
bundle update # Error

(('note:((<“Bundle Update Fails for Rails 6.1 if Ruby >= 2.6 · Issue #37224 · rails/rails”|URL:github.com/rails/rails/issues/37224>))'))

Actual

Bundler could not find compatible versions for gem "ruby":
  In Gemfile:
    ruby

    mysql2 (~> 0.5) was resolved to 0.5.2, which depends on
      ruby (< 2.6) x64-mingw32

    mysql2 (~> 0.5) was resolved to 0.5.2, which depends on
      ruby (< 2.6) x86-mingw32

    nokogiri (>= 1.8.1) was resolved to 1.10.4, which depends on
      ruby (>= 2.3) x64-mingw32

    nokogiri (>= 1.8.1) was resolved to 1.10.4, which depends on
      ruby (>= 2.3) x86-mingw32

    rails was resolved to 6.1.0.alpha, which depends on
      ruby (>= 2.5.0)

Detailsn(('note:詳細'))

Omitn (('note:省略'))

How to resolve this?n(('note:解決法は?'))

* I've been fixed this\n
  (('note:直しておいた'))
  * ((<"rubygems/bundler#7522"|URL:https://github.com/rubygems/bundler/pull/7522>))
  * If you're interested in fixing problems in upstream, ClearCode is a good corporation:\n
    (('note:問題をアップストリームで直すことが好きな人はクリアコードといういい会社があるよ!'))\n
    ((<URL:https://www.clear-code.com/recruitment/>))
* Use Bundler 2.2.0 or later(('note:(not released yet)'))\n
  (('note:未リリースだけどBundler 2.2.0以降を使えば解決する'))

Wrap upn(('note:まとめ'))

* Fat gem was useful until Ruby 2.3\n
  (('note:Ruby 2.3まではfat gemは有用だった'))
* We can stop using fat gem now!\n
  (('note:今はfat gemをやめられる!'))

Wrap upn(('note:まとめ'))

Thanks fat gem!n Goodbye fat gem!n (('note:ありがとうfat gem!'))n (('note:さよならfat gem!'))