bootsnap 1.10.2 → 1.11.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22aca6043d2a55eb7747cb38aec8365879c37e3fbdbd0cdfdd040098dd945fdd
4
- data.tar.gz: 69085ad28c61b3dbffeab8e50a49f0655d05c9639cae9e4ba0a9cc2f2b7c9051
3
+ metadata.gz: 1cc142349008790c310bcbc875a437996403242e579f85fb48f46eceecb383ad
4
+ data.tar.gz: fa1d21fafa8a4fa4d44ced76d597b773d3321e077dc815ccbcae3fe9dc4e661a
5
5
  SHA512:
6
- metadata.gz: f63a81e86c721f737e5f29b9696abbcbc118e5bf703bf994dbf275d9939203d1eb6aebabc8ccad927f818b2bf9311d4174dde0338b7cd74f4e71e7e9727e6692
7
- data.tar.gz: 3209a8fcdbb446c0f32738f2f580e7adc573d556da298560718c23d25963dc3f9dcf8fd5794a79864bac203089aef6be3da7743fde8bcc906f469b20461bb7aa
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 wether it was generated in safe mode or not.
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 Pysch 4. (#368)
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 encoutering various file system errors. The cache is now best effort,
85
- if somehow it can't be saved, bootsnapp will gracefully fallback to the original operation (e.g. `Kernel.require`).
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
@@ -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
- UnsupportedTags = Class.new(StandardError)
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
- Bootsnap::CompileCache::Native.precompile(
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(0x00, Symbol)
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 = CompileCache::YAML.strict_load(contents)
152
+ obj = ::YAML.unsafe_load(contents)
123
153
  packer = CompileCache::YAML.msgpack_factory.packer
124
154
  packer.pack(false) # not safe loaded
125
- packer.pack(obj)
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 = ::YAML.load(contents)
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
- packer.pack(obj)
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 - ::Bootsnap::CompileCache::YAML.supported_options).empty?
227
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
187
228
  end
188
229
 
189
230
  begin
190
- ::Bootsnap::CompileCache::Native.fetch(
191
- Bootsnap::CompileCache::YAML.cache_dir,
231
+ CompileCache::Native.fetch(
232
+ CompileCache::YAML.cache_dir,
192
233
  File.realpath(path),
193
- ::Bootsnap::CompileCache::YAML::Psych4::SafeLoad,
234
+ CompileCache::YAML::Psych4::SafeLoad,
194
235
  kwargs,
195
236
  )
196
237
  rescue Errno::EACCES
197
- ::Bootsnap::CompileCache.permission_error(path)
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 - ::Bootsnap::CompileCache::YAML.supported_options).empty?
251
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
209
252
  end
210
253
 
211
254
  begin
212
- ::Bootsnap::CompileCache::Native.fetch(
213
- Bootsnap::CompileCache::YAML.cache_dir,
255
+ CompileCache::Native.fetch(
256
+ CompileCache::YAML.cache_dir,
214
257
  File.realpath(path),
215
- ::Bootsnap::CompileCache::YAML::Psych4::UnsafeLoad,
258
+ CompileCache::YAML::Psych4::UnsafeLoad,
216
259
  kwargs,
217
260
  )
218
261
  rescue Errno::EACCES
219
- ::Bootsnap::CompileCache.permission_error(path)
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 = CompileCache::YAML.strict_load(contents)
274
+ obj = ::YAML.load(contents)
232
275
  packer = CompileCache::YAML.msgpack_factory.packer
233
276
  packer.pack(false) # not safe loaded
234
- packer.pack(obj)
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 - ::Bootsnap::CompileCache::YAML.supported_options).empty?
307
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
261
308
  end
262
309
 
263
310
  begin
264
- ::Bootsnap::CompileCache::Native.fetch(
265
- Bootsnap::CompileCache::YAML.cache_dir,
311
+ CompileCache::Native.fetch(
312
+ CompileCache::YAML.cache_dir,
266
313
  File.realpath(path),
267
- ::Bootsnap::CompileCache::YAML::Psych3,
314
+ CompileCache::YAML::Psych3,
268
315
  kwargs,
269
316
  )
270
317
  rescue Errno::EACCES
271
- ::Bootsnap::CompileCache.permission_error(path)
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 - ::Bootsnap::CompileCache::YAML.supported_options).empty?
331
+ return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
283
332
  end
284
333
 
285
334
  begin
286
- ::Bootsnap::CompileCache::Native.fetch(
287
- Bootsnap::CompileCache::YAML.cache_dir,
335
+ CompileCache::Native.fetch(
336
+ CompileCache::YAML.cache_dir,
288
337
  File.realpath(path),
289
- ::Bootsnap::CompileCache::YAML::Psych3,
338
+ CompileCache::YAML::Psych3,
290
339
  kwargs,
291
340
  )
292
341
  rescue Errno::EACCES
293
- ::Bootsnap::CompileCache.permission_error(path)
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.to_s
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
- File.expand_path(path).freeze
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
- @dirty = true
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
- FileUtils.mkpath(File.dirname(tmp))
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, freeze: true)
105
+ MessagePack.dump(@data, io)
103
106
  end
104
- FileUtils.mv(tmp, @store_path)
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, :realpath_cache)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- VERSION = "1.10.2"
4
+ VERSION = "1.11.1"
5
5
  end
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
- def self.logger=(logger)
20
- @logger = logger
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
- def self.instrumentation=(callback)
29
- @instrumentation = callback
30
- if respond_to?(:instrumentation_enabled=, true)
31
- self.instrumentation_enabled = !!callback
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
- def self.setup(
40
- cache_dir:,
41
- development_mode: true,
42
- load_path_cache: true,
43
- autoload_paths_cache: nil,
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
- unless disable_trace.nil?
56
- warn "[DEPRECATED] Bootsnap's `disable_trace:` option is deprecated and will be removed. " \
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
- if compile_cache_iseq && !iseq_cache_supported?
61
- warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommened " \
62
- "to turn `compile_cache_iseq` off on Ruby 2.5"
63
- end
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
- if load_path_cache
66
- Bootsnap::LoadPathCache.setup(
67
- cache_path: cache_dir + "/bootsnap/load-path-cache",
68
- development_mode: development_mode,
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
- Bootsnap::CompileCache.setup(
73
- cache_dir: cache_dir + "/bootsnap/compile-cache",
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
- def self.iseq_cache_supported?
81
- return @iseq_cache_supported if defined? @iseq_cache_supported
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
- ruby_version = Gem::Version.new(RUBY_VERSION)
84
- @iseq_cache_supported = ruby_version < Gem::Version.new("2.5.0") || ruby_version >= Gem::Version.new("2.6.0")
85
- end
86
+ def default_setup
87
+ env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["ENV"]
88
+ development_mode = ["", nil, "development"].include?(env)
86
89
 
87
- def self.default_setup
88
- env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["ENV"]
89
- development_mode = ["", nil, "development"].include?(env)
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
- unless ENV["DISABLE_BOOTSNAP"]
92
- cache_dir = ENV["BOOTSNAP_CACHE_DIR"]
93
- unless cache_dir
94
- config_dir_frame = caller.detect do |line|
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
- unless config_dir_frame
99
- $stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
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
- raise("couldn't infer bootsnap cache directory")
104
- end
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
- path = config_dir_frame.split(/:\d+:/).first
107
- path = File.dirname(path) until File.basename(path) == "config"
108
- app_root = File.dirname(path)
109
+ cache_dir = File.join(app_root, "tmp", "cache")
110
+ end
109
111
 
110
- cache_dir = File.join(app_root, "tmp", "cache")
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
- setup(
114
- cache_dir: cache_dir,
115
- development_mode: development_mode,
116
- load_path_cache: !ENV["DISABLE_BOOTSNAP_LOAD_PATH_CACHE"],
117
- compile_cache_iseq: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"] && iseq_cache_supported?,
118
- compile_cache_yaml: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
119
- compile_cache_json: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
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
- if RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
129
- def self.absolute_path?(path)
130
- path[1] == ":"
131
- end
132
- else
133
- def self.absolute_path?(path)
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.10.2
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-01-21 00:00:00.000000000 Z
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.3.0
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