こんにちは osyoyu です。いよいよ RubyKaigi 本番が近づいていますね。
スマートバンクは RubyKaigi のスポンサーです! Hydration Sponsor として、会場でドリンクをご提供して参加者の皆様の喉をどんどん潤します。 なんと発表者も3名(!)いますので、どうぞよろしくお願いします。
さて、今回は「どうせ RubyKaigi に行くなら最先端の Ruby をビルドしていかないか?」という内容です。ビルドにそんなに時間もかからないし、いいこといっぱいのはず。
この記事では "ruby/ruby" を "CRuby" や "MRI" と同じ意味で使っています。普段使っている ruby
コマンドのことですね。
Ruby をビルドするといいこといっぱい (?)
RubyKaigi で発表された機能をいち早く試せる
RubyKaigi では Ruby の未来の話が多く交わされます。会期中に実装が進むことも多いですが(RubyKaigi 2023の現場でbisonがLramaに置き換えられたのは印象的でしたね)、手元に最新の Ruby があれば半年先取りで体験できてしまうわけです。これはすごい!
RubyKaigi の話を聞いて、自分でちょっと試してみたいかも! という実験もすぐできて良いですね。
ruby/ruby のコードが身近になるかも
Ruby を書いていると、ふとしたときにその中の実装が気になることがありますが[要出典]、これが GitHub だとなかなか読みづらい…… と思っています。自分は Ruby コードを読むときは git grep や rg で十分派ですが、C には関数定義のキーワードがないこともあり、定義ジャンプがかなりほしくなります。手元に ruby/ruby のコードがあるとスッと読めて便利。
master ブランチで暮らせる
日常から Ruby の master ブランチを使っていると、機能の deprecation などに伴う警告を意外とたくさん見ることになります。On the edge 感があってカッコイイ! というのはともかく、各種の gem にパッチを送るチャンスにもなります。
実際にビルドする
公式ドキュメントでもビルドの仕方は丁寧に解説されているので、そちらも併せて参照してください。
building_ruby - Documentation for Ruby 3.4
方針: rbenv の管理下に入れる
手元のマシンで複数のrubyのバージョンを管理するためにrbenvを使われている方は多いと思います。rbenv は ~/.rbenv/versions
以下のディレクトリをバージョンとして認識する仕組みになっており、 ~/.rbenv/versions/master
にインストールすることで rbenv global master
できるようになります。便利。
下準備
ここでは macOS で Homebrew を使っている場合の手順だけ掲載します。
なにはともあれ、C コンパイラが必要です。手元で gcc --version
もしくは clang --version
のどちらかが成功すれば良さそうです。入っていない場合、macOS の場合は Command Line Tools をインストールするのが良いでしょう。
https://developer.apple.com/xcode/resources/
続いて、ruby が依存するいくつかのライブラリをインストールする必要があります。
$ brew install autoconf automake libyaml openssl
また、YJIT は Rust で実装されているため、有効にするには Rust コンパイラ (rustc) が必要です。Homebrew で入れることもできますが、Rust のツールチェインマネージャである rustup でインストールすることを個人的に推奨します*1。
# https://rustup.rs/ も参照してください $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh $ rustc --version rustc 1.78.0 (9b00956e5 2024-04-29) # ここでシェルの再起動が必要になるかも
ruby/ruby の clone とビルドディレクトリの準備
手元に ruby/ruby を clone して、 ./configure
を生成します。また、build
ディレクトリを作成して、cd しておきます。
$ git clone git@github.com:ruby/ruby.git $ ./autogen.sh # autoconf で ./configure を生成 $ mkdir build $ cd build
./configure && make
いよいよビルドの段階です。先ほど作成した build
ディレクトリにいることを確認してください。
./configure
にはいくつかのオプションを渡すと便利です。
--prefix=$HOME/.rbenv/versions/master
: ruby をインストールする先の設定です。ここでは rbenv の管理下に入るよう.rbenv/versions
以下を指定しています。rbenv を使っていない場合は適当に読み替えてください。--config-cache
: autoconf の各種チェック (check for unistd.h ...
) の結果をキャッシュできるオプションです。繰り返しビルドする際に便利です。--with-openssl-dir
,--with-libyaml-dir
: リンクする libssl と libyaml のパスの設定です。--enable-yjit=stats
: YJIT を有効化する設定です。=stats
を指定することでruby --yjit-stats
オプションを使えるようになり、各種 YJIT メトリクスが収集されるようになります。- (
--disable-install-doc
): ri, rdoc の生成をスキップします。ビルド時間の短縮にはなりますが、irb でドキュメントを読む機能などが使えなくなります。
make
に -j8
オプションを渡すことで、ビルドが8並列で行われるようになります。8コア以上ある場合は8以上を指定してかまいません。コアがたくさんあるCPUを使われているかたは胸を張れるポイントです。
$ pwd /Users/osyoyu/ruby/build $ ../configure \ --config-cache \ # autoconf (check for unistd.h ... のやつ) の結果のキャッシュ --prefix=$HOME/.rbenv/versions/master \ --with-openssl-dir=$(brew --prefix openssl) \ # macOS でない場合は適宜読み替えてください --with-libyaml-dir=$(brew --prefix libyaml) \ --enable-yjit=stats \ # ruby --yjit=stats で起動したとき、終了時に統計情報を表示 --disable-install-doc # ri, rdoc のビルドをスキップしたい場合 $ make -j8
動作確認 && インストール
ここまできたら ruby 本体のビルドは完了しているはずです。 ./build/ruby
を実行することで動作確認ができます。
動作していそうであれば、 make install
コマンドを実行することで --prefix
で指定した rbenv の管理下のパスにインストール(コピー)できます。これでビルドは無事完了です! rbenv global master
で master 暮らしするもよしです。
$ pwd /Users/osyoyu/ruby/build # ./build/ruby に使える ruby ができているはず $ ./ruby -e 'p "Hello, world!"' "Hello, world!" # prefix (~/.rbenv/versions/master) にコピー + default gems のインストール $ make install # master で暮らす $ rbenv global master $ ruby -v ruby 3.4.0dev (2024-03-25T06:16:22Z master bd85fd6db9) [arm64-darwin23] # これでも可 $ RBENV_VERSION=master ruby -v ruby 3.4.0dev (2024-03-25T06:16:22Z master bd85fd6db9) [arm64-darwin23]
コードを更新したあとの再ビルド
コードを編集したり、git pull した後は(当然)再ビルドが必要です。再度 configure する必要はなく、make && make install するだけです。
$ pwd /Users/osyoyu/ruby/build $ git pull $ make -j8 $ make install
簡単でしたね!
おまけ: ruby/ruby の読みかた
ruby/ruby は大変読み手フレンドリーで、ディレクトリ構造は大変浅く整理されています。たとえば Array クラスの実装はトップレベルの array.c
、Hash クラスの実装は hash.c
、という具合です。
Ruby のあの機能の実装の探しかた
そうは言っても、具体的なメソッドを探したり、ファイル名に表れない機能を探すとなるとやはり検索が便利です。ここでは細かすぎて伝わらなさそうな tip をいくつか紹介します。
たとえば Array#map
の実装を探しているときは git grep '"map"'
のように "keyword"
で検索すると便利です。単に map
で検索すると7000件近くヒットするところ、 ”map”
ならば十数件まで的確に絞れます。
% git grep '"map"' array.c: rb_define_method(rb_cArray, "map", rb_ary_collect, 0); enum.c: rb_define_method(rb_mEnumerable, "map", enum_collect, 0); enumerator.c: rb_define_alias(rb_cLazy, "_enumerable_map", "map"); enumerator.c: rb_define_method(rb_cLazy, "map", lazy_map, 0); enumerator.c: rb_hash_aset(lazy_use_super_method, sym("map"), sym("_enumerable_map")); (snip)
いかにも、な雰囲気ですね。Ruby の世界で使われる名前は C の世界では大抵 string literal なので、string literal の記法で検索すると探しやすい、という話です。
ここでは正解は一番上に出ている array.c の以下の関数でした。ary を for でループで回しているコードで、なんだか友達になれそうな気がします。
static VALUE rb_ary_collect(VALUE ary) { long i; VALUE collect; RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); collect = rb_ary_new2(RARRAY_LEN(ary)); for (i = 0; i < RARRAY_LEN(ary); i++) { rb_ary_push(collect, rb_yield(RARRAY_AREF(ary, i))); } return collect; }
コードジャンプの設定
上のコードでも RARRAY_LEN
や RETURN_SIZED_ENUMERATOR
などが出現しているように、ruby/ruby ではプリプロセッサマクロ (#define
) が多用されています。他にも多くの struct が定義されていたりするので、コードジャンプがあったほうが100倍読みやすいです(私見)。
手元で ruby をビルドした後であればエディタの環境を整えることでコードリーディングがグッと快適になります。C の開発環境に特にこだわりがなければ、VS Code の “C/C++ for Visual Studio Code” 拡張を使うのがセットアップが楽です。
あわせてc_cpp_properties.json も適当に作ると良いです。https://github.com/rizsotto/Bear で compile_commands.json もつくれる。
Ruby を改造してみる
ここまできたら自分のアイデアで Ruby を改造し放題です。Ruby Hack Challenge (RHC) のリポジトリに良い題材がまとまっているので、取り組むのも面白いでしょう。
ということで、せっかく RubyKaigi にいくなら Ruby をビルドしていきませんか? という記事でした。
自分の発表は ruby/ruby のことを少し知っていると面白くなるはず(予定)です。ぜひ聞きにきてください!
それでは沖縄で会いましょう!
*1:ここでは rustc を間接的に使うだけですが、普通に Rust を書く場合は rustup は非常に便利なツールにです