bootsnap 1.4.1 → 1.10.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +67 -18
  5. data/exe/bootsnap +5 -0
  6. data/ext/bootsnap/bootsnap.c +319 -119
  7. data/ext/bootsnap/extconf.rb +22 -14
  8. data/lib/bootsnap/bundler.rb +2 -0
  9. data/lib/bootsnap/cli/worker_pool.rb +136 -0
  10. data/lib/bootsnap/cli.rb +281 -0
  11. data/lib/bootsnap/compile_cache/iseq.rb +65 -18
  12. data/lib/bootsnap/compile_cache/json.rb +88 -0
  13. data/lib/bootsnap/compile_cache/yaml.rb +332 -39
  14. data/lib/bootsnap/compile_cache.rb +35 -7
  15. data/lib/bootsnap/explicit_require.rb +5 -3
  16. data/lib/bootsnap/load_path_cache/cache.rb +83 -32
  17. data/lib/bootsnap/load_path_cache/change_observer.rb +6 -1
  18. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +39 -47
  19. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +12 -0
  20. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +69 -26
  21. data/lib/bootsnap/load_path_cache/path.rb +8 -5
  22. data/lib/bootsnap/load_path_cache/path_scanner.rb +56 -29
  23. data/lib/bootsnap/load_path_cache/realpath_cache.rb +6 -5
  24. data/lib/bootsnap/load_path_cache/store.rb +49 -18
  25. data/lib/bootsnap/load_path_cache.rb +20 -32
  26. data/lib/bootsnap/setup.rb +3 -33
  27. data/lib/bootsnap/version.rb +3 -1
  28. data/lib/bootsnap.rb +126 -36
  29. metadata +15 -97
  30. data/.gitignore +0 -17
  31. data/.rubocop.yml +0 -20
  32. data/.travis.yml +0 -24
  33. data/CODE_OF_CONDUCT.md +0 -74
  34. data/CONTRIBUTING.md +0 -21
  35. data/Gemfile +0 -8
  36. data/README.jp.md +0 -231
  37. data/Rakefile +0 -12
  38. data/bin/ci +0 -10
  39. data/bin/console +0 -14
  40. data/bin/setup +0 -8
  41. data/bin/test-minimal-support +0 -7
  42. data/bin/testunit +0 -8
  43. data/bootsnap.gemspec +0 -45
  44. data/dev.yml +0 -10
  45. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -100
  46. data/shipit.rubygems.yml +0 -0
@@ -1,18 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require("mkmf")
2
- $CFLAGS << ' -O3 '
3
- $CFLAGS << ' -std=c99'
4
4
 
5
- # ruby.h has some -Wpedantic fails in some cases
6
- # (e.g. https://github.com/Shopify/bootsnap/issues/15)
7
- unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
8
- $CFLAGS << ' -Wall'
9
- $CFLAGS << ' -Werror'
10
- $CFLAGS << ' -Wextra'
11
- $CFLAGS << ' -Wpedantic'
5
+ if RUBY_ENGINE == "ruby"
6
+ $CFLAGS << " -O3 "
7
+ $CFLAGS << " -std=c99"
12
8
 
13
- $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
14
- $CFLAGS << ' -Wno-keyword-macro' # hiding return
15
- $CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno
16
- end
9
+ # ruby.h has some -Wpedantic fails in some cases
10
+ # (e.g. https://github.com/Shopify/bootsnap/issues/15)
11
+ unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
12
+ $CFLAGS << " -Wall"
13
+ $CFLAGS << " -Werror"
14
+ $CFLAGS << " -Wextra"
15
+ $CFLAGS << " -Wpedantic"
17
16
 
18
- create_makefile("bootsnap/bootsnap")
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"
21
+ end
22
+
23
+ create_makefile("bootsnap/bootsnap")
24
+ else
25
+ File.write("Makefile", dummy_makefile($srcdir).join(""))
26
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bootsnap
2
4
  extend(self)
