ruby-build: Apple M1: `rbenv install 3.1.2` fails but installing ruby 3.1.2 from source succeeds

Hardware: Apple M1 MacBook Air running Big Sur 11.6.8 OpenSSL: Both 1.1 and 3.0 installed using brew with 3.0 as the linked default

Step 1: Run rbenv install 3.1.2

milos@air:~$ rbenv install 3.1.2
To follow progress, use 'tail -f /var/folders/y5/vfyf5lqj655g_zbm8pvdgmkr0000gn/T/ruby-build.20221029165815.98770.log' or pass --verbose
Downloading openssl-3.0.5.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a
Installing openssl-3.0.5...
Installed openssl-3.0.5 to /Users/milos/.rbenv/versions/3.1.2

Downloading ruby-3.1.2.tar.gz...
-> https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.2.tar.gz
Installing ruby-3.1.2...
ruby-build: using readline from homebrew
ruby-build: using gmp from homebrew

BUILD FAILED (macOS 11.6.8 using ruby-build 20221004)

Inspect or clean up the working tree at /var/folders/y5/vfyf5lqj655g_zbm8pvdgmkr0000gn/T/ruby-build.20221029165815.98770.ztN4k5
Results logged to /var/folders/y5/vfyf5lqj655g_zbm8pvdgmkr0000gn/T/ruby-build.20221029165815.98770.log

