bootsnap 1.4.5 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +17 -3
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +183 -65
- data/ext/bootsnap/extconf.rb +1 -0
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli/worker_pool.rb +131 -0
- data/lib/bootsnap/cli.rb +246 -0
- data/lib/bootsnap/compile_cache/iseq.rb +22 -7
- data/lib/bootsnap/compile_cache/yaml.rb +89 -39
- data/lib/bootsnap/compile_cache.rb +3 -2
- data/lib/bootsnap/explicit_require.rb +1 -0
- data/lib/bootsnap/load_path_cache/cache.rb +8 -8
- data/lib/bootsnap/load_path_cache/change_observer.rb +2 -1
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +1 -0
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -5
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +33 -10
- data/lib/bootsnap/load_path_cache/path.rb +3 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +39 -26
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +6 -5
- data/lib/bootsnap/load_path_cache.rb +1 -1
- data/lib/bootsnap/setup.rb +1 -0
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +4 -2
- metadata +15 -28
- data/.github/CODEOWNERS +0 -2
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -21
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/README.jp.md +0 -231
- data/Rakefile +0 -12
- data/bin/ci +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -45
- data/dev.yml +0 -10
- data/shipit.rubygems.yml +0 -0
@@ -0,0 +1,131 @@
|
|
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
|
41
|
+
@pid = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def write(message, block: true)
|
45
|
+
payload = Marshal.dump(message)
|
46
|
+
if block
|
47
|
+
to_io.write(payload)
|
48
|
+
true
|
49
|
+
else
|
50
|
+
to_io.write_nonblock(payload, exception: false) != :wait_writable
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def close
|
55
|
+
to_io.close
|
56
|
+
end
|
57
|
+
|
58
|
+
def work_loop
|
59
|
+
loop do
|
60
|
+
job, *args = Marshal.load(@pipe_out)
|
61
|
+
return if job == :exit
|
62
|
+
@jobs.fetch(job).call(*args)
|
63
|
+
end
|
64
|
+
rescue IOError
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def spawn
|
69
|
+
@pid = Process.fork do
|
70
|
+
to_io.close
|
71
|
+
work_loop
|
72
|
+
exit!(0)
|
73
|
+
end
|
74
|
+
@pipe_out.close
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(size:, jobs: {})
|
80
|
+
@size = size
|
81
|
+
@jobs = jobs
|
82
|
+
@queue = Queue.new
|
83
|
+
@pids = []
|
84
|
+
end
|
85
|
+
|
86
|
+
def spawn
|
87
|
+
@workers = @size.times.map { Worker.new(@jobs) }
|
88
|
+
@workers.each(&:spawn)
|
89
|
+
@dispatcher_thread = Thread.new { dispatch_loop }
|
90
|
+
@dispatcher_thread.abort_on_exception = true
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
def dispatch_loop
|
95
|
+
loop do
|
96
|
+
case job = @queue.pop
|
97
|
+
when nil
|
98
|
+
@workers.each do |worker|
|
99
|
+
worker.write([:exit])
|
100
|
+
worker.close
|
101
|
+
end
|
102
|
+
return true
|
103
|
+
else
|
104
|
+
unless @workers.sample.write(job, block: false)
|
105
|
+
free_worker.write(job)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def free_worker
|
112
|
+
IO.select(nil, @workers)[1].sample
|
113
|
+
end
|
114
|
+
|
115
|
+
def push(*args)
|
116
|
+
@queue.push(args)
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def shutdown
|
121
|
+
@queue.close
|
122
|
+
@dispatcher_thread.join
|
123
|
+
@workers.each do |worker|
|
124
|
+
_pid, status = Process.wait2(worker.pid)
|
125
|
+
return status.exitstatus unless status.success?
|
126
|
+
end
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/bootsnap/cli.rb
ADDED
@@ -0,0 +1,246 @@
|
|
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, :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
|
+
end
|
36
|
+
|
37
|
+
def precompile_command(*sources)
|
38
|
+
require 'bootsnap/compile_cache/iseq'
|
39
|
+
require 'bootsnap/compile_cache/yaml'
|
40
|
+
|
41
|
+
fix_default_encoding do
|
42
|
+
Bootsnap::CompileCache::ISeq.cache_dir = self.cache_dir
|
43
|
+
Bootsnap::CompileCache::YAML.init!
|
44
|
+
Bootsnap::CompileCache::YAML.cache_dir = self.cache_dir
|
45
|
+
|
46
|
+
@work_pool = WorkerPool.create(size: jobs, jobs: {
|
47
|
+
ruby: method(:precompile_ruby),
|
48
|
+
yaml: method(:precompile_yaml),
|
49
|
+
})
|
50
|
+
@work_pool.spawn
|
51
|
+
|
52
|
+
main_sources = sources.map { |d| File.expand_path(d) }
|
53
|
+
precompile_ruby_files(main_sources)
|
54
|
+
precompile_yaml_files(main_sources)
|
55
|
+
|
56
|
+
if compile_gemfile
|
57
|
+
# Some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
|
58
|
+
gem_exclude = Regexp.union([exclude, '/spec/', '/test/'].compact)
|
59
|
+
precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude)
|
60
|
+
|
61
|
+
# Gems that include YAML files usually don't put them in `lib/`.
|
62
|
+
# So we look at the gem root.
|
63
|
+
gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gem\/[^/]+}
|
64
|
+
gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq
|
65
|
+
precompile_yaml_files(gem_paths, exclude: gem_exclude)
|
66
|
+
end
|
67
|
+
|
68
|
+
if exitstatus = @work_pool.shutdown
|
69
|
+
exit(exitstatus)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
0
|
73
|
+
end
|
74
|
+
|
75
|
+
dir_sort = begin
|
76
|
+
Dir[__FILE__, sort: false]
|
77
|
+
true
|
78
|
+
rescue ArgumentError, TypeError
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
if dir_sort
|
83
|
+
def list_files(path, pattern)
|
84
|
+
if File.directory?(path)
|
85
|
+
Dir[File.join(path, pattern), sort: false]
|
86
|
+
elsif File.exist?(path)
|
87
|
+
[path]
|
88
|
+
else
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
else
|
93
|
+
def list_files(path, pattern)
|
94
|
+
if File.directory?(path)
|
95
|
+
Dir[File.join(path, pattern)]
|
96
|
+
elsif File.exist?(path)
|
97
|
+
[path]
|
98
|
+
else
|
99
|
+
[]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def run
|
105
|
+
parser.parse!(argv)
|
106
|
+
command = argv.shift
|
107
|
+
method = "#{command}_command"
|
108
|
+
if respond_to?(method)
|
109
|
+
public_send(method, *argv)
|
110
|
+
else
|
111
|
+
invalid_usage!("Unknown command: #{command}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def precompile_yaml_files(load_paths, exclude: self.exclude)
|
118
|
+
return unless yaml
|
119
|
+
|
120
|
+
load_paths.each do |path|
|
121
|
+
if !exclude || !exclude.match?(path)
|
122
|
+
list_files(path, '**/*.{yml,yaml}').each do |yaml_file|
|
123
|
+
# We ignore hidden files to not match the various .ci.yml files
|
124
|
+
if !yaml_file.include?('/.') && (!exclude || !exclude.match?(yaml_file))
|
125
|
+
@work_pool.push(:yaml, yaml_file)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def precompile_yaml(*yaml_files)
|
133
|
+
Array(yaml_files).each do |yaml_file|
|
134
|
+
if CompileCache::YAML.precompile(yaml_file, cache_dir: cache_dir)
|
135
|
+
STDERR.puts(yaml_file) if verbose
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def precompile_ruby_files(load_paths, exclude: self.exclude)
|
141
|
+
return unless iseq
|
142
|
+
|
143
|
+
load_paths.each do |path|
|
144
|
+
if !exclude || !exclude.match?(path)
|
145
|
+
list_files(path, '**/*.rb').each do |ruby_file|
|
146
|
+
if !exclude || !exclude.match?(ruby_file)
|
147
|
+
@work_pool.push(:ruby, ruby_file)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def precompile_ruby(*ruby_files)
|
155
|
+
Array(ruby_files).each do |ruby_file|
|
156
|
+
if CompileCache::ISeq.precompile(ruby_file, cache_dir: cache_dir)
|
157
|
+
STDERR.puts(ruby_file) if verbose
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def fix_default_encoding
|
163
|
+
if Encoding.default_external == Encoding::US_ASCII
|
164
|
+
Encoding.default_external = Encoding::UTF_8
|
165
|
+
begin
|
166
|
+
yield
|
167
|
+
ensure
|
168
|
+
Encoding.default_external = Encoding::US_ASCII
|
169
|
+
end
|
170
|
+
else
|
171
|
+
yield
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def invalid_usage!(message)
|
176
|
+
STDERR.puts message
|
177
|
+
STDERR.puts
|
178
|
+
STDERR.puts parser
|
179
|
+
1
|
180
|
+
end
|
181
|
+
|
182
|
+
def cache_dir=(dir)
|
183
|
+
@cache_dir = File.expand_path(File.join(dir, 'bootsnap/compile-cache'))
|
184
|
+
end
|
185
|
+
|
186
|
+
def exclude_pattern(pattern)
|
187
|
+
(@exclude_patterns ||= []) << Regexp.new(pattern)
|
188
|
+
self.exclude = Regexp.union(@exclude_patterns)
|
189
|
+
end
|
190
|
+
|
191
|
+
def parser
|
192
|
+
@parser ||= OptionParser.new do |opts|
|
193
|
+
opts.banner = "Usage: bootsnap COMMAND [ARGS]"
|
194
|
+
opts.separator ""
|
195
|
+
opts.separator "GLOBAL OPTIONS"
|
196
|
+
opts.separator ""
|
197
|
+
|
198
|
+
help = <<~EOS
|
199
|
+
Path to the bootsnap cache directory. Defaults to tmp/cache
|
200
|
+
EOS
|
201
|
+
opts.on('--cache-dir DIR', help.strip) do |dir|
|
202
|
+
self.cache_dir = dir
|
203
|
+
end
|
204
|
+
|
205
|
+
help = <<~EOS
|
206
|
+
Print precompiled paths.
|
207
|
+
EOS
|
208
|
+
opts.on('--verbose', '-v', help.strip) do
|
209
|
+
self.verbose = true
|
210
|
+
end
|
211
|
+
|
212
|
+
help = <<~EOS
|
213
|
+
Number of workers to use. Default to number of processors, set to 0 to disable multi-processing.
|
214
|
+
EOS
|
215
|
+
opts.on('--jobs JOBS', '-j', help.strip) do |jobs|
|
216
|
+
self.jobs = Integer(jobs)
|
217
|
+
end
|
218
|
+
|
219
|
+
opts.separator ""
|
220
|
+
opts.separator "COMMANDS"
|
221
|
+
opts.separator ""
|
222
|
+
opts.separator " precompile [DIRECTORIES...]: Precompile all .rb files in the passed directories"
|
223
|
+
|
224
|
+
help = <<~EOS
|
225
|
+
Precompile the gems in Gemfile
|
226
|
+
EOS
|
227
|
+
opts.on('--gemfile', help) { self.compile_gemfile = true }
|
228
|
+
|
229
|
+
help = <<~EOS
|
230
|
+
Path pattern to not precompile. e.g. --exclude 'aws-sdk|google-api'
|
231
|
+
EOS
|
232
|
+
opts.on('--exclude PATTERN', help) { |pattern| exclude_pattern(pattern) }
|
233
|
+
|
234
|
+
help = <<~EOS
|
235
|
+
Disable ISeq (.rb) precompilation.
|
236
|
+
EOS
|
237
|
+
opts.on('--no-iseq', help) { self.iseq = false }
|
238
|
+
|
239
|
+
help = <<~EOS
|
240
|
+
Disable YAML precompilation.
|
241
|
+
EOS
|
242
|
+
opts.on('--no-yaml', help) { self.yaml = false }
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require('bootsnap/bootsnap')
|
2
3
|
require('zlib')
|
3
4
|
|
@@ -14,7 +15,7 @@ module Bootsnap
|
|
14
15
|
raise(Uncompilable, 'syntax error')
|
15
16
|
end
|
16
17
|
|
17
|
-
def self.storage_to_output(binary)
|
18
|
+
def self.storage_to_output(binary, _args)
|
18
19
|
RubyVM::InstructionSequence.load_from_binary(binary)
|
19
20
|
rescue RuntimeError => e
|
20
21
|
if e.message == 'broken binary format'
|
@@ -25,7 +26,24 @@ module Bootsnap
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
def self.
|
29
|
+
def self.fetch(path, cache_dir: ISeq.cache_dir)
|
30
|
+
Bootsnap::CompileCache::Native.fetch(
|
31
|
+
cache_dir,
|
32
|
+
path.to_s,
|
33
|
+
Bootsnap::CompileCache::ISeq,
|
34
|
+
nil,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.precompile(path, cache_dir: ISeq.cache_dir)
|
39
|
+
Bootsnap::CompileCache::Native.precompile(
|
40
|
+
cache_dir,
|
41
|
+
path.to_s,
|
42
|
+
Bootsnap::CompileCache::ISeq,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.input_to_output(_data, _kwargs)
|
29
47
|
nil # ruby handles this
|
30
48
|
end
|
31
49
|
|
@@ -34,11 +52,7 @@ module Bootsnap
|
|
34
52
|
# Having coverage enabled prevents iseq dumping/loading.
|
35
53
|
return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
|
36
54
|
|
37
|
-
Bootsnap::CompileCache::
|
38
|
-
Bootsnap::CompileCache::ISeq.cache_dir,
|
39
|
-
path.to_s,
|
40
|
-
Bootsnap::CompileCache::ISeq
|
41
|
-
)
|
55
|
+
Bootsnap::CompileCache::ISeq.fetch(path.to_s)
|
42
56
|
rescue Errno::EACCES
|
43
57
|
Bootsnap::CompileCache.permission_error(path)
|
44
58
|
rescue RuntimeError => e
|
@@ -59,6 +73,7 @@ module Bootsnap
|
|
59
73
|
crc = Zlib.crc32(option.inspect)
|
60
74
|
Bootsnap::CompileCache::Native.compile_option_crc32 = crc
|
61
75
|
end
|
76
|
+
compile_option_updated
|
62
77
|
|
63
78
|
def self.install!(cache_dir)
|
64
79
|
Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
|
@@ -1,61 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require('bootsnap/bootsnap')
|
2
3
|
|
3
4
|
module Bootsnap
|
4
5
|
module CompileCache
|
5
6
|
module YAML
|
6
7
|
class << self
|
7
|
-
attr_accessor(:msgpack_factory)
|
8
|
-
end
|
8
|
+
attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
Marshal.dump(obj)
|
19
|
-
end
|
10
|
+
def input_to_storage(contents, _)
|
11
|
+
raise(Uncompilable) if contents.index("!ruby/object")
|
12
|
+
obj = ::YAML.load(contents)
|
13
|
+
msgpack_factory.dump(obj)
|
14
|
+
rescue NoMethodError, RangeError
|
15
|
+
# The object included things that we can't serialize
|
16
|
+
raise(Uncompilable)
|
17
|
+
end
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
Marshal.load(data)
|
27
|
-
else
|
28
|
-
msgpack_factory.unpacker.feed(data).read
|
19
|
+
def storage_to_output(data, kwargs)
|
20
|
+
if kwargs && kwargs.key?(:symbolize_names)
|
21
|
+
kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
|
22
|
+
end
|
23
|
+
msgpack_factory.load(data, kwargs)
|
29
24
|
end
|
30
|
-
end
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
def input_to_output(data, kwargs)
|
27
|
+
::YAML.load(data, **(kwargs || {}))
|
28
|
+
end
|
29
|
+
|
30
|
+
def precompile(path, cache_dir: YAML.cache_dir)
|
31
|
+
Bootsnap::CompileCache::Native.precompile(
|
32
|
+
cache_dir,
|
33
|
+
path.to_s,
|
34
|
+
Bootsnap::CompileCache::YAML,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def install!(cache_dir)
|
39
|
+
self.cache_dir = cache_dir
|
40
|
+
init!
|
41
|
+
::YAML.singleton_class.prepend(Patch)
|
42
|
+
end
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
|
44
|
+
def init!
|
45
|
+
require('yaml')
|
46
|
+
require('msgpack')
|
47
|
+
require('date')
|
39
48
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
49
|
+
# MessagePack serializes symbols as strings by default.
|
50
|
+
# We want them to roundtrip cleanly, so we use a custom factory.
|
51
|
+
# see: https://github.com/msgpack/msgpack-ruby/pull/122
|
52
|
+
factory = MessagePack::Factory.new
|
53
|
+
factory.register_type(0x00, Symbol)
|
54
|
+
factory.register_type(
|
55
|
+
MessagePack::Timestamp::TYPE, # or just -1
|
56
|
+
Time,
|
57
|
+
packer: MessagePack::Time::Packer,
|
58
|
+
unpacker: MessagePack::Time::Unpacker
|
59
|
+
)
|
60
|
+
|
61
|
+
marshal_fallback = {
|
62
|
+
packer: ->(value) { Marshal.dump(value) },
|
63
|
+
unpacker: ->(payload) { Marshal.load(payload) },
|
64
|
+
}
|
65
|
+
{
|
66
|
+
Date => 0x01,
|
67
|
+
Regexp => 0x02,
|
68
|
+
}.each do |type, code|
|
69
|
+
factory.register_type(code, type, marshal_fallback)
|
70
|
+
end
|
71
|
+
|
72
|
+
self.msgpack_factory = factory
|
73
|
+
|
74
|
+
self.supported_options = []
|
75
|
+
params = ::YAML.method(:load).parameters
|
76
|
+
if params.include?([:key, :symbolize_names])
|
77
|
+
self.supported_options << :symbolize_names
|
78
|
+
end
|
79
|
+
if params.include?([:key, :freeze])
|
80
|
+
if factory.load(factory.dump('yaml'), freeze: true).frozen?
|
81
|
+
self.supported_options << :freeze
|
82
|
+
end
|
83
|
+
end
|
84
|
+
self.supported_options.freeze
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module Patch
|
89
|
+
def load_file(path, *args)
|
90
|
+
return super if args.size > 1
|
91
|
+
if kwargs = args.first
|
92
|
+
return super unless kwargs.is_a?(Hash)
|
93
|
+
return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
|
94
|
+
end
|
46
95
|
|
47
|
-
klass = class << ::YAML; self; end
|
48
|
-
klass.send(:define_method, :load_file) do |path|
|
49
96
|
begin
|
50
|
-
Bootsnap::CompileCache::Native.fetch(
|
51
|
-
cache_dir,
|
97
|
+
::Bootsnap::CompileCache::Native.fetch(
|
98
|
+
Bootsnap::CompileCache::YAML.cache_dir,
|
52
99
|
path,
|
53
|
-
Bootsnap::CompileCache::YAML
|
100
|
+
::Bootsnap::CompileCache::YAML,
|
101
|
+
kwargs,
|
54
102
|
)
|
55
103
|
rescue Errno::EACCES
|
56
|
-
Bootsnap::CompileCache.permission_error(path)
|
104
|
+
::Bootsnap::CompileCache.permission_error(path)
|
57
105
|
end
|
58
106
|
end
|
107
|
+
|
108
|
+
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
59
109
|
end
|
60
110
|
end
|
61
111
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module CompileCache
|
3
4
|
Error = Class.new(StandardError)
|
@@ -33,9 +34,9 @@ module Bootsnap
|
|
33
34
|
end
|
34
35
|
|
35
36
|
def self.supported?
|
36
|
-
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
|
37
|
+
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
|
37
38
|
RUBY_ENGINE == 'ruby' &&
|
38
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
|
39
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
|
39
40
|
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
40
41
|
end
|
41
42
|
end
|
@@ -46,7 +46,7 @@ module Bootsnap
|
|
46
46
|
# loadpath.
|
47
47
|
def find(feature)
|
48
48
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
49
|
-
feature = feature.to_s
|
49
|
+
feature = feature.to_s.freeze
|
50
50
|
return feature if absolute_path?(feature)
|
51
51
|
return expand_path(feature) if feature.start_with?('./')
|
52
52
|
@mutex.synchronize do
|
@@ -67,7 +67,7 @@ module Bootsnap
|
|
67
67
|
# native dynamic extension, e.g. .bundle or .so), we know it was a
|
68
68
|
# failure and there's nothing more we can do to find the file.
|
69
69
|
# no extension, .rb, (.bundle or .so)
|
70
|
-
when '', *CACHED_EXTENSIONS
|
70
|
+
when '', *CACHED_EXTENSIONS
|
71
71
|
nil
|
72
72
|
# Ruby allows specifying native extensions as '.so' even when DLEXT
|
73
73
|
# is '.bundle'. This is where we handle that case.
|
@@ -144,7 +144,7 @@ module Bootsnap
|
|
144
144
|
expanded_path = p.expanded_path
|
145
145
|
entries, dirs = p.entries_and_dirs(@store)
|
146
146
|
# push -> low precedence -> set only if unset
|
147
|
-
dirs.each { |dir| @dirs[dir]
|
147
|
+
dirs.each { |dir| @dirs[dir] ||= path }
|
148
148
|
entries.each { |rel| @index[rel] ||= expanded_path }
|
149
149
|
end
|
150
150
|
end
|
@@ -178,25 +178,25 @@ module Bootsnap
|
|
178
178
|
|
179
179
|
if DLEXT2
|
180
180
|
def search_index(f)
|
181
|
-
try_index(f
|
181
|
+
try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
|
182
182
|
end
|
183
183
|
|
184
184
|
def maybe_append_extension(f)
|
185
|
-
try_ext(f
|
185
|
+
try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
|
186
186
|
end
|
187
187
|
else
|
188
188
|
def search_index(f)
|
189
|
-
try_index(f
|
189
|
+
try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
|
190
190
|
end
|
191
191
|
|
192
192
|
def maybe_append_extension(f)
|
193
|
-
try_ext(f
|
193
|
+
try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
197
|
def try_index(f)
|
198
198
|
if (p = @index[f])
|
199
|
-
p
|
199
|
+
"#{p}/#{f}"
|
200
200
|
end
|
201
201
|
end
|
202
202
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module LoadPathCache
|
3
4
|
module ChangeObserver
|
@@ -25,7 +26,7 @@ module Bootsnap
|
|
25
26
|
super
|
26
27
|
end
|
27
28
|
|
28
|
-
# uniq! keeps the first
|
29
|
+
# uniq! keeps the first occurrence of each path, otherwise preserving
|
29
30
|
# order, preserving the effective load path
|
30
31
|
def uniq!(*args)
|
31
32
|
ret = super
|