bootsnap 1.9.1 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/bootsnap/cli.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bootsnap'
4
- require 'bootsnap/cli/worker_pool'
5
- require 'optparse'
6
- require 'fileutils'
7
- require 'etc'
3
+ require "bootsnap"
4
+ require "bootsnap/cli/worker_pool"
5
+ require "optparse"
6
+ require "fileutils"
7
+ require "etc"
8
8
 
9
9
  module Bootsnap
10
10
  class CLI
@@ -25,7 +25,7 @@ module Bootsnap
25
25
 
26
26
  def initialize(argv)
27
27
  @argv = argv
28
- self.cache_dir = ENV.fetch('BOOTSNAP_CACHE_DIR', 'tmp/cache')
28
+ self.cache_dir = ENV.fetch("BOOTSNAP_CACHE_DIR", "tmp/cache")
29
29
  self.compile_gemfile = false
30
30
  self.exclude = nil
31
31
  self.verbose = false
@@ -36,16 +36,16 @@ module Bootsnap
36
36
  end
37
37
 
38
38
  def precompile_command(*sources)
39
- require 'bootsnap/compile_cache/iseq'
40
- require 'bootsnap/compile_cache/yaml'
41
- require 'bootsnap/compile_cache/json'
39
+ require "bootsnap/compile_cache/iseq"
40
+ require "bootsnap/compile_cache/yaml"
41
+ require "bootsnap/compile_cache/json"
42
42
 
43
43
  fix_default_encoding do
44
- Bootsnap::CompileCache::ISeq.cache_dir = self.cache_dir
44
+ Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
45
45
  Bootsnap::CompileCache::YAML.init!
46
- Bootsnap::CompileCache::YAML.cache_dir = self.cache_dir
46
+ Bootsnap::CompileCache::YAML.cache_dir = cache_dir
47
47
  Bootsnap::CompileCache::JSON.init!
48
- Bootsnap::CompileCache::JSON.cache_dir = self.cache_dir
48
+ Bootsnap::CompileCache::JSON.cache_dir = cache_dir
49
49
 
50
50
  @work_pool = WorkerPool.create(size: jobs, jobs: {
51
51
  ruby: method(:precompile_ruby),
@@ -61,18 +61,18 @@ module Bootsnap
61
61
 
62
62
  if compile_gemfile
63
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)
64
+ gem_exclude = Regexp.union([exclude, "/spec/", "/test/"].compact)
65
65
  precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude)
66
66
 
67
67
  # Gems that include JSON or YAML files usually don't put them in `lib/`.
68
68
  # So we look at the gem root.
69
- gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems\/[^/]+}
69
+ gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems/[^/]+}
70
70
  gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq
71
71
  precompile_yaml_files(gem_paths, exclude: gem_exclude)
72
72
  precompile_json_files(gem_paths, exclude: gem_exclude)
73
73
  end
74
74
 
75
- if exitstatus = @work_pool.shutdown
75
+ if (exitstatus = @work_pool.shutdown)
76
76
  exit(exitstatus)
77
77
  end
78
78
  end
@@ -89,7 +89,7 @@ module Bootsnap
89
89
  if dir_sort
90
90
  def list_files(path, pattern)
91
91
  if File.directory?(path)
92
- Dir[File.join(path, pattern), sort: false]
92
+ Dir[File.join(path, pattern), sort: false]
93
93
  elsif File.exist?(path)
94
94
  [path]
95
95
  else
@@ -99,7 +99,7 @@ module Bootsnap
99
99
  else
100
100
  def list_files(path, pattern)
101
101
  if File.directory?(path)
102
- Dir[File.join(path, pattern)]
102
+ Dir[File.join(path, pattern)]
103
103
  elsif File.exist?(path)
104
104
  [path]
105
105
  else
@@ -126,9 +126,9 @@ module Bootsnap
126
126
 
127
127
  load_paths.each do |path|
128
128
  if !exclude || !exclude.match?(path)
129
- list_files(path, '**/*.{yml,yaml}').each do |yaml_file|
129
+ list_files(path, "**/*.{yml,yaml}").each do |yaml_file|
130
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))
131
+ if !File.basename(yaml_file).start_with?(".") && (!exclude || !exclude.match?(yaml_file))
132
132
  @work_pool.push(:yaml, yaml_file)
133
133
  end
134
134
  end
@@ -138,8 +138,8 @@ module Bootsnap
138
138
 
139
139
  def precompile_yaml(*yaml_files)
140
140
  Array(yaml_files).each do |yaml_file|
141
- if CompileCache::YAML.precompile(yaml_file, cache_dir: cache_dir)
142
- STDERR.puts(yaml_file) if verbose
141
+ if CompileCache::YAML.precompile(yaml_file) && verbose
142
+ $stderr.puts(yaml_file)
143
143
  end
144
144
  end
145
145
  end
@@ -149,9 +149,9 @@ module Bootsnap
149
149
 
