M1 Mac で Universal Binary な Ruby をビルドしたい (できてない)。
概要
macOS Big Sur 11.5.2 時点ではシステムに標準で ruby 2.6.3p62 が入っており、ちゃんとユニバーサルバイナリとなっている。
使うかどうかはさておき、自分でもユニバーサルバイナリな ruby をビルドしてみたい!
rbenv などを使って手元で ruby の環境を作るときは arm64 単体をターゲットにビルドすることが多いと思うし、実用的にはそれでいいのであって、とりあえず趣味でビルドをしていきます。
詳細
とりあえず先人がやってないかググってみたが、誰も困ってないようで全然情報が見当たらない。僕はただ Ruby を x86_64 と arm64 の両アーキテクチャ向けにビルドして Mach-O の Universal Binary にまとめたいだけなのに…。
ほぼ唯一ヒットしたのが stack overflow の How to manually build a universal ruby on Mac OS X? How about with rvm? という質問で、なんと 11 年前の 2010 年 6 月のこと。
それによると、
Use the
--with-arch
option to./configure
とのこと。 ./configure
に便利オプションがありそうなので、 --help
を調べにいくことにする。
とりあえず 2.x の最新版の 2.7.4 で試してみることにする。
$ wget https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz
$ tar -zxf ruby-2.7.4.tar.gz
$ cd ruby-2.7.4
./configure --help
を見る。
--enable-multiarch
と --with-arch
あたりがそれっぽい感じ。
$ ./configure --help
[... 中略 ...]
--enable-multiarch enable multiarch compatible directories
[... 中略 ...]
--with-arch=ARCHS build an Apple/NeXT Multi Architecture Binary (MAB);
ARCHS is a comma-delimited list of architectures for
which to build; if this option is disabled or
omitted entirely, then the package will be built
only for the target platform
[... 中略 ...]
今回はクロスコンパイルする訳ではないので --build=BUILD
と --host=HOST
と --target=TARGET
は多分考えなくて良い。
--disable-install-rdoc
は良さそうなので指定しておくとして、 make install
時の prefix を --prefix
で適当に指定してやれば良いだろう。
その他、 CFLAGS
に -DUSE_FFI_CLOSURE_ALLOC
を指定しないといけない可能性がある。
CC
とか CFLAGS
とか LDFLAGS
とか CPPFLAGS
は各々環境依存だと思うのでちゃんと指定されてれば良いと思う。
readline と openssl 関連が鬼門でビルドが通らない。
readline 関連と openssl 関連のリンクかビルドでコケる話はググると沢山見付かる。
要するに --with-readline-dir="$(brew --prefix readline)"
と --with-openssl-dir="$(brew --prefix openssl@1.1)"
を指定してね、っていう話なのだが、ユニバーサルバイナリをビルドしたいとなると話がややこしくて、恐らく readline も openssl もユニバーサルバイナリで用意する必要があるのかな。たぶん。
ちょっとめんどくさいので ./configure
のオプションに --with-out-ext='readline,openssl'
を付けてビルドだけ通してみることにした。
とりあえず make
だけを通す
readline と openssl を抜きにした ruby で何が出来るかというと、マジで何の役にも立たないんだけど、このままやっていきます。
$ ./configure \
--prefix="/path/to/universal_ruby" \
--disable-install-rdoc \
--enable-multiarch --with-arch=x86_64,arm64 \
--with-out-ext='readline,openssl'
$ make -j4
これで手元にユニバーサルバイナリな ruby がビルドされる。
$ file ruby
ruby: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64]
ruby (for architecture x86_64): Mach-O 64-bit executable x86_64
ruby (for architecture arm64): Mach-O 64-bit executable arm64
$ ./ruby -v
ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [universal.arm64-darwin20]
ワンライナーで puts "Hello"
とか試してみると早速怒られる。
$ ./ruby -e 'puts "Hello"'
Traceback (most recent call last):
1: from <internal:gem_prelude>:1:in `<internal:gem_prelude>'
<internal:gem_prelude>:1:in `require': cannot load such file -- rubygems.rb (LoadError)
rubygems.rb
を require
できる場所に配置する必要があるようなので、 make install
をしてやる。
すると cannot load such file -- openssl (LoadError)
と表示され、また怒られる。
make install
の内部ではビルドしたての ruby を使っており、そいつが最終的に openssl を require
するようだ。しかしその辺を無視したので存在しない。途中で無慈悲に落ちる。
make install
は失敗するのだが、途中まではファイルが配置されるようで、それ以降はワンライナーがちゃんと走るようになる。良いんだか悪いんだか。
$ ./ruby -e 'puts "Hello"'
Hello
$ ./ruby -e 'puts (1..10).to_a.join("/")'
1/2/3/4/5/6/7/8/9/10
./configure
にオプションを渡すだけなので rbenv を用いてもここまでは出来ると思われる。
締め
readline と openssl をとりあえず無視 (代償が大きすぎる) すればビルド出来ることが分かったので地道にやれば出来ると思われるのだが、それはそうと明らかに役に立たなそうな ruby をビルドしてしまって反省している。