bootsnap 1.4.5 → 1.6.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/README.md +17 -3
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +183 -65
  6. data/ext/bootsnap/extconf.rb +1 -0
  7. data/lib/bootsnap/bundler.rb +1 -0
  8. data/lib/bootsnap/cli/worker_pool.rb +131 -0
  9. data/lib/bootsnap/cli.rb +246 -0
  10. data/lib/bootsnap/compile_cache/iseq.rb +22 -7
  11. data/lib/bootsnap/compile_cache/yaml.rb +89 -39
  12. data/lib/bootsnap/compile_cache.rb +3 -2
  13. data/lib/bootsnap/explicit_require.rb +1 -0
  14. data/lib/bootsnap/load_path_cache/cache.rb +8 -8
  15. data/lib/bootsnap/load_path_cache/change_observer.rb +2 -1
  16. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +1 -0
  17. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -5
  18. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
  19. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +33 -10
  20. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  21. data/lib/bootsnap/load_path_cache/path_scanner.rb +39 -26
  22. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  23. data/lib/bootsnap/load_path_cache/store.rb +6 -5
  24. data/lib/bootsnap/load_path_cache.rb +1 -1
  25. data/lib/bootsnap/setup.rb +1 -0
  26. data/lib/bootsnap/version.rb +2 -1
  27. data/lib/bootsnap.rb +4 -2
  28. metadata +15 -28
  29. data/.github/CODEOWNERS +0 -2
  30. data/.github/probots.yml +0 -2
  31. data/.gitignore +0 -17
  32. data/.rubocop.yml +0 -20
  33. data/.travis.yml +0 -21
  34. data/CODE_OF_CONDUCT.md +0 -74
  35. data/CONTRIBUTING.md +0 -21
  36. data/Gemfile +0 -8
  37. data/README.jp.md +0 -231
  38. data/Rakefile +0 -12
  39. data/bin/ci +0 -10
  40. data/bin/console +0 -14
  41. data/bin/setup +0 -8
  42. data/bin/test-minimal-support +0 -7
  43. data/bin/testunit +0 -8
  44. data/bootsnap.gemspec +0 -45
  45. data/dev.yml +0 -10
  46. 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
@@ -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.input_to_output(_)
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::Native.fetch(
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
- def self.input_to_storage(contents, _)
11
- raise(Uncompilable) if contents.index("!ruby/object")
12
- obj = ::YAML.load(contents)
13
- msgpack_factory.packer.write(obj).to_s
14
- rescue NoMethodError, RangeError
15
- # if the object included things that we can't serialize, fall back to
16
- # Marshal. It's a bit slower, but can encode anything yaml can.
17
- # NoMethodError is unexpected types; RangeError is Bignums
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
- def self.storage_to_output(data)
22
- # This could have a meaning in messagepack, and we're being a little lazy
23
- # about it. -- but a leading 0x04 would indicate the contents of the YAML
24
- # is a positive integer, which is rare, to say the least.
25
- if data[0] == 0x04.chr && data[1] == 0x08.chr
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
- def self.input_to_output(data)
33
- ::YAML.load(data)
34
- end
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
- def self.install!(cache_dir)
37
- require('yaml')
38
- require('msgpack')
44
+ def init!
45
+ require('yaml')
46
+ require('msgpack')
47
+ require('date')
39
48
 
40
- # MessagePack serializes symbols as strings by default.
41
- # We want them to roundtrip cleanly, so we use a custom factory.
42
- # see: https://github.com/msgpack/msgpack-ruby/pull/122
43
- factory = MessagePack::Factory.new
44
- factory.register_type(0x00, Symbol)
45
- Bootsnap::CompileCache::YAML.msgpack_factory = factory
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
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module ExplicitRequire
3
4
  ARCHDIR = RbConfig::CONFIG['archdir']
@@ -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 # rubocop:disable Performance/CaseWhenSplat
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] ||= path }
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 + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || 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 + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || 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 + DOT_RB) || try_index(f + DLEXT) || 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 + DOT_RB) || try_ext(f + DLEXT) || 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 + '/' + f
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 occurance of each path, otherwise preserving
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
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module CoreExt