bootsnap 1.6.0 → 1.18.3

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
@@ -24,19 +24,28 @@ module Bootsnap
24
24
  @mutex.synchronize { @dirs[dir] }
25
25
  end
26
26
 
27
+ TRUFFLERUBY_LIB_DIR_PREFIX = if RUBY_ENGINE == "truffleruby"
28
+ "#{File.join(RbConfig::CONFIG['libdir'], 'truffle')}#{File::SEPARATOR}"
29
+ end
30
+
27
31
  # { 'enumerator' => nil, 'enumerator.so' => nil, ... }
28
32
  BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
33
+ if TRUFFLERUBY_LIB_DIR_PREFIX && feat.start_with?(TRUFFLERUBY_LIB_DIR_PREFIX)
34
+ feat = feat.byteslice(TRUFFLERUBY_LIB_DIR_PREFIX.bytesize..-1)
35
+ end
36
+
29
37
  # Builtin features are of the form 'enumerator.so'.
30
38
  # All others include paths.
31
- next unless feat.size < 20 && !feat.include?('/')
39
+ next unless feat.size < 20 && !feat.include?("/")
32
40
 
33
- base = File.basename(feat, '.*') # enumerator.so -> enumerator
41
+ base = File.basename(feat, ".*") # enumerator.so -> enumerator
34
42
  ext = File.extname(feat) # .so
35
43
 
36
44
  features[feat] = nil # enumerator.so
37
45
  features[base] = nil # enumerator
38
46
 
39
47
  next unless [DOT_SO, *DL_EXTENSIONS].include?(ext)
48
+
40
49
  DL_EXTENSIONS.each do |dl_ext|
41
50
  features["#{base}#{dl_ext}"] = nil # enumerator.bundle
42
51
  end
@@ -47,8 +56,13 @@ module Bootsnap
47
56
  def find(feature)
48
57
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
49
58
  feature = feature.to_s.freeze
50
- return feature if absolute_path?(feature)
51
- return expand_path(feature) if feature.start_with?('./')
59
+
60
+ return feature if Bootsnap.absolute_path?(feature)
61
+
62
+ if feature.start_with?("./", "../")
63
+ return expand_path(feature)
64
+ end
65
+
52
66
  @mutex.synchronize do
53
67
  x = search_index(feature)
54
68
  return x if x
@@ -58,7 +72,7 @@ module Bootsnap
58
72
  # returns false as if it were already loaded; however, there is no
59
73
  # file to find on disk. We've pre-built a list of these, and we
60
74
  # return false if any of them is loaded.
61
- raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
75
+ return false if BUILTIN_FEATURES.key?(feature)
62
76
 
63
77
  # The feature wasn't found on our preliminary search through the index.
64
78
  # We resolve this differently depending on what the extension was.
@@ -67,13 +81,14 @@ module Bootsnap
67
81
  # native dynamic extension, e.g. .bundle or .so), we know it was a
68
82
  # failure and there's nothing more we can do to find the file.
69
83
  # no extension, .rb, (.bundle or .so)
70
- when '', *CACHED_EXTENSIONS
84
+ when "", *CACHED_EXTENSIONS
71
85
  nil
72
86
  # Ruby allows specifying native extensions as '.so' even when DLEXT
73
87
  # is '.bundle'. This is where we handle that case.
74
88
  when DOT_SO
75
89
  x = search_index(feature[0..-4] + DLEXT)
76
90
  return x if x
91
+
77
92
  if DLEXT2
78
93
  x = search_index(feature[0..-4] + DLEXT2)
79
94
  return x if x
@@ -81,7 +96,7 @@ module Bootsnap
81
96
  else
82
97
  # other, unknown extension. For example, `.rake`. Since we haven't
83
98
  # cached these, we legitimately need to run the load path search.
84
- raise(LoadPathCache::FallbackScan, '', [])
99
+ return FALLBACK_SCAN
85
100
  end
86
101
  end
87
102
 
@@ -89,33 +104,25 @@ module Bootsnap
89
104
  # cases where the file doesn't appear to be on the load path. We should
90
105
  # be able to detect newly-created files without rebooting the
91
106
  # 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
107
+ return FALLBACK_SCAN if @development_mode
103
108
  end
104
109
 
105
110
  def unshift_paths(sender, *paths)
106
111
  return unless sender == @path_obj
112
+
107
113
  @mutex.synchronize { unshift_paths_locked(*paths) }
108
114
  end
109
115
 
110
116
  def push_paths(sender, *paths)
111
117
  return unless sender == @path_obj
118
+
112
119
  @mutex.synchronize { push_paths_locked(*paths) }
113
120
  end
114
121
 
115
122
  def reinitialize(path_obj = @path_obj)
116
123
  @mutex.synchronize do
117
124
  @path_obj = path_obj
118
- ChangeObserver.register(self, @path_obj)
125
+ ChangeObserver.register(@path_obj, self)
119
126
  @index = {}
120
127
  @dirs = {}
121
128
  @generated_at = now
@@ -141,6 +148,9 @@ module Bootsnap
141
148
  p = Path.new(path)
142
149
  @has_relative_paths = true if p.relative?
143
150
  next if p.non_directory?
151
+
152
+ p = p.to_realpath
153
+
144
154
  expanded_path = p.expanded_path
