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.rbrequire できる場所に配置する必要があるようなので、 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 をビルドしてしまって反省している。