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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4565db3033321cbf9866e33e0d42021d76f164974154bfb62dc9bcb02082763c
4
- data.tar.gz: 48424a914d66bebe2029e2bbdb86aef1c0d684a9a773f219a54b4c2675aec0b4
3
+ metadata.gz: 8741381213652a6e3fc633bb87c69446d65ded8ccc5dcf72e64a8fa4a796955b
4
+ data.tar.gz: e72c311d1417b7c4d174c5b95cc6d95b1b0078dfd5dfb71c213f6704cd011665
5
5
  SHA512:
6
- metadata.gz: 05732a91a5437872eb83b96fcf1fa2db302acf599a53ca5a25c5ef9b628eef780051173f5081261a2491798ea71a005af180504da19707a09c52692eb01d65c3
7
- data.tar.gz: b19e9db48806a6cee08f9de5268c81fb235d577e8771f6acb238843a507856cb120fce3d63bec7d4664d4c88ea60f01ebb7acd8545ffa10291a3157dd25ebf89
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
- def self.input_to_storage(_, path)
13
- RubyVM::InstructionSequence.compile_file(path).to_binary
14
- rescue SyntaxError
15
- raise(Uncompilable, 'syntax error')
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
- return expand_path(feature) if feature.start_with?('./')
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
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
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
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
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
- return load_without_bootsnap(resolved, wrap)
61
- end
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 { |feat| feat =~ pat }
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Bootsnap
3
- VERSION = "1.8.1"
3
+ VERSION = "1.9.3"
4
4
  end
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.8.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-08-27 00:00:00.000000000 Z
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