Skip to content

Commit 408870e

Browse files
authored
Merge pull request #358 from chadlwilson/improve-gem-path-consistency
1.2.x: Ensure that Rubygems is not unnecessarily initialized and Gem.paths are left in consistent state
2 parents 023672b + 7e0b44b commit 408870e

File tree

7 files changed

+104
-63
lines changed

7 files changed

+104
-63
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
- Add missing block-only signature for debug logging
44
- update (bundled) rack to 2.2.20
5+
- Ensure rack boot process leaves ENV['GEM_PATH'] and Gem.paths in a consistent state
6+
- Remove undocumented and unsafe jruby.rack.env.gem_path = false option (unusable on Bundler 1.6+)
7+
- Fix unintended Rubygems initialization too early in boot process with JRuby 9.4+
58

69
## 1.2.5
710

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,12 @@ as context init parameters in web.xml or as VM-wide system properties.
233233
- `jruby.runtime.env.rubyopt`: This option is used for compatibility with the
234234
(deprecated) `jruby.rack.ignore.env` option since it cleared out the ENV after
235235
RUBYOPT has been processed, by setting it to true ENV['RUBYOPT'] will be kept.
236+
- `jruby.rack.env.gem_path`: If set to `true` (the default) jruby-rack will
237+
ensure ENV['GEM_PATH'] is altered to include the `gem.path` above. If you set it to a
238+
value, this value will be used as GEM_PATH, overriding the environment and
239+
ignoring `gem.path` etc. By setting this option to en empty string the ENV['GEM_PATH'] will
240+
not be modified by jruby-rack at all and will retain its original values implied by
241+
the process environment and `jruby.runtime.env` setting.
236242
- `jruby.rack.logging`: Specify the logging device to use. Defaults to
237243
`servlet_context`. See below.
238244
- `jruby.rack.request.size.initial.bytes`: Initial size for request body memory

src/main/ruby/jruby/rack.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ def context!; context || raise('no context available') end
7070
end
7171
end
7272

73-
# TODO remove require 'jruby/rack/version' from jruby-rack in 1.2
7473
require 'jruby/rack/version' unless defined? JRuby::Rack::VERSION
7574
require 'jruby/rack/helpers'
7675
require 'jruby/rack/booter'

src/main/ruby/jruby/rack/booter.rb

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -104,53 +104,45 @@ def boot!
104104
protected
105105

106106
def adjust_gem_path
107-
gem_path = self.gem_path
107+
desired_gem_path = self.gem_path
108+
108109
case set_gem_path = env_gem_path
109-
when true then
110-
if env_path = ENV['GEM_PATH']
111-
if gem_path.nil? || gem_path.empty?
112-
return # keep ENV['GEM_PATH'] as is
113-
elsif env_path != gem_path
114-
separator = File::PATH_SEPARATOR
115-
unless env_path.split(separator).include?(gem_path)
116-
ENV['GEM_PATH'] = "#{gem_path}#{separator}#{env_path}"
117-
end
118-
end
119-
else
120-
ENV['GEM_PATH'] = gem_path
121-
end
122-
when false then
123-
begin
124-
require 'rubygems' unless defined? Gem.path
125-
rescue LoadError
110+
when true then # default behaviour
111+
if (current_env_gem_path = ENV['GEM_PATH'])
112+
# keep ENV['GEM_PATH'] as is if we have nothing to do
113+
return if desired_gem_path.nil? || desired_gem_path.empty?
114+
return if current_env_gem_path == desired_gem_path
115+
return if current_env_gem_path.split(File::PATH_SEPARATOR).include?(desired_gem_path)
116+
117+
# need to prepend it
118+
ENV['GEM_PATH'] = "#{desired_gem_path}#{File::PATH_SEPARATOR}#{current_env_gem_path}"
126119
else
127-
return if gem_path.nil? || gem_path.empty?
128-
Gem.path.unshift(gem_path) unless Gem.path.include?(gem_path)
120+
ENV['GEM_PATH'] = desired_gem_path
129121
end
130-
return false
131-
when nil then # org.jruby.rack.RackLogger::DEBUG
132-
if gem_path && ! gem_path.empty? &&
133-
( ! defined?(Gem.path) || ! Gem.path.include?(gem_path) )
134-
@rack_context.log("Gem.path won't be updated although seems configured: #{gem_path}")
122+
when nil then
123+
if desired_gem_path && !desired_gem_path.empty? && (!defined?(Gem.path) || !Gem.path.include?(desired_gem_path) )
124+
@rack_context.log("Gem.path won't be updated although seems configured: #{desired_gem_path}")
135125
end
136-
return nil
137-
else # 'jruby.rack.env.gem_path' "forced" to an explicit value
126+
return nil # do nothing to ENV['GEM_PATH']
127+
else # "forced" to an explicit value
138128
ENV['GEM_PATH'] = set_gem_path
139129
end
130+
# Whenever we touch ENV['GEM_PATH`], ensure we clear any cached paths. All other cases should exit early.
131+
Gem.clear_paths if defined?(Gem.clear_paths)
140132
end
141133