150
150
  load_paths.each do |path|
151
151
  if !exclude || !exclude.match?(path)
152
- list_files(path, '**/*.json').each do |json_file|
152
+ list_files(path, "**/*.json").each do |json_file|
153
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))
154
+ if !File.basename(json_file).start_with?(".") && (!exclude || !exclude.match?(json_file))
155
155
  @work_pool.push(:json, json_file)
156
156
  end
157
157
  end
@@ -161,8 +161,8 @@ module Bootsnap
161
161
 
162
162
  def precompile_json(*json_files)
163
163
  Array(json_files).each do |json_file|
164
- if CompileCache::JSON.precompile(json_file, cache_dir: cache_dir)
165
- STDERR.puts(json_file) if verbose
164
+ if CompileCache::JSON.precompile(json_file) && verbose
165
+ $stderr.puts(json_file)
166
166
  end
167
167
  end
168
168
  end
@@ -172,7 +172,7 @@ module Bootsnap
172
172
 
173
173
  load_paths.each do |path|
174
174
  if !exclude || !exclude.match?(path)
175
- list_files(path, '**/*.rb').each do |ruby_file|
175
+ list_files(path, "**/{*.rb,*.rake,Rakefile}").each do |ruby_file|
176
176
  if !exclude || !exclude.match?(ruby_file)
177
177
  @work_pool.push(:ruby, ruby_file)
178
178
  end
@@ -183,8 +183,8 @@ module Bootsnap
183
183
 
184
184
  def precompile_ruby(*ruby_files)
185
185
  Array(ruby_files).each do |ruby_file|
186
- if CompileCache::ISeq.precompile(ruby_file, cache_dir: cache_dir)
187
- STDERR.puts(ruby_file) if verbose
186
+ if CompileCache::ISeq.precompile(ruby_file) && verbose
187
+ $stderr.puts(ruby_file)
188
188
  end
189
189
  end
190
190
  end
@@ -203,14 +203,14 @@ module Bootsnap
203
203
  end
204
204
 
205
205
  def invalid_usage!(message)
206
- STDERR.puts message
207
- STDERR.puts
208
- STDERR.puts parser
206
+ $stderr.puts message
207
+ $stderr.puts
208
+ $stderr.puts parser
209
209
  1
210
210
  end
211
211
 
212
212
  def cache_dir=(dir)
213
- @cache_dir = File.expand_path(File.join(dir, 'bootsnap/compile-cache'))
213
+ @cache_dir = File.expand_path(File.join(dir, "bootsnap/compile-cache"))
214
214
  end
215
215
 
216
216
  def exclude_pattern(pattern)
@@ -225,24 +225,24 @@ module Bootsnap
225
225
  opts.separator "GLOBAL OPTIONS"
226
226
  opts.separator ""
227
227
 
228
- help = <<~EOS
228
+ help = <<~HELP
229
229
  Path to the bootsnap cache directory. Defaults to tmp/cache
230
- EOS
231
- opts.on('--cache-dir DIR', help.strip) do |dir|
230
+ HELP
231
+ opts.on("--cache-dir DIR", help.strip) do |dir|
232
232
  self.cache_dir = dir
233
233
  end
234
234
 
235
- help = <<~EOS
235
+ help = <<~HELP
236
236
  Print precompiled paths.
237
- EOS
238
- opts.on('--verbose', '-v', help.strip) do
237
+ HELP
238
+ opts.on("--verbose", "-v", help.strip) do
239
239
  self.verbose = true
240
240
  end
241
241
 
242
- help = <<~EOS
242
+ help = <<~HELP
243
243
  Number of workers to use. Default to number of processors, set to 0 to disable multi-processing.
244
- EOS
245
- opts.on('--jobs JOBS', '-j', help.strip) do |jobs|
244
+ HELP
245
+ opts.on("--jobs JOBS", "-j", help.strip) do |jobs|
246
246
  self.jobs = Integer(jobs)
247
247
  end
248
248
 
@@ -251,30 +251,30 @@ module Bootsnap
251
251
  opts.separator ""
252
252
  opts.separator " precompile [DIRECTORIES...]: Precompile all .rb files in the passed directories"
253
253
 
254
- help = <<~EOS
254
+ help = <<~HELP
255
255
  Precompile the gems in Gemfile
256
- EOS
257
- opts.on('--gemfile', help) { self.compile_gemfile = true }
256
+ HELP
257
+ opts.on("--gemfile", help) { self.compile_gemfile = true }
258
258
 
259
- help = <<~EOS
259
+ help = <<~HELP
260
260
  Path pattern to not precompile. e.g. --exclude 'aws-sdk|google-api'
261
- EOS
262
- opts.on('--exclude PATTERN', help) { |pattern| exclude_pattern(pattern) }
261
+ HELP
262
+ opts.on("--exclude PATTERN", help) { |pattern| exclude_pattern(pattern) }
263
263
 
