diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index ad4aaab384a0..e6c3c7d0de2a 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -355,16 +355,30 @@ def install_default_bundler_gem(bin_dir) loaded_from = current_default_spec.loaded_from File.delete(loaded_from) - # Remove previous default gem executables if they were not shadowed by a regular gem - FileUtils.rm_rf current_default_spec.full_gem_path if all_specs_current_version.size == 1 + previous_specs_dir = File.dirname(loaded_from) + + # Remove previous default gem executables if they were not shadowed by a + # regular gem. They live under the same root as the previous default + # gemspec, which is not necessarily default_dir. + if all_specs_current_version.size == 1 + previous_root = File.dirname(File.dirname(previous_specs_dir)) + FileUtils.rm_rf File.join(previous_root, "gems", current_default_spec.full_name) + end - File.dirname(loaded_from) + previous_specs_dir else target_specs_dir = File.join(default_dir, "specifications", "default") mkdir_p target_specs_dir, mode: 0o755 target_specs_dir end + # Root directory that specs_dir belongs to. It may differ from default_dir + # when Gem.default_specifications_dir is customized to live under a + # different root, like Homebrew does. Executables must be extracted under + # the same root as the default gemspec, since that's where activation of + # the default gem will look for them. + bundler_install_dir = File.dirname(File.dirname(specs_dir)) + new_bundler_spec = Gem::Specification.load("bundler.gemspec") full_name = new_bundler_spec.full_name gemspec_path = "#{full_name}.gemspec" @@ -372,19 +386,20 @@ def install_default_bundler_gem(bin_dir) default_spec_path = File.join(specs_dir, gemspec_path) Gem.write_binary(default_spec_path, new_bundler_spec.to_ruby) - bundler_spec = Gem::Specification.load(default_spec_path) - # Remove gemspec that was same version of vendored bundler. normal_gemspec = File.join(default_dir, "specifications", gemspec_path) if File.file? normal_gemspec File.delete normal_gemspec end - # Remove gem files that were same version of vendored bundler. - if File.directory? bundler_spec.gems_dir - Dir.entries(bundler_spec.gems_dir). - select {|default_gem| File.basename(default_gem) == full_name }. - each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) } + # Remove gem files that were same version of vendored bundler. A regular + # gem lives under default_dir, which is not necessarily the same root as + # the default gemspec. + normal_gems_dir = File.join(default_dir, "gems") + if File.directory? normal_gems_dir + Dir.entries(normal_gems_dir). + select {|normal_gem| File.basename(normal_gem) == full_name }. + each {|normal_gem| rm_r File.join(normal_gems_dir, normal_gem) } end require_relative "../installer" @@ -397,14 +412,14 @@ def install_default_bundler_gem(bin_dir) format_executable: options[:format_executable], force: options[:force], bin_dir: bin_dir, - install_dir: default_dir, + install_dir: bundler_install_dir, wrappers: true ) - # We need to install only executable and default spec files. - # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory. + # We only need to install the executables here. The default spec was + # already written above, and lib/bundler.rb and lib/bundler/* are + # available under the site_ruby directory. installer.extract_bin installer.generate_bin - installer.write_default_spec ensure FileUtils.rm_f built_gem end diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index 7df59ecfc6db..ba410d64e635 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -241,17 +241,44 @@ def test_install_default_bundler_gem def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_dir @cmd.extend FileUtils + # Simulate Homebrew's layout, where Gem.default_dir is moved to a + # different root, while Gem.default_specifications_dir stays inside the + # ruby install tree. gemhome2 = File.join(@tempdir, "gemhome2") Gem.instance_variable_set(:@default_dir, gemhome2) FileUtils.mkdir_p gemhome2 bin_dir = File.join(gemhome2, "bin") + previous_default_gem_dir = File.join(@gemhome, "gems", "bundler-1.15.4") + write_file File.join(previous_default_gem_dir, "exe", "bundle") + + # A regular gem of the same version installed at the default dir, which is + # promoted to the default gem and must be removed. Its files live under + # default_dir, not under the default gemspec root. + normal_gem_dir = File.join(gemhome2, "gems", "bundler-#{bundler_version}") + write_file File.join(normal_gem_dir, "lib", "bundler.rb") + @cmd.install_default_bundler_gem bin_dir # expect to remove other versions of bundler gemspecs on default specification directory. assert_path_not_exist previous_bundler_specification_path assert_path_exist new_bundler_specification_path + + # expect to remove the previous default version executables, which live + # next to the removed gemspec, not under the new default dir. + assert_path_not_exist previous_default_gem_dir + + # expect to remove the promoted regular gem's files under default_dir. + assert_path_not_exist normal_gem_dir + + # expect executables to be installed under the same root as the default + # gemspec, since that's where activation of the default gem looks for them. + spec = Gem::Specification.load(new_bundler_specification_path) + spec.executables.each do |e| + assert_path_exist File.join(@gemhome, "gems", spec.full_name, spec.bindir, e) + end + assert_path_not_exist File.join(gemhome2, "gems", spec.full_name) end def test_install_default_bundler_gem_with_force_flag