142-
# @return whether to update Gem.path and/or the environment GEM_PATH
143-
# - true (default) forces ENV['GEM_PATH'] to be updated due compatibility
144-
# Bundler 1.6 fails to revolve gems correctly when Gem.path is updated
145-
# instead of the ENV['GEM_PATH'] environment variable
146-
# - false disables ENV['GEM_PATH'] mangling for good (updates Gem.path)
134+
# @return whether to update the environment GEM_PATH
135+
# - true (default) forces ENV['GEM_PATH'] to be updated to include the `gem.path` above.
136+
# If you set it to a non-empty value, GEM_PATH will be forced to an explicit value,
137+
# overriding the environment and ignoring `gem.path`, `gem.home` etc.
147138
#
148-
# - if not specified Gem.path will be updated based on setting
139+
# - By setting this option to an empty string the ENV['GEM_PATH'] should not be modified
140+
# at all and will retain its original values implied by the process environment and
141+
# `jruby.runtime.env` setting.
149142
def env_gem_path
150143
gem_path = @rack_context.getInitParameter('jruby.rack.env.gem_path')
151144
return true if gem_path.nil? || gem_path.to_s == 'true'
152-
return false if gem_path.to_s == 'false'
153-
return nil if gem_path.empty? # set to an empty disables mangling
145+
return nil if gem_path.empty? || gem_path.to_s == 'false' # treat false as "don't touch either ENV or Gem.path"
154146
gem_path
155147
end
156148
private :env_gem_path

src/main/ruby/jruby/rack/capture.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55
# See the file LICENSE.txt for details.
66
#++
77

8-
require 'stringio'
9-
108
module JRuby::Rack
119
module Capture
1210
module Base
1311
def output
14-
@output ||= begin; StringIO.new; end
12+
@output ||= begin; require 'stringio' unless defined?(StringIO); StringIO.new; end
1513
end
1614

1715
def capture
@@ -34,6 +32,7 @@ def store
3432
module Exception
3533
def output
3634
@output ||= begin
35+
require 'stringio' unless defined?(StringIO)
3736
io = StringIO.new
3837
io.puts "An exception happened during JRuby-Rack startup", self.to_s
3938
io

src/spec/ruby/jruby/rack/booter_spec.rb

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,24 @@
1717
after(:all) { JRuby::Rack.context = nil }
1818

1919
before do
20-
@rack_env = ENV['RACK_ENV']
21-
@gem_path = Gem.path.to_a
22-
@env_gem_path = ENV['GEM_PATH']
20+
# start clean, in case another test was messing with paths
21+
Gem.clear_paths
22+
@original_rack_env = ENV['RACK_ENV']
23+
@original_gem_path = Gem.path.to_a
24+
@original_env_gem_path = ENV['GEM_PATH']
2325
end
2426

