bootsnap 1.6.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative('../explicit_require')
3
+ require_relative("../explicit_require")
4
4
 
5
5
  module Bootsnap
6
6
  module LoadPathCache
@@ -10,8 +10,8 @@ module Bootsnap
10
10
  def initialize(store, path_obj, development_mode: false)
11
11
  @development_mode = development_mode
12
12
  @store = store
13
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
14
- @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
13
+ @mutex = Mutex.new
14
+ @path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
15
15
  @has_relative_paths = nil
16
16
  reinitialize
17
17
  end
@@ -28,15 +28,16 @@ module Bootsnap
28
28
  BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
29
29
  # Builtin features are of the form 'enumerator.so'.
30
30
  # All others include paths.
31
- next unless feat.size < 20 && !feat.include?('/')
31
+ next unless feat.size < 20 && !feat.include?("/")
32
32
 
33
- base = File.basename(feat, '.*') # enumerator.so -> enumerator
33
+ base = File.basename(feat, ".*") # enumerator.so -> enumerator
34
34
  ext = File.extname(feat) # .so
35
35
 
36
36
  features[feat] = nil # enumerator.so
37
37
  features[base] = nil # enumerator
38
38
 
39
39
  next unless [DOT_SO, *DL_EXTENSIONS].include?(ext)
40
+
40
41
  DL_EXTENSIONS.each do |dl_ext|
41
42
  features["#{base}#{dl_ext}"] = nil # enumerator.bundle
42
43
  end
@@ -47,8 +48,13 @@ module Bootsnap
47
48
  def find(feature)
48
49
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
49
50
  feature = feature.to_s.freeze
50
- return feature if absolute_path?(feature)
51
- return expand_path(feature) if feature.start_with?('./')
51
+
52
+ return feature if Bootsnap.absolute_path?(feature)
53
+
54
+ if feature.start_with?("./", "../")
55
+ return expand_path(feature)
56
+ end
57
+
52
58
  @mutex.synchronize do
53
59
  x = search_index(feature)
54
60
  return x if x
@@ -58,7 +64,7 @@ module Bootsnap
58
64
  # returns false as if it were already loaded; however, there is no
59
65
  # file to find on disk. We've pre-built a list of these, and we
60
66
  # return false if any of them is loaded.
61
- raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
67
+ return false if BUILTIN_FEATURES.key?(feature)
62
68
 
63
69
  # The feature wasn't found on our preliminary search through the index.
64
70
  # We resolve this differently depending on what the extension was.
@@ -67,13 +73,14 @@ module Bootsnap
67
73
  # native dynamic extension, e.g. .bundle or .so), we know it was a
68
74
  # failure and there's nothing more we can do to find the file.
69
75
  # no extension, .rb, (.bundle or .so)
70
- when '', *CACHED_EXTENSIONS
76
+ when "", *CACHED_EXTENSIONS
71
77
  nil
72
78
  # Ruby allows specifying native extensions as '.so' even when DLEXT
73
79
  # is '.bundle'. This is where we handle that case.
74
80
  when DOT_SO
75
81
  x = search_index(feature[0..-4] + DLEXT)
76
82
  return x if x
83
+
77
84
  if DLEXT2
78
85
  x = search_index(feature[0..-4] + DLEXT2)
79
86
  return x if x
@@ -81,7 +88,7 @@ module Bootsnap
81
88
  else
82
89
  # other, unknown extension. For example, `.rake`. Since we haven't
83
90
  # cached these, we legitimately need to run the load path search.
84
- raise(LoadPathCache::FallbackScan, '', [])
91
+ return FALLBACK_SCAN
85
92
  end
86
93
  end
87
94
 
@@ -89,33 +96,25 @@ module Bootsnap
89
96
  # cases where the file doesn't appear to be on the load path. We should
90
97
  # be able to detect newly-created files without rebooting the
91
98
  # application.
