Skip to content

Flatten the Bundler layout to the repository top level#9634

Merged
hsbt merged 9 commits into
masterfrom
claude/exciting-wescoff-37e23d
Jun 24, 2026
Merged

Flatten the Bundler layout to the repository top level#9634
hsbt merged 9 commits into
masterfrom
claude/exciting-wescoff-37e23d

Conversation

@hsbt

@hsbt hsbt commented Jun 23, 2026

Copy link
Copy Markdown
Member

This is the first phase of converging RubyGems and Bundler on a single layout. Bundler currently lives under bundler/ with its own lib, exe, gemspec, and docs, while ruby/ruby already vendors Bundler flattened at the repository top level. The two trees have to be reconciled by hand on every sync. Moving Bundler's runtime tree, executables, gemspec, and docs up to the root removes that divergence and lets a later phase share code directly between the two products.

Bundler is entirely require_relative based, so the source move itself is mechanical. The rest of the change repoints what referenced the old paths. That covers the gemspec file list and metadata URIs, the .rspec and .rubocop load paths, the Rakefile man and gem-build tasks, gem update --system, the automatiek vendoring config, the ruby-core sync shim, and the CI workflows. The docs keep distinct names at the root, CHANGELOG-bundler.md and friends, so they do not collide with the RubyGems ones.

Flattening puts Bundler on $LOAD_PATH next to RubyGems, so a few specs that assumed a separately installed Bundler are adjusted to match the flat layout.

@hsbt hsbt force-pushed the claude/exciting-wescoff-37e23d branch from 77ba7e9 to 49c0360 Compare June 23, 2026 05:32
@Edouard-chin

Edouard-chin commented Jun 23, 2026

Copy link
Copy Markdown
Member

Thanks for working on this @hsbt . I don't know if it's a problem in my environment, but I can no longer run rake spec:deps on this branch. Confirmed that this problem doesn't occur on master.

This is the error I get:

Backtrace
NameError: undefined method 'spec=' for class 'Gem::Package::Old' (NameError)

  undef_method :spec=
  ^^^^^^^^^^^^
/Users/edouard/src/opensource/rubygems/lib/rubygems/package/old.rb:16:in 'Module#undef_method'
/Users/edouard/src/opensource/rubygems/lib/rubygems/package/old.rb:16:in '<class:Old>'
/Users/edouard/src/opensource/rubygems/lib/rubygems/package/old.rb:15:in '<top (required)>'
/Users/edouard/src/opensource/rubygems/lib/rubygems/package.rb:765:in 'Kernel#require_relative'

I also get a ton of "previous definition" warning:

Backtrace ....

Previous definition of HighSecurity was here
/Users/edouard/src/opensource/rubygems/lib/rubygems/security/policies.rb:93: warning: already initialized constant Gem::Security::SigningPolicy
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby/3.4.0/rubygems/security/policies.rb:93: warning: previous definition of SigningPolicy was here
/Users/edouard/src/opensource/rubygems/lib/rubygems/security/policies.rb:106: warning: already initialized constant Gem::Security::Policies
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby/3.4.0/rubygems/security/policies.rb:106: warning: previous definition of Policies was here
/Users/edouard/src/opensource/rubygems/lib/rubygems/security/trust_dir.rb:11: warning: already initialized constant Gem::Security::TrustDir::DEFAULT_PERMISSIONS
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby/3.4.0/rubygems/security/trust_dir.rb:11: warning: previous definition of DEFAULT_PERMISSIONS was here
/Users/edouard/src/opensource/rubygems/lib/rubygems/security/signer.rb:37: warning: already initialized constant Gem::Security::Signer::DEFAULT_OPTIONS
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby/3.4.0/rubygems/security/signer.rb:37: warning: previous definition of DEFAULT_OPTIONS was here


From what I'm seeing, it seems to be related to automatically adding rubygems/lib to the load path and ultimately requiring files from the rubygems source code that were already loaded from the installed rubygems on my system.

  1. I run rake spec:deps
  2. This file is required, and down the line ends up requiring package/old.rb. $LOAD_PATH at this point doesn't include <RUBYGEMS_SOURCE>/rubygems/lib, so the file being required is from system rubygems.
    require "rubygems/package_task"
  3. $LOAD_PATH gets modified and now includes <RUBYGEMS_SOURCE>/rubygems/lib
  4. This codepath is required
    require "rubygems/installer"
  5. That requires rubygems/installer from the local repo instead of system gems.
  6. This ends calling a require_relative and so we end up requiring the same files that we already required from system rubygems
    require_relative "package"

