bootsnap 1.8.1 → 1.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/lib/bootsnap/cli.rb +37 -2
- data/lib/bootsnap/compile_cache/iseq.rb +29 -4
- data/lib/bootsnap/compile_cache/json.rb +79 -0
- data/lib/bootsnap/compile_cache.rb +10 -1
- data/lib/bootsnap/load_path_cache/cache.rb +21 -7
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +3 -19
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +10 -2
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +5 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8741381213652a6e3fc633bb87c69446d65ded8ccc5dcf72e64a8fa4a796955b
|
4
|
+
data.tar.gz: e72c311d1417b7c4d174c5b95cc6d95b1b0078dfd5dfb71c213f6704cd011665
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 804f427e8dd71b5aab1fe772f0f44f9fd8598cd2cd9bfc2348a13bff85fb1d65f59711188ca52b8dabc26e688655b4565e2aca2b2b8bf2a85e2635a299fe2f3a
|
7
|
+
data.tar.gz: a679ccfd21df451edf3e77c750f92388ab746afbb5e5d84e3265e8e2fbd52f6c17d6a303efc179e24fa994de95762d30e2b31683517d500236fe0c2ba5f23d25
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 1.9.3
|
4
|
+
|
5
|
+
* Only disable the compile cache for source files impacted by [Ruby 3.0.3 [Bug 18250]](https://bugs.ruby-lang.org/issues/18250).
|
6
|
+
This should keep the performance loss to a minimum.
|
7
|
+
|
8
|
+
# 1.9.2
|
9
|
+
|
10
|
+
* Disable compile cache if [Ruby 3.0.3's ISeq cache bug](https://bugs.ruby-lang.org/issues/18250) is detected.
|
11
|
+
AKA `iseq.rb:13 to_binary: wrong argument type false (expected Symbol)`
|
12
|
+
* Fix `Kernel.load` behavior: before `load 'a'` would load `a.rb` (and other tried extensions) and wouldn't load `a` unless `development_mode: true`, now only `a` would be loaded and files with extensions wouldn't be.
|
13
|
+
|
14
|
+
# 1.9.1
|
15
|
+
|
16
|
+
* Removed a forgotten debug statement in JSON precompilation.
|
17
|
+
|
18
|
+
# 1.9.0
|
19
|
+
|
20
|
+
* Added a compilation cache for `JSON.load_file`. (#370)
|
21
|
+
|
3
22
|
# 1.8.1
|
4
23
|
|
5
24
|
* Fixed support for older Psych. (#369)
|
data/lib/bootsnap/cli.rb
CHANGED
@@ -21,7 +21,7 @@ module Bootsnap
|
|
21
21
|
|
22
22
|
attr_reader :cache_dir, :argv
|
23
23
|
|
24
|
-
attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :jobs
|
24
|
+
attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :json, :jobs
|
25
25
|
|
26
26
|
def initialize(argv)
|
27
27
|
@argv = argv
|
@@ -32,37 +32,44 @@ module Bootsnap
|
|
32
32
|
self.jobs = Etc.nprocessors
|
33
33
|
self.iseq = true
|
34
34
|
self.yaml = true
|
35
|
+
self.json = true
|
35
36
|
end
|
36
37
|
|
37
38
|
def precompile_command(*sources)
|
38
39
|
require 'bootsnap/compile_cache/iseq'
|
39
40
|
require 'bootsnap/compile_cache/yaml'
|
41
|
+
require 'bootsnap/compile_cache/json'
|
40
42
|
|
41
43
|
fix_default_encoding do
|
42
44
|
Bootsnap::CompileCache::ISeq.cache_dir = self.cache_dir
|
43
45
|
Bootsnap::CompileCache::YAML.init!
|
44
46
|
Bootsnap::CompileCache::YAML.cache_dir = self.cache_dir
|
47
|
+
Bootsnap::CompileCache::JSON.init!
|
48
|
+
Bootsnap::CompileCache::JSON.cache_dir = self.cache_dir
|
45
49
|
|
46
50
|
@work_pool = WorkerPool.create(size: jobs, jobs: {
|
47
51
|
ruby: method(:precompile_ruby),
|
48
52
|
yaml: method(:precompile_yaml),
|
53
|
+
json: method(:precompile_json),
|
49
54
|
})
|
50
55
|
@work_pool.spawn
|
51
56
|
|
52
57
|
main_sources = sources.map { |d| File.expand_path(d) }
|
53
58
|
precompile_ruby_files(main_sources)
|
54
59
|
precompile_yaml_files(main_sources)
|
60
|
+
precompile_json_files(main_sources)
|
55
61
|
|
56
62
|
if compile_gemfile
|
57
63
|
# Some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
|
58
64
|
gem_exclude = Regexp.union([exclude, '/spec/', '/test/'].compact)
|
59
65
|
precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude)
|
60
66
|
|
61
|
-
# Gems that include YAML files usually don't put them in `lib/`.
|
67
|
+
# Gems that include JSON or YAML files usually don't put them in `lib/`.
|
62
68
|
# So we look at the gem root.
|
63
69
|
gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems\/[^/]+}
|
64
70
|
gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq
|
65
71
|
precompile_yaml_files(gem_paths, exclude: gem_exclude)
|
72
|
+
precompile_json_files(gem_paths, exclude: gem_exclude)
|
66
73
|
end
|
67
74
|
|
68
75
|
if exitstatus = @work_pool.shutdown
|
@@ -137,6 +144,29 @@ module Bootsnap
|
|
137
144
|
end
|
138
145
|
end
|
139
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, cache_dir: cache_dir)
|
165
|
+
STDERR.puts(json_file) if verbose
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
140
170
|
def precompile_ruby_files(load_paths, exclude: self.exclude)
|
141
171
|
return unless iseq
|
142
172
|
|
@@ -240,6 +270,11 @@ module Bootsnap
|
|
240
270
|
Disable YAML precompilation.
|
241
271
|
EOS
|
242
272
|
opts.on('--no-yaml', help) { self.yaml = false }
|
273
|
+
|
274
|
+
help = <<~EOS
|
275
|
+
Disable JSON precompilation.
|
276
|
+
EOS
|
277
|
+
opts.on('--no-json', help) { self.json = false }
|
243
278
|
end
|
244
279
|
end
|
245
280
|
end
|
@@ -9,10 +9,35 @@ module Bootsnap
|
|
9
9
|
attr_accessor(:cache_dir)
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
RubyVM::InstructionSequence
|
14
|
-
|
15
|
-
|
12
|
+
has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
|
13
|
+
if defined? RubyVM::InstructionSequence
|
14
|
+
RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary
|
15
|
+
end
|
16
|
+
false
|
17
|
+
rescue TypeError
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
if has_ruby_bug_18250
|
22
|
+
def self.input_to_storage(_, path)
|
23
|
+
iseq = begin
|
24
|
+
RubyVM::InstructionSequence.compile_file(path)
|
25
|
+
rescue SyntaxError
|
26
|
+
raise(Uncompilable, 'syntax error')
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
iseq.to_binary
|
31
|
+
rescue TypeError
|
32
|
+
raise(Uncompilable, 'ruby bug #18250')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
def self.input_to_storage(_, path)
|
37
|
+
RubyVM::InstructionSequence.compile_file(path).to_binary
|
38
|
+
rescue SyntaxError
|
39
|
+
raise(Uncompilable, 'syntax error')
|
40
|
+
end
|
16
41
|
end
|
17
42
|
|
18
43
|
def self.storage_to_output(binary, _args)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require('bootsnap/bootsnap')
|
3
|
+
|
4
|
+
module Bootsnap
|
5
|
+
module CompileCache
|
6
|
+
module JSON
|
7
|
+
class << self
|
8
|
+
attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
|
9
|
+
|
10
|
+
def input_to_storage(payload, _)
|
11
|
+
obj = ::JSON.parse(payload)
|
12
|
+
msgpack_factory.dump(obj)
|
13
|
+
end
|
14
|
+
|
15
|
+
def storage_to_output(data, kwargs)
|
16
|
+
if kwargs && kwargs.key?(:symbolize_names)
|
17
|
+
kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
|
18
|
+
end
|
19
|
+
msgpack_factory.load(data, kwargs)
|
20
|
+
end
|
21
|
+
|
22
|
+
def input_to_output(data, kwargs)
|
23
|
+
::JSON.parse(data, **(kwargs || {}))
|
24
|
+
end
|
25
|
+
|
26
|
+
def precompile(path, cache_dir: self.cache_dir)
|
27
|
+
Bootsnap::CompileCache::Native.precompile(
|
28
|
+
cache_dir,
|
29
|
+
path.to_s,
|
30
|
+
self,
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def install!(cache_dir)
|
35
|
+
self.cache_dir = cache_dir
|
36
|
+
init!
|
37
|
+
if ::JSON.respond_to?(:load_file)
|
38
|
+
::JSON.singleton_class.prepend(Patch)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def init!
|
43
|
+
require('json')
|
44
|
+
require('msgpack')
|
45
|
+
|
46
|
+
self.msgpack_factory = MessagePack::Factory.new
|
47
|
+
self.supported_options = [:symbolize_names]
|
48
|
+
if ::JSON.parse('["foo"]', freeze: true).first.frozen?
|
49
|
+
self.supported_options = [:freeze]
|
50
|
+
end
|
51
|
+
self.supported_options.freeze
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module Patch
|
56
|
+
def load_file(path, *args)
|
57
|
+
return super if args.size > 1
|
58
|
+
if kwargs = args.first
|
59
|
+
return super unless kwargs.is_a?(Hash)
|
60
|
+
return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
begin
|
64
|
+
::Bootsnap::CompileCache::Native.fetch(
|
65
|
+
Bootsnap::CompileCache::JSON.cache_dir,
|
66
|
+
File.realpath(path),
|
67
|
+
::Bootsnap::CompileCache::JSON,
|
68
|
+
kwargs,
|
69
|
+
)
|
70
|
+
rescue Errno::EACCES
|
71
|
+
::Bootsnap::CompileCache.permission_error(path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -4,7 +4,7 @@ module Bootsnap
|
|
4
4
|
Error = Class.new(StandardError)
|
5
5
|
PermissionError = Class.new(Error)
|
6
6
|
|
7
|
-
def self.setup(cache_dir:, iseq:, yaml:)
|
7
|
+
def self.setup(cache_dir:, iseq:, yaml:, json:)
|
8
8
|
if iseq
|
9
9
|
if supported?
|
10
10
|
require_relative('compile_cache/iseq')
|
@@ -22,6 +22,15 @@ module Bootsnap
|
|
22
22
|
warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
if json
|
27
|
+
if supported?
|
28
|
+
require_relative('compile_cache/json')
|
29
|
+
Bootsnap::CompileCache::JSON.install!(cache_dir)
|
30
|
+
elsif $VERBOSE
|
31
|
+
warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
|
32
|
+
end
|
33
|
+
end
|
25
34
|
end
|
26
35
|
|
27
36
|
def self.permission_error(path)
|
@@ -44,14 +44,20 @@ module Bootsnap
|
|
44
44
|
|
45
45
|
# Try to resolve this feature to an absolute path without traversing the
|
46
46
|
# loadpath.
|
47
|
-
def find(feature)
|
47
|
+
def find(feature, try_extensions: true)
|
48
48
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
49
49
|
feature = feature.to_s.freeze
|
50
|
+
|
50
51
|
return feature if absolute_path?(feature)
|
51
|
-
|
52
|
+
|
53
|
+
if feature.start_with?('./', '../')
|
54
|
+
return try_extensions ? expand_path(feature) : File.expand_path(feature).freeze
|
55
|
+
end
|
56
|
+
|
52
57
|
@mutex.synchronize do
|
53
|
-
x = search_index(feature)
|
58
|
+
x = search_index(feature, try_extensions: try_extensions)
|
54
59
|
return x if x
|
60
|
+
return unless try_extensions
|
55
61
|
|
56
62
|
# Ruby has some built-in features that require lies about.
|
57
63
|
# For example, 'enumerator' is built in. If you require it, ruby
|
@@ -177,16 +183,24 @@ module Bootsnap
|
|
177
183
|
end
|
178
184
|
|
179
185
|
if DLEXT2
|
180
|
-
def search_index(f)
|
181
|
-
|
186
|
+
def search_index(f, try_extensions: true)
|
187
|
+
if try_extensions
|
188
|
+
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
|
189
|
+
else
|
190
|
+
try_index(f)
|
191
|
+
end
|
182
192
|
end
|
183
193
|
|
184
194
|
def maybe_append_extension(f)
|
185
195
|
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
|
186
196
|
end
|
187
197
|
else
|
188
|
-
def search_index(f)
|
189
|
-
|
198
|
+
def search_index(f, try_extensions: true)
|
199
|
+
if try_extensions
|
200
|
+
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
|
201
|
+
else
|
202
|
+
try_index(f)
|
203
|
+
end
|
190
204
|
end
|
191
205
|
|
192
206
|
def maybe_append_extension(f)
|
@@ -56,25 +56,9 @@ module Kernel
|
|
56
56
|
|
57
57
|
alias_method(:load_without_bootsnap, :load)
|
58
58
|
def load(path, wrap = false)
|
59
|
-
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
# load also allows relative paths from pwd even when not in $:
|
64
|
-
if File.exist?(relative = File.expand_path(path).freeze)
|
65
|
-
return load_without_bootsnap(relative, wrap)
|
66
|
-
end
|
67
|
-
|
68
|
-
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
69
|
-
rescue LoadError => e
|
70
|
-
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
71
|
-
raise(e)
|
72
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
73
|
-
false
|
74
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
75
|
-
fallback = true
|
76
|
-
ensure
|
77
|
-
if fallback
|
59
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path, try_extensions: false))
|
60
|
+
load_without_bootsnap(resolved, wrap)
|
61
|
+
else
|
78
62
|
load_without_bootsnap(path, wrap)
|
79
63
|
end
|
80
64
|
end
|
@@ -84,10 +84,18 @@ module Bootsnap
|
|
84
84
|
# entry.
|
85
85
|
def register(short, long = nil)
|
86
86
|
if long.nil?
|
87
|
-
pat = %r{/#{Regexp.escape(short)}(\.[^/]+)?$}
|
88
87
|
len = $LOADED_FEATURES.size
|
89
88
|
ret = yield
|
90
|
-
long = $LOADED_FEATURES[len..-1].detect
|
89
|
+
long = $LOADED_FEATURES[len..-1].detect do |feat|
|
90
|
+
offset = 0
|
91
|
+
while offset = feat.index(short, offset)
|
92
|
+
if feat.index(".", offset + 1) && !feat.index("/", offset + 2)
|
93
|
+
break true
|
94
|
+
else
|
95
|
+
offset += 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
91
99
|
else
|
92
100
|
ret = yield
|
93
101
|
end
|
data/lib/bootsnap/version.rb
CHANGED
data/lib/bootsnap.rb
CHANGED
@@ -43,7 +43,8 @@ module Bootsnap
|
|
43
43
|
autoload_paths_cache: nil,
|
44
44
|
disable_trace: nil,
|
45
45
|
compile_cache_iseq: true,
|
46
|
-
compile_cache_yaml: true
|
46
|
+
compile_cache_yaml: true,
|
47
|
+
compile_cache_json: true
|
47
48
|
)
|
48
49
|
unless autoload_paths_cache.nil?
|
49
50
|
warn "[DEPRECATED] Bootsnap's `autoload_paths_cache:` option is deprecated and will be removed. " \
|
@@ -69,7 +70,8 @@ module Bootsnap
|
|
69
70
|
Bootsnap::CompileCache.setup(
|
70
71
|
cache_dir: cache_dir + '/bootsnap/compile-cache',
|
71
72
|
iseq: compile_cache_iseq,
|
72
|
-
yaml: compile_cache_yaml
|
73
|
+
yaml: compile_cache_yaml,
|
74
|
+
json: compile_cache_json,
|
73
75
|
)
|
74
76
|
end
|
75
77
|
|
@@ -113,6 +115,7 @@ module Bootsnap
|
|
113
115
|
load_path_cache: !ENV['DISABLE_BOOTSNAP_LOAD_PATH_CACHE'],
|
114
116
|
compile_cache_iseq: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'] && iseq_cache_supported?,
|
115
117
|
compile_cache_yaml: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'],
|
118
|
+
compile_cache_json: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'],
|
116
119
|
)
|
117
120
|
|
118
121
|
if ENV['BOOTSNAP_LOG']
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bootsnap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -116,6 +116,7 @@ files:
|
|
116
116
|
- lib/bootsnap/cli/worker_pool.rb
|
117
117
|
- lib/bootsnap/compile_cache.rb
|
118
118
|
- lib/bootsnap/compile_cache/iseq.rb
|
119
|
+
- lib/bootsnap/compile_cache/json.rb
|
119
120
|
- lib/bootsnap/compile_cache/yaml.rb
|
120
121
|
- lib/bootsnap/explicit_require.rb
|
121
122
|
- lib/bootsnap/load_path_cache.rb
|