Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Ruby 3.2.0-devをM1 Macbook Pro(Ventura)でビルドする

Ruby 3.2.0-devをM1 Macbook Pro(OS: Ventura)でビルドしようとして少し手こずったので、ソースからのビルドとrbenv+ruby-buildでのビルドについて細かめにメモします。主に3.2.0-devのRust版YJITをビルドしたかったのでした。

注意: 本記事は3.2.0-devが対象のため、最終リリースでは細部が変わる可能性があります。

参考: Ruby 3.2.0 Preview 3 リリース

お気づきの点がありましたら、お気軽に@hachi8833までお知らせください。

追記(2023/01/11): Ruby 3.2.0最終リリースでも本記事の方法で問題なくビルドできました。

🔗 ビルドのためのセットアップ

参考: Quick start guide ruby/building_ruby.md at master · ruby/ruby · GitHub

上のQuick start guideが一次資料です。

M1 Mac(Ventura)で3.2.0-devのビルド環境を最初から構築する手順を、ソースからのビルドとrbenv+ruby-buildの場合についてメモします。基本的にはIntel Mac/M1 Macどちらでも使える手順のはずで、そこにM1固有の注意事項を追加します。

Gitがインストール・設定済みであることと、シェルがbashであることが前提です。環境変数の設定や反映方法については詳しく書きませんのでご了承ください。

以下の手順のうち、既に自分の環境にインストール済みのものについては適宜スキップしてください。

🔗 Homebrewのインストール

Homebrewがインストールされていない場合はインストールします。

参考: macOS(またはLinux)用パッケージマネージャー — Homebrew

Homebrewをインストールするには、上のリンク先にあるインストールスクリプトをコマンドプロンプトで実行します。

インストール後にbrew --versionを実行して以下のように表示されればOKです。

$ brew --version
Homebrew 3.6.12
Homebrew/homebrew-core (git revision a7111e66e7a; last commit 2022-11-26)

🔗 Command Line Toolsのインストール

コンパイラ環境はAppleのCommand Line Toolsを使うことにします。Command Line Tools for Xcodeの現時点のバージョンは14.1です(Ventura対応)。

参考: More - Downloads - Apple Developer

Xcodeをインストールしてもいいのですが、サイズがでかいので自分はCommand Line Toolsを使っています。つわものはgccやclangを使うこともあるそうです。

初めてインストールするのであれば、sudo xcode-select --installでインストールできます(rootパスワードが必要)。

Command Line Toolsについて詳しくは以下の記事をどうぞ。

brew upgrade でのエラー対処からCommand Line Toolsについてまとめてみる

🔗 必要なライブラリのインストール

OpenSSLは3系を使うことにしました。OpenSSL 1系やlibsslについては試していません。

参考: OpenSSL - Wikipedia

以下を実行してライブラリをインストールします。

$ brew upgrade
$ brew install openssl@3 readline libyaml bison gperf zlib libffi
$ brew cleanup
$ brew doctor # 念のため実行

既にインストール済みのライブラリについては適宜スキップしてください。

インストール後、以下のようにbison 3のパスをexportしておく必要があります(--with-bison-dirのようなビルドオプションはなさそうです)。そうしないと、macOSの/usr/bin/bison(2.3)が使われてビルドエラーになります。

export PATH="/opt/homebrew/opt/bison/bin:$PATH"

以下の記事にもあるように、bisonはバージョン3以上が必要です。

参考: Ruby 3.2 (dev) のビルドに Bison 3 以上が必要になる - koicの日記

なお、以下もexportしておくとビルドが並列化されます。

# bash/zsh向けの設定
export MAKEFLAGS="--jobs $(sysctl -n hw.ncpu)"

🔗 OpenSSL 3のパス設定について

OpenSSL 3のパスは、環境変数で設定する方法と、ビルド時に--with-openssl-dir=$(brew --prefix openssl)オプションを追加する方法があります。ビルド時にオプションを指定する方が確実ではありますが、OpenSSL 3は他の言語やツールのビルドでも使われることがあるので、ここでは環境変数で設定することにしました。

私の場合、OpenSSLについてはbashのコンフィグファイル(.profile)でOpenSSL 3のパスをexportしました。

export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH"
export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@3/lib/pkgconfig"

なお、以下のようにbrew --prefix opensslを使って設定すれば、今後Homebrewのパスが変わった場合にも対応できますが、その分コンソールの起動が少し重くなります。Homebrewのパスは忘れた頃に変わったりするので、新しいMacbookを買ったときなどにハマることがありました。

export PATH="export PATH=$(brew --prefix openssl)/bin:$PATH"
export PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig"
export PATH="$(brew --prefix bison)/bin:$PATH"

また、LDFLAGSCPPFLAGSなどの環境変数の設定は、少なくとも私の場合は不要でした(過去バージョンのRubyをインストールする場合は必要になることもありそうです)。

🔗 Rustのインストール

YJITをビルドするにはRust 1.58.0以上が必要です(YJITをビルドしない場合は不要)。

Rustのインストール方法は主に2とおりあります。

1つはHomebrewでrustup-initをインストールして実行する方法です。

参考: rustup-init — Homebrew Formulae

$ brew install rustup-init
$ rustup-init
# シェルを再起動
$ rustc --version

もう1つは、以下に記載されているRust公式のスクリプトを実行する方法です。

参考: はじめに - Rustプログラミング言語

Rust公式のスクリプトの方がよさそうに思えましたが、いろいろあって最終的にHomebrewでRustをインストールすることにしました(後述)。

🔗 M1 Macbook固有のRustに関する注意

M1 MacbookにRosetta 2がインストールされている場合、Rust版YJITをビルドするために追加の作業が必要なことがあります。私の場合、ESET Cyber Security ProでRosetta 2が必須なので仕方なくインストールしていました(いつの日か削除したい...)。

参考: Mac に Rosetta をインストールする必要がある場合 - Apple サポート (日本)
参考: Apple M1チップ への対応について | ESETサポート情報 | 法人向けクライアント専用製品 | キヤノンITソリューションズ
参考: Rosetta 2 ってなに?|M1 Mac ってなに? ぼくにも使える?

調べ方: rustup toolchain listを実行し、以下のようにstable-aarch64-apple-darwinがデフォルトになっていれば追加作業は不要です。おめでとうございます🎉

$ rustup toolchain list
stable-aarch64-apple-darwin (default)

M1 MacbookにRosetta 2がインストールされている場合、Rustのインストール状況によってはRustのツールチェインが以下のいずれかのようにstable-x86_64-apple-darwinがデフォルトになっていることがあります。この場合、Rust版YJITのビルドが失敗しました。aarch64バイナリをx86_64のツールチェインにリンクできるはずはないので当然ですね。

$ rustup toolchain list
stable-x86_64-apple-darwin (default)

# または
$ rustup toolchain list
stable-aarch64-apple-darwin
stable-x86_64-apple-darwin (default)
▶エラー時のログ(クリックすると表示されます)
(...)
building Rust YJIT (release mode)
touch yjit/target/release/libyjit.a
linking miniruby
ld: warning: ignoring file yjit/target/release/libyjit.a, building for macOS-arm64 but attempting to link with file built for macOS-x86_64
Undefined symbols for architecture arm64:
  "_rb_yjit_before_ractor_spawn", referenced from:
      _ractor_create in ractor.o
  "_rb_yjit_bop_redefined", referenced from:
      _rb_vm_check_redefinition_opt_method in vm.o
  "_rb_yjit_call_threshold", referenced from:
      _rb_vm_exec in vm.o
      _vm_sendish in vm.o
  "_rb_yjit_cme_invalidate", referenced from:
      _clear_method_cache_by_id_in_class in vm.o
  "_rb_yjit_code_gc", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_code_gc in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_constant_ic_update", referenced from:
      _vm_exec_core in vm.o
  "_rb_yjit_constant_state_changed", referenced from:
      _rb_clear_constant_cache_for_id in vm.o
  "_rb_yjit_disasm_iseq", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_disasm_iseq in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_enabled_p", referenced from:
      _rb_jit_cont_finish in cont.o
      _rb_jit_cont_init in cont.o
      _fiber_initialize in cont.o
      _rb_threadptr_root_fiber_setup in cont.o
      _cont_free in cont.o
      _cont_capture in cont.o
      _clear_method_cache_by_id_in_class in vm.o
      ...
  "_rb_yjit_get_exit_locations", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_get_exit_locations in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_get_stats", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_get_stats in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_init_rust", referenced from:
      _rb_yjit_init in yjit.o
  "_rb_yjit_insns_compiled", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_insns_compiled in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_iseq_free", referenced from:
      _rb_iseq_free in iseq.o
  "_rb_yjit_iseq_gen_entry_point", referenced from:
      _rb_yjit_compile_iseq in yjit.o
  "_rb_yjit_iseq_mark", referenced from:
      _rb_iseq_mark in iseq.o
  "_rb_yjit_iseq_update_references", referenced from:
      _rb_iseq_update_references in iseq.o
  "_rb_yjit_parse_option", referenced from:
      _proc_options in ruby.o
  "_rb_yjit_reset_stats_bang", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_reset_stats_bang in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_root_mark", referenced from:
      _yjit_root_type in yjit.o
  "_rb_yjit_simulate_oom_bang", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_simulate_oom_bang in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_stats_enabled_p", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_stats_enabled_p in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_trace_exit_locations_enabled_p", referenced from:
      _mjit_compile_invokebuiltin_for_rb_yjit_trace_exit_locations_enabled_p in yjit.o
      _Init_builtin_yjit.yjit_table in yjit.o
  "_rb_yjit_tracing_invalidate_all", referenced from:
      _tracepoint_enable_m in vm_trace.o
      _update_global_event_hook in vm_trace.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [miniruby] Error 1

その後以下の修正が入り、ビルドより前のconfigureの段階でRustコンパイル環境の問題を検出するようになったようです。

参考: YJIT: Make sure rustc's target matches before enabling (#6804) · ruby/ruby@a81c89b

Rustのツールチェインを修正するには、以下のいずれかを実行します(私の場合は2番目でできました)。特に理由がなければ、このコマンドはおそらくホームディレクトリで実行する方がよいでしょう(このコマンドは、カレントディレクトリ以下を対象にツールチェインを変更します)。

$ rustup toolchain install stable-aarch64-apple-darwin
$ rustup override set stable-aarch64-apple-darwin

# または
$ rustup target add aarch64-apple-darwin
$ rustup toolchain install stable-aarch64-apple-darwin --force-non-host
$ rustup override set stable-aarch64-apple-darwin

終わった後、rustup toolchain listで以下のようにstable-aarch64-apple-darwinがオーバーライドと表示されればOKです。なお、この設定は~/.rustup/settings.tomlファイルに保存されています。

$ rustup toolchain list
stable-aarch64-apple-darwin (override)
stable-x86_64-apple-darwin (default)

このあたりの経緯は以下にあります。

参考 Misc #19146: Failed to activate YJIT for 3.2.0-dev on macOS Ventura (M1: Apple Silicon) - Ruby master - Ruby Issue Tracking System

なお、私の環境にx86_64のツールチェインが入っていた理由は謎です。Rustコンパイルを行う何かの言語だかツールをインストールしたときに入ったのかもしれません。

🔗 最終的なセットアップ

  • M1 Macbook Pro 2021
  • macOS Ventura 13.0.1
  • シェル: bash 3.2.57(1)-release (arm64-apple-darwin22)
  • CommandLine Tools: 14.1.0.0.1.1666437224
  • Ruby: 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21]
    • (ビルドで使われるカレントのRuby)
  • Homebrew: 3.6.12
    • git: 2.38.1
    • openssl@3: stable 3.0.7 (bottled) [keg-only]
    • readline: stable 8.2.1 (bottled) [keg-only]
    • libyaml: stable 0.2.5 (bottled)
    • bison: stable 3.8.2 (bottled) [keg-only]
    • gperf: stable 3.1 (bottled)
    • zlib: stable 1.2.13 (bottled), HEAD [keg-only]
    • libffi: stable 3.4.4 (bottled), HEAD [keg-only]
    • rustc: 1.65.0 (897e37553 2022-11-02)

ここから、ソースビルドとrbenv+ruby-buildそれぞれについてメモします。

🔗 1: ソースからビルドする場合

以下のソースを使いました。リリースも近いので、3.2.0-devは今も刻々とバージョンが新しくなっています。

  • source: 3.2.0-dev(master, 67ae3e9738)

ruby/ruby - GitHub

基本的に一次資料↑に沿って進めます。

以下を実行してソースをcloneします。

$ git clone https://github.com/ruby/ruby.git

cloneしたディレクトリに移動して以下を実行し、ビルドします。

./autogen.sh;mkdir build && cd build
mkdir ~/.rubies
../configure --prefix="${HOME}/.rubies/ruby-master" --disable-install-doc
make install

なお現在の3.2.0-devをソースからビルドする場合は、configureのオプションに--enable-yjitを指定しなくてもデフォルトでYJITがビルドされて有効になります。逆に、ソースビルドでYJITを無効にするには--disable-yjitを指定します。

また、自分のセットアップでは上の手順でOpenSSL 3のパスを環境変数で指定したので、--with-openssl-dir=$(brew --prefix openssl)を指定しなくてもOpenSSL 3は認識されますが、OpenSSL 3を認識できずにエラーになる場合はこれも追加しておきましょう。

また、--disable-install-rdocはRuby 3.2では無効です。