2527
after do
26-
@rack_env.nil? ? ENV.delete('RACK_ENV') : ENV['RACK_ENV'] = @rack_env
27-
Gem.path.replace(@gem_path)
28-
@env_gem_path.nil? ? ENV.delete('GEM_PATH') : ENV['GEM_PATH'] = @env_gem_path
28+
# Ensure everything is reset how it was
29+
@original_rack_env.nil? ? ENV.delete('RACK_ENV') : ENV['RACK_ENV'] = @original_rack_env
30+
@original_env_gem_path.nil? ? ENV.delete('GEM_PATH') : ENV['GEM_PATH'] = @original_env_gem_path
31+
Gem.clear_paths
32+
Gem.path.replace(@original_gem_path)
33+
34+
aggregate_failures("expected Gem.path to be restored after test") do
35+
expect(ENV['GEM_PATH']).to eq @original_env_gem_path
36+
expect(Gem.path).to eql @original_gem_path
37+
end
2938
end
3039

3140
it "should determine the public html root from the 'public.root' init parameter" do
@@ -89,59 +98,90 @@
8998
expect(booter.rack_env).to eq 'production'
9099
end
91100

92-
it "prepends gem_path to Gem.path (when configured to not mangle with ENV)" do
93-
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'false'
94-
Gem.path.replace ['/opt/gems']
95-
booter.gem_path = "wsjar:file:/opt/deploy/sample.war!/WEB-INF/gems"
96-
booter.boot!
97-
98-
expect(Gem.path).to eql ['wsjar:file:/opt/deploy/sample.war!/WEB-INF/gems', '/opt/gems']
99-
end
100-
101101
it "prepends gem_path to Gem.path if not already present" do
102-
Gem.path.replace ["file:/home/gems", "/usr/local/gems"]
102+
ENV['GEM_PATH'] = "file:/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
103+
Gem.clear_paths
104+
103105
booter.gem_path = '/usr/local/gems'
104106
booter.boot!
105107

106-
expect(Gem.path).to eql ["file:/home/gems", "/usr/local/gems"]
108+
expect(Gem.path).to start_with ["file:/home/gems", "/usr/local/gems"]
109+
expect(ENV['GEM_PATH']).to eq "file:/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
107110
end
108111

109112
it "does not change Gem.path if gem_path empty" do
110-
Gem.path.replace ['/opt/gems']
113+
ENV['GEM_PATH'] = '/opt/gems'
114+
Gem.clear_paths
115+
111116
booter.gem_path = ""
112117
booter.boot!
113118

114-
expect(Gem.path).to eql ['/opt/gems']
119+
expect(Gem.path).to start_with ['/opt/gems']
120+
expect(ENV['GEM_PATH']).to eq '/opt/gems'
115121
end
116122

117123
it "prepends gem_path to ENV['GEM_PATH'] if jruby.rack.gem_path set to true" do
118124
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'true'
119125
ENV['GEM_PATH'] = '/opt/gems'
126+
Gem.clear_paths
120127
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF").and_return "/opt/deploy/sample.war!/WEB-INF"
121128
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF/gems").and_return "/opt/deploy/sample.war!/WEB-INF/gems"
122129

123130
booter.boot!
124131

125132
expect(ENV['GEM_PATH']).to eq "/opt/deploy/sample.war!/WEB-INF/gems#{File::PATH_SEPARATOR}/opt/gems"
133+
expect(Gem.path).to start_with ["/opt/deploy/sample.war!/WEB-INF/gems", "/opt/gems"]
126134
end
127135

128136
it "does not prepend gem_path to ENV['GEM_PATH'] if jruby.rack.gem_path set not set" do
129137
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return ''
130138
ENV['GEM_PATH'] = '/opt/gems'
139+
Gem.clear_paths
131140
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF").and_return "/opt/deploy/sample.war!/WEB-INF"
132141
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF/gems").and_return "/opt/deploy/sample.war!/WEB-INF/gems"
133142

134143
booter.boot!
135144

