From 6c98e335297aacdff38b888af0c42529379fe554 Mon Sep 17 00:00:00 2001 From: Islam Elsayed Date: Tue, 8 Sep 2026 13:23:21 +0300 Subject: [PATCH] Apply --platform in gem build when it matches the local platform `gem build` never applied `--platform` itself. It relied on the platform inference in `Gem::Specification#initialize`, which reads `Gem.platforms.last` but deliberately skips it when that equals `Gem::Platform.local`: if (platform = Gem.platforms.last) && platform != Gem::Platform::RUBY && platform != Gem::Platform.local That guard is load-bearing. `Gem.platforms` includes the local platform by default, so without it every spec instantiated anywhere in the process would silently become platform specific. The cost is that `gem build --platform x86_64-linux` on x86_64-linux produced `hello-0.1.0.gem` rather than `hello-0.1.0-x86_64-linux.gem`. Passing `--platform` resets `Gem.platforms` to `[RUBY]` before appending the requested value and sets `options[:added_platform]`, so an explicit request is already distinguishable from the ambient default. Apply it in `build_package` when that flag is set, leaving the shared guard alone. --- lib/rubygems/commands/build_command.rb | 9 ++++++ .../test_gem_commands_build_command.rb | 30 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 9e4d812c3626..ce24084894a7 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -90,6 +90,15 @@ def build_gem def build_package(gemspec) spec = Gem::Specification.load(gemspec) if spec + # Gem::Specification#initialize infers the platform from + # Gem.platforms.last, but skips it when that equals Gem::Platform.local, + # because Gem.platforms includes the local platform by default and every + # spec would otherwise become platform specific. Passing --platform + # resets Gem.platforms, so the request is unambiguous and is applied here + # rather than left to that inference, which would drop it whenever the + # requested platform happened to be the local one. + spec.platform = Gem.platforms.last if options[:added_platform] + Gem::Package.build( spec, options[:force], diff --git a/test/rubygems/test_gem_commands_build_command.rb b/test/rubygems/test_gem_commands_build_command.rb index 771eb07dbc9c..fc49b32822db 100644 --- a/test/rubygems/test_gem_commands_build_command.rb +++ b/test/rubygems/test_gem_commands_build_command.rb @@ -122,6 +122,36 @@ def test_execute util_test_build_gem @gem end + # Regression test for https://github.com/rubygems/rubygems/issues/9344: + # `gem build --platform X` must produce a platform-specific gem even when X + # happens to equal the local platform. `Gem::Specification#initialize` infers + # the platform from `Gem.platforms.last` but deliberately skips it when that + # equals `Gem::Platform.local`, because `Gem.platforms` includes the local + # platform by default and every spec would otherwise become platform-specific. + # `gem build` relied entirely on that inference, so the one case where the + # requested platform matched the local one was silently dropped. + def test_execute_with_platform_matching_local_platform + gemspec_file = File.join(@tempdir, @gem.spec_name) + + File.open gemspec_file, "w" do |gs| + gs.write @gem.to_ruby + end + + @cmd.handle_options [gemspec_file, "--platform", Gem::Platform.local.to_s] + + use_ui @ui do + Dir.chdir @tempdir do + @cmd.execute + end + end + + gem_file = File.join @tempdir, "some_gem-2-#{Gem::Platform.local}.gem" + assert File.exist?(gem_file), "expected a platform-specific gem at #{gem_file}" + + spec = Gem::Package.new(gem_file).spec + assert_equal Gem::Platform.local, spec.platform + end + def test_ruby_abi_rejects_ruby_platform gem = util_spec "some_gem" do |s| s.license = "AGPL-3.0-only"