参考: CONFIGURE_OPTS="--disable-install-rdoc" rbenv install 3.2.0 not working · rbenv ruby-build · Discussion #2122

Ruby実行時にYJITを有効にするには、以下のどちらかの環境変数をexportしておくか、Ruby実行時に--yjitを追加します。

export RUBY_YJIT_ENABLE=1
export RUBYOPT='--yjit'

これで、~/.rubies/ruby-master/bin/に配置された3.2.0-devを実行できるようになります。YJITも有効になっています。

$ ~/.rubies/ruby-master/bin/ruby -v --yjit
ruby 3.2.0dev (2022-11-29T09:23:00Z master 67ae3e9738) +YJIT [arm64-darwin22.1.0]

なお、RUBY_YJIT_ENABLE=1は、YJITが無効なビルドでもwarningを表示しませんが、--yjitはwarningを表示します。

# YJITが無効なビルド
$ RUBY_YJIT_ENABLE=1 ~/.rubies/ruby-master/bin/ruby -v
ruby 3.2.0dev (2022-11-30T21:27:39Z master 5752d11f1f) [arm64-darwin22]
# YJITが無効なビルド
$ ~/.rubies/ruby-master/bin/ruby -v --yjit
/Users/hachi8833/.rubies/ruby-master/bin/ruby: warning: Ruby was built without YJIT support
ruby 3.2.0dev (2022-11-30T21:27:39Z master 5752d11f1f) [arm64-darwin22]

🔗 2: rbenv+ruby-buildの場合

rbenv/rbenv - GitHub

rbenv/ruby-build - GitHub

rbenvとruby-buildのインストール方法については省略します。方法はいくつもありますが、たとえばRails Girlsサイトにあるインストール方法を参考にするとよいでしょう。

参考: Rails Girls - Japanese

RUBY_CONFIGURE_OPTS環境変数でビルドオプションをミニマムに設定します。docとrdocのインストールを無効にするのは、ビルド時間を短縮するための定番オプションです。

# YJITを有効にしない場合
export RUBY_CONFIGURE_OPTS="--disable-install-doc"

なお、現時点のrbenv+ruby-buildでは、ソースからのビルドと異なり、--enable-yjitを明示的に指定しないと3.2.0-devでRust環境の検出とRust版YJITのビルドを行いません#2094コメント)。私は当然--enable-yjitを指定しました。

# YJITを有効にする場合
export RUBY_CONFIGURE_OPTS="--enable-yjit --disable-install-doc"

これで、以下を実行すれば3.2.0-devがビルドされます。

$ rbenv install 3.2.0-dev
$ ruby -v --yjit
ruby 3.2.0dev (2022-11-30T08:20:38Z master c8bfbbc25e) +YJIT [arm64-darwin22]

🔗 おまけ

実はこの記事を書いていて、3.2.0-devでRust版YJITのビルドがなかなかうまくいかずハマりました。--disable-yjitでYJITをビルドしない指定にすると成功するのですが。

ソースからのビルドについては、その後上述のrustup toolchainの修正でRust版YJITのビルドに成功するようになりましたが、rbenv+ruby-buildでなかなかうまくいきませんでした。

最終的に、rbenv+ruby-buildでのビルドでハマった原因は、実はRustを上述の「両方の」方法で重複インストールしていたせいだったことにやっと気づきました😅。Rustが2つも入っていたとは凡ミスの極み...

そういうわけで、私の場合は最終的にHomebrewでインストールしたRustを残し、Rust公式のインストーラで入れたRustをrustup self uninstallで削除したところ、やっとrbenv+ruby-buildでも3.2.0-devのRust版YJITを問題なくビルドできるようになりました。

$ rbenv install 3.2.0-dev
$ ruby -v --yjit
ruby 3.2.0dev (2022-11-30T08:20:38Z master c8bfbbc25e) +YJIT [arm64-darwin22]

なお、Rustのツールチェインからいまいましいstable-x86_64-apple-darwinも消え去りました。

$ rustup toolchain list
stable-aarch64-apple-darwin (default)

このあたりの経緯は以下にあります。

参考: Failed to activate YJIT for 3.2.0-dev on macOS Ventura (M1: Apple Silicon) · Discussion #2094 · rbenv/ruby-build


個人的にはHomebrewや環境変数を整理できて、一足早い年末大掃除感がありました。
本記事が皆さんへのクリスマスプレゼントになりますように。


関連記事

YJIT: CRuby向けの新しいJITコンパイラを構築する(翻訳)

Bash: .bashrcと.bash_profileの違いを今度こそ理解する


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。