Skip to content

Apply --platform in gem build when it matches the local platform - #9863

Open
IslamElsayed wants to merge 1 commit into
ruby:masterfrom
IslamElsayed:fix-gem-build-local-platform
Open

Apply --platform in gem build when it matches the local platform#9863
IslamElsayed wants to merge 1 commit into
ruby:masterfrom
IslamElsayed:fix-gem-build-local-platform

Conversation

@IslamElsayed

Copy link
Copy Markdown

Fixes #9344. @kou wrote there that "gem build --platform should build a platform-specific gem" — this makes it do that in the one case where it currently doesn't.

What was the end-user or developer problem that led to this PR?

gem build --platform aarch64-linux on aarch64 Linux produces hello-0.1.0.gem instead of hello-0.1.0-aarch64-linux.gem. The flag is silently dropped, but only when the requested platform happens to equal the machine's own — on any other platform it works, which is what makes it easy to miss.

What is your fix for the problem, implemented in this PR?

gem build never applied --platform itself. It relied on the inference in Gem::Specification#initialize:

if (platform = Gem.platforms.last) && platform != Gem::Platform::RUBY && platform != Gem::Platform.local
  self.platform = platform
end

That != Gem::Platform.local guard is load-bearing and I deliberately left it alone: Gem.platforms is [RUBY, Gem::Platform.local] by default, so without it every spec instantiated anywhere in the process would silently become platform-specific. The cost is that a legitimate explicit request matching the local platform is indistinguishable from the ambient default, and gets dropped.

But it is distinguishable one level up. add_platform_option resets Gem.platforms to [RUBY] before appending the requested value and sets options[:added_platform], so at the command layer an explicit --platform is unambiguous. The fix applies it there:

spec.platform = Gem.platforms.last if options[:added_platform]

One line in build_package, plus a comment explaining why the inference can't be relied on. Nothing shared changes, so no other caller of Gem::Specification.new is affected.

Alternative considered: relaxing the guard in Gem::Specification#initialize to consult added_platform. I didn't, because that guard protects every spec instantiation in the process and the command layer already has the unambiguous signal — fixing it where the ambiguity doesn't exist seemed safer than widening the blast radius.

Make sure the following tasks are checked

  • Describe the problem / feature
  • Write tests for features and bug fixes

test_execute_with_platform_matching_local_platform builds with --platform Gem::Platform.local and asserts both the output filename and spec.platform. It fails on master:

Failure: test_execute_with_platform_matching_local_platform:
  expected a platform-specific gem at .../some_gem-2-x86-darwin-8.gem.
  <false> is not true.

Verified green: the full test_gem_commands_build_command.rb (35 tests), plus test_gem_specification.rb (296), test_gem_package.rb (78), test_gem_platform.rb (33) and test_gem_commands_install_command.rb (87) for regressions. bin/rake rubocop reports no offenses across 852 files.

Disclosure

This was written with AI assistance (Claude). I can explain every line, and the behaviour was verified by running the tests rather than inferred.

`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.
@kou

kou commented Sep 9, 2026

Copy link
Copy Markdown
Member

Does this work with extension libraries?

@IslamElsayed

Copy link
Copy Markdown
Author

Yes. I checked it rather than reasoning about it — building the same gemspec with extensions set, once with the local platform and once with a non-local one, before and after the change:

Before

--platform x86-darwin-8   -> gem_local-2.gem                 platform=ruby           extensions=["ext/g/extconf.rb"]
--platform arm64-darwin   -> gem_remote-2-arm64-darwin.gem   platform=arm64-darwin   extensions=["ext/g/extconf.rb"]

After

--platform x86-darwin-8   -> gem_local-2-x86-darwin-8.gem    platform=x86-darwin-8   extensions=["ext/g/extconf.rb"]
--platform arm64-darwin   -> gem_remote-2-arm64-darwin.gem   platform=arm64-darwin   extensions=["ext/g/extconf.rb"]

So the local case now does what the non-local case already did, and extensions is carried through untouched in every case. The change only assigns spec.platform; it doesn't go near extension handling.

Worth being explicit about what it still doesn't do, since it's your area: gem build --platform has never cleared spec.extensions or bundled prebuilt artifacts, for any platform. A gem built this way is still a source gem that compiles on install — it just carries a platform-specific name now. Making a genuinely precompiled binary gem is what rake-compiler and the rubygems-build-binary approach you linked on the issue are for, and this PR doesn't change that either way.

If you'd rather --platform also warn (or refuse) when a spec declares extensions, that seems like a separate discussion — happy to open an issue for it rather than widen this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Running gem build --platform aarch64-linux on aarch64 Linux creates a non platform-specific gem

2 participants