My $LOAD_PATH on master when 4. is called looks like this

/Users/edouard/src/opensource/rubygems/bundler/lib <----
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/did_you_mean-2.0.0/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/rake-13.4.2/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/openssl-3.3.0/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/openssl-3.3.0
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/ipaddr-1.2.7/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/tsort-0.2.0/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/rdoc-7.0.3/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/shellwords-0.2.2/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/psych-5.3.1/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/psych-5.3.1
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/date-3.5.1/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/date-3.5.1
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/zlib-3.2.1/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/zlib-3.2.1
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/stringio-3.2.0/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/stringio-3.2.0
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby/3.4.0
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby/3.4.0/arm64-darwin25
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/vendor_ruby/3.4.0
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/vendor_ruby/3.4.0/arm64-darwin25
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/vendor_ruby
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/3.4.0
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/3.4.0/arm64-darwin25

On this branch, my $LOAD_PATH looks like this:

/Users/edouard/src/opensource/rubygems/lib <------
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/did_you_mean-2.0.0/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/rake-13.4.2/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/openssl-3.3.0/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/openssl-3.3.0
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/ipaddr-1.2.7/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/tsort-0.2.0/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/rdoc-7.0.3/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/shellwords-0.2.2/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/psych-5.3.1/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/psych-5.3.1
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/date-3.5.1/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/date-3.5.1
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/zlib-3.2.1/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/zlib-3.2.1
/Users/edouard/.gem/ruby/ruby-3.4.7/gems/stringio-3.2.0/lib
/Users/edouard/.gem/ruby/ruby-3.4.7/extensions/arm64-darwin-25/3.4.0-static/stringio-3.2.0
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby/3.4.0
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby/3.4.0/arm64-darwin25
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/site_ruby
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/vendor_ruby/3.4.0
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/vendor_ruby/3.4.0/arm64-darwin25
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/vendor_ruby
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/3.4.0
/Users/edouard/.rubies/ruby-3.4.7/lib/ruby/3.4.0/arm64-darwin25

@hsbt hsbt force-pushed the claude/exciting-wescoff-37e23d branch from 49c0360 to a875111 Compare June 24, 2026 00:14
hsbt added 9 commits June 24, 2026 09:23
Flatten bundler/lib, bundler/exe and bundler/bundler.gemspec into the
repository's top-level lib/, exe/ and bundler.gemspec to converge on the
flat layout ruby/ruby already uses. Bundler is fully require_relative
based, so the source move is mechanical and updates only the load-path
references in .rspec, .rubocop.yml, Rakefile, the spec path helper and
the CI workflows.

The Bundler docs stay under bundler/ for now and move in a later commit.
setup_command builds and installs the default Bundler gem from the
source tree. With Bundler flattened into the top-level lib/ and
bundler.gemspec, the separate "bundler/lib" library and the chdir into
bundler/ are gone: the single lib/ install now carries both RubyGems and
Bundler, and the gem is built straight from the top-level gemspec.
Move bundler/{CHANGELOG,LICENSE,README}.md to the top level as
CHANGELOG-bundler.md, LICENSE-bundler.md and README-bundler.md so they
no longer collide with the RubyGems documents while keeping the
CHANGELOG*/LICENSE*/README* prefixes that license scanners rely on.
bundler/.document is dropped since lib/bundler/.document already excludes
the runtime tree from RDoc, leaving bundler/ empty.

