Skip to content

Scope CA plugin stubs by Ruby ABI - #192

Merged
girachawda merged 1 commit into
feature-branch-ca-changes-rubygemsfrom
gc/ca-plugin-abi-stubs
Sep 1, 2026
Merged

Scope CA plugin stubs by Ruby ABI#192
girachawda merged 1 commit into
feature-branch-ca-changes-rubygemsfrom
gc/ca-plugin-abi-stubs

Conversation

@girachawda

@girachawda girachawda commented Sep 1, 2026

Copy link
Copy Markdown

What

This updates RubyGems plugin stub handling for content-addressed (CA/skinny) gems so ABI-specific plugin stubs are installed under an ABI-scoped plugin directory.

A regular non-CA gem continues to write its generated plugin stub to:

plugins/foo_plugin.rb

A CA gem for Ruby ABI 3.4 now writes its generated plugin stub to:

plugins/3.4/foo_plugin.rb

Gem.load_plugins now checks both the root plugin directory and the current Ruby ABI plugin directory:

plugins/*.rb
plugins/<current_ruby_abi>/*.rb

When RubyGems regenerates a plugin stub, it also removes stale same-gem stubs from the other plugin location. That means installing a CA gem removes an older root stub for that gem, and installing a non-CA gem removes older ABI-scoped stubs for that gem.

Why

CA/skinny gems can be Ruby ABI-specific, but plugin loading currently is not.

RubyGems loads plugin stubs by scanning plugins/*.rb. If a CA gem for Ruby 3.4 installs plugins/foo_plugin.rb, then any older Ruby sharing that GEM_HOME can load and execute that ABI-specific plugin code, even if the gem should not apply to that Ruby.

Installing CA plugin stubs under plugins/<ruby_abi>/ keeps them invisible to older RubyGems, while new RubyGems can explicitly load only the current Ruby ABI’s plugin stubs.

The cleanup is needed so plugin loading follows the gem RubyGems selected when regenerating plugins, instead of leaving an older root or ABI-scoped stub behind for the same gem name.

Tophat

Script
set -euo pipefail

repo=$(pwd)
tmpdir=$(mktemp -d "$repo/tmp/ca_plugin_tophat_run.XXXXXX")
gem_home="$tmpdir/gem_home"
ca_workdir="$tmpdir/ca_work"
fat_workdir="$tmpdir/fat_work"
marker="$tmpdir/plugin_marker.txt"

mkdir -p "$gem_home/plugins" "$ca_workdir/lib" "$fat_workdir/lib"

ruby_abi=$(ruby -Ilib -e 'print Gem.ruby_abi')

cat > "$ca_workdir/lib/rubygems_plugin.rb" <<'RUBY'
File.open(ENV.fetch("PLUGIN_MARKER"), "a") {|f| f.puts "ca-1.0" }
RUBY

cat > "$ca_workdir/ca_plugin_demo.gemspec" <<'RUBY'
Gem::Specification.new do |s|
  s.name = "ca_plugin_demo"
  s.version = "1.0.0"
  s.summary = "CA plugin tophat"
  s.authors = ["RubyGems"]
  s.files = ["lib/rubygems_plugin.rb"]
  s.platform = "x86_64-linux"
end
RUBY

# Simulate an old/root plugin stub for the same gem. Installing the CA gem
# should replace it with an ABI-scoped stub.
cat > "$gem_home/plugins/ca_plugin_demo_plugin.rb" <<'RUBY'
File.open(ENV.fetch("PLUGIN_MARKER"), "a") {|f| f.puts "root-old" }
RUBY

(
  cd "$ca_workdir"
  ruby -I"$repo/lib" "$repo/exe/gem" build ca_plugin_demo.gemspec --ruby-abi "$ruby_abi" >/dev/null
  ca_gem=$(ls ca_plugin_demo-1.0.0-*.gem)
  ruby -I"$repo/lib" "$repo/exe/gem" install --local "$ca_gem" --install-dir "$gem_home" --force --no-document >/dev/null
)

abi_stub="$gem_home/plugins/$ruby_abi/ca_plugin_demo_plugin.rb"
root_stub="$gem_home/plugins/ca_plugin_demo_plugin.rb"

test -f "$abi_stub"
test ! -f "$root_stub"

PLUGIN_MARKER="$marker" GEM_HOME="$gem_home" GEM_PATH="$gem_home" ruby -I"$repo/lib" -rrubygems -e '
  Gem.use_paths ENV.fetch("GEM_HOME"), ENV.fetch("GEM_PATH")
  Gem.load_plugins
'

loaded=$(cat "$marker")
if [ "$loaded" != "ca-1.0" ]; then
  echo "Expected only CA plugin to load after CA install, got:" >&2
  cat "$marker" >&2
  exit 1
fi

cat > "$fat_workdir/lib/rubygems_plugin.rb" <<'RUBY'
File.open(ENV.fetch("PLUGIN_MARKER"), "a") {|f| f.puts "fat-2.0" }
RUBY

cat > "$fat_workdir/ca_plugin_demo.gemspec" <<'RUBY'
Gem::Specification.new do |s|
  s.name = "ca_plugin_demo"
  s.version = "2.0.0"
  s.summary = "Fat plugin tophat"
  s.authors = ["RubyGems"]
  s.files = ["lib/rubygems_plugin.rb"]
end
RUBY

(
  cd "$fat_workdir"
  ruby -I"$repo/lib" "$repo/exe/gem" build ca_plugin_demo.gemspec >/dev/null
  ruby -I"$repo/lib" "$repo/exe/gem" install --local ca_plugin_demo-2.0.0.gem --install-dir "$gem_home" --force --no-document >/dev/null
)

test -f "$root_stub"
test ! -f "$abi_stub"

: > "$marker"
PLUGIN_MARKER="$marker" GEM_HOME="$gem_home" GEM_PATH="$gem_home" ruby -I"$repo/lib" -rrubygems -e '
  Gem.use_paths ENV.fetch("GEM_HOME"), ENV.fetch("GEM_PATH")
  Gem.load_plugins
'

loaded=$(cat "$marker")
if [ "$loaded" != "fat-2.0" ]; then
  echo "Expected only fat plugin to load after fat install, got:" >&2
  cat "$marker" >&2
  exit 1
fi

echo "PASS: CA plugin stub is ABI-scoped, and later fat install replaces stale ABI stub"
echo "tmpdir: $tmpdir"
Output
PASS: CA plugin stub is ABI-scoped, and later fat install replaces stale ABI stub
tmpdir: /Users/girachawda/src/github.com/Shopify/rubygems/tmp/ca_plugin_tophat_run.mVYqoM

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This pull request updates RubyGems’ plugin stub install and load behavior so content-addressed (CA/skinny) gems write plugin stubs under an ABI-scoped plugin directory (e.g., plugins/3.4/), while preserving the existing root plugins/ stubs as a fat/non-CA fallback. Gem.load_plugins is updated to load both locations and to prefer ABI-scoped stubs when duplicates exist.

Changes:

  • Install CA gem plugin stubs under plugins/<ruby_abi>/ and generate require_relative paths relative to that ABI directory.
  • Update Gem.load_plugins to scan both plugins/ and plugins/<current_ruby_abi>/, preferring ABI-scoped stubs when the same stub filename exists in both.
  • Add test coverage for ABI-preference plugin loading and ABI-scoped stub generation/removal for CA gems.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
test/rubygems/test_gem.rb Adds a regression test ensuring ABI-scoped plugin stubs shadow same-named root stubs and unrelated ABIs are ignored.
test/rubygems/test_gem_installer.rb Adds installer tests ensuring CA plugin stubs are written/removed under plugins/<ruby_abi>/ and still allow a root fallback stub to coexist.
lib/rubygems/installer_uninstaller_utils.rb Routes CA plugin stub generation/removal to an ABI-scoped plugin directory and fixes require_relative computation accordingly.
lib/rubygems.rb Updates plugin discovery/loading to include ABI-scoped stubs and prefer ABI-specific stubs over root when both exist.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@jenshenny jenshenny left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great work!! Just a few minor details I wanted to raise

Comment thread lib/rubygems.rb Outdated
Comment thread test/rubygems/test_gem_installer.rb Outdated
Comment thread test/rubygems/test_gem.rb Outdated
Comment thread lib/rubygems.rb Outdated
@girachawda
girachawda force-pushed the gc/ca-plugin-abi-stubs branch from 8381383 to 60b4d97 Compare September 1, 2026 18:56
@girachawda
girachawda requested a review from jenshenny September 1, 2026 19:24
@girachawda
girachawda force-pushed the gc/ca-plugin-abi-stubs branch from 60b4d97 to f1cae41 Compare September 1, 2026 19:24
Comment thread lib/rubygems.rb
Comment thread lib/rubygems/installer_uninstaller_utils.rb
@girachawda
girachawda force-pushed the gc/ca-plugin-abi-stubs branch from f1cae41 to deeff6a Compare September 1, 2026 20:04
Assisted-By: devx/7307f7b7-fac2-4c30-9651-f8ce7643f65c
@girachawda
girachawda force-pushed the gc/ca-plugin-abi-stubs branch from deeff6a to 51ba7c2 Compare September 1, 2026 20:27
@girachawda
girachawda requested a review from jenshenny September 1, 2026 20:34
@girachawda
girachawda merged commit 1821756 into feature-branch-ca-changes-rubygems Sep 1, 2026
106 checks passed
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.

3 participants