From 109fff52c1dd29d6cae2efe50a17d6ecef71f467 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Mon, 29 Jan 2024 22:09:03 -0500 Subject: [PATCH] set `required_rubygems_version` for native gems that specify the linux 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: - https://github.com/rake-compiler/rake-compiler-dock/pull/111 - https://github.com/flavorjones/ruby-c-extensions-explained/pull/27 - https://github.com/sparklemotion/sqlite3-ruby/pull/442 - https://github.com/sparklemotion/nokogiri/pull/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 # 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. --- lib/rake/extensiontask.rb | 10 ++++ spec/lib/rake/extensiontask_spec.rb | 87 +++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/lib/rake/extensiontask.rb b/lib/rake/extensiontask.rb index 46329a2..3baa798 100644 --- a/lib/rake/extensiontask.rb +++ b/lib/rake/extensiontask.rb @@ -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 diff --git a/spec/lib/rake/extensiontask_spec.rb b/spec/lib/rake/extensiontask_spec.rb index 367c501..b13f1c0 100644 --- a/spec/lib/rake/extensiontask_spec.rb +++ b/spec/lib/rake/extensiontask_spec.rb @@ -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