Skip to content

Add content addressable gems support - #9773

Open
jenshenny wants to merge 8 commits into
ruby:masterfrom
Shopify:feature-branch-ca-changes-rubygems
Open

Add content addressable gems support#9773
jenshenny wants to merge 8 commits into
ruby:masterfrom
Shopify:feature-branch-ca-changes-rubygems

Conversation

@jenshenny

@jenshenny jenshenny commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

#9654

TL;DR

Adds RubyGems and Bundler client support for content-addressable ("skinny") binary gems: one artifact per Ruby ABI, named with a SHA-256 prefix.

nokogiri-1.18.9-x86_64-linux.gem # traditional platform gem
nokogiri-1.18.9-78be552b.gem     # content-addressable gem

The branch covers build, discovery, install, display, yank, lockfiles, caching, and bundle install --local. Existing source and platform gems are unchanged.

Why

"Fat" binary gems contain every supported Ruby ABI and keep growing. Skinny binaries are smaller, but builds for the same gem, version, and platform need distinct filenames. A content-derived suffix gives each artifact a unique identity.

A content address must be 8–64 lowercase hexadecimal characters. RubyGems only treats it as one when the gem also has a non-Ruby platform and constrained required_ruby_version, avoiding false matches with ordinary filenames.

Follow ups

  • Update guides

gem build

ref: Shopify/rubygems#171.

User behaviour

  • gem build nokogiri.gemspec --ruby-abi 3.4 builds a skinny gem named nokogiri-1.18.9-78be552b.gem, where the suffix is the SHA-256 digest of the gem contents.
  • Without --ruby-abi, behaviour is unchanged.

Details

  • --ruby-abi validates the ABI format (X.Y), requires a non-Ruby platform to be set, and constrains required_ruby_version: if unset it defaults to ~> X.Y.0; a mismatched existing requirement is rejected.
  • The gem is built in memory and validated against the requested ABI; the passed-in spec is only updated once the build succeeds, so a failed build leaves the spec untouched.
  • The hash is deterministic for a given set of gem contents.

gem install

ref: Shopify/rubygems#172 (local) and #173 (remote).

Local

  • Reads the archive, derives the hash from its filename, verifies it against the file's SHA-256, and preserves both hash and platform. The installed directory and gemspec use the hash. A mismatch fails; reinstalling is idempotent.
  • gem install --local GEMNAME is content-addressable aware.

Every install of a CA gem writes a gemspec stub whose # stub: suffix is the hash (so the name-version-<sha> directory resolves). The real platform rides on a separate # stub-target: line that older RubyGems ignore — backwards compatible, while current RubyGems recover both. File: specifications/mygem-1.0-78be552b.gemspec:

# -*- encoding: utf-8 -*-
# stub: mygem 1.0 78be552b lib
# stub: ext/mygem/extconf.rb
# stub-target: platform=x86_64-linux

Gem::Specification.new do |s|
  s.name = 'mygem'
  s.version = Gem::Version.new '1.0'
end

Remote

  • Decodes the compact-index hash and platform:= metadata separately. Distinct hashes remain distinct candidates.

For example, a server info/nokogiri response with two skinny variants (different Ruby ABIs) plus a platform fallback:

---
1.18.9-78be552b |checksum:…,ruby:~> 3.4.0,platform:= x86_64-linux
1.18.9-a1b2c3d4 |checksum:…,ruby:~> 3.3.0,platform:= x86_64-linux
1.18.9-x86_64-linux |checksum:…

The hash is carried in the version token (1.18.9-78be552b); the real platform and Ruby requirement travel in the platform:= / ruby: metadata. The two hashes stay distinct resolver candidates, and 1.18.9-x86_64-linux is the platform fallback.

  • Prefers a compatible skinny gem; resolution falls back to a traditional platform gem, then a source gem.
  • Downloads the SHA-named archive and the SHA-named gemspec for development dependencies.

gem push

ref: Shopify/rubygems#174.