3
5
 
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bootsnap
4
+ class CLI
5
+ class WorkerPool
6
+ class << self
7
+ def create(size:, jobs:)
8
+ if size > 0 && Process.respond_to?(:fork)
9
+ new(size: size, jobs: jobs)
10
+ else
11
+ Inline.new(jobs: jobs)
12
+ end
13
+ end
14
+ end
15
+
16
+ class Inline
17
+ def initialize(jobs: {})
18
+ @jobs = jobs
19
+ end
20
+
21
+ def push(job, *args)
22
+ @jobs.fetch(job).call(*args)
23
+ nil
24
+ end
25
+
26
+ def spawn
27
+ # noop
28
+ end
29
+
30
+ def shutdown
31
+ # noop
32
+ end
33
+ end
34
+
35
+ class Worker
36
+ attr_reader :to_io, :pid
37
+
38
+ def initialize(jobs)
39
+ @jobs = jobs
40
+ @pipe_out, @to_io = IO.pipe(binmode: true)
41
+ # Set the writer encoding to binary since IO.pipe only sets it for the reader.
42
+ # https://github.com/rails/rails/issues/16514#issuecomment-52313290
43
+ @to_io.set_encoding(Encoding::BINARY)
44
+
45
+ @pid = nil
46
+ end
47
+
48
+ def write(message, block: true)
49
+ payload = Marshal.dump(message)
50
+ if block
51
+ to_io.write(payload)
52
+ true
53
+ else
54
+ to_io.write_nonblock(payload, exception: false) != :wait_writable
55
+ end
56
+ end
57
+
58
+ def close
59
+ to_io.close
60
+ end
61
+
62
+ def work_loop
63
+ loop do
64
+ job, *args = Marshal.load(@pipe_out)
65
+ return if job == :exit
66
+
67
+ @jobs.fetch(job).call(*args)
68
+ end
69
+ rescue IOError
70
+ nil
71
+ end
72
+
73
+ def spawn
74
+ @pid = Process.fork do
75
+ to_io.close
76
+ work_loop
77
+ exit!(0)
78
+ end
79
+ @pipe_out.close
80
+ true
81
+ end
82
+ end
83
+
84
+ def initialize(size:, jobs: {})
85
+ @size = size
86
+ @jobs = jobs
87
+ @queue = Queue.new
88
+ @pids = []
89
+ end
90
+
91
+ def spawn
92
+ @workers = @size.times.map { Worker.new(@jobs) }
93
+ @workers.each(&:spawn)
94
+ @dispatcher_thread = Thread.new { dispatch_loop }
95
+ @dispatcher_thread.abort_on_exception = true
96
+ true
97
+ end
98
+
99
+ def dispatch_loop
100
+ loop do
101
+ case job = @queue.pop
102
+ when nil
103
+ @workers.each do |worker|
104
+ worker.write([:exit])
105
+ worker.close
106
+ end
107
+ return true
108
+ else
109
+ unless @workers.sample.write(job, block: false)
110
+ free_worker.write(job)
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def free_worker
117
+ IO.select(nil, @workers)[1].sample
118
+ end
119
+
120
+ def push(*args)
121
+ @queue.push(args)
122
+ nil
123
+ end
124
+
125
+ def shutdown
126
+ @queue.close
127
+ @dispatcher_thread.join
128
+ @workers.each do |worker|
129
+ _pid, status = Process.wait2(worker.pid)
130
+ return status.exitstatus unless status.success?
131
+ end
132
+ nil
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bootsnap"
4
+ require "bootsnap/cli/worker_pool"
5
+ require "optparse"
6
+ require "fileutils"
7
+ require "etc"
8
+
9
+ module Bootsnap
10
+ class CLI
11
+ unless Regexp.method_defined?(:match?)
12
+ module RegexpMatchBackport
13
+ refine Regexp do
14
+ def match?(string)
15
+ !!match(string)
16
+ end
17
+ end
18
+ end
19
+ using RegexpMatchBackport
20
+ end
21
+
22
+ attr_reader :cache_dir, :argv
23
+
24
+ attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :json, :jobs
25
+
26
+ def initialize(argv)
27
+ @argv = argv
28
+ self.cache_dir = ENV.fetch("BOOTSNAP_CACHE_DIR", "tmp/cache")
29
+ self.compile_gemfile = false
30
+ self.exclude = nil
31
+ self.verbose = false
32
+ self.jobs = Etc.nprocessors
33
+ self.iseq = true
34
+ self.yaml = true
35
+ self.json = true
36
+ end
37
+
38
+ def precompile_command(*sources)
39
+ require "bootsnap/compile_cache/iseq"
40
+ require "bootsnap/compile_cache/yaml"
41
+ require "bootsnap/compile_cache/json"
42
+
43
+ fix_default_encoding do
44
+ Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
45
+ Bootsnap::CompileCache::YAML.init!
46
+ Bootsnap::CompileCache::YAML.cache_dir = cache_dir
47
+ Bootsnap::CompileCache::JSON.init!
48
+ Bootsnap::CompileCache::JSON.cache_dir = cache_dir
49
+
50
+ @work_pool = WorkerPool.create(size: jobs, jobs: {
51
+ ruby: method(:precompile_ruby),
52
+ yaml: method(:precompile_yaml),
53
+ json: method(:precompile_json),
54
+ })
55
+ @work_pool.spawn
56
+
57
+ main_sources = sources.map { |d| File.expand_path(d) }
58
+ precompile_ruby_files(main_sources)
59
+ precompile_yaml_files(main_sources)
60
+ precompile_json_files(main_sources)
61
+
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
+ # Gems that include JSON or YAML files usually don't put them in `lib/`.
68
+ # 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
71
+ precompile_yaml_files(gem_paths, exclude: gem_exclude)
72
+ precompile_json_files(gem_paths, exclude: gem_exclude)
73
+ end
74
+
75
+ if (exitstatus = @work_pool.shutdown)
76
+ exit(exitstatus)
77
+ end
78
+ end
79
+ 0
80
+ end
81
+
82
+ dir_sort = begin
83
+ Dir[__FILE__, sort: false]
84
+ true
85
+ rescue ArgumentError, TypeError
86
+ false
87
+ end
88
+
89
+ if dir_sort
90
+ def list_files(path, pattern)
91
+ if File.directory?(path)
92
+ Dir[File.join(path, pattern), sort: false]
93
+ elsif File.exist?(path)
94
+ [path]
95
+ else
96
+ []
97
+ end
98
+ end
99
+ else
100
+ def list_files(path, pattern)
101
+ if File.directory?(path)
102
+ Dir[File.join(path, pattern)]
103
+ elsif File.exist?(path)
104
+ [path]
105
+ else
106
+ []
107
+ end
108
+ end
109
+ end
110
+
111
+ def run
112
+ parser.parse!(argv)
113
+ command = argv.shift
114
+ method = "#{command}_command"
115
+ if respond_to?(method)
116
+ public_send(method, *argv)
117
+ else
118
+ invalid_usage!("Unknown command: #{command}")
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def precompile_yaml_files(load_paths, exclude: self.exclude)
125
+ return unless yaml
126
+
127
+ load_paths.each do |path|
128
+ if !exclude || !exclude.match?(path)
129
+ list_files(path, "**/*.{yml,yaml}").each do |yaml_file|
130
+ # We ignore hidden files to not match the various .ci.yml files
131
+ if !File.basename(yaml_file).start_with?(".") && (!exclude || !exclude.match?(yaml_file))
132
+ @work_pool.push(:yaml, yaml_file)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ def precompile_yaml(*yaml_files)
140
+ Array(yaml_files).each do |yaml_file|
141
+ if CompileCache::YAML.precompile(yaml_file)
142
+ STDERR.puts(yaml_file) if verbose
143
+ end
144
+ end
145
+ end
146
+
147
+ def precompile_json_files(load_paths, exclude: self.exclude)
148
+ return unless json
149
+
150
+ load_paths.each do |path|
151
+ if !exclude || !exclude.match?(path)
152
+ list_files(path, "**/*.json").each do |json_file|
153
+ # We ignore hidden files to not match the various .config.json files
154
+ if !File.basename(json_file).start_with?(".") && (!exclude || !exclude.match?(json_file))
155
+ @work_pool.push(:json, json_file)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def precompile_json(*json_files)
163
+ Array(json_files).each do |json_file|
164
+ if CompileCache::JSON.precompile(json_file)
165
+ STDERR.puts(json_file) if verbose
166
+ end
167
+ end
168
+ end
169
+
170
+ def precompile_ruby_files(load_paths, exclude: self.exclude)
171
+ return unless iseq
172
+
173
+ load_paths.each do |path|
174
+ if !exclude || !exclude.match?(path)
175
+ list_files(path, "**/*.rb").each do |ruby_file|
176
+ if !exclude || !exclude.match?(ruby_file)
177
+ @work_pool.push(:ruby, ruby_file)
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ def precompile_ruby(*ruby_files)
185
+ Array(ruby_files).each do |ruby_file|
186
+ if CompileCache::ISeq.precompile(ruby_file)
187
+ STDERR.puts(ruby_file) if verbose
188
+ end
189
+ end
190
+ end
191
+
192
+ def fix_default_encoding
193
+ if Encoding.default_external == Encoding::US_ASCII
194
+ Encoding.default_external = Encoding::UTF_8
195
+ begin
196
+ yield
197
+ ensure
198
+ Encoding.default_external = Encoding::US_ASCII
199
+ end
200
+ else
201
+ yield
202
+ end
203
+ end
204
+
205
+ def invalid_usage!(message)
206
+ STDERR.puts message
207
+ STDERR.puts
208
+ STDERR.puts parser
209
+ 1
210
+ end
211
+
212
+ def cache_dir=(dir)
213
+ @cache_dir = File.expand_path(File.join(dir, "bootsnap/compile-cache"))
214
+ end
215
+
216
+ def exclude_pattern(pattern)
217
+ (@exclude_patterns ||= []) << Regexp.new(pattern)
218
+ self.exclude = Regexp.union(@exclude_patterns)
219
+ end
220
+
221
+ def parser
222
+ @parser ||= OptionParser.new do |opts|
223
+ opts.banner = "Usage: bootsnap COMMAND [ARGS]"
224
+ opts.separator ""
225
+ opts.separator "GLOBAL OPTIONS"
226
+ opts.separator ""
227
+
228
+ help = <<~HELP
229
+ Path to the bootsnap cache directory. Defaults to tmp/cache
230
+ HELP
231
+ opts.on("--cache-dir DIR", help.strip) do |dir|
232
+ self.cache_dir = dir
233
+ end
234
+
235
+ help = <<~HELP
236
+ Print precompiled paths.
237
+ HELP
238
+ opts.on("--verbose", "-v", help.strip) do
239
+ self.verbose = true
240
+ end
241
+
242
+ help = <<~HELP
243
+ Number of workers to use. Default to number of processors, set to 0 to disable multi-processing.
244
+ HELP
245
+ opts.on("--jobs JOBS", "-j", help.strip) do |jobs|
246
+ self.jobs = Integer(jobs)
247
+ end
248
+
249
+ opts.separator ""
250
+ opts.separator "COMMANDS"
251
+ opts.separator ""
252
+ opts.separator " precompile [DIRECTORIES...]: Precompile all .rb files in the passed directories"
253
+
254
+ help = <<~HELP
255
+ Precompile the gems in Gemfile
256
+ HELP
257
+ opts.on("--gemfile", help) { self.compile_gemfile = true }
258
+
259
+ help = <<~HELP
260
+ Path pattern to not precompile. e.g. --exclude 'aws-sdk|google-api'
261
+ HELP
262
+ opts.on("--exclude PATTERN", help) { |pattern| exclude_pattern(pattern) }
263
+
264
+ help = <<~HELP
265
+ Disable ISeq (.rb) precompilation.
266
+ HELP
267
+ opts.on("--no-iseq", help) { self.iseq = false }
268
+
269
+ help = <<~HELP
270
+ Disable YAML precompilation.
271
+ HELP
272
+ opts.on("--no-yaml", help) { self.yaml = false }
273
+
274
+ help = <<~HELP
275
+ Disable JSON precompilation.
276
+ HELP
277
+ opts.on("--no-json", help) { self.json = false }
278
+ end
279
+ end
280
+ end
281
+ end
@@ -1,23 +1,54 @@
1
- require('bootsnap/bootsnap')
2
- require('zlib')
1
+ # frozen_string_literal: true
2
+
3
+ require("bootsnap/bootsnap")
4
+ require("zlib")
3
5
 
4
6
  module Bootsnap
5
7
  module CompileCache
6
8
  module ISeq
7
9
  class << self
8
- attr_accessor(:cache_dir)
10
+ attr_reader(:cache_dir)
11
+
12
+ def cache_dir=(cache_dir)
13
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq"
14
+ end
9
15
  end
10
16
 
11
- def self.input_to_storage(_, path)
12
- RubyVM::InstructionSequence.compile_file(path).to_binary
13
- rescue SyntaxError
14
- raise(Uncompilable, 'syntax error')
17
+ has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
18
+ if defined? RubyVM::InstructionSequence
19
+ RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary
20
+ end
21
+ false
22
+ rescue TypeError
23
+ true
15
24
  end
16
25
 
17
- def self.storage_to_output(binary)
26
+ if has_ruby_bug_18250
27
+ def self.input_to_storage(_, path)
28
+ iseq = begin
29
+ RubyVM::InstructionSequence.compile_file(path)
30
+ rescue SyntaxError
31
+ return UNCOMPILABLE # syntax error
32
+ end
33
+
34
+ begin
35
+ iseq.to_binary
36
+ rescue TypeError
37
+ return UNCOMPILABLE # ruby bug #18250
38
+ end
39
+ end
40
+ else
41
+ def self.input_to_storage(_, path)
42
+ RubyVM::InstructionSequence.compile_file(path).to_binary
43
+ rescue SyntaxError
44
+ return UNCOMPILABLE # syntax error
45
+ end
46
+ end
47
+
48
+ def self.storage_to_output(binary, _args)
18
49
  RubyVM::InstructionSequence.load_from_binary(binary)