Last 10 log lines:
        from ./tool/rbinstall.rb:899:in `block in install'
        from ./tool/rbinstall.rb:713:in `no_write'
        from ./tool/rbinstall.rb:899:in `install'
        from ./tool/rbinstall.rb:1068:in `block (2 levels) in <main>'
        from ./tool/rbinstall.rb:1043:in `foreach'
        from ./tool/rbinstall.rb:1043:in `block in <main>'
        from ./tool/rbinstall.rb:1127:in `block in <main>'
        from ./tool/rbinstall.rb:1124:in `each'
        from ./tool/rbinstall.rb:1124:in `<main>'
make: *** [do-install-all] Error 1

Step 2: Notice that rbenv versions does not show 3.1.2 because it failed to install using ruby-build

milos@air:~$ rbenv versions
  system
* 2.7.4 (set by /Users/milos/.rbenv/version)
  3.0.4

Step 3: Install 3.1.2 manually from source, bypassing ruby-build entirely

wget https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.2.tar.gz
tar xf ruby-3.1.2.tar.gz
cd ruby-3.1.2
./configure --prefix=/Users/milos/.rbenv/versions/3.1.2 # add `--with-openssl-dir=/opt/homebrew/opt/openssl\@1.1` or @3 as preferred, if needed
make -j9
make install

Step 4: Notice that rbenv versions now shows 3.1.2 successfully installed

milos@air:~$ rbenv versions
  system
* 2.7.4 (set by /Users/milos/.rbenv/version)
  3.0.4
  3.1.2
milos@air:~$ rbenv global 3.1.2

milos@air:~$ ruby -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin20]

milos@air:~$ ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'
OpenSSL 3.0.5 5 Jul 2022

Please do not move this to the Discussions tab as the installation works when not using ruby-build. This leads me to believe that it is indeed a ruby-build issue.

For what it’s worth, I can install ruby 3.1.2 this way with either OpenSSL 3.0 or OpenSSL 1.1 depending on what I provide in the configure step. I can provide --with-openssl-dir=/opt/homebrew/opt/openssl\@1.1 to override the 3.0 default that brew linked. Both work. However, again, with ruby-build the installation fails as per my report in https://github.com/rbenv/ruby-build/discussions/1961

Thanks to https://github.com/rbenv/ruby-build/discussions/1961#discussioncomment-3953774 for finding this workaround that makes the source build usable with rbenv.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 38 (15 by maintainers)

Most upvoted comments

Hi there, I’ve investigated this issue a little bit, and finally got a repro on my end. Let me put my idea before going that direction.

The essential requirement for repro is that SIP is disabled.

TL;DR: Please re-enable SIP by following the official instruction and retry rbenv install 3.1.3

Why?

ruby-3.1.2/libruby.3.1.dylib: incompatible cpu-subtype: 0x00000000

The error repoted during rbs_extension build is produced by the dynamic loader dyld. It says that the Mach-O executable file and the inserted Mach-O dylib by DYLD_INSERT_LIBRARIES have incompatible cpu-subtype. Especially, the executable (/usr/bin/clang) is compiled for arm64e but the dylib (libruby.3.1.dylib) is compiled for arm64. (arm64e is a new arch supported by recent Apple devices basically arm64 + pointer authentication support.)

Okay, so we can’t set DYLD_INSERT_LIBRARIES while running /usr/bin/clang. But why it fails only when SIP is disabled?

Executable binaries that is code-signed with Apple certificates are called platform binaries: https://developer.apple.com/documentation/endpointsecurity/es_process_t/3228979-is_platform_binary Platform binaries are usually located under /usr/bin, and macOS applies additional security protections for them when SIP is enabled. (Hardened Runtime: https://developer.apple.com/documentation/security/hardened_runtime).

The additional security protections, Hardened Runtime, disables DYLD_* environment variables, so DYLD_INSERT_LIBRARIES does not have any effect under SIP. Therefore the arch incompatibility error is not raised under SIP.

Considering the above discussion, we have two workarounds for now:

Workaround 1: Re-enable SIP

This is a little bit tricky, but enabling SIP would also solve the issue because it prevents loading incompatible library through DYLD_INSERT_LIBRARIES

(Potential) Workaround 2: Use clang compiled for arm64

Warning I couldn’t succeed to build by this way, but I think it works in theory. I think there should be something wrong in configure options.

/usr/bin/clang is compiled for arm64e, but it’s just a shim and the actual clang binary is usually compiled for arm64:

$ xcrun -f clang
/Applications/Xcode-14.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
$ file $(xcrun -f clang)
/Applications/Xcode-14.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64]
/Applications/Xcode-14.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang (for architecture x86_64):    Mach-O 64-bit executable x86_64
/Applications/Xcode-14.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang (for architecture arm64):     Mach-O 64-bit executable arm64

So building with explicit compiler path may solve the issue:

$ export SDKROOT="$(xcrun -show-sdk-path -sdk macosx)" # because the /usr/bin/clang shim automatically set it
$ export RUBY_CONFIGURE_OPTS="CC=$(xcrun -f clang)"
$ rbenv install 3.1.3

Yes, I’ll track this issue now. But I couldn’t reproduce this with Ventura 13.1 beta and Xcode 14 🤔

$ RUBY_CONFIGURE_OPTS='--enable-shared --disable-install-doc' rbenv install 3.1.2
To follow progress, use 'tail -f /var/folders/_z/1_hsk6zx07v57yg5wlsjxvc80000gn/T/ruby-build.20221117160651.3444.log' or pass --verbose
Installing openssl-3.0.7...
Installed openssl-3.0.7 to /Users/hsbt/.local/share/rbenv/versions/3.1.2

Installing ruby-3.1.2...
ruby-build: using readline from homebrew
ruby-build: using gmp from homebrew
Installed ruby-3.1.2 to /Users/hsbt/.local/share/rbenv/versions/3.1.2

$ otool -L ~/.local/share/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/debug-1.4.0/ext/debug/debug.bundle
/Users/hsbt/.local/share/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/debug-1.4.0/ext/debug/debug.bundle:
        /Users/hsbt/.local/share/rbenv/versions/3.1.2/lib/libruby.3.1.dylib (compatibility version 3.1.0, current version 3.1.2)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)

We will release all stable versions includes the fixes of Ventura and Xcode14 in Nov 2022. We will wait to try them for users who get same issues.

I just found that there is a major difference in the ./configure output of the rbenv installation vs manual installation:

rbenv:

Configuration summary for ruby version 3.1.2

   * Installation prefix: /Users/milos/.rbenv/versions/3.1.2
   * exec prefix:         ${prefix}
   * arch:                arm64-darwin20
   * site arch:           ${arch}
   * RUBY_BASE_NAME:      ruby
   * enable shared:       yes
   * ruby lib prefix:     ${libdir}/${RUBY_BASE_NAME}
   * site libraries path: ${rubylibprefix}/${sitearch}
   * vendor path:         ${rubylibprefix}/vendor_ruby
   * target OS:           darwin20
   * compiler:            clang
   * with pthread:        yes
   * with coroutine:      arm64
   * enable shared libs:  yes
   * dynamic library ext: bundle
   * CFLAGS:              -fdeclspec ${optflags} ${debugflags} ${warnflags}
   * LDFLAGS:             -L. -L/Users/milos/.rbenv/versions/3.1.2/lib  \
                          -fstack-protector-strong
   * DLDFLAGS:            -L/Users/milos/.rbenv/versions/3.1.2/lib  \
                          -Wl,-undefined,dynamic_lookup \
                          -Wl,-multiply_defined,suppress
   * optflags:            -O3 -fno-fast-math
   * debugflags:          -ggdb3
   * warnflags:           -Wall -Wextra -Wdeprecated-declarations \
                          -Wdivision-by-zero \
                          -Wimplicit-function-declaration -Wimplicit-int \
                          -Wmisleading-indentation -Wpointer-arith \
                          -Wshorten-64-to-32 -Wwrite-strings \
                          -Wold-style-definition -Wmissing-noreturn \
                          -Wno-constant-logical-operand -Wno-long-long \
                          -Wno-missing-field-initializers \
                          -Wno-overlength-strings -Wno-parentheses-equality \
                          -Wno-self-assign -Wno-tautological-compare \
                          -Wno-unused-parameter -Wno-unused-value \
                          -Wunused-variable -Wextra-tokens -Wundef
   * strip command:       strip -A -n
   * install doc:         rdoc
   * JIT support:         yes
   * man page type:       doc
   * BASERUBY -v:         ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) \
                          [arm64-darwin20]

manual:

Configuration summary for ruby version 3.1.2

   * Installation prefix: /Users/milos/.rbenv/versions/3.1.2
   * exec prefix:         ${prefix}
   * arch:                arm64-darwin20
   * site arch:           ${arch}
   * RUBY_BASE_NAME:      ruby

   * ruby lib prefix:     ${libdir}/${RUBY_BASE_NAME}
   * site libraries path: ${rubylibprefix}/${sitearch}
   * vendor path:         ${rubylibprefix}/vendor_ruby
   * target OS:           darwin20
   * compiler:            clang
   * with pthread:        yes
   * with coroutine:      arm64
   * enable shared libs:  no
   * dynamic library ext: bundle
   * CFLAGS:              -fdeclspec ${optflags} ${debugflags} ${warnflags}
   * LDFLAGS:             -L. -fstack-protector-strong

   * DLDFLAGS:            -Wl,-undefined,dynamic_lookup \
                          -Wl,-multiply_defined,suppress
                          
   * optflags:            -O3 -fno-fast-math
   * debugflags:          -ggdb3
   * warnflags:           -Wall -Wextra -Wdeprecated-declarations \
                          -Wdivision-by-zero \
                          -Wimplicit-function-declaration -Wimplicit-int \
                          -Wmisleading-indentation -Wpointer-arith \
                          -Wshorten-64-to-32 -Wwrite-strings \
                          -Wold-style-definition -Wmissing-noreturn \
                          -Wno-constant-logical-operand -Wno-long-long \
                          -Wno-missing-field-initializers \
                          -Wno-overlength-strings -Wno-parentheses-equality \
                          -Wno-self-assign -Wno-tautological-compare \
                          -Wno-unused-parameter -Wno-unused-value \
                          -Wunused-variable -Wextra-tokens -Wundef
   * strip command:       strip -A -n
   * install doc:         rdoc
   * JIT support:         yes
   * man page type:       doc
   * BASERUBY -v:         ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) \
                          [arm64-darwin20]

We can see that * enable shared libs: no is shown when compiling manually per Step 3. This means that the difference between the the manual step vs rbenv is that rbenv enables shared libraries by default, and for some reason that creates an incompatible libruby3.1.dylib. Since we do not specify --enable-shared when compiling manually, we do not get the cpu subtype error since there is no shared library to load.

To recap:

When compiling manually, the full ./configure command (as per Step 3) is:

./configure --prefix=/Users/milos/.rbenv/versions/3.1.2

If we then add --enable-shared like this:

./configure --prefix=/Users/milos/.rbenv/versions/3.1.2 --enable-shared

Then we get the same error as with rbenv.

So I think you may be right - it may not be a ruby-build issue after all. Please do try to check if it’s an issue with ruby’s build system or toolchain and let me know if I can help with troubleshooting since I can reproduce both scenarios consistently.

Thanks so much @milosivanovic for excellent sleuthing and @hsbt for helping.

Agreed this should be primarily handled in the Ruby issue tracker. But, is there something that we could do from ruby-build in the meantime as a workaround for all people affected?