User behaviour

  • gem push name-*.gem --platform x86_64-linux --ruby-abi 3.4 reads the specs of the SHA-named files and pushes the single matching artifact.
  • No match or multiple matches raise instead of guessing. Passing one exact filename remains unchanged.

Details

  • Adds --platform and --ruby-abi selectors. Given multiple SHA-named files, RubyGems reads each specification and selects the one whose platform and required_ruby_version satisfy both selectors.
  • ruby_matches? does not check platform (a RUBY-platform gem with a matching ~> X.Y.0 can be selected by --ruby-abi); this is documented in the tests rather than special-cased.

gem yank

ref: Shopify/rubygems#176.

User behaviour

  • gem yank mygem -v 1.0.0 --platform x86_64-linux --ruby-abi 3.4 sends gem name, version, platform, and ABI so the server can select one skinny variant.
  • Existing yank behaviour is unchanged without --ruby-abi.

Remote queries (gem list / search / info -r)

ref: Shopify/rubygems#175.

User behaviour

  • Shows the real platform and Ruby ABI, not the hash. Versions and platforms are separated; ABIs for the same version/platform are grouped. Examples:
$ gem list -r mygem        # single CA gem on the server
mygem (1.0 Platform: x86_64-linux, Ruby ABI: 3.3)

$ gem search -r mygem      # same version, different platforms/ABIs
mygem (1.0 Platform: arm64-darwin, Ruby ABI: 3.4
         1.0 Platform: x86_64-linux, Ruby ABI: 3.3)

$ gem search -r mygem      # same version+platform, multiple ABIs grouped
mygem (1.0 Platform: x86_64-linux, Ruby ABI: 3.3, 3.4)

$ gem list -r mygem        # multiple versions, each on its own line
mygem (3.0 Platform: arm64-darwin, Ruby ABI: 3.4
         2.0 Platform: x86_64-linux, Ruby ABI: 3.4
         1.0 Platform: x86_64-linux, Ruby ABI: 3.3)

$ gem list -r mygem        # mixed CA, platform (1.0), and source gems
mygem (3.0 Platform: arm64-darwin, Ruby ABI: 3.4
         2.0 Platform: x86_64-linux, Ruby ABI: 3.3
         1.0 Platform: x86_64-linux)

$ gem info -r mygem        # info uses a Platforms: section, ABIs grouped per platform
mygem (1.0)
    Author: A User
    Homepage: http://example.com

    this is a summary

Platforms:
    x86_64-linux Ruby ABI: 3.3, 3.4
  • Unscoped remote queries skip CA metadata (no platform/ABI rows injected).

bundle install (lockfile + local cache)

ref: Shopify/rubygems#177 (remote) and #178 (lockfile + local cache).

Remote

  • Separates the compact-index hash from the real platform, includes the hash in candidate identity, selects an ABI-compatible skinny gem, and falls back to platform/source gems when needed.

Lockfile and local cache

  • Writes platform and hash to Gemfile.lock and parses both on the next run.
  • Installs the SHA-named artifact from vendor/cache with bundle install --local. Remote and local paths produce the same installed directory; checksums remain keyed by the platform lock name. A CA gem locks with the hash in the version and the real platform beside it.
    • The content address in the version token identifies the artifact, but says nothing about which platform it targets. The trailing platform is what Bundler uses for platform matching on the next run and to preserve the real platform identity when materializing from vendor/cache
GEM
  remote: https://rubygems.org/
  specs:
    content_addressable_test (0.1.0-a56253e0) arm64-darwin

PLATFORMS
  arm64-darwin

DEPENDENCIES
  content_addressable_test

CHECKSUMS
  content_addressable_test (0.1.0-a56253e0) sha256=a56253e023bdae7b291f44ff21a02dd12f2609418eb60ad22327a7ae5cd4f353

BUNDLED WITH
   4.1.0.dev

@jenshenny
jenshenny force-pushed the feature-branch-ca-changes-rubygems branch 2 times, most recently from 4a5def3 to d849a56 Compare August 10, 2026 18:27
@OughtPuts
OughtPuts force-pushed the feature-branch-ca-changes-rubygems branch 3 times, most recently from ce7e319 to 8d050c3 Compare August 24, 2026 15:53

