Skip to content

Commit d6e0f43

Browse files
committed
Fix Bundler that re-exec $0 when a version is present in the config:
- ### Problem If you have a `version` in your config file (this feature was introduced in #6817), then running any `bundle` command will make Bundler re-exec and ultimately run the `bundle` binstub twice. ### Details When the `bundle` binstub gets executed, a `require "bundler"` is evaluated. RubyGems tries to require the `bundler.rb` file from the right `bundler` gem (in the event where you have multiple bundler versions in your system). RubyGems will prioritize a bundler version based on a few heurisitics. https://github.com/ruby/rubygems/blob/b50c40c92abb00bb172f1579356cc73c242b1849/lib/rubygems/bundler_version_finder.rb#L19-L21 This prioritize logic doesn't take into account the bundler version a user has specific in this config. So what happens is: 1. User execute the `bundle` binstub 2. `require 'bundler'` is evaluated. 3. RubyGems prioritize activating the bundler version specified in the Gemfile.lock 4. The CLI starts, and [Auto switch kicks in](https://github.com/ruby/rubygems/blob/b50c40c92abb00bb172f1579356cc73c242b1849/bundler/lib/bundler/cli.rb#L81). Bundler detects that user specifed a version in its config and the current Bundler version doesn't match. 5. Bundler exit and re-exec with the right bundler version. ### Solution This patch introduce two fixes. First, it reads the bundler config file and check for the local config first and then the global config. This is because the local has precedence over global. Second, the prioritization takes into account the version in config and let RubyGems activate the right version in order to prevent re-exec moments later. Finally, I also want to fix this problem because its a step toward fixing #8106. I'll open a follow up patch to explain.
1 parent e7cb043 commit d6e0f43

3 files changed

Lines changed: 84 additions & 32 deletions

File tree

bundler/spec/runtime/self_management_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,24 @@
171171
expect(out).to eq(previous_minor)
172172
end
173173

174+
it "requires the right bundler version from the config and run bundle CLI without re-exec" do
175+
unless Bundler.rubygems.provides?(">= 4.1.0.dev")
176+
skip "This spec can only run when Gem::BundlerVersionFinder.bundler_versions reads bundler configs"
177+
end
178+
179+
lockfile_bundled_with(current_version)
180+
181+
bundle "config set --local version #{previous_minor}"
182+
bundle "config set --local path.system true"
183+
bundle "install"
184+
185+
script = bundled_app("script.rb")
186+
create_file(script, "p 'executed once'")
187+
188+
bundle "-v", env: { "RUBYOPT" => "-r#{script}" }
189+
expect(out).to eq(%("executed once"\n9.3.0))
190+
end
191+
174192
it "does not try to install when using bundle config version global" do
175193
lockfile_bundled_with(previous_minor)
176194

lib/rubygems/bundler_version_finder.rb

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ def self.bundler_version
1010
v ||= bundle_update_bundler_version
1111
return if v == true
1212

13+
v ||= bundle_config_version
14+
1315
v ||= lockfile_version
1416
return unless v
1517

@@ -49,21 +51,7 @@ def self.lockfile_version
4951
private_class_method :lockfile_version
5052

5153
def self.lockfile_contents
52-
gemfile = ENV["BUNDLE_GEMFILE"]
53-
gemfile = nil if gemfile&.empty?
54-
55-
unless gemfile
56-
begin
57-
Gem::Util.traverse_parents(Dir.pwd) do |directory|
58-
next unless gemfile = Gem::GEM_DEP_FILES.find {|f| File.file?(f) }
59-
60-
gemfile = File.join directory, gemfile
61-
break
62-
end
63-
rescue Errno::ENOENT
64-
return
65-
end
66-
end
54+
gemfile = gemfile_path
6755

6856
return unless gemfile
6957

@@ -82,19 +70,24 @@ def self.lockfile_contents
8270
private_class_method :lockfile_contents
8371

8472
def self.bundle_config_version
85-
config_file = bundler_config_file
86-
return unless config_file && File.file?(config_file)
73+
version = nil
8774

88-
contents = File.read(config_file)
89-
contents =~ /^BUNDLE_VERSION:\s*["']?([^"'\s]+)["']?\s*$/
75+
[bundler_local_config_file, bundler_global_config_file].each do |config_file|
76+
next unless config_file && File.file?(config_file)
9077

