Skip to content

Commit

Permalink
set required_rubygems_version for native gems that specify the linu…
Browse files Browse the repository at this point in the history
…x libc (#236)

### Problem I'm trying to solve

Rubygems does not correctly recognize `-musl` or `-gnu` platform
suffixes until v3.3.22.

### Solution

If rake-compiler is building a linux native gem that specifies a libc in
its platform object, then add ">= 3.3.22" to the
required_rubygems_version requirement.


https://github.com/rubygems/rubygems/blob/master/CHANGELOG.md#3322--2022-09-07

### Context

While working on musl support in the precompilation toolchain:

- rake-compiler/rake-compiler-dock#111
- flavorjones/ruby-c-extensions-explained#27
- sparklemotion/sqlite3-ruby#442
- sparklemotion/nokogiri#3111

I noticed that Ruby 3.0 is still shipping with Rubygems 3.2.33, which
does not recognize these gem platforms.

Specifying the rubygems requirement changes the error experienced by
users during gem installation from:

> ERROR:  While executing gem ... (Gem::Exception)
> Unable to find spec for #<Gem::NameTuple rcee_precompiled,
0.6.test.2024.0128.1724, aarch64-linux>

to:

> ERROR: Error installing
rcee_precompiled-0.6.test.2024.0128.1735-x86_64-linux-musl.gem:
> rcee_precompiled-0.6.test.2024.0128.1735-x86_64-linux-musl requires
RubyGems version >= 3.3.22.
> The current RubyGems version is 3.2.33. Try 'gem update --system' to
update RubyGems itself.
  • Loading branch information
flavorjones committed Jan 30, 2024
1 parent 3ffaf97 commit 109fff5
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 0 deletions.
10 changes: 10 additions & 0 deletions lib/rake/extensiontask.rb
Expand Up @@ -278,6 +278,16 @@ def define_native_tasks(for_platform = nil, ruby_ver = RUBY_VERSION, callback =
"< #{ruby_api_version(sorted_ruby_versions.last).succ}.dev"
]

# set rubygems version constraints
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("3.3.22") &&
spec.platform.os == "linux" && !spec.platform.version.nil?
spec.required_rubygems_version = if spec.required_rubygems_version == Gem::Requirement.default
[">= 3.3.22"]
else
Gem::Requirement.new(gem_spec.required_rubygems_version, ">= 3.3.22")
end
end

# clear the extensions defined in the specs
spec.extensions.clear

Expand Down
87 changes: 87 additions & 0 deletions spec/lib/rake/extensiontask_spec.rb
Expand Up @@ -620,6 +620,93 @@
spec.metadata['allowed_push_host'].should eq 'http://test'
end

it "should set required_rubygems_version when building a gem for `-linux-gnu` or `-linux-musl`" do
platforms = ["arm64-darwin", "arm-linux", "arm-linux-gnu", "arm-linux-musl"]
ruby_cc_versions = ["3.3.0"]
ENV["RUBY_CC_VERSION"] = ruby_cc_versions.join(":")

ruby_cc_versions.each do |ruby_cc_version|
platforms.each do |platform|
rbconf = "/rubies/#{ruby_cc_version}/rbconfig.rb"
allow_any_instance_of(Rake::CompilerConfig).to(
receive(:find)
.with(ruby_cc_version, platform)
.and_return(rbconf)
)
end
end

allow(Gem).to receive_message_chain(:configuration, :verbose=).and_return(true)

spec = Gem::Specification.new do |s|
s.name = 'my_gem'
s.platform = Gem::Platform::RUBY
s.extensions = ['ext/somegem/extconf.rb']
end

cross_specs = {} # platform => spec
Rake::ExtensionTask.new("extension_one", spec) do |ext|
ext.cross_platform = platforms
ext.cross_compile = true
ext.cross_compiling do |cross_spec|
cross_specs[cross_spec.platform.to_s] = cross_spec
end
end
platforms.each do |platform|
Rake::Task["native:my_gem:#{platform}"].execute
end

cross_specs["arm64-darwin"].required_rubygems_version.should eq(Gem::Requirement.default)
cross_specs["arm-linux"].required_rubygems_version.should eq(Gem::Requirement.default)

if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("3.3.22")
expected_rubygems_version = Gem::Requirement.new([">= 3.3.22"])
cross_specs["arm-linux-gnu"].required_rubygems_version.should eq(expected_rubygems_version)
cross_specs["arm-linux-musl"].required_rubygems_version.should eq(expected_rubygems_version)
end
end

it "should append to required_rubygems_version if it's set" do
platform = "arm-linux-musl"
ruby_cc_versions = ["3.3.0"]
ENV["RUBY_CC_VERSION"] = ruby_cc_versions.join(":")

ruby_cc_versions.each do |ruby_cc_version|
rbconf = "/rubies/#{ruby_cc_version}/rbconfig.rb"
allow_any_instance_of(Rake::CompilerConfig).to(
receive(:find)
.with(ruby_cc_version, platform)
.and_return(rbconf)
)
end

allow(Gem).to receive_message_chain(:configuration, :verbose=).and_return(true)

spec = Gem::Specification.new do |s|
s.name = 'my_gem'
s.platform = Gem::Platform::RUBY
s.extensions = ['ext/somegem/extconf.rb']
s.required_rubygems_version = "!= 3.4.1" # keep this around
end

actual_cross_spec = nil
Rake::ExtensionTask.new("extension_one", spec) do |ext|
ext.cross_platform = [platform]
ext.cross_compile = true
ext.cross_compiling do |cross_spec|
actual_cross_spec = cross_spec
end
end
Rake::Task["native:my_gem:#{platform}"].execute

if Gem::Version.new(Gem::VERSION) < Gem::Version.new("3.3.22")
actual_cross_spec.required_rubygems_version.should eq(Gem::Requirement.new(["!= 3.4.1"]))
else
expected_rubygems_version = Gem::Requirement.new(["!= 3.4.1", ">= 3.3.22"])
actual_cross_spec.required_rubygems_version.should eq(expected_rubygems_version)
end
end

after :each do
ENV.delete('RUBY_CC_VERSION')
end
Expand Down

0 comments on commit 109fff5

Please sign in to comment.