bootsnap 1.9.1 → 1.16.0

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