Skip to content

[Bug] - ruby4.0: default gems reported as "extensions are not built"; require "bigdecimal" fails` #1117

Description

@multiverse2011

Describe the bug

On Amazon Linux 2023, ruby4.0's RubyGems reports the RPM-provided default gems bigdecimal, io-console, and psych as having unbuilt extensions, even though their .so files and gem.build_complete markers are present. require "bigdecimal" then fails with LoadError outside Bundler.

Gem.default_ext_dir_for("/usr/share/gems/ruby4.0") returns /usr/share/ruby4.0/lib64/gems/ruby/ruby, which does not exist. The distro operating_system.rb derives that path by matching a directory component equal to RUBY_INSTALL_NAME ("ruby"), but the install directories are named ruby4.0, so the match fails and the prefix is wrong.

To Reproduce

Reproducible in a fresh amazonlinux:2023 container:

$ dnf install -y ruby4.0 ruby4.0-rubygems ruby4.0-rubygem-bigdecimal \
    ruby4.0-rubygem-psych ruby4.0-rubygem-io-console
$ rpm -q ruby4.0 ruby4.0-rubygems
ruby4.0-4.0.1-32.amzn2023.0.2.aarch64
ruby4.0-rubygems-4.0.3-32.amzn2023.0.2.noarch

$ ruby -e 'require "bigdecimal"'
Ignoring bigdecimal-4.0.1 because its extensions are not built. Try: gem pristine bigdecimal --version 4.0.1
Ignoring io-console-0.8.2 because its extensions are not built. Try: gem pristine io-console --version 0.8.2
Ignoring psych-5.3.1 because its extensions are not built. Try: gem pristine psych --version 5.3.1
Ignoring bigdecimal-4.0.1 because its extensions are not built. Try: gem pristine bigdecimal --version 4.0.1
Ignoring io-console-0.8.2 because its extensions are not built. Try: gem pristine io-console --version 0.8.2
Ignoring psych-5.3.1 because its extensions are not built. Try: gem pristine psych --version 5.3.1
/usr/share/ruby4.0/did_you_mean/core_ext/name_error.rb:11: warning: bigdecimal is not part of the default gems since Ruby 3.4.0. Install bigdecimal from RubyGems.
-e:1:in 'Kernel#require': cannot load such file -- bigdecimal (LoadError)
      from -e:1:in '<main>'

# Wrong extension dir vs. the config it is derived from:
$ ruby -e 'require "rbconfig"; puts RbConfig::CONFIG["RUBY_INSTALL_NAME"], RbConfig::CONFIG["vendordir"]'
ruby
/usr/share/ruby4.0/vendor_ruby
$ ruby -e 'require "rubygems"; puts Gem.default_ext_dir_for("/usr/share/gems/ruby4.0")'
/usr/share/ruby4.0/lib64/gems/ruby/ruby
$ ls -d /usr/share/ruby4.0/lib64/gems/ruby/ruby
ls: cannot access '/usr/share/ruby4.0/lib64/gems/ruby/ruby': No such file or directory

# The extensions and markers actually exist here, and the packages are unmodified:
$ find /usr/lib64/gems/ruby4.0 /usr/share/gems/ruby4.0/extensions -name gem.build_complete
/usr/lib64/gems/ruby4.0/json-2.18.0/gem.build_complete
/usr/lib64/gems/ruby4.0/bigdecimal-4.0.1/gem.build_complete
/usr/lib64/gems/ruby4.0/psych-5.3.1/gem.build_complete
/usr/lib64/gems/ruby4.0/io-console-0.8.2/gem.build_complete
/usr/share/gems/ruby4.0/extensions/aarch64-linux/4.0.0/bigdecimal-4.0.1/gem.build_complete
$ rpm -V ruby4.0 ruby4.0-rubygems ruby4.0-rubygem-bigdecimal ruby4.0-rubygem-psych ruby4.0-rubygem-io-console
$   # (no output)

The suggested gem pristine cannot restore these: the RPM packages ship no cached .gem, so it falls back to fetching from rubygems.org and rebuilding, which fails without a Ruby development environment. Note where it writes its build log — the nonexistent extension dir derived above (run this last; it creates that directory and touches files under /usr/share/gems, which perturbs the rpm -V / ls -d checks):

$ gem pristine bigdecimal --version 4.0.1
[...Ignoring warnings as above...]
Restoring gems to pristine condition...
Cached gem for bigdecimal-4.0.1 not found, attempting to fetch...
Building native extensions. This could take a while...
ERROR:  While executing gem ... (Gem::Ext::BuildError)
    ERROR: Failed to build gem native extension.

    current directory: /usr/share/gems/ruby4.0/gems/bigdecimal-4.0.1/ext/bigdecimal
/usr/bin/ruby -I/usr/share/gems/ruby4.0 extconf.rb
[...]
mkmf.rb can't find header files for ruby at /usr/share/include/ruby.h
[...]
Results logged to /usr/share/ruby4.0/lib64/gems/ruby/ruby/bigdecimal-4.0.1/gem_make.out

The derivation, from /usr/share/gems/ruby4.0/rubygems/defaults/operating_system.rb (unmodified, owned by ruby4.0-rubygems):

def previous_but_one_dir_to(path, dir)
  return unless path
  split_path = path.split(File::SEPARATOR)
  File.join(split_path.take_while { |one_dir| one_dir !~ /^#{dir}$/ }[0..-2])
end

def default_locations
  @default_locations ||= {
    :system => previous_but_one_dir_to(RbConfig::CONFIG['vendordir'], RbConfig::CONFIG['RUBY_INSTALL_NAME']),
    ...

take_while { |d| d !~ /^ruby$/ } on /usr/share/ruby4.0/vendor_ruby never stops at ruby4.0, so it returns /usr/share/ruby4.0 instead of /usr.

Expected behavior

No "extensions are not built" warnings; require "bigdecimal" succeeds; Gem.default_ext_dir_for("/usr/share/gems/ruby4.0") resolves to /usr/lib64/gems/ruby4.0.

Environment

  • Amazon Linux 2023, aarch64. Observed on Amazon Linux 2023.12.20260622 and reproduced in the amazonlinux:2023 container (Amazon Linux 2023.12.20260710).
  • ruby4.0-4.0.1-32.amzn2023.0.2, ruby4.0-rubygems-4.0.3-32.amzn2023.0.2, RubyGems 4.0.3.
  • No fixed package available (dnf check-update 'ruby4.0*' returns nothing).

Notes

  • psych and io-console also have symlinks on the arch load path (/usr/lib64/ruby4.0/psych.so, /usr/lib64/ruby4.0/io/console.so/usr/lib64/gems/ruby4.0/...), so require works for them despite the warning; only bigdecimal fails to load.
  • Workaround: under Bundler with the gem installed to a project-local path (bundle config set --local path vendor/bundle), the functional LoadError is avoided because Bundler builds and loads its own copy (requires gcc, make, ruby4.0-devel); the warnings still print.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions