bootsnap 1.11.1 → 1.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,26 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require("mkmf")
3
+ require "mkmf"
4
4
 
5
- if RUBY_ENGINE == "ruby"
6
- $CFLAGS << " -O3 "
7
- $CFLAGS << " -std=c99"
5
+ if %w[ruby truffleruby].include?(RUBY_ENGINE)
6
+ have_func "fdatasync", "unistd.h"
7
+
8
+ unless RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
9
+ append_cppflags ["-D_GNU_SOURCE"] # Needed of O_NOATIME
10
+ end
11
+
12
+ append_cflags ["-O3", "-std=c99"]
8
13
 
9
14
  # ruby.h has some -Wpedantic fails in some cases
10
15
  # (e.g. https://github.com/Shopify/bootsnap/issues/15)
11
16
  unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
12
- $CFLAGS << " -Wall"
13
- $CFLAGS << " -Werror"
14
- $CFLAGS << " -Wextra"
15
- $CFLAGS << " -Wpedantic"
17
+ append_cflags([
18
+ "-Wall",
19
+ "-Werror",
20
+ "-Wextra",
21
+ "-Wpedantic",
16
22
 
17
- $CFLAGS << " -Wno-unused-parameter" # VALUE self has to be there but we don't care what it is.
18
- $CFLAGS << " -Wno-keyword-macro" # hiding return
19
- $CFLAGS << " -Wno-gcc-compat" # ruby.h 2.6.0 on macos 10.14, dunno
20
- $CFLAGS << " -Wno-compound-token-split-by-macro"
23
+ "-Wno-unused-parameter", # VALUE self has to be there but we don't care what it is.
24
+ "-Wno-keyword-macro", # hiding return
25
+ "-Wno-gcc-compat", # ruby.h 2.6.0 on macos 10.14, dunno
26
+ "-Wno-compound-token-split-by-macro",
27
+ ])
21
28
  end
22
29
 
23
30
  create_makefile("bootsnap/bootsnap")
24
31
  else
25
- File.write("Makefile", dummy_makefile($srcdir).join(""))
32
+ File.write("Makefile", dummy_makefile($srcdir).join)
26
33
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- extend(self)
4
+ extend self
5
5
 
6
6
  def bundler?
7
7
  return false unless defined?(::Bundler)
data/lib/bootsnap/cli.rb CHANGED
@@ -60,14 +60,16 @@ module Bootsnap
60
60
  precompile_json_files(main_sources)
61
61
 
62
62
  if compile_gemfile
63
- # Some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
64
- gem_exclude = Regexp.union([exclude, "/spec/", "/test/"].compact)
65
- precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude)
66
-
67
63
  # Gems that include JSON or YAML files usually don't put them in `lib/`.
68
64
  # So we look at the gem root.
69
- gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems\/[^/]+}
70
- gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq
65
+ # Similarly, gems that include Rails engines generally file Ruby files in `app/`.
66
+ # However some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
67
+ gem_exclude = Regexp.union([exclude, "/spec/", "/test/", "/features/"].compact)
68
+
69
+ gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems/[^/]+}
70
+ gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] || p }.uniq
71
+
72
+ precompile_ruby_files(gem_paths, exclude: gem_exclude)
71
73
  precompile_yaml_files(gem_paths, exclude: gem_exclude)
72
74
  precompile_json_files(gem_paths, exclude: gem_exclude)
73
75
  end
@@ -138,8 +140,8 @@ module Bootsnap
138
140
 
139
141
  def precompile_yaml(*yaml_files)
140
142
  Array(yaml_files).each do |yaml_file|
141
- if CompileCache::YAML.precompile(yaml_file)
142
- STDERR.puts(yaml_file) if verbose
143
+ if CompileCache::YAML.precompile(yaml_file) && verbose
144
+ $stderr.puts(yaml_file)
143
145
  end
144
146
  end
145
147
  end
@@ -161,8 +163,8 @@ module Bootsnap
161
163
 