92
- raise(LoadPathCache::FallbackScan, '', []) if @development_mode
93
- end
94
-
95
- if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
96
- def absolute_path?(path)
97
- path[1] == ':'
98
- end
99
- else
100
- def absolute_path?(path)
101
- path.start_with?(SLASH)
102
- end
99
+ return FALLBACK_SCAN if @development_mode
103
100
  end
104
101
 
105
102
  def unshift_paths(sender, *paths)
106
103
  return unless sender == @path_obj
104
+
107
105
  @mutex.synchronize { unshift_paths_locked(*paths) }
108
106
  end
109
107
 
110
108
  def push_paths(sender, *paths)
111
109
  return unless sender == @path_obj
110
+
112
111
  @mutex.synchronize { push_paths_locked(*paths) }
113
112
  end
114
113
 
115
114
  def reinitialize(path_obj = @path_obj)
116
115
  @mutex.synchronize do
117
116
  @path_obj = path_obj
118
- ChangeObserver.register(self, @path_obj)
117
+ ChangeObserver.register(@path_obj, self)
119
118
  @index = {}
120
119
  @dirs = {}
121
120
  @generated_at = now
@@ -141,6 +140,9 @@ module Bootsnap
141
140
  p = Path.new(path)
142
141
  @has_relative_paths = true if p.relative?
143
142
  next if p.non_directory?
143
+
144
+ p = p.to_realpath
145
+
144
146
  expanded_path = p.expanded_path
145
147
  entries, dirs = p.entries_and_dirs(@store)
146
148
  # push -> low precedence -> set only if unset
@@ -155,6 +157,9 @@ module Bootsnap
155
157
  paths.map(&:to_s).reverse_each do |path|
156
158
  p = Path.new(path)
157
159
  next if p.non_directory?
160
+
161
+ p = p.to_realpath
162
+
158
163
  expanded_path = p.expanded_path
159
164
  entries, dirs = p.entries_and_dirs(@store)
160
165
  # unshift -> high precedence -> unconditional set
@@ -177,31 +182,54 @@ module Bootsnap
177
182
  end
178
183
 
179
184
  if DLEXT2
180
- def search_index(f)
181
- try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
185
+ def search_index(feature)
186
+ try_index(feature + DOT_RB) ||
187
+ try_index(feature + DLEXT) ||
188
+ try_index(feature + DLEXT2) ||
189
+ try_index(feature)
182
190
  end
183
191
 
184
- def maybe_append_extension(f)
185
- try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
192
+ def maybe_append_extension(feature)
193
+ try_ext(feature + DOT_RB) ||
194
+ try_ext(feature + DLEXT) ||
195
+ try_ext(feature + DLEXT2) ||
196
+ feature
186
197
  end
187
198
  else
188
- def search_index(f)
189
- try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
199
+ def search_index(feature)
200
+ try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
190
201
  end
191
202
 
192
- def maybe_append_extension(f)
193
- try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
203
+ def maybe_append_extension(feature)
204
+ try_ext(feature + DOT_RB) || try_ext(feature + DLEXT) || feature
194
205
  end
195
206
  end
196
207
 
197
- def try_index(f)
198
- if (p = @index[f])
199
- "#{p}/#{f}"
208
+ s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
209
+ if s.respond_to?(:-@)
210
+ if ((-s).equal?(s) && (-s.dup).equal?(s)) || RUBY_VERSION >= "2.7"
211
+ def try_index(feature)
212
+ if (path = @index[feature])
213
+ -File.join(path, feature).freeze
214
+ end
215
+ end
216
+ else
217
+ def try_index(feature)
218
+ if (path = @index[feature])
219
+ -File.join(path, feature).untaint
220
+ end
221
+ end
222
+ end
223
+ else
224
+ def try_index(feature)
225
+ if (path = @index[feature])
226
+ File.join(path, feature)
227
+ end
200
228
  end
201
229
  end
202
230
 
