bootsnap 1.9.2 → 1.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -1
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/exe/bootsnap +1 -1
- data/ext/bootsnap/bootsnap.c +32 -34
- data/ext/bootsnap/extconf.rb +13 -11
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli/worker_pool.rb +1 -0
- data/lib/bootsnap/cli.rb +49 -49
- data/lib/bootsnap/compile_cache/iseq.rb +42 -12
- data/lib/bootsnap/compile_cache/json.rb +15 -8
- data/lib/bootsnap/compile_cache/yaml.rb +216 -78
- data/lib/bootsnap/compile_cache.rb +10 -7
- data/lib/bootsnap/explicit_require.rb +4 -3
- data/lib/bootsnap/load_path_cache/cache.rb +43 -41
- data/lib/bootsnap/load_path_cache/change_observer.rb +2 -0
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +30 -21
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +31 -28
- data/lib/bootsnap/load_path_cache/path.rb +5 -3
- data/lib/bootsnap/load_path_cache/path_scanner.rb +6 -5
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +1 -0
- data/lib/bootsnap/load_path_cache/store.rb +24 -7
- data/lib/bootsnap/load_path_cache.rb +15 -15
- data/lib/bootsnap/setup.rb +2 -1
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +44 -56
- metadata +5 -75
|
@@ -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
|
|
@@ -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,
|
|
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
|
|
@@ -48,9 +49,9 @@ module Bootsnap
|
|
|
48
49
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
|
49
50
|
feature = feature.to_s.freeze
|
|
50
51
|
|
|
51
|
-
return feature if absolute_path?(feature)
|
|
52
|
+
return feature if Bootsnap.absolute_path?(feature)
|
|
52
53
|
|
|
53
|
-
if feature.start_with?(
|
|
54
|
+
if feature.start_with?("./", "../")
|
|
54
55
|
return try_extensions ? expand_path(feature) : File.expand_path(feature).freeze
|
|
55
56
|
end
|
|
56
57
|
|
|
@@ -64,7 +65,7 @@ module Bootsnap
|
|
|
64
65
|
# returns false as if it were already loaded; however, there is no
|
|
65
66
|
# file to find on disk. We've pre-built a list of these, and we
|
|
66
67
|
# return false if any of them is loaded.
|
|
67
|
-
raise(LoadPathCache::ReturnFalse,
|
|
68
|
+
raise(LoadPathCache::ReturnFalse, "", []) if BUILTIN_FEATURES.key?(feature)
|
|
68
69
|
|
|
69
70
|
# The feature wasn't found on our preliminary search through the index.
|
|
70
71
|
# We resolve this differently depending on what the extension was.
|
|
@@ -73,13 +74,14 @@ module Bootsnap
|
|
|
73
74
|
# native dynamic extension, e.g. .bundle or .so), we know it was a
|
|
74
75
|
# failure and there's nothing more we can do to find the file.
|
|
75
76
|
# no extension, .rb, (.bundle or .so)
|
|
76
|
-
when
|
|
77
|
+
when "", *CACHED_EXTENSIONS
|
|
77
78
|
nil
|
|
78
79
|
# Ruby allows specifying native extensions as '.so' even when DLEXT
|
|
79
80
|
# is '.bundle'. This is where we handle that case.
|
|
80
81
|
when DOT_SO
|
|
81
82
|
x = search_index(feature[0..-4] + DLEXT)
|
|
82
83
|
return x if x
|
|
84
|
+
|
|
83
85
|
if DLEXT2
|
|
84
86
|
x = search_index(feature[0..-4] + DLEXT2)
|
|
85
87
|
return x if x
|
|
@@ -87,7 +89,7 @@ module Bootsnap
|
|
|
87
89
|
else
|
|
88
90
|
# other, unknown extension. For example, `.rake`. Since we haven't
|
|
89
91
|
# cached these, we legitimately need to run the load path search.
|
|
90
|
-
raise(LoadPathCache::FallbackScan,
|
|
92
|
+
raise(LoadPathCache::FallbackScan, "", [])
|
|
91
93
|
end
|
|
92
94
|
end
|
|
93
95
|
|
|
@@ -95,26 +97,18 @@ module Bootsnap
|
|
|
95
97
|
# cases where the file doesn't appear to be on the load path. We should
|
|
96
98
|
# be able to detect newly-created files without rebooting the
|
|
97
99
|
# application.
|
|
98
|
-
raise(LoadPathCache::FallbackScan,
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
|
102
|
-
def absolute_path?(path)
|
|
103
|
-
path[1] == ':'
|
|
104
|
-
end
|
|
105
|
-
else
|
|
106
|
-
def absolute_path?(path)
|
|
107
|
-
path.start_with?(SLASH)
|
|
108
|
-
end
|
|
100
|
+
raise(LoadPathCache::FallbackScan, "", []) if @development_mode
|
|
109
101
|
end
|
|
110
102
|
|
|
111
103
|
def unshift_paths(sender, *paths)
|
|
112
104
|
return unless sender == @path_obj
|
|
105
|
+
|
|
113
106
|
@mutex.synchronize { unshift_paths_locked(*paths) }
|
|
114
107
|
end
|
|
115
108
|
|
|
116
109
|
def push_paths(sender, *paths)
|
|
117
110
|
return unless sender == @path_obj
|
|
111
|
+
|
|
118
112
|
@mutex.synchronize { push_paths_locked(*paths) }
|
|
119
113
|
end
|
|
120
114
|
|
|
@@ -147,6 +141,7 @@ module Bootsnap
|
|
|
147
141
|
p = Path.new(path)
|
|
148
142
|
@has_relative_paths = true if p.relative?
|
|
149
143
|
next if p.non_directory?
|
|
144
|
+
|
|
150
145
|
expanded_path = p.expanded_path
|
|
151
146
|
entries, dirs = p.entries_and_dirs(@store)
|
|
152
147
|
# push -> low precedence -> set only if unset
|
|
@@ -161,6 +156,7 @@ module Bootsnap
|
|
|
161
156
|
paths.map(&:to_s).reverse_each do |path|
|
|
162
157
|
p = Path.new(path)
|
|
163
158
|
next if p.non_directory?
|
|
159
|
+
|
|
164
160
|
expanded_path = p.expanded_path
|
|
165
161
|
entries, dirs = p.entries_and_dirs(@store)
|
|
166
162
|
# unshift -> high precedence -> unconditional set
|
|
@@ -183,56 +179,62 @@ module Bootsnap
|
|
|
183
179
|
end
|
|
184
180
|
|
|
185
181
|
if DLEXT2
|
|
186
|
-
def search_index(
|
|
182
|
+
def search_index(feature, try_extensions: true)
|
|
187
183
|
if try_extensions
|
|
188
|
-
try_index(
|
|
184
|
+
try_index(feature + DOT_RB) ||
|
|
185
|
+
try_index(feature + DLEXT) ||
|
|
186
|
+
try_index(feature + DLEXT2) ||
|
|
187
|
+
try_index(feature)
|
|
189
188
|
else
|
|
190
|
-
try_index(
|
|
189
|
+
try_index(feature)
|
|
191
190
|
end
|
|
192
191
|
end
|
|
193
192
|
|
|
194
|
-
def maybe_append_extension(
|
|
195
|
-
try_ext(
|
|
193
|
+
def maybe_append_extension(feature)
|
|
194
|
+
try_ext(feature + DOT_RB) ||
|
|
195
|
+
try_ext(feature + DLEXT) ||
|
|
196
|
+
try_ext(feature + DLEXT2) ||
|
|
197
|
+
feature
|
|
196
198
|
end
|
|
197
199
|
else
|
|
198
|
-
def search_index(
|
|
200
|
+
def search_index(feature, try_extensions: true)
|
|
199
201
|
if try_extensions
|
|
200
|
-
try_index(
|
|
202
|
+
try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
|
|
201
203
|
else
|
|
202
|
-
try_index(
|
|
204
|
+
try_index(feature)
|
|
203
205
|
end
|
|
204
206
|
end
|
|
205
207
|
|
|
206
|
-
def maybe_append_extension(
|
|
207
|
-
try_ext(
|
|
208
|
+
def maybe_append_extension(feature)
|
|
209
|
+
try_ext(feature + DOT_RB) || try_ext(feature + DLEXT) || feature
|
|
208
210
|
end
|
|
209
211
|
end
|
|
210
212
|
|
|
211
213
|
s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
|
|
212
214
|
if s.respond_to?(:-@)
|
|
213
|
-
if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >=
|
|
214
|
-
def try_index(
|
|
215
|
-
if (
|
|
216
|
-
-
|
|
215
|
+
if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= "2.7"
|
|
216
|
+
def try_index(feature)
|
|
217
|
+
if (path = @index[feature])
|
|
218
|
+
-File.join(path, feature).freeze
|
|
217
219
|
end
|
|
218
220
|
end
|
|
219
221
|
else
|
|
220
|
-
def try_index(
|
|
221
|
-
if (
|
|
222
|
-
-File.join(
|
|
222
|
+
def try_index(feature)
|
|
223
|
+
if (path = @index[feature])
|
|
224
|
+
-File.join(path, feature).untaint
|
|
223
225
|
end
|
|
224
226
|
end
|
|
225
227
|
end
|
|
226
228
|
else
|
|
227
|
-
def try_index(
|
|
228
|
-
if (
|
|
229
|
-
File.join(
|
|
229
|
+
def try_index(feature)
|
|
230
|
+
if (path = @index[feature])
|
|
231
|
+
File.join(path, feature)
|
|
230
232
|
end
|
|
231
233
|
end
|
|
232
234
|
end
|
|
233
235
|
|
|
234
|
-
def try_ext(
|
|
235
|
-
|
|
236
|
+
def try_ext(feature)
|
|
237
|
+
feature if File.exist?(feature)
|
|
236
238
|
end
|
|
237
239
|
end
|
|
238
240
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Bootsnap
|
|
3
4
|
module LoadPathCache
|
|
4
5
|
module ChangeObserver
|
|
@@ -57,6 +58,7 @@ module Bootsnap
|
|
|
57
58
|
|
|
58
59
|
def self.register(observer, arr)
|
|
59
60
|
return if arr.frozen? # can't register observer, but no need to.
|
|
61
|
+
|
|
60
62
|
arr.instance_variable_set(:@lpc_observer, observer)
|
|
61
63
|
arr.extend(ArrayMixin)
|
|
62
64
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Bootsnap
|
|
3
4
|
module LoadPathCache
|
|
4
5
|
module CoreExt
|
|
@@ -13,35 +14,42 @@ module Bootsnap
|
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
module Kernel
|
|
16
|
-
module_function
|
|
17
|
+
module_function
|
|
17
18
|
|
|
18
19
|
alias_method(:require_without_bootsnap, :require)
|
|
19
20
|
|
|
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
21
|
def require(path)
|
|
28
|
-
|
|
22
|
+
fallback = false
|
|
23
|
+
string_path = path.to_s
|
|
24
|
+
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
|
|
29
25
|
|
|
30
|
-
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(
|
|
31
|
-
|
|
26
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(string_path))
|
|
27
|
+
# Note that require registers to $LOADED_FEATURES while load does not.
|
|
28
|
+
ret = require_without_bootsnap(resolved)
|
|
29
|
+
Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
|
|
30
|
+
return ret
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
|
35
|
-
rescue LoadError =>
|
|
36
|
-
|
|
37
|
-
raise(
|
|
34
|
+
rescue LoadError => error
|
|
35
|
+
error.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
36
|
+
raise(error)
|
|
38
37
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
39
38
|
false
|
|
40
39
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
41
40
|
fallback = true
|
|
42
41
|
ensure
|
|
42
|
+
# We raise from `ensure` so that any further exception don't have FallbackScan as a cause
|
|
43
|
+
# See: https://github.com/Shopify/bootsnap/issues/250
|
|
43
44
|
if fallback
|
|
44
|
-
|
|
45
|
+
if (cursor = Bootsnap::LoadPathCache.loaded_features_index.cursor(string_path))
|
|
46
|
+
ret = require_without_bootsnap(path)
|
|
47
|
+
resolved = Bootsnap::LoadPathCache.loaded_features_index.identify(string_path, cursor)
|
|
48
|
+
Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
|
|
49
|
+
ret
|
|
50
|
+
else # If we're not given a cursor, it means we don't need to register the path (likely an absolute path)
|
|
51
|
+
require_without_bootsnap(path)
|
|
52
|
+
end
|
|
45
53
|
end
|
|
46
54
|
end
|
|
47
55
|
|
|
@@ -67,6 +75,7 @@ end
|
|
|
67
75
|
class Module
|
|
68
76
|
alias_method(:autoload_without_bootsnap, :autoload)
|
|
69
77
|
def autoload(const, path)
|
|
78
|
+
fallback = false
|
|
70
79
|
# NOTE: This may defeat LoadedFeaturesIndex, but it's not immediately
|
|
71
80
|
# obvious how to make it work. This feels like a pretty niche case, unclear
|
|
72
81
|
# if it will ever burn anyone.
|
|
@@ -75,16 +84,16 @@ class Module
|
|
|
75
84
|
# added to $LOADED_FEATURES and won't be able to hook that modification
|
|
76
85
|
# since it's done in C-land.
|
|
77
86
|
autoload_without_bootsnap(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path)
|
|
78
|
-
rescue LoadError =>
|
|
79
|
-
|
|
80
|
-
raise(
|
|
87
|
+
rescue LoadError => error
|
|
88
|
+
error.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
89
|
+
raise(error)
|
|
81
90
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
82
91
|
false
|
|
83
92
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
84
93
|
fallback = true
|
|
85
94
|
ensure
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
# We raise from `ensure` so that any further exception don't have FallbackScan as a cause
|
|
96
|
+
# See: https://github.com/Shopify/bootsnap/issues/250
|
|
97
|
+
autoload_without_bootsnap(const, path)
|
|
89
98
|
end
|
|
90
99
|
end
|
|
@@ -29,14 +29,15 @@ module Bootsnap
|
|
|
29
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
43
|
short = feat[(lpe.length + 1)..-1]
|
|
@@ -68,6 +69,25 @@ 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..-1].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
|
#
|
|
@@ -82,23 +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
|
-
len = $LOADED_FEATURES.size
|
|
88
|
-
ret = yield
|
|
89
|
-
long = $LOADED_FEATURES[len..-1].detect do |feat|
|
|
90
|
-
offset = 0
|
|
91
|
-
while offset = feat.index(short, offset)
|
|
92
|
-
if feat.index(".", offset + 1) && !feat.index("/", offset + 2)
|
|
93
|
-
break true
|
|
94
|
-
else
|
|
95
|
-
offset += 1
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
else
|
|
100
|
-
ret = yield
|
|
101
|
-
end
|
|
105
|
+
def register(short, long)
|
|
106
|
+
return if Bootsnap.absolute_path?(short)
|
|
102
107
|
|
|
103
108
|
hash = long.hash
|
|
104
109
|
|
|
@@ -117,13 +122,11 @@ module Bootsnap
|
|
|
117
122
|
@lfi[short] = hash
|
|
118
123
|
(@lfi[altname] = hash) if altname
|
|
119
124
|
end
|
|
120
|
-
|
|
121
|
-
ret
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
private
|
|
125
128
|
|
|
126
|
-
STRIP_EXTENSION = /\.[^.]
|
|
129
|
+
STRIP_EXTENSION = /\.[^.]*?$/.freeze
|
|
127
130
|
private_constant(:STRIP_EXTENSION)
|
|
128
131
|
|
|
129
132
|
# Might Ruby automatically search for this extension if
|
|
@@ -140,15 +143,15 @@ module Bootsnap
|
|
|
140
143
|
# with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
|
|
141
144
|
#
|
|
142
145
|
# See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
|
|
143
|
-
def extension_elidable?(
|
|
144
|
-
|
|
146
|
+
def extension_elidable?(feature)
|
|
147
|
+
feature.to_s.end_with?(".rb", ".so", ".o", ".dll", ".dylib")
|
|
145
148
|
end
|
|
146
149
|
|
|
147
|
-
def strip_extension_if_elidable(
|
|
148
|
-
if extension_elidable?(
|
|
149
|
-
|
|
150
|
+
def strip_extension_if_elidable(feature)
|
|
151
|
+
if extension_elidable?(feature)
|
|
152
|
+
feature.sub(STRIP_EXTENSION, "")
|
|
150
153
|
else
|
|
151
|
-
|
|
154
|
+
feature
|
|
152
155
|
end
|
|
153
156
|
end
|
|
154
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
|
|
@@ -43,6 +44,7 @@ module Bootsnap
|
|
|
43
44
|
# set to zero anyway, just in case we change the stability heuristics.
|
|
44
45
|
_, entries, dirs = store.get(expanded_path)
|
|
45
46
|
return [entries, dirs] if entries # cache hit
|
|
47
|
+
|
|
46
48
|
entries, dirs = scan!
|
|
47
49
|
store.set(expanded_path, [0, entries, dirs])
|
|
48
50
|
return [entries, dirs]
|
|
@@ -93,8 +95,8 @@ module Bootsnap
|
|
|
93
95
|
|
|
94
96
|
# Built-in ruby lib stuff doesn't change, but things can occasionally be
|
|
95
97
|
# installed into sitedir, which generally lives under libdir.
|
|
96
|
-
RUBY_LIBDIR = RbConfig::CONFIG[
|
|
97
|
-
RUBY_SITEDIR = RbConfig::CONFIG[
|
|
98
|
+
RUBY_LIBDIR = RbConfig::CONFIG["libdir"]
|
|
99
|
+
RUBY_SITEDIR = RbConfig::CONFIG["sitedir"]
|
|
98
100
|
|
|
99
101
|
def stability
|
|
100
102
|
@stability ||= begin
|
|
@@ -1,18 +1,18 @@
|
|
|
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
|
|
7
7
|
module PathScanner
|
|
8
8
|
REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
|
|
9
9
|
NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
|
|
10
|
-
ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z
|
|
10
|
+
ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/.freeze
|
|
11
11
|
|
|
12
12
|
BUNDLE_PATH = if Bootsnap.bundler?
|
|
13
13
|
(Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
|
|
14
14
|
else
|
|
15
|
-
|
|
15
|
+
""
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
class << self
|
|
@@ -44,7 +44,8 @@ module Bootsnap
|
|
|
44
44
|
|
|
45
45
|
def walk(absolute_dir_path, relative_dir_path, &block)
|
|
46
46
|
Dir.foreach(absolute_dir_path) do |name|
|
|
47
|
-
next if name.start_with?(
|
|
47
|
+
next if name.start_with?(".")
|
|
48
|
+
|
|
48
49
|
relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
|
|
49
50
|
|
|
50
51
|
absolute_path = "#{absolute_dir_path}/#{name}"
|
|
@@ -58,7 +59,7 @@ module Bootsnap
|
|
|
58
59
|
end
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
if RUBY_VERSION >=
|
|
62
|
+
if RUBY_VERSION >= "3.1"
|
|
62
63
|
def os_path(path)
|
|
63
64
|
path.freeze
|
|
64
65
|
end
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require_relative('../explicit_require')
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
require_relative("../explicit_require")
|
|
4
|
+
|
|
5
|
+
Bootsnap::ExplicitRequire.with_gems("msgpack") { require("msgpack") }
|
|
6
6
|
|
|
7
7
|
module Bootsnap
|
|
8
8
|
module LoadPathCache
|
|
9
9
|
class Store
|
|
10
|
+
VERSION_KEY = "__bootsnap_ruby_version__"
|
|
11
|
+
CURRENT_VERSION = "#{RUBY_REVISION}-#{RUBY_PLATFORM}".freeze # rubocop:disable Style/RedundantFreeze
|
|
12
|
+
|
|
10
13
|
NestedTransactionError = Class.new(StandardError)
|
|
11
14
|
SetOutsideTransactionNotAllowed = Class.new(StandardError)
|
|
12
15
|
|
|
@@ -23,6 +26,7 @@ module Bootsnap
|
|
|
23
26
|
|
|
24
27
|
def fetch(key)
|
|
25
28
|
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
|
29
|
+
|
|
26
30
|
v = get(key)
|
|
27
31
|
unless v
|
|
28
32
|
@dirty = true
|
|
@@ -34,6 +38,7 @@ module Bootsnap
|
|
|
34
38
|
|
|
35
39
|
def set(key, value)
|
|
36
40
|
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
|
41
|
+
|
|
37
42
|
if value != @data[key]
|
|
38
43
|
@dirty = true
|
|
39
44
|
@data[key] = value
|
|
@@ -42,6 +47,7 @@ module Bootsnap
|
|
|
42
47
|
|
|
43
48
|
def transaction
|
|
44
49
|
raise(NestedTransactionError) if @txn_mutex.owned?
|
|
50
|
+
|
|
45
51
|
@txn_mutex.synchronize do
|
|
46
52
|
begin
|
|
47
53
|
yield
|
|
@@ -62,15 +68,20 @@ module Bootsnap
|
|
|
62
68
|
|
|
63
69
|
def load_data
|
|
64
70
|
@data = begin
|
|
65
|
-
File.open(@store_path, encoding: Encoding::BINARY) do |io|
|
|
71
|
+
data = File.open(@store_path, encoding: Encoding::BINARY) do |io|
|
|
66
72
|
MessagePack.load(io)
|
|
67
73
|
end
|
|
74
|
+
if data.is_a?(Hash) && data[VERSION_KEY] == CURRENT_VERSION
|
|
75
|
+
data
|
|
76
|
+
else
|
|
77
|
+
default_data
|
|
78
|
+
end
|
|
68
79
|
# handle malformed data due to upgrade incompatibility
|
|
69
80
|
rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
|
|
70
|
-
|
|
81
|
+
default_data
|
|
71
82
|
rescue ArgumentError => error
|
|
72
83
|
if error.message =~ /negative array size/
|
|
73
|
-
|
|
84
|
+
default_data
|
|
74
85
|
else
|
|
75
86
|
raise
|
|
76
87
|
end
|
|
@@ -78,9 +89,11 @@ module Bootsnap
|
|
|
78
89
|
end
|
|
79
90
|
|
|
80
91
|
def dump_data
|
|
92
|
+
require "fileutils" unless defined? FileUtils
|
|
93
|
+
|
|
81
94
|
# Change contents atomically so other processes can't get invalid
|
|
82
95
|
# caches if they read at an inopportune time.
|
|
83
|
-
tmp = "#{@store_path}.#{Process.pid}.#{(rand *
|
|
96
|
+
tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100_000).to_i}.tmp"
|
|
84
97
|
FileUtils.mkpath(File.dirname(tmp))
|
|
85
98
|
exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
|
|
86
99
|
# `encoding:` looks redundant wrt `binwrite`, but necessary on windows
|
|
@@ -93,6 +106,10 @@ module Bootsnap
|
|
|
93
106
|
retry
|
|
94
107
|
rescue SystemCallError
|
|
95
108
|
end
|
|
109
|
+
|
|
110
|
+
def default_data
|
|
111
|
+
{VERSION_KEY => CURRENT_VERSION}
|
|
112
|
+
end
|
|
96
113
|
end
|
|
97
114
|
end
|
|
98
115
|
end
|
|
@@ -5,9 +5,9 @@ module Bootsnap
|
|
|
5
5
|
ReturnFalse = Class.new(StandardError)
|
|
6
6
|
FallbackScan = Class.new(StandardError)
|
|
7
7
|
|
|
8
|
-
DOT_RB =
|
|
9
|
-
DOT_SO =
|
|
10
|
-
SLASH =
|
|
8
|
+
DOT_RB = ".rb"
|
|
9
|
+
DOT_SO = ".so"
|
|
10
|
+
SLASH = "/"
|
|
11
11
|
|
|
12
12
|
# If a NameError happens several levels deep, don't re-handle it
|
|
13
13
|
# all the way up the chain: mark it once and bubble it up without
|
|
@@ -15,7 +15,7 @@ module Bootsnap
|
|
|
15
15
|
ERROR_TAG_IVAR = :@__bootsnap_rescued
|
|
16
16
|
|
|
17
17
|
DL_EXTENSIONS = ::RbConfig::CONFIG
|
|
18
|
-
.values_at(
|
|
18
|
+
.values_at("DLEXT", "DLEXT2")
|
|
19
19
|
.reject { |ext| !ext || ext.empty? }
|
|
20
20
|
.map { |ext| ".#{ext}" }
|
|
21
21
|
.freeze
|
|
@@ -42,24 +42,24 @@ module Bootsnap
|
|
|
42
42
|
@realpath_cache = RealpathCache.new
|
|
43
43
|
|
|
44
44
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
|
45
|
-
require_relative(
|
|
46
|
-
require_relative(
|
|
45
|
+
require_relative("load_path_cache/core_ext/kernel_require")
|
|
46
|
+
require_relative("load_path_cache/core_ext/loaded_features")
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def supported?
|
|
50
|
-
RUBY_ENGINE ==
|
|
51
|
-
|
|
50
|
+
RUBY_ENGINE == "ruby" &&
|
|
51
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
if Bootsnap::LoadPathCache.supported?
|
|
58
|
-
require_relative(
|
|
59
|
-
require_relative(
|
|
60
|
-
require_relative(
|
|
61
|
-
require_relative(
|
|
62
|
-
require_relative(
|
|
63
|
-
require_relative(
|
|
64
|
-
require_relative(
|
|
58
|
+
require_relative("load_path_cache/path_scanner")
|
|
59
|
+
require_relative("load_path_cache/path")
|
|
60
|
+
require_relative("load_path_cache/cache")
|
|
61
|
+
require_relative("load_path_cache/store")
|
|
62
|
+
require_relative("load_path_cache/change_observer")
|
|
63
|
+
require_relative("load_path_cache/loaded_features_index")
|
|
64
|
+
require_relative("load_path_cache/realpath_cache")
|
|
65
65
|
end
|
data/lib/bootsnap/setup.rb
CHANGED
data/lib/bootsnap/version.rb
CHANGED