162
164
  def precompile_json(*json_files)
163
165
  Array(json_files).each do |json_file|
164
- if CompileCache::JSON.precompile(json_file)
165
- STDERR.puts(json_file) if verbose
166
+ if CompileCache::JSON.precompile(json_file) && verbose
167
+ $stderr.puts(json_file)
166
168
  end
167
169
  end
168
170
  end
@@ -172,7 +174,7 @@ module Bootsnap
172
174
 
173
175
  load_paths.each do |path|
174
176
  if !exclude || !exclude.match?(path)
175
- list_files(path, "**/*.rb").each do |ruby_file|
177
+ list_files(path, "**/{*.rb,*.rake,Rakefile}").each do |ruby_file|
176
178
  if !exclude || !exclude.match?(ruby_file)
177
179
  @work_pool.push(:ruby, ruby_file)
178
180
  end
@@ -183,8 +185,8 @@ module Bootsnap
183
185
 
184
186
  def precompile_ruby(*ruby_files)
185
187
  Array(ruby_files).each do |ruby_file|
186
- if CompileCache::ISeq.precompile(ruby_file)
187
- STDERR.puts(ruby_file) if verbose
188
+ if CompileCache::ISeq.precompile(ruby_file) && verbose
189
+ $stderr.puts(ruby_file)
188
190
  end
189
191
  end
190
192
  end
@@ -203,9 +205,9 @@ module Bootsnap
203
205
  end
204
206
 
205
207
  def invalid_usage!(message)
206
- STDERR.puts message
207
- STDERR.puts
208
- STDERR.puts parser
208
+ $stderr.puts message
209
+ $stderr.puts
210
+ $stderr.puts parser
209
211
  1
210
212
  end
211
213
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require("bootsnap/bootsnap")
4
- require("zlib")
3
+ require "bootsnap/bootsnap"
4
+ require "zlib"
5
5
 
6
6
  module Bootsnap
7
7
  module CompileCache
@@ -12,6 +12,10 @@ module Bootsnap
12
12
  def cache_dir=(cache_dir)
13
13
  @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq"
14
14
  end
15
+
16
+ def supported?
17
+ CompileCache.supported? && defined?(RubyVM)
18
+ end
15
19
  end
16
20
 
17
21
  has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
@@ -34,14 +38,14 @@ module Bootsnap
34
38
  begin
35
39
  iseq.to_binary
36
40
  rescue TypeError
37
- return UNCOMPILABLE # ruby bug #18250
41
+ UNCOMPILABLE # ruby bug #18250
38
42
  end
39
43
  end
40
44
  else
41
45
  def self.input_to_storage(_, path)
42
46
  RubyVM::InstructionSequence.compile_file(path).to_binary
43
47
  rescue SyntaxError
44
- return UNCOMPILABLE # syntax error
48
+ UNCOMPILABLE # syntax error
45
49
  end
46
50
  end
47
51
 
@@ -49,7 +53,7 @@ module Bootsnap
49
53
  RubyVM::InstructionSequence.load_from_binary(binary)
50
54
  rescue RuntimeError => error
51
55
  if error.message == "broken binary format"
52
- STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
56
+ $stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
53
57
  nil
54
58
  else
55
59
  raise
@@ -83,8 +87,6 @@ module Bootsnap
83
87
  return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
84
88
 
85
89
  Bootsnap::CompileCache::ISeq.fetch(path.to_s)
86
- rescue Errno::EACCES
87
- Bootsnap::CompileCache.permission_error(path)
88
90
  rescue RuntimeError => error
89
91
  if error.message =~ /unmatched platform/
90
92
  puts("unmatched platform for file #{path}")
@@ -103,11 +105,15 @@ module Bootsnap
103
105
  crc = Zlib.crc32(option.inspect)
104
106
  Bootsnap::CompileCache::Native.compile_option_crc32 = crc
105
107
  end
106
- compile_option_updated
108
+ compile_option_updated if supported?
107
109
 
108
110
  def self.install!(cache_dir)
109
111
  Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
112
+
113
+ return unless supported?
114
+
110
115
  Bootsnap::CompileCache::ISeq.compile_option_updated
116
+
111
117
  class << RubyVM::InstructionSequence
112
118
  prepend(InstructionSequenceMixin)
113
119
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require("bootsnap/bootsnap")
3
+ require "bootsnap/bootsnap"
4
4
 
5
5
  module Bootsnap
6
6
  module CompileCache
@@ -46,18 +46,23 @@ module Bootsnap
46
46
  end
47
47
 
48
48
  def init!
49
- require("json")
50
- require("msgpack")
49
+ require "json"
50
+ require "msgpack"
51
51
 
52
52
  self.msgpack_factory = MessagePack::Factory.new
53
53
  self.supported_options = [:symbolize_names]
54
- if ::JSON.parse('["foo"]', freeze: true).first.frozen?
55
- if MessagePack.load(MessagePack.dump("foo"), freeze: true).frozen?
56
- self.supported_options = [:freeze]
57
- end
54
+ if supports_freeze?
55
+ self.supported_options = [:freeze]
58
56
  end
59
57
  supported_options.freeze
60
58
  end
59
+
60
+ private
61
+
62
+ def supports_freeze?
63
+ ::JSON.parse('["foo"]', freeze: true).first.frozen? &&
64
+ MessagePack.load(MessagePack.dump("foo"), freeze: true).frozen?
65
+ end
61
66
  end
62
67
 
63
68
  module Patch
@@ -69,16 +74,12 @@ module Bootsnap
69
74
  return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
70
75
  end
71
76
 
72
- begin
73
- ::Bootsnap::CompileCache::Native.fetch(
74
- Bootsnap::CompileCache::JSON.cache_dir,
75
- File.realpath(path),
76
- ::Bootsnap::CompileCache::JSON,
77
- kwargs,
78
- )
79
- rescue Errno::EACCES
80
- ::Bootsnap::CompileCache.permission_error(path)
81
- end
77
+ ::Bootsnap::CompileCache::Native.fetch(
78
+ Bootsnap::CompileCache::JSON.cache_dir,
79
+ File.realpath(path),
80
+ ::Bootsnap::CompileCache::JSON,
81
+ kwargs,
82
+ )
82
83
  end
83
84
 
84
85
  ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require("bootsnap/bootsnap")
3
+ require "bootsnap/bootsnap"
4
4
 
5
5
  module Bootsnap
6
6
  module CompileCache
@@ -55,15 +55,28 @@ module Bootsnap
55
55
  end
56
56
 
57
57
  def init!
58
- require("yaml")
59
- require("msgpack")
60
- require("date")
58
+ require "yaml"
59
+ require "msgpack"
60
+ require "date"
61
61
 
62
62
  @implementation = ::YAML::VERSION >= "4" ? Psych4 : Psych3
63
63
  if @implementation::Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file)
64
64
  @implementation::Patch.send(:remove_method, :unsafe_load_file)
65
65
  end
66
66
 
67
+ unless const_defined?(:NoTagsVisitor)
68
+ visitor = Class.new(Psych::Visitors::NoAliasRuby) do
69
+ def visit(target)
70
+ if target.tag
71
+ raise UnsupportedTags, "YAML tags are not supported: #{target.tag}"
72
+ end
73
+
74
+ super
75
+ end
76
+ end
77
+ const_set(:NoTagsVisitor, visitor)
78
+ end
79
+
67
80
  # MessagePack serializes symbols as strings by default.
68
81
  # We want them to roundtrip cleanly, so we use a custom factory.
69
82
  # see: https://github.com/msgpack/msgpack-ruby/pull/122
@@ -102,10 +115,8 @@ module Bootsnap
102
115
  if params.include?([:key, :symbolize_names])
103
116
  supported_options << :symbolize_names