264
- help = <<~EOS
264
+ help = <<~HELP
265
265
  Disable ISeq (.rb) precompilation.
266
- EOS
267
- opts.on('--no-iseq', help) { self.iseq = false }
266
+ HELP
267
+ opts.on("--no-iseq", help) { self.iseq = false }
268
268
 
269
- help = <<~EOS
269
+ help = <<~HELP
270
270
  Disable YAML precompilation.
271
- EOS
272
- opts.on('--no-yaml', help) { self.yaml = false }
271
+ HELP
272
+ opts.on("--no-yaml", help) { self.yaml = false }
273
273
 
274
- help = <<~EOS
274
+ help = <<~HELP
275
275
  Disable JSON precompilation.
276
- EOS
277
- opts.on('--no-json', help) { self.json = false }
276
+ HELP
277
+ opts.on("--no-json", help) { self.json = false }
278
278
  end
279
279
  end
280
280
  end
@@ -1,25 +1,55 @@
1
1
  # frozen_string_literal: true
2
- require('bootsnap/bootsnap')
3
- require('zlib')
2
+
3
+ require("bootsnap/bootsnap")
4
+ require("zlib")
4
5
 
5
6
  module Bootsnap
6
7
  module CompileCache
7
8
  module ISeq
8
9
  class << self
9
- 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
15
+ end
16
+
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
10
24
  end
11
25
 
12
- def self.input_to_storage(_, path)
13
- RubyVM::InstructionSequence.compile_file(path).to_binary
14
- rescue SyntaxError
15
- raise(Uncompilable, 'syntax error')
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
+ 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
+ UNCOMPILABLE # syntax error
45
+ end
16
46
  end
17
47
 
18
48
  def self.storage_to_output(binary, _args)
19
49
  RubyVM::InstructionSequence.load_from_binary(binary)
20
- rescue RuntimeError => e
21
- if e.message == 'broken binary format'
22
- STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
50
+ rescue RuntimeError => error
51
+ if error.message == "broken binary format"
52
+ $stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
23
53
  nil
24
54
  else
25
55
  raise
@@ -35,7 +65,7 @@ module Bootsnap
35
65
  )
36
66
  end
37
67
 
38
- def self.precompile(path, cache_dir: ISeq.cache_dir)
68
+ def self.precompile(path)
39
69
  Bootsnap::CompileCache::Native.precompile(
40
70
  cache_dir,
41
71
  path.to_s,
@@ -55,8 +85,8 @@ module Bootsnap
55
85
  Bootsnap::CompileCache::ISeq.fetch(path.to_s)
56
86
  rescue Errno::EACCES
57
87
  Bootsnap::CompileCache.permission_error(path)
58
- rescue RuntimeError => e
59
- if e.message =~ /unmatched platform/
88
+ rescue RuntimeError => error
89
+ if error.message =~ /unmatched platform/
60
90
  puts("unmatched platform for file #{path}")
61
91
  end
62
92
  raise
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
- require('bootsnap/bootsnap')
2
+
3
+ require("bootsnap/bootsnap")
3
4
 
4
5
  module Bootsnap
5
6
  module CompileCache
6
7
  module JSON
7
8
  class << self
8
- attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
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
9
15
 
10
16
  def input_to_storage(payload, _)
11
17
  obj = ::JSON.parse(payload)
@@ -13,7 +19,7 @@ module Bootsnap
13
19
  end
14
20
 
15
21
  def storage_to_output(data, kwargs)
16
- if kwargs && kwargs.key?(:symbolize_names)
22
+ if kwargs&.key?(:symbolize_names)
17
23
  kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
18
24
  end
19
25
  msgpack_factory.load(data, kwargs)
@@ -23,7 +29,7 @@ module Bootsnap
23
29
  ::JSON.parse(data, **(kwargs || {}))
24
30
  end
25
31
 
26
- def precompile(path, cache_dir: self.cache_dir)
32
+ def precompile(path)
27
33
  Bootsnap::CompileCache::Native.precompile(
28
34
  cache_dir,
29
35
  path.to_s,
@@ -40,22 +46,30 @@ module Bootsnap
40
46
  end
41
47
 
42
48
  def init!
43
- require('json')
44
- require('msgpack')
49
+ require("json")
50
+ require("msgpack")
45
51
 
46
52
  self.msgpack_factory = MessagePack::Factory.new
47
53
  self.supported_options = [:symbolize_names]
48
- if ::JSON.parse('["foo"]', freeze: true).first.frozen?
54
+ if supports_freeze?
49
55
  self.supported_options = [:freeze]
50
56
  end
51
- self.supported_options.freeze
57
+ supported_options.freeze
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?
52
65
  end
53
66
  end
54
67
 
55
68
  module Patch
56
69
  def load_file(path, *args)
57
70
  return super if args.size > 1
58
- if kwargs = args.first
71
+
72
+ if (kwargs = args.first)
59
73
  return super unless kwargs.is_a?(Hash)
60
74
  return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
61
75
  end