Ruby 3.2.0-devをM1 Macbook Pro(OS: Ventura)でビルドしようとして少し手こずったので、ソースからのビルドとrbenv+ruby-buildでのビルドについて細かめにメモします。主に3.2.0-devのRust版YJITをビルドしたかったのでした。
注意: 本記事は3.2.0-devが対象のため、最終リリースでは変わる可能性があります。
お気づきの点がありましたら、お気軽に@hachi8833までお知らせください。
ビルドのためのセットアップ
参考: 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について詳しくは以下の記事をどうぞ。
必要なライブラリのインストール
OpenSSLは3系を使うことにしました。OpenSSL 1系やlibsslについては試していません。
以下を実行してライブラリをインストールします。
$ 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の日記
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"
また、LDFLAGS
やCPPFLAGS
などの環境変数の設定は、少なくとも私の場合は不要でした。
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公式のスクリプトの方がよさそうに思えましたが、いろいろあって最終的に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)
このあたりの経緯は以下にあります。
なお、私の環境に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.1openssl@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)
- Quick start guide: ruby/building_ruby.md at master · 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 --disable-install-rdoc
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を認識できずにエラーになる場合はこれも追加しておきましょう。
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とruby-buildのインストール方法については省略します。方法はいくつもありますが、たとえばRails Girlsサイトにあるインストール方法を参考にするとよいでしょう。
RUBY_CONFIGURE_OPTS
環境変数でビルドオプションをミニマムに設定します。docとrdocのインストールを無効にするのは、ビルド時間を短縮するための定番オプションです。
# YJITを有効にしない場合
export RUBY_CONFIGURE_OPTS="--disable-install-doc --disable-install-rdoc"
なお、現時点の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 --disable-install-rdoc"
これで、以下を実行すれば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)
このあたりの経緯は以下にあります。
個人的にはHomebrewや環境変数を整理できて、一足早い年末大掃除感がありました。
本記事が皆さんへのクリスマスプレゼントになりますように。
関連記事
The post Ruby 3.2.0-devをM1 Macbook Pro(Ventura)でビルドする first appeared on TechRacho.