@tenderlove tenderlove left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't review the specs super closely, I trust they are covering useful scenarios.

It looks like we've got a lot of array / hash manipulation going on in this PR. Should we be thinking about making real, named objects?

The direction looks good here IMO


def hash
@set.hash ^ @name.hash ^ @version.hash ^ @platform.hash
@set.hash ^ @name.hash ^ @version.hash ^ @platform.hash ^ @content_address.hash

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No impact on this PR, but this is a bad hash and we should fix it upstream.

We should be doing [@set, @name, ...].hash

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!


def hash # :nodoc:
name.hash ^ version.hash
[name, version, platform, content_address].hash

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

end

def spec_platforms(entry, platforms)
platforms = platforms.transform_values(&:uniq)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because it's now a hash of hashes? What is the structure of platforms?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

platforms comes from line 213 in output_versions. It's a one-level hash

platforms = Hash.new {|h,version| h[version] = [] }

In reality it would look something like this:

  {
    Gem::Version.new("1.0.0") => [
      Gem::Platform::RUBY,
      Gem::Platform.new("x86_64-linux"),
      Gem::Platform.new("x86_64-linux")
    ],
    Gem::Version.new("0.9.0") => [
      Gem::Platform::RUBY
    ]
  }

And then `transform_values(&:uniq) would remove any dups in the values.

Comment thread lib/rubygems/source.rb

Gem::NameTuple.new(name, version, platform || "ruby")
suffix ||= "ruby"
content_address = suffix if Gem::ContentAddress.match?(suffix)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is because we're using this same loop with the CA and non-CA RubyGems endpoints?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. The compact-index /versions response uses the same suffix field for both formats, legacy entries contain a platform, while CA entries contain a content-address token. This handles both, so it detects CA suffixes and stores them as content_address. The actual platform is decoded later from the gem’s /info metadata!

Comment thread lib/rubygems/source.rb
platform: platform,
ruby_abi: ruby_abi_from(requirements[:ruby]),
}
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like we should make a real object here. Just spitballing but like:

class GemInfo < Struct.new(:version, :suffix, :platform, :ruby_abi)
  def hash; suffix; end
  def eql?(other); other.version == version && other.suffix == suffix; end
end

Though now that I type this out, it seems very similar to NameTuple? It feels like we could be doing more simple code here with set intersections. e.g. wanted_rows.map { make_obj(_1) } & compact_index_info_rows(name).map { make_object(_1) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tenderlove have implemented a solution in this commit, interested in your thoughts!

OughtPuts and others added 2 commits August 30, 2026 13:56
Co-authored-by: Jenny Shen <jenny.shen@shopify.com>
Co-authored-by: Harriet Oughton <harriet.oughton@shopify.com>
Co-authored-by: Jenny Shen <jenny.shen@shopify.com>
@jenshenny
jenshenny force-pushed the feature-branch-ca-changes-rubygems branch 6 times, most recently from 1fe9ebc to 2b193ae Compare August 30, 2026 21:51
OughtPuts and others added 3 commits August 30, 2026 18:13
Co-authored-by: Gira Chawda <gira.chawda@shopify.com>
Co-authored-by: Jenny Shen <jenny.shen@shopify.com>
Co-authored-by: Jenny Shen <jenny.shen@shopify.com>
Co-authored-by: Jenny Shen <jenny.shen@shopify.com>
@jenshenny
jenshenny force-pushed the feature-branch-ca-changes-rubygems branch from 2b193ae to 467a7a5 Compare August 30, 2026 22:13
@jenshenny

jenshenny commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Testing

With: https://rubygems.org/gems/content_addressable_test

1.1 gem list -r

$ ruby -I$RG/lib $RG/exe/gem list content_addressable_test -r

*** REMOTE GEMS ***

content_addressable_test (0.1.0 Platform: arm64-darwin, Ruby ABI: 3.3, 4.0
                          0.1.0 Platform: x86_64-linux, Ruby ABI: 3.3)

1.2 gem search -r

$ ruby -I$RG/lib $RG/exe/gem search content_addressable_test -r
*** REMOTE GEMS ***

content_addressable_test (0.1.0 Platform: arm64-darwin, Ruby ABI: 3.3, 4.0
                          0.1.0 Platform: x86_64-linux, Ruby ABI: 3.3)

1.3 gem info -r

$ ruby -I$RG/lib $RG/exe/gem info content_addressable_test -r
   content_addressable_test (0.2.0, 0.1.0)                                                                                          
       Platforms:                                                                                                                   
           0.1.0: arm64-darwin Ruby ABI: 3.3, 4.0, x86_64-linux Ruby ABI: 3.3                                                       
           0.2.0: arm64-darwin Ruby ABI: 3.4                                                                                        
       Author: Jenny Shen                                                                                                           
       License: MIT                                                                                                                 
                                                                                                                                    
       Content-addressable gem test  
2. `gem install` from the remote

2.1 Install, filesystem layout, idempotent reinstall, require

$ export GEM_HOME=$(mktemp -d) GEM_PATH=$GEM_HOME
$ ruby -I$RG/lib $RG/exe/gem install content_addressable_test
Fetching content_addressable_test-0.1.0-a56253e0.gem
Successfully installed content_addressable_test-0.1.0-a56253e0
1 gem installed

$ ls $GEM_HOME/gems $GEM_HOME/specifications
gems:
content_addressable_test-0.1.0-a56253e0
specifications:
content_addressable_test-0.1.0-a56253e0.gemspec

$ grep "stub" $GEM_HOME/specifications/*.gemspec
# stub: content_addressable_test 0.1.0 a56253e0 lib
# stub-target: platform=arm64-darwin

$ ruby -I$RG/lib $RG/exe/gem install content_addressable_test   # reinstall
$ ls $GEM_HOME/gems | grep -c content
1

$ ruby -I$RG/lib -e 'require "ca_test"; puts "require OK"'
require OK

Activation detail:

$ ruby -I$RG/lib -e 'gem "content_addressable_test"; spec = Gem.loaded_specs["content_addressable_test"]; ...'
activated: content_addressable_test-0.1.0-a56253e0, platform=arm64-darwin, ca=a56253e0
load path entry: .../gems/content_addressable_test-0.1.0-a56253e0/lib
3. `bundle install` from the remote + lockfile format
$ printf 'source "https://rubygems.org"\ngem "content_addressable_test"\n' > Gemfile
$ ruby -I$RG/lib $RG/exe/bundle install
(completed)

$ cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    content_addressable_test (0.1.0-a56253e0) arm64-darwin

PLATFORMS
  arm64-darwin

DEPENDENCIES
  content_addressable_test

CHECKSUMS
  content_addressable_test (0.1.0-a56253e0) sha256=a56253e023bdae7b291f44ff21a02dd12f2609418eb60ad22327a7ae5cd4f353

BUNDLED WITH
  4.1.0.dev

$ ruby -I$RG/lib $RG/exe/bundle check
The Gemfile's dependencies are satisfied

$ ruby -I$RG/lib $RG/exe/bundle exec ruby -e 'require "ca_test"; puts "bundle exec require OK"'
bundle exec require OK
4. `vendor/cache` and `bundle install --local`
$ ruby -I$RG/lib $RG/exe/bundle cache
Updating files in vendor/cache
  * content_addressable_test-0.1.0-a56253e0.gem

$ ls vendor/cache
content_addressable_test-0.1.0-a56253e0.gem

$ cp Gemfile.lock /tmp/lock.before
$ rm -rf vendor/bundle
$ ruby -I$RG/lib $RG/exe/bundle install --local
Installing content_addressable_test 0.1.0 (arm64-darwin)
Bundle complete! 1 Gemfile dependency, 1 gem now installed.

$ diff /tmp/lock.before Gemfile.lock && echo "lockfile round-trip stable"
lockfile round-trip stable

$ ruby -I$RG/lib $RG/exe/bundle exec ruby -e 'require "ca_test"; puts "local install require OK"'
local install require OK
5. `gem build`

Fixture: a minimal gemspec (content_addressable_test 0.2.0, platform
arm64-darwin) with a single file lib/ca_test.rb whose content differs per
variant so we can identify which artifact loads at runtime.

$ ruby -I$RG/lib $RG/exe/gem build content_addressable_test.gemspec --ruby-abi 3.4
  Version: 0.2.0
  File: content_addressable_test-0.2.0-7431c024.gem

$ ruby -I$RG/lib $RG/exe/gem build content_addressable_test.gemspec --ruby-abi 3.4   # identical content, rebuild
  File: content_addressable_test-0.2.0-7431c024.gem                                  # deterministic

$ # change lib/ca_test.rb content, build for ABI 3.3:
$ ruby -I$RG/lib $RG/exe/gem build content_addressable_test.gemspec --ruby-abi 3.3
  File: content_addressable_test-0.2.0-2213391a.gem
$ ls *.gem
content_addressable_test-0.2.0-2213391a.gem
content_addressable_test-0.2.0-7431c024.gem

$ ruby -I$RG/lib $RG/exe/gem build content_addressable_test.gemspec --ruby-abi invalid
ERROR:  While executing gem ... (Gem::OptionParser::InvalidArgument)
    invalid argument: --ruby-abi invalid: Ruby ABI must be in X.Y format
(exit 1, no file created)

$ ruby -I$RG/lib $RG/exe/gem build content_addressable_test.gemspec --ruby-abi 3.4.1
ERROR:  While executing gem ... (Gem::OptionParser::InvalidArgument)
(exit 1 — ABI must be X.Y, not a full version)

$ ruby -I$RG/lib $RG/exe/gem build content_addressable_test.gemspec --ruby-abi 3.4 -o out.gem
ERROR:  While executing gem ... (ArgumentError)
(exit 1, no out.gem — content-addressable gems must use the generated file name)

$ # gemspec with required_ruby_version "~> 3.3.0", building for 3.4:
Cannot build gem for Ruby ABI 3.4 because required_ruby_version is set to ~> 3.3.0. Please set required_ruby_version to "~> 3.4.0".
(exit 1)

$ # gemspec without a platform, building with --ruby-abi 3.4:
Cannot build a gem scoped to a single Ruby ABI as no platform or a Ruby platform has been set
(exit 1)

$ ruby -I$RG/lib $RG/exe/gem build content_addressable_test.gemspec   # no --ruby-abi
  File: content_addressable_test-0.2.0-arm64-darwin.gem               # traditional fat gem

$ # gemspec without platform, no --ruby-abi:
  File: content_addressable_test-0.2.0.gem                            # traditional source gem
6. `gem push` with platform/ABI selectors (against production)

Local directory contained: source, fat, skinny-3.3 (2213391a) and
skinny-3.4 (7431c024) builds of 0.2.0. The three refusal cases upload
nothing; only the final command transmits.

$ ruby -I$RG/lib $RG/exe/gem push content_addressable_test-0.2.0*.gem   # 4 files, no selectors
ERROR:  While executing gem ... (Gem::CommandLineError)
    Too many gem names; please specify only one (Gem::CommandLineError)
exit=1

$ ruby -I$RG/lib $RG/exe/gem push content_addressable_test-0.2.0-*.gem --platform arm64-darwin
ERROR:  While executing gem ... (Gem::CommandLineError)
    Multiple gems matched platform arm64-darwin: content_addressable_test-0.2.0-2213391a.gem, content_addressable_test-0.2.0-7431c024.gem, content_addressable_test-0.2.0-arm64-darwin.gem
Specify --ruby-abi with one of: 3.3, 3.4
To push a gem without a Ruby ABI, pass the exact filename.
exit=1

$ ruby -I$RG/lib $RG/exe/gem push content_addressable_test-0.2.0-*.gem --platform arm64-darwin --ruby-abi 3.9
ERROR:  While executing gem ... (Gem::CommandLineError)
    No gem matched platform arm64-darwin and Ruby ABI 3.9
exit=1

$ ruby -I$RG/lib $RG/exe/gem push content_addressable_test-0.2.0-*.gem --platform arm64-darwin --ruby-abi 3.4
You are already signed in on RubyGems.org.
Pushing gem to https://rubygems.org...
You have enabled multi-factor authentication. Please enter OTP code.
Code:   ******
Successfully registered gem: content_addressable_test (0.2.0-7431c024, Platform: arm64-darwin, Ruby ABI 3.4)
exit=0

Index propagation check (~20s after push):

$ curl -s https://rubygems.org/info/content_addressable_test | grep 0.2.0
0.2.0-7431c024 |checksum:7431c024...aba89b,ruby:~> 3.4.0,rubygems:>= 4.1.0.beta1,platform:= arm64-darwin,created_at:2026-08-30T20:27:18Z
7. Version-level ABI fallback

With 0.2.0 published for ABI 3.4 only, a Ruby 4.0 client must skip the newer
version and install the compatible 0.1.0 build.

$ ruby -I$RG/lib $RG/exe/gem list content_addressable_test -r
content_addressable_test (0.2.0 Platform: arm64-darwin, Ruby ABI: 3.4
                          0.1.0 Platform: arm64-darwin, Ruby ABI: 3.3, 4.0
                          0.1.0 Platform: x86_64-linux, Ruby ABI: 3.3)

$ ruby -I$RG/lib $RG/exe/gem install content_addressable_test   # Ruby 4.0.6
Successfully installed content_addressable_test-0.1.0-a56253e0
$ ls $GEM_HOME/gems
content_addressable_test-0.1.0-a56253e0
8. Fat fallback within a version, and `gem update` to a CA build (Ruby 3.4.7)

0.1.0 has no 3.4 skinny → the pinned install should choose the fat gem;
gem update should then move to the 3.4 skinny 0.2.0.

$ ~/.rubies/ruby-3.4.7/bin/ruby -I$RG/lib $RG/exe/gem install content_addressable_test -v 0.1.0
Successfully installed content_addressable_test-0.1.0-arm64-darwin
1 gem installed

$ ~/.rubies/ruby-3.4.7/bin/ruby -I$RG/lib $RG/exe/gem update content_addressable_test
Updating installed gems
Updating content_addressable_test
Successfully installed content_addressable_test-0.2.0-7431c024

$ ls $GEM_HOME/gems
content_addressable_test-0.1.0-arm64-darwin
content_addressable_test-0.2.0-7431c024

$ ruby -e 'gem "content_addressable_test", "0.2.0"; require "ca_test"; puts CA_TEST_VARIANT'
0.2.0 skinny ruby-3.4 arm64-darwin
9. Cooldown embargo of a freshly published CA gem (Ruby 3.4.7)

0.2.0 was published the same day (created_at 20:27Z), so a 7-day cooldown must
hold it back.

$ ~/.rubies/ruby-3.4.7/bin/ruby -I$RG/lib $RG/exe/gem install content_addressable_test -v 0.1.0
Successfully installed content_addressable_test-0.1.0-arm64-darwin

$ ~/.rubies/ruby-3.4.7/bin/ruby -I$RG/lib $RG/exe/gem update content_addressable_test --cooldown 7
Updating installed gems
Nothing to update
Gems already up-to-date: content_addressable_test
The following gem versions were skipped by the cooldown setting:
  * content_addressable_test 0.2.0 (available in 7 days), resolved 0.1.0 instead

$ ls $GEM_HOME/gems
content_addressable_test-0.1.0-arm64-darwin
10. Backward compatibility: stable client on the same server
$ bundle --version
Bundler version 4.0.16

$ bundle install
Installing content_addressable_test 0.1.0 (arm64-darwin)
Bundle complete! 1 Gemfile dependency, 1 gem now installed.

$ grep "content_addressable_test (" Gemfile.lock
    content_addressable_test (0.1.0-arm64-darwin)
  content_addressable_test (0.1.0-arm64-darwin) sha256=326ceb39...

$ ls vendor/bundle/ruby/*/gems
content_addressable_test-0.1.0-arm64-darwin
11. `gem yank` refusal paths (against production)
$ ruby -I$RG/lib $RG/exe/gem yank content_addressable_test -v 0.2.0 --platform arm64-darwin --ruby-abi 3.9; echo "exit=$?"
You are already signed in on RubyGems.org.
Yanking gem from https://rubygems.org...
The version 0.2.0 (arm64-darwin) (Ruby ABI 3.9) does not exist.
exit=0
13. `gem fetch` and `gem dependency --remote`
$ ruby -I$RG/lib $RG/exe/gem fetch content_addressable_test   # Ruby 4.0
Downloaded content_addressable_test-0.1.0-a56253e0
$ ls *.gem
content_addressable_test-0.1.0-a56253e0.gem
$ ruby -I$RG/lib $RG/exe/gem dependency content_addressable_test --remote
   Gem content_addressable_test-0.1.0-arm64-darwin                                                                                  
                                                                                                                                    
   Gem content_addressable_test-0.1.0-796dad80 (Platform: arm64-darwin Ruby ABI: 3.3)                                               
                                                                                                                                    
   Gem content_addressable_test-0.1.0-fe340269 (Platform: x86_64-linux Ruby ABI: 3.3)                                               
                                                                                                                                    
   Gem content_addressable_test-0.1.0-a56253e0 (Platform: arm64-darwin Ruby ABI: 4.0)                                               
                                                                                                                                    
   Gem content_addressable_test-0.2.0-7431c024 (Platform: arm64-darwin Ruby ABI: 3.4)  

@jenshenny
jenshenny marked this pull request as ready for review August 30, 2026 22:15
Comment on lines +12 to +14
def present?
respond_to?(:empty?) ? !empty? : !!self
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the server to not use present?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jenshenny
jenshenny force-pushed the feature-branch-ca-changes-rubygems branch from 467a7a5 to d6fbf45 Compare August 30, 2026 23:06
…al cache

Co-authored-by: Jenny Shen <jenny.shen@shopify.com>
@jenshenny
jenshenny force-pushed the feature-branch-ca-changes-rubygems branch 2 times, most recently from 7ace6cb to 6dc3207 Compare August 30, 2026 23:24
jenshenny and others added 2 commits August 30, 2026 19:28
Gem::Indexer (rubygems-generate_index) loads the V1-only CompactIndex
constants, either from the compact_index gem or from its own embedded
copy, so the artifice's copy of rubygems.org's V2-only implementation
can never share that name safely.

Check in a copy of rubygems.org's lib/compact_index, renamed to the
VendoredCompactIndex namespace, under spec/support/vendor/compact_index,
and load it from the artifice with a plain require. Checking the copy in
(rather than downloading it during the test run) keeps the suite
hermetic and offline, makes the namespace rewrite visible in review, and
lets parallel workers and CI runners that skip the test-deps setup load
it without falling back to the incompatible gem.

Refresh the copy with `rake vendor:compact_index`;
`rake vendor:compact_index_check` fails if the checked-in copy drifts
from the pinned upstream ref.

Assisted-By: devx/8309afec-0948-41c8-a29d-3b1ea27ef32a
@jenshenny
jenshenny force-pushed the feature-branch-ca-changes-rubygems branch from 6dc3207 to f854b32 Compare August 30, 2026 23:28
jenshenny added a commit to rubygems/rubygems.org that referenced this pull request Aug 31, 2026
Replace Object#present? (ActiveSupport) with a plain truthy check on
content_address in CompactIndex::GemVersionMethods, so the vendored
lib/compact_index* files no longer rely on Rails being loaded.

content_address is either nil or a non-empty hex string (the Version
model's CONTENT_ADDRESS_FORMAT validation rejects empty/other values,
allow_nil: true), so a truthy check is equivalent to present? here.

Ref: ruby/rubygems#9773 (comment)
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.

4 participants