203
- def try_ext(f)
204
- f if File.exist?(f)
231
+ def try_ext(feature)
232
+ feature if File.exist?(feature)
205
233
  end
206
234
  end
207
235
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bootsnap
3
4
  module LoadPathCache
4
5
  module ChangeObserver
@@ -15,11 +16,13 @@ module Bootsnap
15
16
  @lpc_observer.push_paths(self, *entries.map(&:to_s))
16
17
  super
17
18
  end
19
+ alias_method :append, :push
18
20
 
19
21
  def unshift(*entries)
20
22
  @lpc_observer.unshift_paths(self, *entries.map(&:to_s))
21
23
  super
22
24
  end
25
+ alias_method :prepend, :unshift
23
26
 
24
27
  def concat(entries)
25
28
  @lpc_observer.push_paths(self, *entries.map(&:to_s))
@@ -53,10 +56,22 @@ module Bootsnap
53
56
  end
54
57
  end
55
58
 
56
- def self.register(observer, arr)
59
+ def self.register(arr, observer)
57
60
  return if arr.frozen? # can't register observer, but no need to.
61
+
58
62
  arr.instance_variable_set(:@lpc_observer, observer)
59
- arr.extend(ArrayMixin)
63
+ ArrayMixin.instance_methods.each do |method_name|
64
+ arr.singleton_class.send(:define_method, method_name, ArrayMixin.instance_method(method_name))
65
+ end
66
+ end
67
+
68
+ def self.unregister(arr)
69
+ return unless arr.instance_variable_defined?(:@lpc_observer) && arr.instance_variable_get(:@lpc_observer)
70
+
71
+ ArrayMixin.instance_methods.each do |method_name|
72
+ arr.singleton_class.send(:remove_method, method_name)
73
+ end
74
+ arr.instance_variable_set(:@lpc_observer, nil)
60
75
  end
61
76
  end
62
77
  end
@@ -1,105 +1,37 @@
1
1
  # frozen_string_literal: true
2
- module Bootsnap
3
- module LoadPathCache
4
- module CoreExt
5
- def self.make_load_error(path)
6
- err = LoadError.new(+"cannot load such file -- #{path}")
7
- err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
8
- err.define_singleton_method(:path) { path }
9
- err
10
- end
11
- end
12
- end
13
- end
14
2
 
15
3
  module Kernel
16
- module_function # rubocop:disable Style/ModuleFunction
4
+ module_function
17
5
 
18
6
  alias_method(:require_without_bootsnap, :require)
19
7
 
20
- # Note that require registers to $LOADED_FEATURES while load does not.
21
- def require_with_bootsnap_lfi(path, resolved = nil)
22
- Bootsnap::LoadPathCache.loaded_features_index.register(path, resolved) do
23
- require_without_bootsnap(resolved || path)
24
- end
25
- end
26
-
27
8
  def require(path)
