bootsnap 1.4.5 → 1.6.0

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