104
117
  end
105
- if params.include?([:key, :freeze])
106
- if factory.load(factory.dump("yaml"), freeze: true).frozen?
107
- supported_options << :freeze
108
- end
118
+ if params.include?([:key, :freeze]) && factory.load(factory.dump("yaml"), freeze: true).frozen?
119
+ supported_options << :freeze
109
120
  end
110
121
  supported_options.freeze
111
122
  end
@@ -118,19 +129,10 @@ module Bootsnap
118
129
  ast = ::YAML.parse(payload)
119
130
  return ast unless ast
120
131
 
121
- strict_visitor.create.visit(ast)
122
- end
123
-
124
- def strict_visitor
125
- self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do
126
- def visit(target)
127
- if target.tag
128
- raise UnsupportedTags, "YAML tags are not supported: #{target.tag}"
129
- end
132
+ loader = ::Psych::ClassLoader::Restricted.new(["Symbol"], [])
133
+ scanner = ::Psych::ScalarScanner.new(loader)
130
134
 
131
- super
132
- end
133
- end
135
+ NoTagsVisitor.new(scanner, loader).visit(ast)
134
136
  end
135
137
  end
136
138
 
@@ -227,16 +229,12 @@ module Bootsnap
227
229
  return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
228
230
  end
229
231
 
230
- begin
231
- CompileCache::Native.fetch(
232
- CompileCache::YAML.cache_dir,
233
- File.realpath(path),
234
- CompileCache::YAML::Psych4::SafeLoad,
235
- kwargs,
236
- )
237
- rescue Errno::EACCES
238
- CompileCache.permission_error(path)
239
- end
232
+ CompileCache::Native.fetch(
233
+ CompileCache::YAML.cache_dir,
234
+ File.realpath(path),
235
+ CompileCache::YAML::Psych4::SafeLoad,
236
+ kwargs,
237
+ )
240
238
  end
241
239
 
242
240
  ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
@@ -251,16 +249,12 @@ module Bootsnap
251
249
  return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
252
250
  end
253
251
 
254
- begin
255
- CompileCache::Native.fetch(
256
- CompileCache::YAML.cache_dir,
257
- File.realpath(path),
258
- CompileCache::YAML::Psych4::UnsafeLoad,
259
- kwargs,
260
- )
261
- rescue Errno::EACCES
262
- CompileCache.permission_error(path)
263
- end
252
+ CompileCache::Native.fetch(
253
+ CompileCache::YAML.cache_dir,
254
+ File.realpath(path),
255
+ CompileCache::YAML::Psych4::UnsafeLoad,
256
+ kwargs,
257
+ )
264
258
  end
265
259
 
266
260
  ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
@@ -307,16 +301,12 @@ module Bootsnap
307
301
  return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
308
302
  end
309
303
 
310
- begin
311
- CompileCache::Native.fetch(
312
- CompileCache::YAML.cache_dir,
313
- File.realpath(path),
314
- CompileCache::YAML::Psych3,
315
- kwargs,
316
- )
317
- rescue Errno::EACCES
318
- CompileCache.permission_error(path)
319
- end
304
+ CompileCache::Native.fetch(
305
+ CompileCache::YAML.cache_dir,
306
+ File.realpath(path),
307
+ CompileCache::YAML::Psych3,
308
+ kwargs,
309
+ )
320
310
  end
321
311
 
322
312
  ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
@@ -331,16 +321,12 @@ module Bootsnap
331
321
  return super unless (kwargs.keys - CompileCache::YAML.supported_options).empty?
332
322
  end
333
323
 
334
- begin
335
- CompileCache::Native.fetch(
336
- CompileCache::YAML.cache_dir,
337
- File.realpath(path),
338
- CompileCache::YAML::Psych3,
339
- kwargs,
340
- )
341
- rescue Errno::EACCES
342
- CompileCache.permission_error(path)
343
- end
324
+ CompileCache::Native.fetch(
325
+ CompileCache::YAML.cache_dir,
326
+ File.realpath(path),
327
+ CompileCache::YAML::Psych3,
328
+ kwargs,
329
+ )
344
330
  end
