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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +126 -3
- data/LICENSE.txt +1 -1
- data/README.md +12 -6
- data/exe/bootsnap +1 -1
- data/ext/bootsnap/bootsnap.c +69 -91
- data/ext/bootsnap/extconf.rb +14 -12
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli/worker_pool.rb +1 -0
- data/lib/bootsnap/cli.rb +56 -56
- data/lib/bootsnap/compile_cache/iseq.rb +43 -13
- data/lib/bootsnap/compile_cache/json.rb +23 -9
- data/lib/bootsnap/compile_cache/yaml.rb +274 -85
- data/lib/bootsnap/compile_cache.rb +16 -8
- data/lib/bootsnap/explicit_require.rb +4 -3
- data/lib/bootsnap/load_path_cache/cache.rb +51 -40
- data/lib/bootsnap/load_path_cache/change_observer.rb +15 -2
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +27 -96
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +33 -22
- data/lib/bootsnap/load_path_cache/path.rb +40 -18
- data/lib/bootsnap/load_path_cache/path_scanner.rb +12 -5
- data/lib/bootsnap/load_path_cache/store.rb +52 -20
- data/lib/bootsnap/load_path_cache.rb +32 -26
- data/lib/bootsnap/setup.rb +2 -1
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +103 -91
- metadata +7 -78
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
data/lib/bootsnap/cli.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
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(
|
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
|
40
|
-
require
|
41
|
-
require
|
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 =
|
44
|
+
Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
|
45
45
|
Bootsnap::CompileCache::YAML.init!
|
46
|
-
Bootsnap::CompileCache::YAML.cache_dir =
|
46
|
+
Bootsnap::CompileCache::YAML.cache_dir = cache_dir
|
47
47
|
Bootsnap::CompileCache::JSON.init!
|
48
|
-
Bootsnap::CompileCache::JSON.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,
|
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,
|
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,
|
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,
|
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?(
|
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
|
142
|
-
|
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,
|
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?(
|
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
|
165
|
-
|
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,
|
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
|
187
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
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,
|
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 = <<~
|
228
|
+
help = <<~HELP
|
229
229
|
Path to the bootsnap cache directory. Defaults to tmp/cache
|
230
|
-
|
231
|
-
opts.on(
|
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 = <<~
|
235
|
+
help = <<~HELP
|
236
236
|
Print precompiled paths.
|
237
|
-
|
238
|
-
opts.on(
|
237
|
+
HELP
|
238
|
+
opts.on("--verbose", "-v", help.strip) do
|
239
239
|
self.verbose = true
|
240
240
|
end
|
241
241
|
|
242
|
-
help = <<~
|
242
|
+
help = <<~HELP
|
243
243
|
Number of workers to use. Default to number of processors, set to 0 to disable multi-processing.
|
244
|
-
|
245
|
-
opts.on(
|
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 = <<~
|
254
|
+
help = <<~HELP
|
255
255
|
Precompile the gems in Gemfile
|
256
|
-
|
257
|
-
opts.on(
|
256
|
+
HELP
|
257
|
+
opts.on("--gemfile", help) { self.compile_gemfile = true }
|
258
258
|
|
259
|
-
help = <<~
|
259
|
+
help = <<~HELP
|
260
260
|
Path pattern to not precompile. e.g. --exclude 'aws-sdk|google-api'
|
261
|
-
|
262
|
-
opts.on(
|
261
|
+
HELP
|
262
|
+
opts.on("--exclude PATTERN", help) { |pattern| exclude_pattern(pattern) }
|
263
263
|
|
264
|
-
help = <<~
|
264
|
+
help = <<~HELP
|
265
265
|
Disable ISeq (.rb) precompilation.
|
266
|
-
|
267
|
-
opts.on(
|
266
|
+
HELP
|
267
|
+
opts.on("--no-iseq", help) { self.iseq = false }
|
268
268
|
|
269
|
-
help = <<~
|
269
|
+
help = <<~HELP
|
270
270
|
Disable YAML precompilation.
|
271
|
-
|
272
|
-
opts.on(
|
271
|
+
HELP
|
272
|
+
opts.on("--no-yaml", help) { self.yaml = false }
|
273
273
|
|
274
|
-
help = <<~
|
274
|
+
help = <<~HELP
|
275
275
|
Disable JSON precompilation.
|
276
|
-
|
277
|
-
opts.on(
|
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
|
-
|
3
|
-
require(
|
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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 =>
|
21
|
-
if
|
22
|
-
|
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
|
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 =>
|
59
|
-
if
|
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
|
-
|
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, :
|
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
|
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
|
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(
|
44
|
-
require(
|
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
|
54
|
+
if supports_freeze?
|
49
55
|
self.supported_options = [:freeze]
|
50
56
|
end
|
51
|
-
|
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
|
-
|
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
|