bootsnap 1.10.1 → 1.11.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 +44 -5
- data/ext/bootsnap/bootsnap.c +9 -0
- data/lib/bootsnap/compile_cache/json.rb +3 -1
- 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 +7 -3
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +24 -60
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +1 -1
- data/lib/bootsnap/load_path_cache/path.rb +24 -2
- data/lib/bootsnap/load_path_cache/store.rb +19 -6
- data/lib/bootsnap/load_path_cache.rb +2 -10
- 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: af127c58fb31e53309de406862116407d9f57d43514937d400a0f87c13080447
|
4
|
+
data.tar.gz: 497f3a471eed46b68e624f8cda87bc6d5e97d5a17bdf4cbeb2221019fb21d531
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32c3bba78bf66bb71b47d78e06592e4a670735d581c84043b4c125704fb91d4cff900da0e2884c910f3c66ddf9e31f4860a33718da978cc9f2719e32a9f1f617
|
7
|
+
data.tar.gz: 11a5bdee97b35fc99d346ae2cd44e95e8fc3cfd949bddc06a7561b7329fd42a0e63ef5e747a90fa599125100cc368d9ef6c9ad207a1a69fe51f6c32e71992611
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,47 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 1.11.0
|
4
|
+
|
5
|
+
* Drop dependency on `fileutils`.
|
6
|
+
|
7
|
+
* Better respect `Kernel#require` duck typing. While it almost never comes up in practice, `Kernel#require`
|
8
|
+
follow a fairly intricate duck-typing protocol on its argument implemented as `rb_get_path(VALUE)` in MRI.
|
9
|
+
So when applicable we bind `rb_get_path` and use it for improved compatibility. See #396 and #406.
|
10
|
+
|
11
|
+
* Get rid of the `Kernel.require_relative` decorator by resolving `$LOAD_PATH` members to their real path.
|
12
|
+
This way we handle symlinks in `$LOAD_PATH` much more efficiently. See #402 for the detailed explanation.
|
13
|
+
|
14
|
+
* Drop support for Ruby 2.3 (to allow getting rid of the `Kernel.require_relative` decorator).
|
15
|
+
|
16
|
+
# 1.10.3
|
17
|
+
|
18
|
+
* Fix Regexp and Date type support in YAML compile cache. (#400)
|
19
|
+
|
20
|
+
* Improve the YAML compile cache to support `UTF-8` symbols. (#398, #399)
|
21
|
+
[The default `MessagePack` symbol serializer assumes all symbols are ASCII](https://github.com/msgpack/msgpack-ruby/pull/211),
|
22
|
+
because of this, non-ASCII compatible symbol would be restored with `ASCII_8BIT` encoding (AKA `BINARY`).
|
23
|
+
Bootsnap now properly cache them in `UTF-8`.
|
24
|
+
|
25
|
+
Note that the above only apply for actual YAML symbols (e..g `--- :foo`).
|
26
|
+
The issue is still present for string keys parsed with `YAML.load_file(..., symbolize_names: true)`, that is a bug
|
27
|
+
in `msgpack` that will hopefully be solved soon, see: https://github.com/msgpack/msgpack-ruby/pull/246
|
28
|
+
|
29
|
+
* Entirely disable the YAML compile cache if `Encoding.default_internal` is set to an encoding not supported by `msgpack`. (#398)
|
30
|
+
`Psych` coerce strings to `Encoding.default_internal`, but `MessagePack` doesn't. So in this scenario we can't provide
|
31
|
+
YAML caching at all without returning the strings in the wrong encoding.
|
32
|
+
This never came up in practice but might as well be safe.
|
33
|
+
|
34
|
+
# 1.10.2
|
35
|
+
|
36
|
+
* Reduce the `Kernel.require` extra stack frames some more. Now bootsnap should only add one extra frame per `require` call.
|
37
|
+
|
38
|
+
* Better check `freeze` option support in JSON compile cache.
|
39
|
+
Previously `JSON.load_file(..., freeze: true)` would be cached even when the msgpack version is missing support for it.
|
40
|
+
|
3
41
|
# 1.10.1
|
4
42
|
|
5
|
-
* Fix `Kernel#autoload`'s fallback path always
|
43
|
+
* Fix `Kernel#autoload`'s fallback path always being executed.
|
44
|
+
|
6
45
|
* Consider `unlink` failing with `ENOENT` as a success.
|
7
46
|
|
8
47
|
# 1.10.0
|
@@ -14,7 +53,7 @@
|
|
14
53
|
Since `1.8.0`, `YAML.load_file` was no longer cached when Psych 4 was used. This is because `load_file` loads
|
15
54
|
in safe mode by default, so the Bootsnap cache could defeat that safety.
|
16
55
|
Now when precompiling YAML files, Bootsnap first try to parse them in safe mode, and if it can't fallback to unsafe mode,
|
17
|
-
and the cache contains a flag that records
|
56
|
+
and the cache contains a flag that records whether it was generated in safe mode or not.
|
18
57
|
`YAML.unsafe_load_file` will use safe caches just fine, but `YAML.load_file` will fallback to uncached YAML parsing
|
19
58
|
if the cache was generated using unsafe parsing.
|
20
59
|
|
@@ -55,7 +94,7 @@
|
|
55
94
|
|
56
95
|
# 1.8.0
|
57
96
|
|
58
|
-
* Improve support for
|
97
|
+
* Improve support for Psych 4. (#368)
|
59
98
|
|
60
99
|
# 1.7.7
|
61
100
|
|
@@ -73,8 +112,8 @@
|
|
73
112
|
|
74
113
|
# 1.7.4
|
75
114
|
|
76
|
-
* Stop raising errors when
|
77
|
-
if somehow it can't be saved,
|
115
|
+
* Stop raising errors when encountering various file system errors. The cache is now best effort,
|
116
|
+
if somehow it can't be saved, bootsnap will gracefully fallback to the original operation (e.g. `Kernel.require`).
|
78
117
|
(#353, #177, #262)
|
79
118
|
|
80
119
|
# 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"));
|
@@ -52,7 +52,9 @@ module Bootsnap
|
|
52
52
|
self.msgpack_factory = MessagePack::Factory.new
|
53
53
|
self.supported_options = [:symbolize_names]
|
54
54
|
if ::JSON.parse('["foo"]', freeze: true).first.frozen?
|
55
|
-
|
55
|
+
if MessagePack.load(MessagePack.dump("foo"), freeze: true).frozen?
|
56
|
+
self.supported_options = [:freeze]
|
57
|
+
end
|
56
58
|
end
|
57
59
|
supported_options.freeze
|
58
60
|
end
|
@@ -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
|
@@ -65,7 +65,7 @@ module Bootsnap
|
|
65
65
|
# returns false as if it were already loaded; however, there is no
|
66
66
|
# file to find on disk. We've pre-built a list of these, and we
|
67
67
|
# return false if any of them is loaded.
|
68
|
-
|
68
|
+
return false if BUILTIN_FEATURES.key?(feature)
|
69
69
|
|
70
70
|
# The feature wasn't found on our preliminary search through the index.
|
71
71
|
# We resolve this differently depending on what the extension was.
|
@@ -89,7 +89,7 @@ module Bootsnap
|
|
89
89
|
else
|
90
90
|
# other, unknown extension. For example, `.rake`. Since we haven't
|
91
91
|
# cached these, we legitimately need to run the load path search.
|
92
|
-
|
92
|
+
return FALLBACK_SCAN
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
@@ -97,7 +97,7 @@ module Bootsnap
|
|
97
97
|
# cases where the file doesn't appear to be on the load path. We should
|
98
98
|
# be able to detect newly-created files without rebooting the
|
99
99
|
# application.
|
100
|
-
|
100
|
+
return FALLBACK_SCAN if @development_mode
|
101
101
|
end
|
102
102
|
|
103
103
|
def unshift_paths(sender, *paths)
|
@@ -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
|
@@ -1,70 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Bootsnap
|
4
|
-
module LoadPathCache
|
5
|
-
module CoreExt
|
6
|
-
def self.make_load_error(path)
|
7
|
-
err = LoadError.new(+"cannot load such file -- #{path}")
|
8
|
-
err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
9
|
-
err.define_singleton_method(:path) { path }
|
10
|
-
err
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
3
|
module Kernel
|
17
4
|
module_function
|
18
5
|
|
19
6
|
alias_method(:require_without_bootsnap, :require)
|
20
7
|
|
21
8
|
def require(path)
|
22
|
-
|
23
|
-
string_path = path.to_s
|
9
|
+
string_path = Bootsnap.rb_get_path(path)
|
24
10
|
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
|
25
11
|
|
26
|
-
|
27
|
-
|
28
|
-
ret = require_without_bootsnap(resolved)
|
29
|
-
Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
|
30
|
-
return ret
|
31
|
-
end
|
32
|
-
|
33
|
-
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
34
|
-
rescue LoadError => error
|
35
|
-
error.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
36
|
-
raise(error)
|
37
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
38
|
-
false
|
39
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
40
|
-
fallback = true
|
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
|
44
|
-
if fallback
|
12
|
+
resolved = Bootsnap::LoadPathCache.load_path_cache.find(string_path)
|
13
|
+
if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
|
45
14
|
if (cursor = Bootsnap::LoadPathCache.loaded_features_index.cursor(string_path))
|
46
15
|
ret = require_without_bootsnap(path)
|
47
16
|
resolved = Bootsnap::LoadPathCache.loaded_features_index.identify(string_path, cursor)
|
48
17
|
Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
|
49
|
-
ret
|
50
|
-
else
|
51
|
-
require_without_bootsnap(path)
|
18
|
+
return ret
|
19
|
+
else
|
20
|
+
return require_without_bootsnap(path)
|
52
21
|
end
|
22
|
+
elsif false == resolved
|
23
|
+
return false
|
24
|
+
elsif resolved.nil?
|
25
|
+
error = LoadError.new(+"cannot load such file -- #{path}")
|
26
|
+
error.instance_variable_set(:@path, path)
|
27
|
+
raise error
|
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
|
53
33
|
end
|
54
34
|
end
|
55
35
|
|
56
|
-
alias_method(:require_relative_without_bootsnap, :require_relative)
|
57
|
-
def require_relative(path)
|
58
|
-
location = caller_locations(1..1).first
|
59
|
-
realpath = Bootsnap::LoadPathCache.realpath_cache.call(
|
60
|
-
location.absolute_path || location.path, path
|
61
|
-
)
|
62
|
-
require(realpath)
|
63
|
-
end
|
64
|
-
|
65
36
|
alias_method(:load_without_bootsnap, :load)
|
66
37
|
def load(path, wrap = false)
|
67
|
-
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))
|
68
39
|
load_without_bootsnap(resolved, wrap)
|
69
40
|
else
|
70
41
|
load_without_bootsnap(path, wrap)
|
@@ -75,7 +46,6 @@ end
|
|
75
46
|
class Module
|
76
47
|
alias_method(:autoload_without_bootsnap, :autoload)
|
77
48
|
def autoload(const, path)
|
78
|
-
fallback = false
|
79
49
|
# NOTE: This may defeat LoadedFeaturesIndex, but it's not immediately
|
80
50
|
# obvious how to make it work. This feels like a pretty niche case, unclear
|
81
51
|
# if it will ever burn anyone.
|
@@ -83,19 +53,13 @@ class Module
|
|
83
53
|
# The challenge is that we don't control the point at which the entry gets
|
84
54
|
# added to $LOADED_FEATURES and won't be able to hook that modification
|
85
55
|
# since it's done in C-land.
|
86
|
-
|
87
|
-
|
88
|
-
error.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
89
|
-
raise(error)
|
90
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
91
|
-
false
|
92
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
93
|
-
fallback = true
|
94
|
-
ensure
|
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
|
-
if fallback
|
56
|
+
resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path))
|
57
|
+
if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
|
98
58
|
autoload_without_bootsnap(const, path)
|
59
|
+
elsif resolved == false
|
60
|
+
return false
|
61
|
+
else
|
62
|
+
autoload_without_bootsnap(const, resolved || path)
|
99
63
|
end
|
100
64
|
end
|
101
65
|
end
|
@@ -92,7 +92,7 @@ module Bootsnap
|
|
92
92
|
# entry:
|
93
93
|
#
|
94
94
|
# If the user asked for e.g. `require 'bundler'`, and we went through the
|
95
|
-
# `
|
95
|
+
# `FALLBACK_SCAN` pathway in `kernel_require.rb` and therefore did not
|
96
96
|
# pass `long` (the full expanded absolute path), then we did are not able
|
97
97
|
# to confidently add the `bundler.rb` form to @lfi.
|
98
98
|
#
|
@@ -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
|
@@ -69,7 +69,7 @@ module Bootsnap
|
|
69
69
|
def load_data
|
70
70
|
@data = begin
|
71
71
|
data = File.open(@store_path, encoding: Encoding::BINARY) do |io|
|
72
|
-
MessagePack.load(io)
|
72
|
+
MessagePack.load(io, freeze: true)
|
73
73
|
end
|
74
74
|
if data.is_a?(Hash) && data[VERSION_KEY] == CURRENT_VERSION
|
75
75
|
data
|
@@ -89,19 +89,17 @@ module Bootsnap
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def dump_data
|
92
|
-
require "fileutils" unless defined? FileUtils
|
93
|
-
|
94
92
|
# Change contents atomically so other processes can't get invalid
|
95
93
|
# caches if they read at an inopportune time.
|
96
94
|
tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100_000).to_i}.tmp"
|
97
|
-
|
95
|
+
mkdir_p(File.dirname(tmp))
|
98
96
|
exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
|
99
97
|
# `encoding:` looks redundant wrt `binwrite`, but necessary on windows
|
100
98
|
# because binary is part of mode.
|
101
99
|
File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
|
102
|
-
MessagePack.dump(@data, io
|
100
|
+
MessagePack.dump(@data, io)
|
103
101
|
end
|
104
|
-
|
102
|
+
File.rename(tmp, @store_path)
|
105
103
|
rescue Errno::EEXIST
|
106
104
|
retry
|
107
105
|
rescue SystemCallError
|
@@ -110,6 +108,21 @@ module Bootsnap
|
|
110
108
|
def default_data
|
111
109
|
{VERSION_KEY => CURRENT_VERSION}
|
112
110
|
end
|
111
|
+
|
112
|
+
def mkdir_p(path)
|
113
|
+
stack = []
|
114
|
+
until File.directory?(path)
|
115
|
+
stack.push path
|
116
|
+
path = File.dirname(path)
|
117
|
+
end
|
118
|
+
stack.reverse_each do |dir|
|
119
|
+
begin
|
120
|
+
Dir.mkdir(dir)
|
121
|
+
rescue SystemCallError
|
122
|
+
raise unless File.directory?(dir)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
113
126
|
end
|
114
127
|
end
|
115
128
|
end
|
@@ -2,18 +2,12 @@
|
|
2
2
|
|
3
3
|
module Bootsnap
|
4
4
|
module LoadPathCache
|
5
|
-
|
6
|
-
FallbackScan = Class.new(StandardError)
|
5
|
+
FALLBACK_SCAN = BasicObject.new
|
7
6
|
|
8
7
|
DOT_RB = ".rb"
|
9
8
|
DOT_SO = ".so"
|
10
9
|
SLASH = "/"
|
11
10
|
|
12
|
-
# If a NameError happens several levels deep, don't re-handle it
|
13
|
-
# all the way up the chain: mark it once and bubble it up without
|
14
|
-
# more retries.
|
15
|
-
ERROR_TAG_IVAR = :@__bootsnap_rescued
|
16
|
-
|
17
11
|
DL_EXTENSIONS = ::RbConfig::CONFIG
|
18
12
|
.values_at("DLEXT", "DLEXT2")
|
19
13
|
.reject { |ext| !ext || ext.empty? }
|
@@ -28,7 +22,7 @@ module Bootsnap
|
|
28
22
|
CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
|
29
23
|
|
30
24
|
class << self
|
31
|
-
attr_reader(:load_path_cache, :loaded_features_index
|
25
|
+
attr_reader(:load_path_cache, :loaded_features_index)
|
32
26
|
|
33
27
|
def setup(cache_path:, development_mode:)
|
34
28
|
unless supported?
|
@@ -39,7 +33,6 @@ module Bootsnap
|
|
39
33
|
store = Store.new(cache_path)
|
40
34
|
|
41
35
|
@loaded_features_index = LoadedFeaturesIndex.new
|
42
|
-
@realpath_cache = RealpathCache.new
|
43
36
|
|
44
37
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
45
38
|
require_relative("load_path_cache/core_ext/kernel_require")
|
@@ -61,5 +54,4 @@ if Bootsnap::LoadPathCache.supported?
|
|
61
54
|
require_relative("load_path_cache/store")
|
62
55
|
require_relative("load_path_cache/change_observer")
|
63
56
|
require_relative("load_path_cache/loaded_features_index")
|
64
|
-
require_relative("load_path_cache/realpath_cache")
|
65
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.0
|
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
|