bootsnap 1.11.1 → 1.18.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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