91-
$1
78+
contents = File.read(config_file)
79+
contents =~ /^BUNDLE_VERSION:\s*["']?([^"'\s]+)["']?\s*$/
80+
81+
version = $1
82+
break if version
83+
end
84+
85+
version
9286
end
9387
private_class_method :bundle_config_version
9488

95-
def self.bundler_config_file
96-
# see Bundler::Settings#global_config_file and local_config_file
97-
# global
89+
def self.bundler_global_config_file
90+
# see Bundler::Settings#global_config_file
9891
if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty?
9992
ENV["BUNDLE_CONFIG"]
10093
elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty?
@@ -103,10 +96,36 @@ def self.bundler_config_file
10396
ENV["BUNDLE_USER_HOME"] + "config"
10497
elsif Gem.user_home && !Gem.user_home.empty?
10598
Gem.user_home + ".bundle/config"
106-
else
107-
# local
108-
"config"
10999
end
110100
end
111-
private_class_method :bundler_config_file
101+
private_class_method :bundler_global_config_file
102+
103+
def self.bundler_local_config_file
104+
gemfile = gemfile_path
105+
return unless gemfile
106+
107+
File.join(File.dirname(gemfile), ".bundle", "config")
108+
end
109+
private_class_method :bundler_local_config_file
110+
111+
def self.gemfile_path
112+
gemfile = ENV["BUNDLE_GEMFILE"]
113+
gemfile = nil if gemfile&.empty?
114+
115+
unless gemfile
116+
begin
117+
Gem::Util.traverse_parents(Dir.pwd) do |directory|
118+
next unless gemfile = Gem::GEM_DEP_FILES.find {|f| File.file?(f) }
119+
120+
gemfile = File.join directory, gemfile
121+
break
122+
end
123+
rescue Errno::ENOENT
124+
return
125+
end
126+
end
127+
128+
gemfile
129+
end
130+
private_class_method :gemfile_path
112131
end

test/rubygems/test_gem_bundler_version_finder.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def test_bundler_version_with_bundle_config
6666
f.write(config_content)
6767
f.flush
6868

69-
bvf.stub(:bundler_config_file, f.path) do
69+
bvf.stub(:bundler_global_config_file, f.path) do
7070
assert_nil bvf.bundler_version
7171
end
7272
end
@@ -81,7 +81,7 @@ def test_bundler_version_with_bundle_config_single_quoted
8181
f.write(config_with_single_quoted_version)
8282
f.flush
8383

84-
bvf.stub(:bundler_config_file, f.path) do
84+
bvf.stub(:bundler_global_config_file, f.path) do
8585
assert_nil bvf.bundler_version
8686
end
8787
end
@@ -98,18 +98,33 @@ def test_bundler_version_with_bundle_config_version
9898
f.write(config_content)
9999
f.flush
100100

101-
bvf.stub(:bundler_config_file, f.path) do
101+
bvf.stub(:bundler_global_config_file, f.path) do
102102
assert_equal v("1.1.1.1"), bvf.bundler_version
103103
end
104104
end
105105
end
106106

107107
def test_bundler_version_with_bundle_config_non_existent_file
108-
bvf.stub(:bundler_config_file, "/non/existent/path") do
108+
bvf.stub(:bundler_global_config_file, "/non/existent/path") do
109109
assert_nil bvf.bundler_version
110110
end
111111
end
112112

113+
def test_bundler_version_set_on_local_config
114+
config_content = <<~CONFIG
115+
BUNDLE_VERSION: "1.2.3"
116+
CONFIG
117+
118+
Tempfile.create("bundle_config") do |f|
119+
f.write(config_content)
120+
f.flush
121+
122+
bvf.stub(:bundler_local_config_file, f.path) do
123+
assert_equal v("1.2.3"), bvf.bundler_version
124+
end
125+
end
126+
end
127+
113128
def test_bundler_version_with_bundle_config_without_version
114129
config_without_version = <<~CONFIG
115130
BUNDLE_JOBS: "8"
@@ -120,7 +135,7 @@ def test_bundler_version_with_bundle_config_without_version
120135
f.write(config_without_version)
121136
f.flush
122137

123-
bvf.stub(:bundler_config_file, f.path) do
138+
bvf.stub(:bundler_global_config_file, f.path) do
124139
assert_nil bvf.bundler_version
125140
end
126141
end

0 commit comments

Comments
 (0)