bootsnap 1.10.2 → 1.11.1
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 +39 -4
- data/ext/bootsnap/bootsnap.c +9 -0
- data/lib/bootsnap/compile_cache/yaml.rb +84 -35
- data/lib/bootsnap/compile_cache.rb +4 -3
- data/lib/bootsnap/load_path_cache/cache.rb +4 -0
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +3 -12
- data/lib/bootsnap/load_path_cache/path.rb +24 -2
- data/lib/bootsnap/load_path_cache/store.rb +26 -8
- data/lib/bootsnap/load_path_cache.rb +1 -3
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +107 -96
- metadata +3 -4
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cc142349008790c310bcbc875a437996403242e579f85fb48f46eceecb383ad
|
4
|
+
data.tar.gz: fa1d21fafa8a4fa4d44ced76d597b773d3321e077dc815ccbcae3fe9dc4e661a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81445538692ae0dc34b7309c44195fce5c864e508afe6eed4124d3d87bc1bfa07fd2ddb2c602faa2409f1537f8da63b885fd692bdc74e961febdd2a39e6e1919
|
7
|
+
data.tar.gz: 6389ce3a667c528660976665ce8184fb61d638da6cd4040e7b2d55441679574bdf691e4991294396c19a6e79bbf185ba1cdca848d5ea499a5b8e197dca143a5f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,40 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 1.11.1
|
4
|
+
|
5
|
+
* Fix the `can't modify frozen Hash` error on load path cache mutation. See #411.
|
6
|
+
|
7
|
+
# 1.11.0
|
8
|
+
|
9
|
+
* Drop dependency on `fileutils`.
|
10
|
+
|
11
|
+
* Better respect `Kernel#require` duck typing. While it almost never comes up in practice, `Kernel#require`
|
12
|
+
follow a fairly intricate duck-typing protocol on its argument implemented as `rb_get_path(VALUE)` in MRI.
|
13
|
+
So when applicable we bind `rb_get_path` and use it for improved compatibility. See #396 and #406.
|
14
|
+
|
15
|
+
* Get rid of the `Kernel.require_relative` decorator by resolving `$LOAD_PATH` members to their real path.
|
16
|
+
This way we handle symlinks in `$LOAD_PATH` much more efficiently. See #402 for the detailed explanation.
|
17
|
+
|
18
|
+
* Drop support for Ruby 2.3 (to allow getting rid of the `Kernel.require_relative` decorator).
|
19
|
+
|
20
|
+
# 1.10.3
|
21
|
+
|
22
|
+
* Fix Regexp and Date type support in YAML compile cache. (#400)
|
23
|
+
|
24
|
+
* Improve the YAML compile cache to support `UTF-8` symbols. (#398, #399)
|
25
|
+
[The default `MessagePack` symbol serializer assumes all symbols are ASCII](https://github.com/msgpack/msgpack-ruby/pull/211),
|
26
|
+
because of this, non-ASCII compatible symbol would be restored with `ASCII_8BIT` encoding (AKA `BINARY`).
|
27
|
+
Bootsnap now properly cache them in `UTF-8`.
|
28
|
+
|
29
|
+
Note that the above only apply for actual YAML symbols (e..g `--- :foo`).
|
30
|
+
The issue is still present for string keys parsed with `YAML.load_file(..., symbolize_names: true)`, that is a bug
|
31
|
+
in `msgpack` that will hopefully be solved soon, see: https://github.com/msgpack/msgpack-ruby/pull/246
|
32
|
+
|
33
|
+
* Entirely disable the YAML compile cache if `Encoding.default_internal` is set to an encoding not supported by `msgpack`. (#398)
|
34
|
+
`Psych` coerce strings to `Encoding.default_internal`, but `MessagePack` doesn't. So in this scenario we can't provide
|
35
|
+
YAML caching at all without returning the strings in the wrong encoding.
|
36
|
+
This never came up in practice but might as well be safe.
|
37
|
+
|
3
38
|
# 1.10.2
|
4
39
|
|
5
40
|
* Reduce the `Kernel.require` extra stack frames some more. Now bootsnap should only add one extra frame per `require` call.
|
@@ -22,7 +57,7 @@
|
|
22
57
|
Since `1.8.0`, `YAML.load_file` was no longer cached when Psych 4 was used. This is because `load_file` loads
|
23
58
|
in safe mode by default, so the Bootsnap cache could defeat that safety.
|
24
59
|
Now when precompiling YAML files, Bootsnap first try to parse them in safe mode, and if it can't fallback to unsafe mode,
|
25
|
-
and the cache contains a flag that records
|
60
|
+
and the cache contains a flag that records whether it was generated in safe mode or not.
|
26
61
|
`YAML.unsafe_load_file` will use safe caches just fine, but `YAML.load_file` will fallback to uncached YAML parsing
|
27
62
|
if the cache was generated using unsafe parsing.
|
28
63
|
|
@@ -63,7 +98,7 @@
|
|
63
98
|
|
64
99
|
# 1.8.0
|
65
100
|
|
66
|
-
* Improve support for
|
101
|
+
* Improve support for Psych 4. (#368)
|
67
102
|
|
68
103
|
# 1.7.7
|
69
104
|
|
@@ -81,8 +116,8 @@
|
|
81
116
|
|
82
117
|
# 1.7.4
|
83
118
|
|
84
|
-
* Stop raising errors when
|
85
|
-
if somehow it can't be saved,
|
119
|
+
* Stop raising errors when encountering various file system errors. The cache is now best effort,
|
120
|
+
if somehow it can't be saved, bootsnap will gracefully fallback to the original operation (e.g. `Kernel.require`).
|
86
121
|
(#353, #177, #262)
|
87
122
|
|
88
123
|
# 1.7.3
|
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -135,6 +135,12 @@ bs_rb_coverage_running(VALUE self)
|
|
135
135
|
return RTEST(cov) ? Qtrue : Qfalse;
|
136
136
|
}
|
137
137
|
|
138
|
+
static VALUE
|
139
|
+
bs_rb_get_path(VALUE self, VALUE fname)
|
140
|
+
{
|
141
|
+
return rb_get_path(fname);
|
142
|
+
}
|
143
|
+
|
138
144
|
/*
|
139
145
|
* Ruby C extensions are initialized by calling Init_<extname>.
|
140
146
|
*
|
@@ -146,6 +152,9 @@ void
|
|
146
152
|
Init_bootsnap(void)
|
147
153
|
{
|
148
154
|
rb_mBootsnap = rb_define_module("Bootsnap");
|
155
|
+
|
156
|
+
rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
|
157
|
+
|
149
158
|
rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
|
150
159
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
151
160
|
rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
|
@@ -5,7 +5,15 @@ require("bootsnap/bootsnap")
|
|
5
5
|
module Bootsnap
|
6
6
|
module CompileCache
|
7
7
|
module YAML
|
8
|
-
|
8
|
+
Uncompilable = Class.new(StandardError)
|
9
|
+
UnsupportedTags = Class.new(Uncompilable)
|
10
|
+
|
11
|
+
SUPPORTED_INTERNAL_ENCODINGS = [
|
12
|
+
nil, # UTF-8
|
13
|
+
Encoding::UTF_8,
|
14
|
+
Encoding::ASCII,
|
15
|
+
Encoding::BINARY,
|
16
|
+
].freeze
|
9
17
|
|
10
18
|
class << self
|
11
19
|
attr_accessor(:msgpack_factory, :supported_options)
|
@@ -16,7 +24,9 @@ module Bootsnap
|
|
16
24
|
end
|
17
25
|
|
18
26
|
def precompile(path)
|
19
|
-
|
27
|
+
return false unless CompileCache::YAML.supported_internal_encoding?
|
28
|
+
|
29
|
+
CompileCache::Native.precompile(
|
20
30
|
cache_dir,
|
21
31
|
path.to_s,
|
22
32
|
@implementation,
|
@@ -29,6 +39,21 @@ module Bootsnap
|
|
29
39
|
::YAML.singleton_class.prepend(@implementation::Patch)
|
30
40
|
end
|
31
41
|
|
42
|
+
# Psych coerce strings to `Encoding.default_internal` but Message Pack only support
|
43
|
+
# UTF-8, US-ASCII and BINARY. So if Encoding.default_internal is set to anything else
|
44
|
+
# we can't safely use the cache
|
45
|
+
def supported_internal_encoding?
|
46
|
+
SUPPORTED_INTERNAL_ENCODINGS.include?(Encoding.default_internal)
|
47
|
+
end
|
48
|
+
|
49
|
+
module EncodingAwareSymbols
|
50
|
+
extend self
|
51
|
+
|
52
|
+
def unpack(payload)
|
53
|
+
(+payload).force_encoding(Encoding::UTF_8).to_sym
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
32
57
|
def init!
|
33
58
|
require("yaml")
|
34
59
|
require("msgpack")
|
@@ -43,7 +68,12 @@ module Bootsnap
|
|
43
68
|
# We want them to roundtrip cleanly, so we use a custom factory.
|
44
69
|
# see: https://github.com/msgpack/msgpack-ruby/pull/122
|
45
70
|
factory = MessagePack::Factory.new
|
46
|
-
factory.register_type(
|
71
|
+
factory.register_type(
|
72
|
+
0x00,
|
73
|
+
Symbol,
|
74
|
+
packer: :to_msgpack_ext,
|
75
|
+
unpacker: EncodingAwareSymbols.method(:unpack).to_proc,
|
76
|
+
)
|
47
77
|
|
48
78
|
if defined? MessagePack::Timestamp
|
49
79
|
factory.register_type(
|
@@ -119,13 +149,15 @@ module Bootsnap
|
|
119
149
|
extend self
|
120
150
|
|
121
151
|
def input_to_storage(contents, _)
|
122
|
-
obj =
|
152
|
+
obj = ::YAML.unsafe_load(contents)
|
123
153
|
packer = CompileCache::YAML.msgpack_factory.packer
|
124
154
|
packer.pack(false) # not safe loaded
|
125
|
-
|
155
|
+
begin
|
156
|
+
packer.pack(obj)
|
157
|
+
rescue NoMethodError, RangeError
|
158
|
+
return UNCOMPILABLE # The object included things that we can't serialize
|
159
|
+
end
|
126
160
|
packer.to_s
|
127
|
-
rescue NoMethodError, RangeError, UnsupportedTags
|
128
|
-
UNCOMPILABLE # The object included things that we can't serialize
|
129
161
|
end
|
130
162
|
|
131
163
|
def storage_to_output(data, kwargs)
|
@@ -148,13 +180,20 @@ module Bootsnap
|
|
148
180
|
extend self
|
149
181
|
|
150
182
|
def input_to_storage(contents, _)
|
151
|
-
obj =
|
183
|
+
obj = begin
|
184
|
+
CompileCache::YAML.strict_load(contents)
|
185
|
+
rescue Psych::DisallowedClass, Psych::BadAlias, Uncompilable
|
186
|
+
return UNCOMPILABLE
|
187
|
+
end
|
188
|
+
|
152
189
|
packer = CompileCache::YAML.msgpack_factory.packer
|
153
190
|
packer.pack(true) # safe loaded
|
154
|
-
|
191
|
+
begin
|
192
|
+
packer.pack(obj)
|
193
|
+
rescue NoMethodError, RangeError
|
194
|
+
return UNCOMPILABLE
|
195
|
+
end
|
155
196
|
packer.to_s
|
156
|
-
rescue NoMethodError, RangeError, Psych::DisallowedClass, Psych::BadAlias
|
157
|
-
UNCOMPILABLE # The object included things that we can't serialize
|
158
197
|
end
|
159
198
|
|
160
199
|
def storage_to_output(data, kwargs)
|
@@ -179,44 +218,48 @@ module Bootsnap
|
|
179
218
|
|
180
219
|
module Patch
|
181
220
|
def load_file(path, *args)
|
221
|
+
return super unless CompileCache::YAML.supported_internal_encoding?
|
222
|
+
|
182
223
|
return super if args.size > 1
|
183
224
|
|
184
225
|
if (kwargs = args.first)
|
185
226
|
return super unless kwargs.is_a?(Hash)
|
186
|
-
return super unless (kwargs.keys -
|
227
|
+
return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
|
187
228
|
end
|
188
229
|
|
189
230
|
begin
|
190
|
-
|
191
|
-
|
231
|
+
CompileCache::Native.fetch(
|
232
|
+
CompileCache::YAML.cache_dir,
|
192
233
|
File.realpath(path),
|
193
|
-
|
234
|
+
CompileCache::YAML::Psych4::SafeLoad,
|
194
235
|
kwargs,
|
195
236
|
)
|
196
237
|
rescue Errno::EACCES
|
197
|
-
|
238
|
+
CompileCache.permission_error(path)
|
198
239
|
end
|
199
240
|
end
|
200
241
|
|
201
242
|
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
202
243
|
|
203
244
|
def unsafe_load_file(path, *args)
|
245
|
+
return super unless CompileCache::YAML.supported_internal_encoding?
|
246
|
+
|
204
247
|
return super if args.size > 1
|
205
248
|
|
206
249
|
if (kwargs = args.first)
|
207
250
|
return super unless kwargs.is_a?(Hash)
|
208
|
-
return super unless (kwargs.keys -
|
251
|
+
return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
|
209
252
|
end
|
210
253
|
|
211
254
|
begin
|
212
|
-
|
213
|
-
|
255
|
+
CompileCache::Native.fetch(
|
256
|
+
CompileCache::YAML.cache_dir,
|
214
257
|
File.realpath(path),
|
215
|
-
|
258
|
+
CompileCache::YAML::Psych4::UnsafeLoad,
|
216
259
|
kwargs,
|
217
260
|
)
|
218
261
|
rescue Errno::EACCES
|
219
|
-
|
262
|
+
CompileCache.permission_error(path)
|
220
263
|
end
|
221
264
|
end
|
222
265
|
|
@@ -228,13 +271,15 @@ module Bootsnap
|
|
228
271
|
extend self
|
229
272
|
|
230
273
|
def input_to_storage(contents, _)
|
231
|
-
obj =
|
274
|
+
obj = ::YAML.load(contents)
|
232
275
|
packer = CompileCache::YAML.msgpack_factory.packer
|
233
276
|
packer.pack(false) # not safe loaded
|
234
|
-
|
277
|
+
begin
|
278
|
+
packer.pack(obj)
|
279
|
+
rescue NoMethodError, RangeError
|
280
|
+
return UNCOMPILABLE # The object included things that we can't serialize
|
281
|
+
end
|
235
282
|
packer.to_s
|
236
|
-
rescue NoMethodError, RangeError, UnsupportedTags
|
237
|
-
UNCOMPILABLE # The object included things that we can't serialize
|
238
283
|
end
|
239
284
|
|
240
285
|
def storage_to_output(data, kwargs)
|
@@ -253,44 +298,48 @@ module Bootsnap
|
|
253
298
|
|
254
299
|
module Patch
|
255
300
|
def load_file(path, *args)
|
301
|
+
return super unless CompileCache::YAML.supported_internal_encoding?
|
302
|
+
|
256
303
|
return super if args.size > 1
|
257
304
|
|
258
305
|
if (kwargs = args.first)
|
259
306
|
return super unless kwargs.is_a?(Hash)
|
260
|
-
return super unless (kwargs.keys -
|
307
|
+
return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
|
261
308
|
end
|
262
309
|
|
263
310
|
begin
|
264
|
-
|
265
|
-
|
311
|
+
CompileCache::Native.fetch(
|
312
|
+
CompileCache::YAML.cache_dir,
|
266
313
|
File.realpath(path),
|
267
|
-
|
314
|
+
CompileCache::YAML::Psych3,
|
268
315
|
kwargs,
|
269
316
|
)
|
270
317
|
rescue Errno::EACCES
|
271
|
-
|
318
|
+
CompileCache.permission_error(path)
|
272
319
|
end
|
273
320
|
end
|
274
321
|
|
275
322
|
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
276
323
|
|
277
324
|
def unsafe_load_file(path, *args)
|
325
|
+
return super unless CompileCache::YAML.supported_internal_encoding?
|
326
|
+
|
278
327
|
return super if args.size > 1
|
279
328
|
|
280
329
|
if (kwargs = args.first)
|
281
330
|
return super unless kwargs.is_a?(Hash)
|
282
|
-
return super unless (kwargs.keys -
|
331
|
+
return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
|
283
332
|
end
|
284
333
|
|
285
334
|
begin
|
286
|
-
|
287
|
-
|
335
|
+
CompileCache::Native.fetch(
|
336
|
+
CompileCache::YAML.cache_dir,
|
288
337
|
File.realpath(path),
|
289
|
-
|
338
|
+
CompileCache::YAML::Psych3,
|
290
339
|
kwargs,
|
291
340
|
)
|
292
341
|
rescue Errno::EACCES
|
293
|
-
|
342
|
+
CompileCache.permission_error(path)
|
294
343
|
end
|
295
344
|
end
|
296
345
|
|
@@ -3,6 +3,9 @@
|
|
3
3
|
module Bootsnap
|
4
4
|
module CompileCache
|
5
5
|
UNCOMPILABLE = BasicObject.new
|
6
|
+
def UNCOMPILABLE.inspect
|
7
|
+
"<Bootsnap::UNCOMPILABLE>"
|
8
|
+
end
|
6
9
|
|
7
10
|
Error = Class.new(StandardError)
|
8
11
|
PermissionError = Class.new(Error)
|
@@ -47,9 +50,7 @@ module Bootsnap
|
|
47
50
|
|
48
51
|
def self.supported?
|
49
52
|
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
|
50
|
-
RUBY_ENGINE == "ruby" &&
|
51
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
|
52
|
-
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
53
|
+
RUBY_ENGINE == "ruby" && RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
@@ -142,6 +142,8 @@ module Bootsnap
|
|
142
142
|
@has_relative_paths = true if p.relative?
|
143
143
|
next if p.non_directory?
|
144
144
|
|
145
|
+
p = p.to_realpath
|
146
|
+
|
145
147
|
expanded_path = p.expanded_path
|
146
148
|
entries, dirs = p.entries_and_dirs(@store)
|
147
149
|
# push -> low precedence -> set only if unset
|
@@ -157,6 +159,8 @@ module Bootsnap
|
|
157
159
|
p = Path.new(path)
|
158
160
|
next if p.non_directory?
|
159
161
|
|
162
|
+
p = p.to_realpath
|
163
|
+
|
160
164
|
expanded_path = p.expanded_path
|
161
165
|
entries, dirs = p.entries_and_dirs(@store)
|
162
166
|
# unshift -> high precedence -> unconditional set
|
@@ -6,7 +6,7 @@ module Kernel
|
|
6
6
|
alias_method(:require_without_bootsnap, :require)
|
7
7
|
|
8
8
|
def require(path)
|
9
|
-
string_path = path
|
9
|
+
string_path = Bootsnap.rb_get_path(path)
|
10
10
|
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
|
11
11
|
|
12
12
|
resolved = Bootsnap::LoadPathCache.load_path_cache.find(string_path)
|
@@ -33,18 +33,9 @@ module Kernel
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
alias_method(:require_relative_without_bootsnap, :require_relative)
|
37
|
-
def require_relative(path)
|
38
|
-
location = caller_locations(1..1).first
|
39
|
-
realpath = Bootsnap::LoadPathCache.realpath_cache.call(
|
40
|
-
location.absolute_path || location.path, path
|
41
|
-
)
|
42
|
-
require(realpath)
|
43
|
-
end
|
44
|
-
|
45
36
|
alias_method(:load_without_bootsnap, :load)
|
46
37
|
def load(path, wrap = false)
|
47
|
-
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path, try_extensions: false))
|
38
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path), try_extensions: false))
|
48
39
|
load_without_bootsnap(resolved, wrap)
|
49
40
|
else
|
50
41
|
load_without_bootsnap(path, wrap)
|
@@ -62,7 +53,7 @@ class Module
|
|
62
53
|
# The challenge is that we don't control the point at which the entry gets
|
63
54
|
# added to $LOADED_FEATURES and won't be able to hook that modification
|
64
55
|
# since it's done in C-land.
|
65
|
-
resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
|
56
|
+
resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path))
|
66
57
|
if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
|
67
58
|
autoload_without_bootsnap(const, path)
|
68
59
|
elsif resolved == false
|
@@ -21,8 +21,26 @@ module Bootsnap
|
|
21
21
|
|
22
22
|
attr_reader(:path)
|
23
23
|
|
24
|
-
def initialize(path)
|
24
|
+
def initialize(path, real: false)
|
25
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
|
+
Path.new(realpath, real: true)
|
40
|
+
else
|
41
|
+
@real = true
|
42
|
+
self
|
43
|
+
end
|
26
44
|
end
|
27
45
|
|
28
46
|
# True if the path exists, but represents a non-directory object
|
@@ -62,7 +80,11 @@ module Bootsnap
|
|
62
80
|
end
|
63
81
|
|
64
82
|
def expanded_path
|
65
|
-
|
83
|
+
if @real
|
84
|
+
path
|
85
|
+
else
|
86
|
+
@expanded_path ||= File.expand_path(path).freeze
|
87
|
+
end
|
66
88
|
end
|
67
89
|
|
68
90
|
private
|
@@ -29,8 +29,8 @@ module Bootsnap
|
|
29
29
|
|
30
30
|
v = get(key)
|
31
31
|
unless v
|
32
|
-
@dirty = true
|
33
32
|
v = yield
|
33
|
+
mark_for_mutation!
|
34
34
|
@data[key] = v
|
35
35
|
end
|
36
36
|
v
|
@@ -40,7 +40,7 @@ module Bootsnap
|
|
40
40
|
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
41
41
|
|
42
42
|
if value != @data[key]
|
43
|
-
|
43
|
+
mark_for_mutation!
|
44
44
|
@data[key] = value
|
45
45
|
end
|
46
46
|
end
|
@@ -59,6 +59,11 @@ module Bootsnap
|
|
59
59
|
|
60
60
|
private
|
61
61
|
|
62
|
+
def mark_for_mutation!
|
63
|
+
@dirty = true
|
64
|
+
@data = @data.dup if @data.frozen?
|
65
|
+
end
|
66
|
+
|
62
67
|
def commit_transaction
|
63
68
|
if @dirty
|
64
69
|
dump_data
|
@@ -69,7 +74,7 @@ module Bootsnap
|
|
69
74
|
def load_data
|
70
75
|
@data = begin
|
71
76
|
data = File.open(@store_path, encoding: Encoding::BINARY) do |io|
|
72
|
-
MessagePack.load(io)
|
77
|
+
MessagePack.load(io, freeze: true)
|
73
78
|
end
|
74
79
|
if data.is_a?(Hash) && data[VERSION_KEY] == CURRENT_VERSION
|
75
80
|
data
|
@@ -89,19 +94,17 @@ module Bootsnap
|
|
89
94
|
end
|
90
95
|
|
91
96
|
def dump_data
|
92
|
-
require "fileutils" unless defined? FileUtils
|
93
|
-
|
94
97
|
# Change contents atomically so other processes can't get invalid
|
95
98
|
# caches if they read at an inopportune time.
|
96
99
|
tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100_000).to_i}.tmp"
|
97
|
-
|
100
|
+
mkdir_p(File.dirname(tmp))
|
98
101
|
exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
|
99
102
|
# `encoding:` looks redundant wrt `binwrite`, but necessary on windows
|
100
103
|
# because binary is part of mode.
|
101
104
|
File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
|
102
|
-
MessagePack.dump(@data, io
|
105
|
+
MessagePack.dump(@data, io)
|
103
106
|
end
|
104
|
-
|
107
|
+
File.rename(tmp, @store_path)
|
105
108
|
rescue Errno::EEXIST
|
106
109
|
retry
|
107
110
|
rescue SystemCallError
|
@@ -110,6 +113,21 @@ module Bootsnap
|
|
110
113
|
def default_data
|
111
114
|
{VERSION_KEY => CURRENT_VERSION}
|
112
115
|
end
|
116
|
+
|
117
|
+
def mkdir_p(path)
|
118
|
+
stack = []
|
119
|
+
until File.directory?(path)
|
120
|
+
stack.push path
|
121
|
+
path = File.dirname(path)
|
122
|
+
end
|
123
|
+
stack.reverse_each do |dir|
|
124
|
+
begin
|
125
|
+
Dir.mkdir(dir)
|
126
|
+
rescue SystemCallError
|
127
|
+
raise unless File.directory?(dir)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
113
131
|
end
|
114
132
|
end
|
115
133
|
end
|
@@ -22,7 +22,7 @@ module Bootsnap
|
|
22
22
|
CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
|
23
23
|
|
24
24
|
class << self
|
25
|
-
attr_reader(:load_path_cache, :loaded_features_index
|
25
|
+
attr_reader(:load_path_cache, :loaded_features_index)
|
26
26
|
|
27
27
|
def setup(cache_path:, development_mode:)
|
28
28
|
unless supported?
|
@@ -33,7 +33,6 @@ module Bootsnap
|
|
33
33
|
store = Store.new(cache_path)
|
34
34
|
|
35
35
|
@loaded_features_index = LoadedFeaturesIndex.new
|
36
|
-
@realpath_cache = RealpathCache.new
|
37
36
|
|
38
37
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
39
38
|
require_relative("load_path_cache/core_ext/kernel_require")
|
@@ -55,5 +54,4 @@ if Bootsnap::LoadPathCache.supported?
|
|
55
54
|
require_relative("load_path_cache/store")
|
56
55
|
require_relative("load_path_cache/change_observer")
|
57
56
|
require_relative("load_path_cache/loaded_features_index")
|
58
|
-
require_relative("load_path_cache/realpath_cache")
|
59
57
|
end
|
data/lib/bootsnap/version.rb
CHANGED
data/lib/bootsnap.rb
CHANGED
@@ -10,128 +10,139 @@ module Bootsnap
|
|
10
10
|
|
11
11
|
class << self
|
12
12
|
attr_reader :logger
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.log!
|
16
|
-
self.logger = $stderr.method(:puts)
|
17
|
-
end
|
18
13
|
|
19
|
-
|
20
|
-
|
21
|
-
self.instrumentation = if logger.respond_to?(:debug)
|
22
|
-
->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") }
|
23
|
-
else
|
24
|
-
->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") }
|
14
|
+
def log!
|
15
|
+
self.logger = $stderr.method(:puts)
|
25
16
|
end
|
26
|
-
end
|
27
17
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
18
|
+
def logger=(logger)
|
19
|
+
@logger = logger
|
20
|
+
self.instrumentation = if logger.respond_to?(:debug)
|
21
|
+
->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") }
|
22
|
+
else
|
23
|
+
->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") }
|
24
|
+
end
|
32
25
|
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def self._instrument(event, path)
|
36
|
-
@instrumentation.call(event, path)
|
37
|
-
end
|
38
26
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
disable_trace: nil,
|
45
|
-
compile_cache_iseq: true,
|
46
|
-
compile_cache_yaml: true,
|
47
|
-
compile_cache_json: true
|
48
|
-
)
|
49
|
-
unless autoload_paths_cache.nil?
|
50
|
-
warn "[DEPRECATED] Bootsnap's `autoload_paths_cache:` option is deprecated and will be removed. " \
|
51
|
-
"If you use Zeitwerk this option is useless, and if you are still using the classic autoloader " \
|
52
|
-
"upgrading is recommended."
|
27
|
+
def instrumentation=(callback)
|
28
|
+
@instrumentation = callback
|
29
|
+
if respond_to?(:instrumentation_enabled=, true)
|
30
|
+
self.instrumentation_enabled = !!callback
|
31
|
+
end
|
53
32
|
end
|
54
33
|
|
55
|
-
|
56
|
-
|
57
|
-
"If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended."
|
34
|
+
def _instrument(event, path)
|
35
|
+
@instrumentation.call(event, path)
|
58
36
|
end
|
59
37
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
38
|
+
def setup(
|
39
|
+
cache_dir:,
|
40
|
+
development_mode: true,
|
41
|
+
load_path_cache: true,
|
42
|
+
autoload_paths_cache: nil,
|
43
|
+
disable_trace: nil,
|
44
|
+
compile_cache_iseq: true,
|
45
|
+
compile_cache_yaml: true,
|
46
|
+
compile_cache_json: true
|
47
|
+
)
|
48
|
+
unless autoload_paths_cache.nil?
|
49
|
+
warn "[DEPRECATED] Bootsnap's `autoload_paths_cache:` option is deprecated and will be removed. " \
|
50
|
+
"If you use Zeitwerk this option is useless, and if you are still using the classic autoloader " \
|
51
|
+
"upgrading is recommended."
|
52
|
+
end
|
64
53
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
54
|
+
unless disable_trace.nil?
|
55
|
+
warn "[DEPRECATED] Bootsnap's `disable_trace:` option is deprecated and will be removed. " \
|
56
|
+
"If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended."
|
57
|
+
end
|
58
|
+
|
59
|
+
if compile_cache_iseq && !iseq_cache_supported?
|
60
|
+
warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommened " \
|
61
|
+
"to turn `compile_cache_iseq` off on Ruby 2.5"
|
62
|
+
end
|
63
|
+
|
64
|
+
if load_path_cache
|
65
|
+
Bootsnap::LoadPathCache.setup(
|
66
|
+
cache_path: cache_dir + "/bootsnap/load-path-cache",
|
67
|
+
development_mode: development_mode,
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
Bootsnap::CompileCache.setup(
|
72
|
+
cache_dir: cache_dir + "/bootsnap/compile-cache",
|
73
|
+
iseq: compile_cache_iseq,
|
74
|
+
yaml: compile_cache_yaml,
|
75
|
+
json: compile_cache_json,
|
69
76
|
)
|
70
77
|
end
|
71
78
|
|
72
|
-
|
73
|
-
|
74
|
-
iseq: compile_cache_iseq,
|
75
|
-
yaml: compile_cache_yaml,
|
76
|
-
json: compile_cache_json,
|
77
|
-
)
|
78
|
-
end
|
79
|
+
def iseq_cache_supported?
|
80
|
+
return @iseq_cache_supported if defined? @iseq_cache_supported
|
79
81
|
|
80
|
-
|
81
|
-
|
82
|
+
ruby_version = Gem::Version.new(RUBY_VERSION)
|
83
|
+
@iseq_cache_supported = ruby_version < Gem::Version.new("2.5.0") || ruby_version >= Gem::Version.new("2.6.0")
|
84
|
+
end
|
82
85
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
+
def default_setup
|
87
|
+
env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["ENV"]
|
88
|
+
development_mode = ["", nil, "development"].include?(env)
|
86
89
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
+
unless ENV["DISABLE_BOOTSNAP"]
|
91
|
+
cache_dir = ENV["BOOTSNAP_CACHE_DIR"]
|
92
|
+
unless cache_dir
|
93
|
+
config_dir_frame = caller.detect do |line|
|
94
|
+
line.include?("/config/")
|
95
|
+
end
|
90
96
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
line.include?("/config/")
|
96
|
-
end
|
97
|
+
unless config_dir_frame
|
98
|
+
$stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
|
99
|
+
$stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
|
100
|
+
$stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
|
97
101
|
|
98
|
-
|
99
|
-
|
100
|
-
$stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
|
101
|
-
$stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
|
102
|
+
raise("couldn't infer bootsnap cache directory")
|
103
|
+
end
|
102
104
|
|
103
|
-
|
104
|
-
|
105
|
+
path = config_dir_frame.split(/:\d+:/).first
|
106
|
+
path = File.dirname(path) until File.basename(path) == "config"
|
107
|
+
app_root = File.dirname(path)
|
105
108
|
|
106
|
-
|
107
|
-
|
108
|
-
app_root = File.dirname(path)
|
109
|
+
cache_dir = File.join(app_root, "tmp", "cache")
|
110
|
+
end
|
109
111
|
|
110
|
-
|
112
|
+
setup(
|
113
|
+
cache_dir: cache_dir,
|
114
|
+
development_mode: development_mode,
|
115
|
+
load_path_cache: !ENV["DISABLE_BOOTSNAP_LOAD_PATH_CACHE"],
|
116
|
+
compile_cache_iseq: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"] && iseq_cache_supported?,
|
117
|
+
compile_cache_yaml: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
|
118
|
+
compile_cache_json: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
|
119
|
+
)
|
120
|
+
|
121
|
+
if ENV["BOOTSNAP_LOG"]
|
122
|
+
log!
|
123
|
+
end
|
111
124
|
end
|
125
|
+
end
|
112
126
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
)
|
121
|
-
|
122
|
-
if ENV["BOOTSNAP_LOG"]
|
123
|
-
log!
|
127
|
+
if RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
|
128
|
+
def absolute_path?(path)
|
129
|
+
path[1] == ":"
|
130
|
+
end
|
131
|
+
else
|
132
|
+
def absolute_path?(path)
|
133
|
+
path.start_with?("/")
|
124
134
|
end
|
125
135
|
end
|
126
|
-
end
|
127
136
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
path.start_with?("/")
|
137
|
+
# This is a semi-accurate ruby implementation of the native `rb_get_path(VALUE)` function.
|
138
|
+
# The native version is very intricate and may behave differently on windows etc.
|
139
|
+
# But we only use it for non-MRI platform.
|
140
|
+
def rb_get_path(fname)
|
141
|
+
path_path = fname.respond_to?(:to_path) ? fname.to_path : fname
|
142
|
+
String.try_convert(path_path) || raise(TypeError, "no implicit conversion of #{path_path.class} into String")
|
135
143
|
end
|
144
|
+
|
145
|
+
# Allow the C extension to redefine `rb_get_path` without warning.
|
146
|
+
alias_method :rb_get_path, :rb_get_path # rubocop:disable Lint/DuplicateMethods
|
136
147
|
end
|
137
148
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bootsnap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -57,7 +57,6 @@ files:
|
|
57
57
|
- lib/bootsnap/load_path_cache/loaded_features_index.rb
|
58
58
|
- lib/bootsnap/load_path_cache/path.rb
|
59
59
|
- lib/bootsnap/load_path_cache/path_scanner.rb
|
60
|
-
- lib/bootsnap/load_path_cache/realpath_cache.rb
|
61
60
|
- lib/bootsnap/load_path_cache/store.rb
|
62
61
|
- lib/bootsnap/setup.rb
|
63
62
|
- lib/bootsnap/version.rb
|
@@ -77,7 +76,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
76
|
requirements:
|
78
77
|
- - ">="
|
79
78
|
- !ruby/object:Gem::Version
|
80
|
-
version: 2.
|
79
|
+
version: 2.4.0
|
81
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
81
|
requirements:
|
83
82
|
- - ">="
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Bootsnap
|
4
|
-
module LoadPathCache
|
5
|
-
class RealpathCache
|
6
|
-
def initialize
|
7
|
-
@cache = Hash.new { |h, k| h[k] = realpath(*k) }
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(*key)
|
11
|
-
@cache[key]
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def realpath(caller_location, path)
|
17
|
-
base = File.dirname(caller_location)
|
18
|
-
abspath = File.expand_path(path, base).freeze
|
19
|
-
find_file(abspath)
|
20
|
-
end
|
21
|
-
|
22
|
-
def find_file(name)
|
23
|
-
return File.realpath(name).freeze if File.exist?(name)
|
24
|
-
|
25
|
-
CACHED_EXTENSIONS.each do |ext|
|
26
|
-
filename = "#{name}#{ext}"
|
27
|
-
return File.realpath(filename).freeze if File.exist?(filename)
|
28
|
-
end
|
29
|
-
name
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|