19
- rescue RuntimeError => e
20
- if e.message == 'broken binary format'
50
+ rescue RuntimeError => error
51
+ if error.message == "broken binary format"
21
52
  STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
22
53
  nil
23
54
  else
@@ -25,7 +56,24 @@ module Bootsnap
25
56
  end
26
57
  end
27
58
 
28
- def self.input_to_output(_)
59
+ def self.fetch(path, cache_dir: ISeq.cache_dir)
60
+ Bootsnap::CompileCache::Native.fetch(
61
+ cache_dir,
62
+ path.to_s,
63
+ Bootsnap::CompileCache::ISeq,
64
+ nil,
65
+ )
66
+ end
67
+
68
+ def self.precompile(path)
69
+ Bootsnap::CompileCache::Native.precompile(
70
+ cache_dir,
71
+ path.to_s,
72
+ Bootsnap::CompileCache::ISeq,
73
+ )
74
+ end
75
+
76
+ def self.input_to_output(_data, _kwargs)
29
77
  nil # ruby handles this
30
78
  end
31
79
 
@@ -34,13 +82,11 @@ module Bootsnap
34
82
  # Having coverage enabled prevents iseq dumping/loading.
35
83
  return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
36
84
 
37
- Bootsnap::CompileCache::Native.fetch(
38
- Bootsnap::CompileCache::ISeq.cache_dir,
39
- path.to_s,
40
- Bootsnap::CompileCache::ISeq
41
- )
42
- rescue RuntimeError => e
43
- if e.message =~ /unmatched platform/
85
+ Bootsnap::CompileCache::ISeq.fetch(path.to_s)
86
+ rescue Errno::EACCES
87
+ Bootsnap::CompileCache.permission_error(path)
88
+ rescue RuntimeError => error
89
+ if error.message =~ /unmatched platform/
44
90
  puts("unmatched platform for file #{path}")