28
- return false if Bootsnap::LoadPathCache.loaded_features_index.key?(path)
29
-
30
- if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
31
- return require_with_bootsnap_lfi(path, resolved)
32
- end
33
-
34
- raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
35
- rescue LoadError => e
36
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
37
- raise(e)
38
- rescue Bootsnap::LoadPathCache::ReturnFalse
39
- false
40
- rescue Bootsnap::LoadPathCache::FallbackScan
41
- fallback = true
42
- ensure
43
- if fallback
44
- require_with_bootsnap_lfi(path)
45
- end
46
- end
47
-
48
- alias_method(:require_relative_without_bootsnap, :require_relative)
49
- def require_relative(path)
50
- realpath = Bootsnap::LoadPathCache.realpath_cache.call(
51
- caller_locations(1..1).first.absolute_path, path
52
- )
53
- require(realpath)
54
- end
55
-
56
- alias_method(:load_without_bootsnap, :load)
57
- def load(path, wrap = false)
58
- if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
59
- return load_without_bootsnap(resolved, wrap)
60
- end
61
-
62
- # load also allows relative paths from pwd even when not in $:
63
- if File.exist?(relative = File.expand_path(path).freeze)
64
- return load_without_bootsnap(relative, wrap)
65
- end
66
-
67
- raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
68
- rescue LoadError => e
69
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
70
- raise(e)
71
- rescue Bootsnap::LoadPathCache::ReturnFalse
72
- false
73
- rescue Bootsnap::LoadPathCache::FallbackScan
74
- fallback = true
75
- ensure
76
- if fallback
77
- load_without_bootsnap(path, wrap)
78
- end
79
- end
80
- end
81
-
82
- class Module
83
- alias_method(:autoload_without_bootsnap, :autoload)
84
- def autoload(const, path)
85
- # NOTE: This may defeat LoadedFeaturesIndex, but it's not immediately
86
- # obvious how to make it work. This feels like a pretty niche case, unclear
87
- # if it will ever burn anyone.
88
- #
89
- # The challenge is that we don't control the point at which the entry gets
90
- # added to $LOADED_FEATURES and won't be able to hook that modification
91
- # since it's done in C-land.
92
- autoload_without_bootsnap(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path)
93
- rescue LoadError => e
94
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
95
- raise(e)
96
- rescue Bootsnap::LoadPathCache::ReturnFalse
97
- false
98
- rescue Bootsnap::LoadPathCache::FallbackScan
99
- fallback = true
100
- ensure
101
- if fallback
102
- autoload_without_bootsnap(const, path)
9
+ return require_without_bootsnap(path) unless Bootsnap::LoadPathCache.enabled?
10
+
11
+ string_path = Bootsnap.rb_get_path(path)
12
+ return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
13
+
14
+ resolved = Bootsnap::LoadPathCache.load_path_cache.find(string_path)
15
+ if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
16
+ if (cursor = Bootsnap::LoadPathCache.loaded_features_index.cursor(string_path))
17
+ ret = require_without_bootsnap(path)
18
+ resolved = Bootsnap::LoadPathCache.loaded_features_index.identify(string_path, cursor)
19
+ Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
20
+ return ret
21
+ else
22
+ return require_without_bootsnap(path)
23
+ end
24
+ elsif false == resolved
25
+ return false
26
+ elsif resolved.nil?
27
+ error = LoadError.new(+"cannot load such file -- #{path}")
28
+ error.instance_variable_set(:@path, path)
29
+ raise error
30
+ else
31
+ # Note that require registers to $LOADED_FEATURES while load does not.
32
+ ret = require_without_bootsnap(resolved)
33
+ Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
34
+ return ret
103
35
  end
104
36
  end
105
37
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class << $LOADED_FEATURES
3
4
  alias_method(:delete_without_bootsnap, :delete)
4
5
  def delete(key)
@@ -26,20 +26,21 @@ module Bootsnap
26
26
  class LoadedFeaturesIndex
27
27
  def initialize
28
28
  @lfi = {}
29
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
29
+ @mutex = Mutex.new
30
30
 
31
31
  # In theory the user could mutate $LOADED_FEATURES and invalidate our
32
- # cache. If this ever comes up in practice or if you, the
33
- # enterprising reader, feels inclined to solve this problem we could
32
+ # cache. If this ever comes up in practice - or if you, the
33
+ # enterprising reader, feels inclined to solve this problem - we could
34
34
  # parallel the work done with ChangeObserver on $LOAD_PATH to mirror
35
35
  # updates to our @lfi.
36
36
  $LOADED_FEATURES.each do |feat|
37
37
  hash = feat.hash
38
38
  $LOAD_PATH.each do |lpe|
39
39
  next unless feat.start_with?(lpe)
40
+
40
41
  # /a/b/lib/my/foo.rb
41
42
  # ^^^^^^^^^
42
- short = feat[(lpe.length + 1)..-1]
43
+ short = feat[(lpe.length + 1)..]
43
44
  stripped = strip_extension_if_elidable(short)
