inSmartBank

B/43を運営する株式会社スマートバンクのメンバーによるブログです

焼きたての Ruby 3.4.0dev を携えて沖縄に行く #rubykaigi

こんにちは osyoyu です。いよいよ RubyKaigi 本番が近づいていますね。

スマートバンクは RubyKaigi のスポンサーです! Hydration Sponsor として、会場でドリンクをご提供して参加者の皆様の喉をどんどん潤します。 なんと発表者も3名(!)いますので、どうぞよろしくお願いします。

blog.smartbank.co.jp

さて、今回は「どうせ 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_LENRETURN_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) のリポジトリに良い題材がまとまっているので、取り組むのも面白いでしょう。

github.com


ということで、せっかく RubyKaigi にいくなら Ruby をビルドしていきませんか? という記事でした。

自分の発表は ruby/ruby のことを少し知っていると面白くなるはず(予定)です。ぜひ聞きにきてください!

rubykaigi.org

それでは沖縄で会いましょう!

*1:ここでは rustc を間接的に使うだけですが、普通に Rust を書く場合は rustup は非常に便利なツールにです

We create the new normal of easy budgeting, easy banking, and easy living.
In this blog, engineers, product managers, designers, business development, legal, CS, and other members will share their insights.