bootsnap 1.8.0 → 1.9.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a12930656b479ba9d0675f13bf1edd2a52e0d84314f1e528866110a1a36776df
4
- data.tar.gz: 33f4c8ea77733e461c9fe95ba75110e9da0b707e818727ebf5c13605d58eec62
3
+ metadata.gz: de52d100052f98bd60ccbc71d3172b7f7951df2fb45fb8dab4a5c4f8e95e4528
4
+ data.tar.gz: a09dacba13131d47509d41d729748a7d80703779bcbbb0a4c0474f0960cf5025
5
5
  SHA512:
6
- metadata.gz: c366c8c65f4ce3fd73979b68c1201c2d7872d059a71ac79a561bc464b7fdaf1d0ba00acbe59e491d25d66efe967b3c0a25a9542727a43c101c4d2b719b82407c
7
- data.tar.gz: 39d2ec6734c6234114c27f8665efe8a6170e6bcc58f98cec25f1c4386a644d0eec135ababc66b771dc0744c7df961f63d95669ebdb08db14c3b4982b320b5224
6
+ metadata.gz: 31fe33d4e05c9c85f4950434481131396dcc06485651144783767485da5ff887a6293aeb21d6baee22d3beeeb4c8e0a1ddb869f6f50b148bb241c674ee2b9f6a
7
+ data.tar.gz: 3e9a66ee5d26a54fa9c36515806c89f0757232b3191768f9769333155f08b5eda43d426c23a7ac144bd84ebd6d352042562bce8c828cbb2e8d624484d5adeb72
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.9.2
4
+
5
+ * Disable compile cache if Ruby 3.0.3's ISeq cache bug is detected.
6
+ * 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.
7
+
8
+ # 1.9.1
9
+
10
+ * Removed a forgotten debug statement in JSON precompilation.
11
+
12
+ # 1.9.0
13
+
14
+ * Added a compilation cache for `JSON.load_file`. (#370)
15
+
16
+ # 1.8.1
17
+
18
+ * Fixed support for older Psych. (#369)
19
+
3
20
  # 1.8.0
4
21
 
5
22
  * Improve support for Pysch 4. (#368)
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
@@ -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
@@ -23,7 +23,11 @@ module Bootsnap
23
23
  end
24
24
 
25
25
  def input_to_output(data, kwargs)
26
- ::YAML.unsafe_load(data, **(kwargs || {}))
26
+ if ::YAML.respond_to?(:unsafe_load)
27
+ ::YAML.unsafe_load(data, **(kwargs || {}))
28
+ else
29
+ ::YAML.load(data, **(kwargs || {}))
30
+ end
27
31
  end
28
32
 
29
33
  def strict_load(payload, *args)
@@ -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.0"
3
+ VERSION = "1.9.2"
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. " \
@@ -56,9 +57,15 @@ module Bootsnap
56
57
  "If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended."
57
58
  end
58
59
 
59
- if compile_cache_iseq && !iseq_cache_supported?
60
- warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommened " \
61
- "to turn `compile_cache_iseq` off on Ruby 2.5"
60
+ if compile_cache_iseq
61
+ if iseq_cache_tracing_bug?
62
+ warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommended " \
63
+ "to turn `compile_cache_iseq` off on Ruby 2.5"
64
+ end
65
+
66
+ if iseq_cache_anonymous_params_bug?
67
+ warn "Your version of Ruby 3.1.0-dev has a bug that break bytecode caching (https://github.com/ruby/ruby/pull/4961)."
68
+ end
62
69
  end
63
70
 
64
71
  Bootsnap::LoadPathCache.setup(
@@ -69,15 +76,33 @@ module Bootsnap
69
76
  Bootsnap::CompileCache.setup(
70
77
  cache_dir: cache_dir + '/bootsnap/compile-cache',
71
78
  iseq: compile_cache_iseq,
72
- yaml: compile_cache_yaml
79
+ yaml: compile_cache_yaml,
80
+ json: compile_cache_json,
73
81
  )
74
82
  end
75
83
 
76
- def self.iseq_cache_supported?
77
- return @iseq_cache_supported if defined? @iseq_cache_supported
84
+ def self.iseq_cache_tracing_bug?
85
+ return @iseq_cache_tracing_bug if defined? @iseq_cache_tracing_bug
78
86
 
79
87
  ruby_version = Gem::Version.new(RUBY_VERSION)
80
- @iseq_cache_supported = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
88
+ @iseq_cache_tracing_bug = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
89
+ end
90
+
91
+ def self.iseq_cache_anonymous_params_bug?
92
+ return @iseq_cache_anonymous_params_bug if defined? @iseq_cache_anonymous_params_bug
93
+ begin
94
+ if defined? RubyVM::InstructionSequence
95
+ RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end").to_binary
96
+ RubyVM::InstructionSequence.compile("def foo(**); ->{ super }; end").to_binary
97
+ end
98
+ false
99
+ rescue TypeError
100
+ true
101
+ end
102
+ end
103
+
104
+ def self.iseq_cache_supported?
105
+ !(iseq_cache_tracing_bug? || iseq_cache_anonymous_params_bug?)
81
106
  end
82
107
 
83
108
  def self.default_setup
@@ -113,6 +138,7 @@ module Bootsnap
113
138
  load_path_cache: !ENV['DISABLE_BOOTSNAP_LOAD_PATH_CACHE'],
114
139
  compile_cache_iseq: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'] && iseq_cache_supported?,
115
140
  compile_cache_yaml: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'],
141
+ compile_cache_json: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'],
116
142
  )
117
143
 
118
144
  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.0
4
+ version: 1.9.2
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-26 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