136145
expect(ENV['GEM_PATH']).to eq "/opt/gems"
146+
expect(Gem.path).to start_with ["/opt/gems"]
147+
end
148+
149+
it "does not prepend gem_path to ENV['GEM_PATH'] if jruby.rack.gem_path set to false" do
150+
ENV['GEM_PATH'] = '/opt/gems'
151+
Gem.clear_paths
152+
153+
expect(@rack_context).to receive(:getInitParameter).with("jruby.rack.env.gem_path").and_return 'false'
154+
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF").and_return "/opt/deploy/sample.war!/WEB-INF"
155+
expect(@rack_context).to receive(:getRealPath).with("/WEB-INF/gems").and_return "/opt/deploy/sample.war!/WEB-INF/gems"
156+
157+
booter.boot!
158+
159+
expect(ENV['GEM_PATH']).to eq "/opt/gems"
160+
expect(Gem.path).to start_with ["/opt/gems"]
137161
end
138162

139163
it "prepends gem_path to ENV['GEM_PATH'] if not already present" do
140164
ENV['GEM_PATH'] = "/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
165+
Gem.clear_paths
141166
booter.gem_path = '/usr/local/gems'
142167
booter.boot!
143168

144169
expect(ENV['GEM_PATH']).to eq "/home/gems#{File::PATH_SEPARATOR}/usr/local/gems"
170+
expect(Gem.path).to start_with ["/home/gems", "/usr/local/gems"]
171+
end
172+
173+
it "keeps ENV['GEM_PATH'] when gem_path is nil" do
174+
ENV['GEM_PATH'] = '/usr/local/gems'
175+
Gem.clear_paths
176+
177+
booter.layout = layout = double('layout')
178+
allow(layout).to receive(:app_path).and_return '.'
179+
allow(layout).to receive(:public_path).and_return nil
180+
expect(layout).to receive(:gem_path).and_return nil
181+
booter.boot!
182+
183+
expect(ENV['GEM_PATH']).to eq "/usr/local/gems"
184+
expect(Gem.path).to start_with ["/usr/local/gems"]
145185
end
146186

147187
it "sets ENV['GEM_PATH'] to the value of gem_path if ENV['GEM_PATH'] is not present" do
@@ -153,6 +193,7 @@
153193
booter.boot!
154194

155195
expect(ENV['GEM_PATH']).to eq "/blah/gems"
196+
expect(Gem.path).to start_with ["/blah/gems"]
156197
end
157198

158199
it "creates a logger that writes messages to the servlet context (by default)" do
@@ -214,11 +255,12 @@
214255
# at RUBY.boot!(classpath:/jruby/rack/booter.rb:105)
215256
# at RUBY.(root)(classpath:/jruby/rack/boot/rack.rb:10)
216257
app_dir = "#{File.absolute_path Dir.pwd}/sample.war!/WEB-INF"
217-
allow(File).to receive(:directory?).with(app_dir).and_return true
218258
allow(booter).to receive(:layout).and_return layout = double('layout')
219259
allow(layout).to receive(:app_path).and_return app_dir
220260
allow(layout).to receive(:gem_path)
221261
allow(layout).to receive(:public_path)
262+
allow(File).to receive(:directory?).and_wrap_original { |m, *args| m.call(*args) }
263+
expect(File).to receive(:directory?).with(app_dir).and_return true
222264

223265
booter.boot! # expect to_not raise_error
224266
end

src/spec/ruby/jruby/rack/rails_booter_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@
3030
end
3131

3232
before do
33-
@rails_env = ENV['RAILS_ENV']
34-
@rack_env = ENV['RACK_ENV']
33+
@original_rails_env = ENV['RAILS_ENV']
34+
@original_rack_env = ENV['RACK_ENV']
3535
end
3636

3737
after do
38-
@rails_env.nil? ? ENV.delete('RAILS_ENV') : ENV['RAILS_ENV'] = @rails_env
39-
@rack_env.nil? ? ENV.delete('RACK_ENV') : ENV['RACK_ENV'] = @rack_env
38+
@original_rails_env.nil? ? ENV.delete('RAILS_ENV') : ENV['RAILS_ENV'] = @original_rails_env
39+
@original_rack_env.nil? ? ENV.delete('RACK_ENV') : ENV['RACK_ENV'] = @original_rack_env
4040
end
4141

4242
it "should default rails path to /WEB-INF" do

0 commit comments

Comments
 (0)