The single-document direction stays for a later docs merge; this only
de-collides the file names.
The bundler/ subtree is now empty, so drop the dead `bundler/...` arm of
the ProjectFiles exclude and regenerate Manifest.txt. The bundler files
that rubygems-update ships move from bundler/lib, bundler/exe and
bundler/*.md to their top-level locations; the file count is unchanged.
Update the Bundler vendor_lib targets and the corresponding patch
headers from bundler/lib/bundler/vendor to lib/bundler/vendor. The
Bundler:: namespace prefixes are untouched; unifying them under Gem:: is
a later phase.
ruby/ruby's sync_default_gems.rb still copies Bundler sources from
bundler/lib, bundler/exe and bundler/bundler.gemspec. Rewrite those
source paths to the new top-level locations so the ruby-core sync keeps
finding them; the ruby/ruby-side destinations are unchanged.
Catch the leftover bundler/lib and bundler/exe references after the move:
the dev binstubs (bin/mdl, bin/rubocop, bin/test-unit), .gitattributes,
.codespellrc, the release tool version file, the rubygems_ext/ci_detector
cross-reference NOTE comments, and the developer documentation.
After flattening, Bundler shares lib/ with RubyGems, so spec subprocesses
load the in-development Bundler from $LOAD_PATH instead of an installed
gem, the same way ruby-core already does. Four examples assumed a
separately installed Bundler and need updating.

The load-order example pre-activates bundler unconditionally (was
ruby_core? only) so Bundler.setup does not append the default bundler gem.

"does not reveal system gems" relied on bundler showing up in
installed_specs as an installed gem; flattened, bundler loads from
$LOAD_PATH and no longer appears there. Assert the example's actual intent
instead: the bundled gem stays visible and the system gem stays hidden
across Gem.refresh.

"bundle update --bundler" used `the_bundle.include_gems`, whose check
runs `ruby -e "require 'bundler'; Bundler.setup"`; the in-development
Bundler on $LOAD_PATH wins and its setup mismatches the locked version,
so both the bundler and myrack checks fail. Verify the resolved versions
through the binstub instead (`bundle --version` and `bundle list`), where
the self-manager activates the locked Bundler before reading the bundle.

"shows culprit file and line" double-loaded vendored Thor because the
system binstub activated the installed Bundler while $LOAD_PATH carried
the in-development one. Run it through the dev binstub so Bundler resolves
from a single place.
After flattening, lib/ holds both RubyGems and Bundler, so any dev binstub
that puts the worktree lib/ on $LOAD_PATH now overlays the already-booted
system RubyGems with the worktree one. bin/bundle hit this through the
Bundler gemspec activation: the partial overlay mixed a worktree Gem with
an older system Gem::ConfigFile, breaking native extension installs on Ruby
3.2/3.3 (undefined install_extension_in_lib) and corrupting spec names
elsewhere. The same overlay breaks bin/rake, bin/ronn, bin/rubocop and
bin/mdl, which unshift the worktree lib/ (or require it through
rubygems_ext) but boot on the system RubyGems. On a host whose RubyGems
differs from the worktree copy this double-loads files like rubygems/package
and raises (undefined method `spec=' for class Gem::Package::Old).

Route all of them through switch_rubygems, like bin/rspec and bin/test-unit
already do, so they re-exec onto the worktree RubyGems before anything is
loaded. Point the shared gem home job at the worktree copy too, since the
dev binstub can no longer load a separately installed RubyGems, and the
matrix is redundant for now.
@hsbt hsbt force-pushed the claude/exciting-wescoff-37e23d branch from a875111 to f50eb42 Compare June 24, 2026 00:23
@hsbt

hsbt commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

@Edouard-chin This only reproduces in a clean environment where the gems aren't installed yet, so it slipped past CI and my local environment.

I've now applied the same switch_rubygems handling I added to the dev_undle binstub to the other binstubs (bin/rake, bin/ronn, bin/rubocop, bin/mdl), so it should be fixed now. Thanks for the report 🙏

@Edouard-chin

Copy link
Copy Markdown
Member

Confirmed it works with bin/rake , thanks !

Do you think we could add this require_relative "spec/support/switch_rubygems" to the Rakefile directly ? It would make things work with just rake. I tried and it seems to work for me.

@hsbt

hsbt commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

Good point. Some of these are also invoked outside of rake (bin/rubocop and bin/mdl), so they can't all be folded into a single entry point and need to be looked at individually.

I'd like to keep this PR focused, so let me revisit consolidating the switch as a follow-up after it merges.

@hsbt hsbt merged commit 4180751 into master Jun 24, 2026
110 checks passed
@hsbt hsbt deleted the claude/exciting-wescoff-37e23d branch June 24, 2026 03:58
hsbt added a commit to hsbt/ruby that referenced this pull request Jun 24, 2026
ruby/rubygems flattened the Bundler tree from bundler/ up to the
repository root, so repoint the rubygems mappings accordingly. Guard the
gemspec fixup with File.exist? too, since the synthetic parent tree built
for the flattening commit has no lib/bundler/bundler.gemspec yet.

ruby/rubygems#9634

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hsbt added a commit to ruby/ruby that referenced this pull request Jun 24, 2026
ruby/rubygems flattened the Bundler tree from bundler/ up to the
repository root, so repoint the rubygems mappings accordingly. Guard the
gemspec fixup with File.exist? too, since the synthetic parent tree built
for the flattening commit has no lib/bundler/bundler.gemspec yet.

ruby/rubygems#9634

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants