Repository: ruby/rubygems (Ruby)

Issue:
Gem platform should treat `arm` as 32-bit arm instead of generic arm
https://github.com/rubygems/rubygems/blob/197de10d73cc2ebe8f708ebfa0b37ea6341db962/lib/rubygems/platform.rb#L179-L187

In practice, for Linux and Windows builds of Ruby, `arm` is strictly used to represent 32-bit arm, and `aarch64` is 64-bit arm. 

For Mac, historically Ruby before 3.0 incorrectly reports itself as `arm` on 64-bit arm. Since Ruby 3.0 this has been fixed and Mac always report as `arm64`.

The main problem here is that for ARMv8 and newer processors, 32-bit instruction support is not required for 64-bit processors. For example, Apple M family chip is aarch64 only with no hardware support for aarch32 at all. Although most of the aarch64 CPUs on the market other than Apple’s ones do also support aarch32, more aarch64-only processors may come out in the future.

As `arm` is widely used to identify aarch32, and aarch64 (arm64) processors may not necessarily support aarch32, it is not safe to interpret an `arm` gem as generic arm support.


Discussion:
Ironically, although the statement in the code comment about arm is problematic, the actual implementation is fine.

https://github.com/rubygems/rubygems/blob/197de10d73cc2ebe8f708ebfa0b37ea6341db962/lib/rubygems/platform.rb#L201

The implementation matches the gem platform `arm` with runtime platform `/^arm/`. In reality this only matches runtime `arm` (32-bit matches 32-bit) or runtime`arm64` (only used by Mac, 64-bit matches 64-bit). It does not cause any trouble on Linux or Windows as they report as runtime `aarch64`, and thus will not cause false positives.
Thanks for the insight! So this is just a matter of rewording the documentation, correct? Would you be willing to send us a PR?
I would recommend just removing this logic. It is only useful when a gem author incorrectly publish a `arm-darwin` gem, and that is allowed to be installed on `arm64-darwin`. It does not match any other real world scenario. I've checked the rubygems index with the script below and there isn't any gem incorrectly published as `arm-darwin`. Thus this check does not do anything in practice.

``` ruby
Gem::SpecFetcher.fetcher.available_specs(:complete).each do |available_specs|
  available_specs.each do |source, specs|
    specs.each do |spec|
      if spec.platform.start_with?('arm-darwin')
        puts spec
      end
    end
  end
end
``` 
Ok, let's remove it then 👍.
Wait a sec. I found out why this is added. It was https://github.com/rubygems/rubygems/issues/532.

Looks like some distribution has runtime `armv7-linux` instead of `arm-linux`, and removing it might cause problem. This is more complex than it is.

I think arm (32-bit) probably need to match any arm v5 - v7, but it cannot safely match armv8. I will create a PR to limit what `arm` can match against



Patch:
diff --git a/.github/workflows/rubygems.yml b/.github/workflows/rubygems.yml
index dc8066c11f4d..8ade94f577c6 100644
--- a/.github/workflows/rubygems.yml
+++ b/.github/workflows/rubygems.yml
@@ -46,6 +46,8 @@ jobs:
         with:
           ruby-version: ${{ matrix.ruby.value }}
           bundler: none
+      - name: Update RBS # https://github.com/ruby/rbs/pull/1612
+        run: ruby -e 'begin; require "rbs"; rescue LoadError; exit; end; exec(*%w{gem install --version 3.4.0 rbs}) if Gem::Requirement.new([">= 1.6", "< 3.1.1"]).satisfied_by?(Gem::Version.new(RBS::VERSION))'
       - name: Install Dependencies
         run: bin/rake setup
       - name: Run Test
diff --git a/bundler/lib/bundler/rubygems_ext.rb b/bundler/lib/bundler/rubygems_ext.rb
index f2537f716808..3f6b2197b777 100644
--- a/bundler/lib/bundler/rubygems_ext.rb
+++ b/bundler/lib/bundler/rubygems_ext.rb
@@ -258,7 +258,7 @@ def ===(other)
 
         # cpu
         ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu ||
-        (@cpu == "arm" && other.cpu.start_with?("arm"))) &&
+        (@cpu == "arm" && other.cpu.start_with?("armv"))) &&
 
           # os
           @os == other.os &&
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index 9ef61ba21815..450c2141676e 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -177,7 +177,7 @@ def hash # :nodoc:
   # they have the same version, or either one has no version
   #
   # Additionally, the platform will match if the local CPU is 'arm' and the
-  # other CPU starts with "arm" (for generic ARM family support).
+  # other CPU starts with "armv" (for generic 32-bit ARM family support).
   #
   # Of note, this method is not commutative. Indeed the OS 'linux' has a
   # special case: the version is the libc name, yet while "no version" stands
@@ -198,7 +198,7 @@ def ===(other)
 
     # cpu
     ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu ||
-    (@cpu == "arm" && other.cpu.start_with?("arm"))) &&
+    (@cpu == "arm" && other.cpu.start_with?("armv"))) &&
 
       # os
       @os == other.os &&


Test patch:
diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb
index 7c21c734ff27..070c8007bc00 100644
--- a/test/rubygems/test_gem_platform.rb
+++ b/test/rubygems/test_gem_platform.rb
@@ -368,18 +368,27 @@ def test_equals3_cpu_arm
     arm   = Gem::Platform.new "arm-linux"
     armv5 = Gem::Platform.new "armv5-linux"
     armv7 = Gem::Platform.new "armv7-linux"
+    arm64 = Gem::Platform.new "arm64-linux"
 
     util_set_arch "armv5-linux"
     assert((arm   === Gem::Platform.local), "arm   === armv5")
     assert((armv5 === Gem::Platform.local), "armv5 === armv5")
     refute((armv7 === Gem::Platform.local), "armv7 === armv5")
+    refute((arm64 === Gem::Platform.local), "arm64 === armv5")
     refute((Gem::Platform.local === arm), "armv5 === arm")
 
     util_set_arch "armv7-linux"
     assert((arm   === Gem::Platform.local), "arm   === armv7")
     refute((armv5 === Gem::Platform.local), "armv5 === armv7")
     assert((armv7 === Gem::Platform.local), "armv7 === armv7")
+    refute((arm64 === Gem::Platform.local), "arm64 === armv7")
     refute((Gem::Platform.local === arm), "armv7 === arm")
+
+    util_set_arch "arm64-linux"
+    refute((arm   === Gem::Platform.local), "arm   === arm64")
+    refute((armv5 === Gem::Platform.local), "armv5 === arm64")
+    refute((armv7 === Gem::Platform.local), "armv7 === arm64")
+    assert((arm64 === Gem::Platform.local), "arm64 === arm64")
   end
 
   def test_equals3_universal_mingw