145
155
  entries, dirs = p.entries_and_dirs(@store)
146
156
  # push -> low precedence -> set only if unset
@@ -155,6 +165,9 @@ module Bootsnap
155
165
  paths.map(&:to_s).reverse_each do |path|
156
166
  p = Path.new(path)
157
167
  next if p.non_directory?
168
+
169
+ p = p.to_realpath
170
+
158
171
  expanded_path = p.expanded_path
159
172
  entries, dirs = p.entries_and_dirs(@store)
160
173
  # unshift -> high precedence -> unconditional set
@@ -177,31 +190,54 @@ module Bootsnap
177
190
  end
178
191
 
179
192
  if DLEXT2
180
- def search_index(f)
181
- try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
193
+ def search_index(feature)
194
+ try_index(feature + DOT_RB) ||
195
+ try_index(feature + DLEXT) ||
196
+ try_index(feature + DLEXT2) ||
197
+ try_index(feature)
182
198
  end
183
199
 
184
- def maybe_append_extension(f)
185
- try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
200
+ def maybe_append_extension(feature)
201
+ try_ext(feature + DOT_RB) ||
202
+ try_ext(feature + DLEXT) ||
203
+ try_ext(feature + DLEXT2) ||
204
+ feature
186
205
  end
187
206
  else
188
- def search_index(f)
189
- try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
207
+ def search_index(feature)
208
+ try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
190
209
  end
191
210
 
192
- def maybe_append_extension(f)
193
- try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
211
+ def maybe_append_extension(feature)
212
+ try_ext(feature + DOT_RB) || try_ext(feature + DLEXT) || feature
194
213
  end
195
214
  end
196
215
 
197
- def try_index(f)
198
- if (p = @index[f])
199
- "#{p}/#{f}"
216
+ s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
217
+ if s.respond_to?(:-@)
218
+ if ((-s).equal?(s) && (-s.dup).equal?(s)) || RUBY_VERSION >= "2.7"
219
+ def try_index(feature)
220
+ if (path = @index[feature])
221
+ -File.join(path, feature).freeze
222
+ end
223
+ end
224
+ else
225
+ def try_index(feature)
226
+ if (path = @index[feature])
227
+ -File.join(path, feature).untaint
228
+ end
229
+ end
230
+ end
231
+ else
232
+ def try_index(feature)
233
+ if (path = @index[feature])
234
+ File.join(path, feature)
235
+ end
200
236
  end
201
237
  end
202
238
 
203
- def try_ext(f)
204
- f if File.exist?(f)
239
+ def try_ext(feature)
240
+ feature if File.exist?(feature)
205
241
  end
206
242
  end
207
243
  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))
@@ -51,12 +54,30 @@ module Bootsnap
51
54
  ret
52
55
  end
53
56
  end
57
+
58
+ def dup
59
+ [] + self
60
+ end
61
+
62
+ alias_method :clone, :dup
54
63
  end
55
64
 
56
- def self.register(observer, arr)
65
+ def self.register(arr, observer)
57
66
  return if arr.frozen? # can't register observer, but no need to.
67
+
58
68
  arr.instance_variable_set(:@lpc_observer, observer)
59
- arr.extend(ArrayMixin)
69
+ ArrayMixin.instance_methods.each do |method_name|
70
+ arr.singleton_class.send(:define_method, method_name, ArrayMixin.instance_method(method_name))
71
+ end
72
+ end
73
+
74
+ def self.unregister(arr)
75
+ return unless arr.instance_variable_defined?(:@lpc_observer) && arr.instance_variable_get(:@lpc_observer)
76
+
77
+ ArrayMixin.instance_methods.each do |method_name|
78
+ arr.singleton_class.send(:remove_method, method_name)
79
+ end
80
+ arr.instance_variable_set(:@lpc_observer, nil)
60
81
  end
61
82
  end
62
83
  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
+ alias_method :require_without_bootsnap, :require
17
5
 
18
- alias_method(:require_without_bootsnap, :require)
6
+ alias_method :require, :require # Avoid method redefinition warnings
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
8
+ def require(path) # rubocop:disable Lint/DuplicateMethods
9
+ return require_without_bootsnap(path) unless Bootsnap::LoadPathCache.enabled?
26
10
 
27
- 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
11
+ string_path = Bootsnap.rb_get_path(path)
12
+ return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
47
13
 
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)
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
+ return require_without_bootsnap(path)
28
+ else
29
+ # Note that require registers to $LOADED_FEATURES while load does not.
30
+ ret = require_without_bootsnap(resolved)
31
+ Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
32
+ return ret
78
33
  end
79
34
  end
80
- end
81
35
 
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)
103
- end
104
- end
36
+ private :require
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
@@ -92,21 +116,19 @@ module Bootsnap
92
116
  VOLATILE = :volatile
93
117
 
94
118
  # Built-in ruby lib stuff doesn't change, but things can occasionally be
95
- # installed into sitedir, which generally lives under libdir.
96
- RUBY_LIBDIR = RbConfig::CONFIG['libdir']
97
- RUBY_SITEDIR = RbConfig::CONFIG['sitedir']
119
+ # installed into sitedir, which generally lives under rubylibdir.
120
+ RUBY_LIBDIR = RbConfig::CONFIG["rubylibdir"]
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