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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +218 -0
- data/LICENSE.txt +1 -1
- data/README.md +48 -22
- data/exe/bootsnap +1 -1
- data/ext/bootsnap/bootsnap.c +347 -145
- data/ext/bootsnap/extconf.rb +29 -15
- data/lib/bootsnap/bundler.rb +2 -1
- data/lib/bootsnap/cli/worker_pool.rb +6 -1
- data/lib/bootsnap/cli.rb +90 -53
- data/lib/bootsnap/compile_cache/iseq.rb +52 -16
- data/lib/bootsnap/compile_cache/json.rb +89 -0
- data/lib/bootsnap/compile_cache/yaml.rb +285 -60
- data/lib/bootsnap/compile_cache.rb +26 -17
- data/lib/bootsnap/explicit_require.rb +4 -3
- data/lib/bootsnap/load_path_cache/cache.rb +71 -35
- data/lib/bootsnap/load_path_cache/change_observer.rb +23 -2
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +26 -94
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +36 -25
- data/lib/bootsnap/load_path_cache/path.rb +40 -18
- data/lib/bootsnap/load_path_cache/path_scanner.rb +25 -7
- data/lib/bootsnap/load_path_cache/store.rb +64 -24
- data/lib/bootsnap/load_path_cache.rb +40 -38
- data/lib/bootsnap/setup.rb +2 -36
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +139 -36
- metadata +8 -79
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -107
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
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 =
|
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,
|
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
|
-
|
51
|
-
return
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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(
|
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(
|
181
|
-
try_index(
|
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(
|
185
|
-
try_ext(
|
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(
|
189
|
-
try_index(
|
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(
|
193
|
-
try_ext(
|
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
|
-
|
198
|
-
|
199
|
-
|
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(
|
204
|
-
|
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(
|
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
|
-
|
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
|
-
|
4
|
+
alias_method :require_without_bootsnap, :require
|
17
5
|
|
18
|
-
alias_method
|
6
|
+
alias_method :require, :require # Avoid method redefinition warnings
|
19
7
|
|
20
|
-
|
21
|
-
|
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
|
-
|
28
|
-
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
return
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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
|
@@ -26,20 +26,21 @@ module Bootsnap
|
|
26
26
|
class LoadedFeaturesIndex
|
27
27
|
def initialize
|
28
28
|
@lfi = {}
|
29
|
-
@mutex =
|
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
|
33
|
-
# enterprising reader, feels inclined to solve this problem
|
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)
|
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.
|
62
|
+
rejected_hashes = features.each_with_object({}) { |f, h| h[f.hash] = true }
|
62
63
|
@mutex.synchronize do
|
63
|
-
@lfi.reject! { |_, hash| rejected_hashes.
|
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
|
-
# `
|
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
|
86
|
-
if
|
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?(
|
136
|
-
|
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(
|
140
|
-
if extension_elidable?(
|
141
|
-
|
150
|
+
def strip_extension_if_elidable(feature)
|
151
|
+
if extension_elidable?(feature)
|
152
|
+
feature.sub(STRIP_EXTENSION, "")
|
142
153
|
else
|
143
|
-
|
154
|
+
feature
|
144
155
|
end
|
145
156
|
end
|
146
157
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
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
|
-
|
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
|
96
|
-
RUBY_LIBDIR = RbConfig::CONFIG[
|
97
|
-
RUBY_SITEDIR = RbConfig::CONFIG[
|
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 ||=
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|