Allow pushing CA gems#774
Draft
jenshenny wants to merge 2 commits into
Draft
Conversation
OughtPuts
reviewed
Jun 16, 2026
| # Anything broader (`~> 3.3`, `>= 3.2, < 4.1`, blank, multiple clauses) is a "fat" | ||
| # binary and keeps the classic name-version-platform addressing. | ||
| def content_addressed? | ||
| platformed? && ruby_abi_series.present? |
There was a problem hiding this comment.
Would be better to use the ruby_abi attribute here rather than another call to the ruby_abi_series as ruby_abi should have already been set by before_validation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Today a precompiled gem is "addressed" by
name-version-platform(e.g.sqlite3-2.9.0-x86_64-linux.gem), and a(rubygem, version, platform)triple isglobally unique. To support multiple Ruby versions, maintainers ship one fat
binary that bundles
.sofiles for every Ruby minor (3.2, 3.3, 3.4, …), plusruntime logic to load the right one. That makes gems bigger to download and
harder to maintain.
This change lets maintainers instead push multiple skinny binaries — one
precompiled gem per Ruby minor — addressed by the content hash of the gem
rather than by platform, so they don't collide.
The core rule
Classification is driven entirely by
required_ruby_version:~> X.Y.Z(≥ 3 segments, so it pinsexactly one Ruby minor, e.g.
~> 3.3.0→3.3). These arecontent-addressed:
full_name = name-version-<sha10>. Multiple may coexistfor the same
number + platform, but only one per Ruby minor (noduplicates).
~> 3.3,>= 3.2, < 4.1, blank, multi-clause).Keeps the classic
name-version-platformaddressing. One fat per platform,and it can coexist with skinny binaries.
platform == ruby) are unchanged.What each file does
app/models/version.rb(the heart of it)Version.skinny_ruby_minor(req)— parsesrequired_ruby_version; returns"3.3"only for a clean single~> X.Y.Z, otherwisenil.content_addressed?/ruby_minor_series/content_address— classify aversion and compute its sha-based address.
full_nameify!/gem_full_nameify!— producename-version-<sha10>forskinny binaries, classic names otherwise.
set_ruby_minor(before_validation) — stores the normalized minor in theruby_minorcolumn.platform_and_number_are_unique,gem_platform_and_number_are_unique,unique_canonical_number) now scope onruby_minor, so same-minor duplicates are rejected while different minorscoexist.
app/models/pusher.rbrequired_ruby_versionearly infindso the version can be classifiedand named before validation runs.
valid_platform_attributes?— content-address-aware replacement for the oldfull_name == original_nameintegrity check.existing_conflicting_version— a fat/source push conflicts only with anotherfat/source on the same platform; skinny conflicts are left to the uniqueness
validations.
db/migrate/20260604133906_add_ruby_minor_to_versions.rb+db/schema.rbruby_minorcolumn and extends the twoversionsunique indexes toinclude it. This enforces the rules at the DB level:
""for fat/source keepsone-fat-per-platform; distinct minors allow multiple skinny binaries.
app/views/versions/_version.html.erb+config/locales/en.ymlRuby 3.3/Ruby 3.4badge next to the platform on the gem pageversion list, so two same-platform skinny entries are distinguishable.
Tests + demo
test/integration/pusher_test.rb,test/models/version_test.rb, and updatedmocks in
test/models/pusher_test.rb.setup/content_addressable_push_demo.rb— a development-only smoke-test scriptthat builds skinny/fat gems in-process and pushes them through
Pusher.Verified behavior (pushed to a dev server over HTTP)
~> 3.3.0name-0.0.1-<sha>, Ruby 3.3~> 3.4.0~> 3.3.5(same minor)>= 3.2, < 4.1name-0.0.1-x86_64-linux>= 3.0(2nd fat)Scope
This is the server-side push acceptance + display half of the proposal
(https://gist.github.com/tenderlove/bd600206b3d7e163aa696e7d3b6c535d).
Intentionally out of scope:
/v2/infoendpoint exposingplatform:/libc:requirements.
gems.