345
331
 
346
332
  ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
@@ -8,12 +8,11 @@ module Bootsnap
8
8
  end
9
9
 
10
10
  Error = Class.new(StandardError)
11
- PermissionError = Class.new(Error)
12
11
 
13
- def self.setup(cache_dir:, iseq:, yaml:, json:)
12
+ def self.setup(cache_dir:, iseq:, yaml:, json:, readonly: false, revalidation: false)
14
13
  if iseq
15
14
  if supported?
16
- require_relative("compile_cache/iseq")
15
+ require_relative "compile_cache/iseq"
17
16
  Bootsnap::CompileCache::ISeq.install!(cache_dir)
18
17
  elsif $VERBOSE
19
18
  warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby")
@@ -22,7 +21,7 @@ module Bootsnap
22
21
 
23
22
  if yaml
24
23
  if supported?
25
- require_relative("compile_cache/yaml")
24
+ require_relative "compile_cache/yaml"
26
25
  Bootsnap::CompileCache::YAML.install!(cache_dir)
27
26
  elsif $VERBOSE
28
27
  warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
@@ -31,26 +30,23 @@ module Bootsnap
31
30
 
32
31
  if json
33
32
  if supported?
34
- require_relative("compile_cache/json")
33
+ require_relative "compile_cache/json"
35
34
  Bootsnap::CompileCache::JSON.install!(cache_dir)
36
35
  elsif $VERBOSE
37
36
  warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
38
37
  end
39
38
  end
40
- end
41
39
 
42
- def self.permission_error(path)
43
- cpath = Bootsnap::CompileCache::ISeq.cache_dir
44
- raise(
45
- PermissionError,
46
- "bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
47
- "(or, less likely, doesn't have permission to read '#{path}')",
48
- )
40
+ if supported? && defined?(Bootsnap::CompileCache::Native)
41
+ Bootsnap::CompileCache::Native.readonly = readonly
42
+ Bootsnap::CompileCache::Native.revalidation = revalidation
43
+ end
49
44
  end
50
45
 
51
46
  def self.supported?
52
- # only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
53
- RUBY_ENGINE == "ruby" && RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
47
+ # only enable on 'ruby' (MRI) and TruffleRuby for POSIX (darwin, linux, *bsd), Windows (RubyInstaller2)
48
+ %w[ruby truffleruby].include?(RUBY_ENGINE) &&
49
+ RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
54
50
  end
55
51
  end
56
52
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative("../explicit_require")
3
+ require_relative "../explicit_require"
4
4
 
5
5
  module Bootsnap
6
6
  module LoadPathCache
@@ -24,8 +24,16 @@ module Bootsnap
24
24
  @mutex.synchronize { @dirs[dir] }
25
25
  end
26
26
 
27
+ TRUFFLERUBY_LIB_DIR_PREFIX = if RUBY_ENGINE == "truffleruby"
28
+ "#{File.join(RbConfig::CONFIG['libdir'], 'truffle')}#{File::SEPARATOR}"
29
+ end
30
+
27
31
  # { 'enumerator' => nil, 'enumerator.so' => nil, ... }
28
32
  BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
33
+ if TRUFFLERUBY_LIB_DIR_PREFIX && feat.start_with?(TRUFFLERUBY_LIB_DIR_PREFIX)
34
+ feat = feat.byteslice(TRUFFLERUBY_LIB_DIR_PREFIX.bytesize..-1)
35
+ end
36
+
29
37
  # Builtin features are of the form 'enumerator.so'.
30
38
  # All others include paths.
31
39
  next unless feat.size < 20 && !feat.include?("/")
@@ -45,20 +53,19 @@ module Bootsnap
45
53
 
46
54
  # Try to resolve this feature to an absolute path without traversing the