44
45
  @lfi[short] = hash
45
46
  @lfi[stripped] = hash
@@ -58,9 +59,9 @@ module Bootsnap
58
59
  end
59
60
 
60
61
  def purge_multi(features)
61
- rejected_hashes = features.map(&:hash).to_set
62
+ rejected_hashes = features.each_with_object({}) { |f, h| h[f.hash] = true }
62
63
  @mutex.synchronize do
63
- @lfi.reject! { |_, hash| rejected_hashes.include?(hash) }
64
+ @lfi.reject! { |_, hash| rejected_hashes.key?(hash) }
64
65
  end
65
66
  end
66
67
 
@@ -68,11 +69,30 @@ module Bootsnap
68
69
  @mutex.synchronize { @lfi.key?(feature) }
69
70
  end
70
71
 
72
+ def cursor(short)
73
+ unless Bootsnap.absolute_path?(short.to_s)
74
+ $LOADED_FEATURES.size
75
+ end
76
+ end
77
+
78
+ def identify(short, cursor)
79
+ $LOADED_FEATURES[cursor..].detect do |feat|
80
+ offset = 0
81
+ while (offset = feat.index(short, offset))
82
+ if feat.index(".", offset + 1) && !feat.index("/", offset + 2)
83
+ break true
84
+ else
85
+ offset += 1
86
+ end
87
+ end
88
+ end
89
+ end
90
+
71
91
  # There is a relatively uncommon case where we could miss adding an
72
92
  # entry:
73
93
  #
74
94
  # If the user asked for e.g. `require 'bundler'`, and we went through the
75
- # `FallbackScan` pathway in `kernel_require.rb` and therefore did not
95
+ # `FALLBACK_SCAN` pathway in `kernel_require.rb` and therefore did not
76
96
  # pass `long` (the full expanded absolute path), then we did are not able
77
97
  # to confidently add the `bundler.rb` form to @lfi.
78
98
  #
@@ -82,15 +102,8 @@ module Bootsnap
82
102
  # not quite right; or
83
103
  # 2. Inspect $LOADED_FEATURES upon return from yield to find the matching
84
104
  # entry.