45
91
  end
46
92
  raise
@@ -57,6 +103,7 @@ module Bootsnap
57
103
  crc = Zlib.crc32(option.inspect)
58
104
  Bootsnap::CompileCache::Native.compile_option_crc32 = crc
59
105
  end
106
+ compile_option_updated
60
107
 
61
108
  def self.install!(cache_dir)
62
109
  Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("bootsnap/bootsnap")
4
+
5
+ module Bootsnap
6
+ module CompileCache
7
+ module JSON
8
+ class << self
9
+ attr_accessor(:msgpack_factory, :supported_options)
10
+ attr_reader(:cache_dir)
11
+
12
+ def cache_dir=(cache_dir)
13
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}json" : "#{cache_dir}-json"
14
+ end
15
+
16
+ def input_to_storage(payload, _)
17
+ obj = ::JSON.parse(payload)
18
+ msgpack_factory.dump(obj)
19
+ end
20
+
21
+ def storage_to_output(data, kwargs)
22
+ if kwargs&.key?(:symbolize_names)
23
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
24
+ end
25
+ msgpack_factory.load(data, kwargs)
26
+ end
27
+
28
+ def input_to_output(data, kwargs)
29
+ ::JSON.parse(data, **(kwargs || {}))
30
+ end
31
+
32
+ def precompile(path)
33
+ Bootsnap::CompileCache::Native.precompile(
34
+ cache_dir,
35
+ path.to_s,
36
+ self,
37
+ )
38
+ end
39
+
40
+ def install!(cache_dir)
41
+ self.cache_dir = cache_dir
42
+ init!
43
+ if ::JSON.respond_to?(:load_file)
44
+ ::JSON.singleton_class.prepend(Patch)
45
+ end
46
+ end
47
+
48
+ def init!
49
+ require("json")
50
+ require("msgpack")
51
+
52
+ self.msgpack_factory = MessagePack::Factory.new
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
58
+ end
59
+ supported_options.freeze
60
+ end
61
+ end
62
+
63
+ module Patch
64
+ def load_file(path, *args)
65
+ return super if args.size > 1
66
+
67
+ if (kwargs = args.first)
68
+ return super unless kwargs.is_a?(Hash)
69
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
70
+ end
71
+
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
82
+ end
83
+
84
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
85
+ end
86
+ end
87
+ end
88
+ end