47
55
  # loadpath.
48
- def find(feature, try_extensions: true)
56
+ def find(feature)
49
57
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
50
58
  feature = feature.to_s.freeze
51
59
 
52
60
  return feature if Bootsnap.absolute_path?(feature)
53
61
 
54
62
  if feature.start_with?("./", "../")
55
- return try_extensions ? expand_path(feature) : File.expand_path(feature).freeze
63
+ return expand_path(feature)
56
64
  end
57
65
 
58
66
  @mutex.synchronize do
59
- x = search_index(feature, try_extensions: try_extensions)
67
+ x = search_index(feature)
60
68
  return x if x
61
- return unless try_extensions
62
69
 
63
70
  # Ruby has some built-in features that require lies about.
64
71
  # For example, 'enumerator' is built in. If you require it, ruby
@@ -115,7 +122,7 @@ module Bootsnap
115
122
  def reinitialize(path_obj = @path_obj)
116
123
  @mutex.synchronize do
117
124
  @path_obj = path_obj
118
- ChangeObserver.register(self, @path_obj)
125
+ ChangeObserver.register(@path_obj, self)
119
126
  @index = {}
120
127
  @dirs = {}
121
128
  @generated_at = now
@@ -183,15 +190,11 @@ module Bootsnap
183
190
  end
184
191
 
185
192
  if DLEXT2
186
- def search_index(feature, try_extensions: true)
187
- if try_extensions
188
- try_index(feature + DOT_RB) ||
189
- try_index(feature + DLEXT) ||
190
- try_index(feature + DLEXT2) ||
191
- try_index(feature)
192
- else
193
+ def search_index(feature)
194
+ try_index(feature + DOT_RB) ||
195
+ try_index(feature + DLEXT) ||
196
+ try_index(feature + DLEXT2) ||
193
197
  try_index(feature)
194
- end
195
198
  end
196
199
 
197
200
  def maybe_append_extension(feature)
@@ -201,12 +204,8 @@ module Bootsnap
201
204
  feature
202
205
  end
203
206
  else
204
- def search_index(feature, try_extensions: true)
205
- if try_extensions
206
- try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
207
- else
208
- try_index(feature)
209
- end
207
+ def search_index(feature)
208
+ try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
210
209
  end
211
210
 
212
211
  def maybe_append_extension(feature)
@@ -216,7 +215,7 @@ module Bootsnap
216
215
 
217
216
  s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
218
217
  if s.respond_to?(:-@)
219
- if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= "2.7"
218
+ if ((-s).equal?(s) && (-s.dup).equal?(s)) || RUBY_VERSION >= "2.7"
220
219
  def try_index(feature)
221
220
  if (path = @index[feature])
222
221
  -File.join(path, feature).freeze
@@ -54,13 +54,30 @@ module Bootsnap
54
54
  ret
55
55
  end
56
56
  end
57
+
58
+ def dup
59
+ [] + self
60
+ end
61
+
62
+ alias_method :clone, :dup
57
63
  end
58
64
 
59
- def self.register(observer, arr)
65
+ def self.register(arr, observer)
60
66
  return if arr.frozen? # can't register observer, but no need to.
61
67
 
62
68
  arr.instance_variable_set(:@lpc_observer, observer)
63
- arr.extend(ArrayMixin)
69
+ ArrayMixin.instance_methods.each do |method_name|
70
+ arr.singleton_class.send(:define_method, method_name, ArrayMixin.instance_method(method_name))
71
+ end
72
+ end
73
+
74
+ def self.unregister(arr)
75
+ return unless arr.instance_variable_defined?(:@lpc_observer) && arr.instance_variable_get(:@lpc_observer)
76
+
77
+ ArrayMixin.instance_methods.each do |method_name|
78
+ arr.singleton_class.send(:remove_method, method_name)
79
+ end
80
+ arr.instance_variable_set(:@lpc_observer, nil)
64
81
  end
65
82
  end
66
83
  end