85
- def register(short, long = nil)
86
- if long.nil?
87
- pat = %r{/#{Regexp.escape(short)}(\.[^/]+)?$}
88
- len = $LOADED_FEATURES.size
89
- ret = yield
90
- long = $LOADED_FEATURES[len..-1].detect { |feat| feat =~ pat }
91
- else
92
- ret = yield
93
- end
105
+ def register(short, long)
106
+ return if Bootsnap.absolute_path?(short)
94
107
 
95
108
  hash = long.hash
96
109
 
@@ -109,13 +122,11 @@ module Bootsnap
109
122
  @lfi[short] = hash
110
123
  (@lfi[altname] = hash) if altname
111
124
  end
112
-
113
- ret
114
125
  end
115
126
 
116
127
  private
117
128
 
118
- STRIP_EXTENSION = /\.[^.]*?$/
129
+ STRIP_EXTENSION = /\.[^.]*?$/.freeze
119
130
  private_constant(:STRIP_EXTENSION)
120
131
 
121
132
  # Might Ruby automatically search for this extension if
@@ -132,15 +143,15 @@ module Bootsnap
132
143
  # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
133
144
  #
134
145
  # See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
135
- def extension_elidable?(f)
136
- f.to_s.end_with?('.rb', '.so', '.o', '.dll', '.dylib')
146
+ def extension_elidable?(feature)
147
+ feature.to_s.end_with?(".rb", ".so", ".o", ".dll", ".dylib")
137
148
  end
138
149
 
139
- def strip_extension_if_elidable(f)
140
- if extension_elidable?(f)
141
- f.sub(STRIP_EXTENSION, '')
150
+ def strip_extension_if_elidable(feature)
151
+ if extension_elidable?(feature)
152
+ feature.sub(STRIP_EXTENSION, "")
142
153
  else
143
- f
154
+ feature
144
155
  end
145
156
  end
146
157
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require_relative('path_scanner')
2
+
3
+ require_relative("path_scanner")
3
4
 
4
5
  module Bootsnap
5
6
  module LoadPathCache
@@ -20,14 +21,32 @@ module Bootsnap
20
21
 
21
22
  attr_reader(:path)
22
23
 
23
- def initialize(path)
24
+ def initialize(path, real: false)
24
25
  @path = path.to_s.freeze
26
+ @real = real
27
+ end
28
+
29
+ def to_realpath
30
+ return self if @real
31
+
32
+ realpath = begin
33
+ File.realpath(path)
34
+ rescue Errno::ENOENT
35
+ return self
36
+ end
37
+
38
+ if realpath == path
39
+ @real = true
40
+ self
41
+ else
42
+ Path.new(realpath, real: true)
43
+ end
25
44
  end
26
45
 
27
46
  # True if the path exists, but represents a non-directory object
28
47
  def non_directory?
29
48
  !File.stat(path).directory?
30
- rescue Errno::ENOENT, Errno::ENOTDIR
49
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
31
50
  false
32
51
  end
33
52
 
@@ -43,6 +62,7 @@ module Bootsnap
43
62
  # set to zero anyway, just in case we change the stability heuristics.
44
63
  _, entries, dirs = store.get(expanded_path)
45
64
  return [entries, dirs] if entries # cache hit
65
+
46
66
  entries, dirs = scan!
47
67
  store.set(expanded_path, [0, entries, dirs])
48
68
  return [entries, dirs]
@@ -60,7 +80,11 @@ module Bootsnap
60
80
  end
61
81
 
62
82
  def expanded_path
63
- File.expand_path(path).freeze
83
+ if @real
84
+ path
85
+ else
86
+ @expanded_path ||= File.expand_path(path).freeze
87
+ end
64
88
  end
65
89
 
66
90
  private
@@ -77,7 +101,7 @@ module Bootsnap
77
101
  ["", *dirs].each do |dir|
78
102
  curr = begin
79
103
  File.mtime("#{path}/#{dir}").to_i
80
- rescue Errno::ENOENT, Errno::ENOTDIR
104
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
81
105
  -1
82
106
  end
83
107
  max = curr if curr > max
@@ -93,20 +117,18 @@ module Bootsnap
93
117
 
94
118
  # Built-in ruby lib stuff doesn't change, but things can occasionally be
95
119
  # installed into sitedir, which generally lives under libdir.
96
- RUBY_LIBDIR = RbConfig::CONFIG['libdir']
97
- RUBY_SITEDIR = RbConfig::CONFIG['sitedir']
120
+ RUBY_LIBDIR = RbConfig::CONFIG["libdir"]
121
+ RUBY_SITEDIR = RbConfig::CONFIG["sitedir"]
98
122
 
99
123
  def stability
100
- @stability ||= begin
101
- if Gem.path.detect { |p| expanded_path.start_with?(p.to_s) }
102
- STABLE
103
- elsif Bootsnap.bundler? && expanded_path.start_with?(Bundler.bundle_path.to_s)
104
- STABLE
105
- elsif expanded_path.start_with?(RUBY_LIBDIR) && !expanded_path.start_with?(RUBY_SITEDIR)
106
- STABLE
107
- else
108
- VOLATILE
109
- end
124
+ @stability ||= if Gem.path.detect { |p| expanded_path.start_with?(p.to_s) }
125
+ STABLE
126
+ elsif Bootsnap.bundler? && expanded_path.start_with?(Bundler.bundle_path.to_s)
127
+ STABLE
128
+ elsif expanded_path.start_with?(RUBY_LIBDIR) && !expanded_path.start_with?(RUBY_SITEDIR)
129
+ STABLE
130
+ else
131
+ VOLATILE
110
132
  end
111
133
  end
112
134
  end