bootsnap 1.9.1 → 1.10.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative('../explicit_require')
3
+ require_relative("../explicit_require")
4
4
 
5
5
  module Bootsnap
6
6
  module LoadPathCache
@@ -28,15 +28,16 @@ module Bootsnap
28
28
  BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
29
29
  # Builtin features are of the form 'enumerator.so'.
30
30
  # All others include paths.
31
- next unless feat.size < 20 && !feat.include?('/')
31
+ next unless feat.size < 20 && !feat.include?("/")
32
32
 
33
- base = File.basename(feat, '.*') # enumerator.so -> enumerator
33
+ base = File.basename(feat, ".*") # enumerator.so -> enumerator
34
34
  ext = File.extname(feat) # .so
35
35
 
36
36
  features[feat] = nil # enumerator.so
37
37
  features[base] = nil # enumerator
38
38
 
39
39
  next unless [DOT_SO, *DL_EXTENSIONS].include?(ext)
40
+
40
41
  DL_EXTENSIONS.each do |dl_ext|
41
42
  features["#{base}#{dl_ext}"] = nil # enumerator.bundle
42
43
  end
@@ -44,21 +45,27 @@ module Bootsnap
44
45
 
45
46
  # Try to resolve this feature to an absolute path without traversing the
46
47
  # loadpath.
47
- def find(feature)
48
+ def find(feature, try_extensions: true)
48
49
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
49
50
  feature = feature.to_s.freeze
50
- return feature if absolute_path?(feature)
51
- return expand_path(feature) if feature.start_with?('./')
51
+
52
+ return feature if Bootsnap.absolute_path?(feature)
53
+
54
+ if feature.start_with?("./", "../")
55
+ return try_extensions ? expand_path(feature) : File.expand_path(feature).freeze
56
+ end
57
+
52
58
  @mutex.synchronize do
53
- x = search_index(feature)
59
+ x = search_index(feature, try_extensions: try_extensions)
54
60
  return x if x
61
+ return unless try_extensions
55
62
 
56
63
  # Ruby has some built-in features that require lies about.
57
64
  # For example, 'enumerator' is built in. If you require it, ruby
58
65
  # returns false as if it were already loaded; however, there is no
59
66
  # file to find on disk. We've pre-built a list of these, and we
60
67
  # return false if any of them is loaded.
61
- raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
68
+ return false if BUILTIN_FEATURES.key?(feature)
62
69
 
63
70
  # The feature wasn't found on our preliminary search through the index.
64
71
  # We resolve this differently depending on what the extension was.
@@ -67,13 +74,14 @@ module Bootsnap
67
74
  # native dynamic extension, e.g. .bundle or .so), we know it was a
68
75
  # failure and there's nothing more we can do to find the file.
69
76
  # no extension, .rb, (.bundle or .so)
70
- when '', *CACHED_EXTENSIONS
77
+ when "", *CACHED_EXTENSIONS
71
78
  nil
72
79
  # Ruby allows specifying native extensions as '.so' even when DLEXT
73
80
  # is '.bundle'. This is where we handle that case.
74
81
  when DOT_SO
75
82
  x = search_index(feature[0..-4] + DLEXT)
76
83
  return x if x
84
+
77
85
  if DLEXT2
78
86
  x = search_index(feature[0..-4] + DLEXT2)
79
87
  return x if x
@@ -81,7 +89,7 @@ module Bootsnap
81
89
  else
82
90
  # other, unknown extension. For example, `.rake`. Since we haven't
83
91
  # cached these, we legitimately need to run the load path search.
84
- raise(LoadPathCache::FallbackScan, '', [])
92
+ return FALLBACK_SCAN
85
93
  end
86
94
  end
87
95
 
@@ -89,26 +97,18 @@ module Bootsnap
89
97
  # cases where the file doesn't appear to be on the load path. We should
90
98
  # be able to detect newly-created files without rebooting the
91
99
  # application.
92
- raise(LoadPathCache::FallbackScan, '', []) if @development_mode
93
- end
94
-
95
- if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
96
- def absolute_path?(path)
97
- path[1] == ':'
98
- end
99
- else
100
- def absolute_path?(path)
101
- path.start_with?(SLASH)
102
- end
100
+ return FALLBACK_SCAN if @development_mode
103
101
  end
104
102
 
105
103
  def unshift_paths(sender, *paths)
106
104
  return unless sender == @path_obj
105
+
107
106
  @mutex.synchronize { unshift_paths_locked(*paths) }
108
107
  end
109
108
 
110
109
  def push_paths(sender, *paths)
111
110
  return unless sender == @path_obj
111
+
112
112
  @mutex.synchronize { push_paths_locked(*paths) }
113
113
  end
114
114
 
@@ -141,6 +141,7 @@ module Bootsnap
141
141
  p = Path.new(path)
142
142
  @has_relative_paths = true if p.relative?
143
143
  next if p.non_directory?
144
+
144
145
  expanded_path = p.expanded_path
145
146
  entries, dirs = p.entries_and_dirs(@store)
146
147
  # push -> low precedence -> set only if unset
@@ -155,6 +156,7 @@ module Bootsnap
155
156
  paths.map(&:to_s).reverse_each do |path|
156
157
  p = Path.new(path)
157
158
  next if p.non_directory?
159
+
158
160
  expanded_path = p.expanded_path
159
161
  entries, dirs = p.entries_and_dirs(@store)
160
162
  # unshift -> high precedence -> unconditional set
@@ -177,48 +179,62 @@ module Bootsnap
177
179
  end
178
180
 
179
181
  if DLEXT2
180
- def search_index(f)
181
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
182
+ def search_index(feature, try_extensions: true)
183
+ if try_extensions
184
+ try_index(feature + DOT_RB) ||
185
+ try_index(feature + DLEXT) ||
186
+ try_index(feature + DLEXT2) ||
187
+ try_index(feature)
188
+ else
189
+ try_index(feature)
190
+ end
182
191
  end
183
192
 
184
- def maybe_append_extension(f)
185
- try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
193
+ def maybe_append_extension(feature)
194
+ try_ext(feature + DOT_RB) ||
195
+ try_ext(feature + DLEXT) ||
196
+ try_ext(feature + DLEXT2) ||
197
+ feature
186
198
  end
187
199
  else
188
- def search_index(f)
189
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
200
+ def search_index(feature, try_extensions: true)
201
+ if try_extensions
202
+ try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
203
+ else
204
+ try_index(feature)
205
+ end
190
206
  end
191
207
 
192
- def maybe_append_extension(f)
193
- try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
208
+ def maybe_append_extension(feature)
209
+ try_ext(feature + DOT_RB) || try_ext(feature + DLEXT) || feature
194
210
  end
195
211
  end
196
212
 
197
213
  s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
198
214
  if s.respond_to?(:-@)
199
- if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= '2.7'
200
- def try_index(f)
201
- if (p = @index[f])
202
- -(File.join(p, f).freeze)
215
+ if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= "2.7"
216
+ def try_index(feature)
217
+ if (path = @index[feature])
218
+ -File.join(path, feature).freeze
203
219
  end
204
220
  end
205
221
  else
206
- def try_index(f)
207
- if (p = @index[f])
208
- -File.join(p, f).untaint
222
+ def try_index(feature)
223
+ if (path = @index[feature])
224
+ -File.join(path, feature).untaint
209
225
  end
210
226
  end
211
227
  end
212
228
  else
213
- def try_index(f)
214
- if (p = @index[f])
215
- File.join(p, f)
229
+ def try_index(feature)
230
+ if (path = @index[feature])
231
+ File.join(path, feature)
216
232
  end
217
233
  end
218
234
  end
219
235
 
220
- def try_ext(f)
221
- f if File.exist?(f)
236
+ def try_ext(feature)
237
+ feature if File.exist?(feature)
222
238
  end
223
239
  end
224
240
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bootsnap
3
4
  module LoadPathCache
4
5
  module ChangeObserver
@@ -57,6 +58,7 @@ module Bootsnap
57
58
 
58
59
  def self.register(observer, arr)
59
60
  return if arr.frozen? # can't register observer, but no need to.
61
+
60
62
  arr.instance_variable_set(:@lpc_observer, observer)
61
63
  arr.extend(ArrayMixin)
62
64
  end
@@ -1,47 +1,35 @@
1
1
  # frozen_string_literal: true
2
- module Bootsnap
3
- module LoadPathCache
4
- module CoreExt
5
- def self.make_load_error(path)
6
- err = LoadError.new(+"cannot load such file -- #{path}")
7
- err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
8
- err.define_singleton_method(:path) { path }
9
- err
10
- end
11
- end
12
- end
13
- end
14
2
 
15
3
  module Kernel
16
- module_function # rubocop:disable Style/ModuleFunction
4
+ module_function
17
5
 
18
6
  alias_method(:require_without_bootsnap, :require)
19
7
 
20
- # Note that require registers to $LOADED_FEATURES while load does not.
21
- def require_with_bootsnap_lfi(path, resolved = nil)
22
- Bootsnap::LoadPathCache.loaded_features_index.register(path, resolved) do
23
- require_without_bootsnap(resolved || path)
24
- end
25
- end
26
-
27
8
  def require(path)
28
- return false if Bootsnap::LoadPathCache.loaded_features_index.key?(path)
9
+ string_path = path.to_s
10
+ return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
29
11
 
30
- if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
31
- return require_with_bootsnap_lfi(path, resolved)
32
- end
33
-
34
- raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
35
- rescue LoadError => e
36
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
37
- raise(e)
38
- rescue Bootsnap::LoadPathCache::ReturnFalse
39
- false
40
- rescue Bootsnap::LoadPathCache::FallbackScan
41
- fallback = true
42
- ensure
43
- if fallback
44
- require_with_bootsnap_lfi(path)
12
+ resolved = Bootsnap::LoadPathCache.load_path_cache.find(string_path)
13
+ if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
14
+ if (cursor = Bootsnap::LoadPathCache.loaded_features_index.cursor(string_path))
15
+ ret = require_without_bootsnap(path)
16
+ resolved = Bootsnap::LoadPathCache.loaded_features_index.identify(string_path, cursor)
17
+ Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
18
+ return ret
19
+ else
20
+ return require_without_bootsnap(path)
21
+ end
22
+ elsif false == resolved
23
+ return false
24
+ elsif resolved.nil?
25
+ error = LoadError.new(+"cannot load such file -- #{path}")
26
+ error.instance_variable_set(:@path, path)
27
+ raise error
28
+ else
29
+ # Note that require registers to $LOADED_FEATURES while load does not.
30
+ ret = require_without_bootsnap(resolved)
31
+ Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
32
+ return ret
45
33
  end
46
34
  end
47
35
 
@@ -56,25 +44,9 @@ module Kernel
56
44
 
57
45
  alias_method(:load_without_bootsnap, :load)
58
46
  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
47
+ if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path, try_extensions: false))
48
+ load_without_bootsnap(resolved, wrap)
49
+ else
78
50
  load_without_bootsnap(path, wrap)
79
51
  end
80
52
  end
@@ -90,17 +62,13 @@ class Module
90
62
  # The challenge is that we don't control the point at which the entry gets
91
63
  # added to $LOADED_FEATURES and won't be able to hook that modification
92
64
  # since it's done in C-land.
93
- autoload_without_bootsnap(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path)
94
- rescue LoadError => e
95
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
96
- raise(e)
97
- rescue Bootsnap::LoadPathCache::ReturnFalse
98
- false
99
- rescue Bootsnap::LoadPathCache::FallbackScan
100
- fallback = true
101
- ensure
102
- if fallback
65
+ resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
66
+ if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
103
67
  autoload_without_bootsnap(const, path)
68
+ elsif resolved == false
69
+ return false
70
+ else
71
+ autoload_without_bootsnap(const, resolved || path)
104
72
  end
105
73
  end
106
74
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class << $LOADED_FEATURES
3
4
  alias_method(:delete_without_bootsnap, :delete)
4
5
  def delete(key)
@@ -29,14 +29,15 @@ module Bootsnap
29
29
  @mutex = Mutex.new
30
30
 
31
31
  # In theory the user could mutate $LOADED_FEATURES and invalidate our
32
- # cache. If this ever comes up in practice or if you, the
33
- # enterprising reader, feels inclined to solve this problem we could
32
+ # cache. If this ever comes up in practice - or if you, the
33
+ # enterprising reader, feels inclined to solve this problem - we could
34
34
  # parallel the work done with ChangeObserver on $LOAD_PATH to mirror
35
35
  # updates to our @lfi.
36
36
  $LOADED_FEATURES.each do |feat|
37
37
  hash = feat.hash
38
38
  $LOAD_PATH.each do |lpe|
39
39
  next unless feat.start_with?(lpe)
40
+
40
41
  # /a/b/lib/my/foo.rb
41
42
  # ^^^^^^^^^
42
43
  short = feat[(lpe.length + 1)..-1]
@@ -68,11 +69,30 @@ module Bootsnap
68
69
  @mutex.synchronize { @lfi.key?(feature) }
69
70
  end
70
71
 
72
+ def cursor(short)
73
+ unless Bootsnap.absolute_path?(short.to_s)
74
+ $LOADED_FEATURES.size
75
+ end
76
+ end
77
+
78
+ def identify(short, cursor)
79
+ $LOADED_FEATURES[cursor..-1].detect do |feat|
80
+ offset = 0
81
+ while (offset = feat.index(short, offset))
82
+ if feat.index(".", offset + 1) && !feat.index("/", offset + 2)
83
+ break true
84
+ else
85
+ offset += 1
86
+ end
87
+ end
88
+ end
89
+ end
90
+
71
91
  # There is a relatively uncommon case where we could miss adding an
72
92
  # entry:
73
93
  #
74
94
  # If the user asked for e.g. `require 'bundler'`, and we went through the
75
- # `FallbackScan` pathway in `kernel_require.rb` and therefore did not
95
+ # `FALLBACK_SCAN` pathway in `kernel_require.rb` and therefore did not
76
96
  # pass `long` (the full expanded absolute path), then we did are not able
77
97
  # to confidently add the `bundler.rb` form to @lfi.
78
98
  #
@@ -82,15 +102,8 @@ module Bootsnap
82
102
  # not quite right; or
83
103
  # 2. Inspect $LOADED_FEATURES upon return from yield to find the matching
84
104
  # entry.
85
- def register(short, long = nil)
86
- if long.nil?
87
- pat = %r{/#{Regexp.escape(short)}(\.[^/]+)?$}
88
- len = $LOADED_FEATURES.size
89
- ret = yield
90
- long = $LOADED_FEATURES[len..-1].detect { |feat| feat =~ pat }
91
- else
92
- ret = yield
93
- end
105
+ def register(short, long)
106
+ return if Bootsnap.absolute_path?(short)
94
107
 
95
108
  hash = long.hash
96
109
 
@@ -109,13 +122,11 @@ module Bootsnap
109
122
  @lfi[short] = hash
110
123
  (@lfi[altname] = hash) if altname
111
124
  end
112
-
113
- ret
114
125
  end
115
126
 
116
127
  private
117
128
 
118
- STRIP_EXTENSION = /\.[^.]*?$/
129
+ STRIP_EXTENSION = /\.[^.]*?$/.freeze
119
130
  private_constant(:STRIP_EXTENSION)
120
131
 
121
132
  # Might Ruby automatically search for this extension if
@@ -132,15 +143,15 @@ module Bootsnap
132
143
  # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
133
144
  #
134
145
  # See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
135
- def extension_elidable?(f)
136
- f.to_s.end_with?('.rb', '.so', '.o', '.dll', '.dylib')
146
+ def extension_elidable?(feature)
147
+ feature.to_s.end_with?(".rb", ".so", ".o", ".dll", ".dylib")
137
148
  end
138
149
 
139
- def strip_extension_if_elidable(f)
140
- if extension_elidable?(f)
141
- f.sub(STRIP_EXTENSION, '')
150
+ def strip_extension_if_elidable(feature)
151
+ if extension_elidable?(feature)
152
+ feature.sub(STRIP_EXTENSION, "")
142
153
  else
143
- f
154
+ feature
144
155
  end
145
156
  end
146
157
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require_relative('path_scanner')
2
+
3
+ require_relative("path_scanner")
3
4
 
4
5
  module Bootsnap
5
6
  module LoadPathCache
@@ -43,6 +44,7 @@ module Bootsnap
43
44
  # set to zero anyway, just in case we change the stability heuristics.
44
45
  _, entries, dirs = store.get(expanded_path)
45
46
  return [entries, dirs] if entries # cache hit
47
+
46
48
  entries, dirs = scan!
47
49
  store.set(expanded_path, [0, entries, dirs])
48
50
  return [entries, dirs]
@@ -93,8 +95,8 @@ module Bootsnap
93
95
 
94
96
  # Built-in ruby lib stuff doesn't change, but things can occasionally be
95
97
  # installed into sitedir, which generally lives under libdir.
96
- RUBY_LIBDIR = RbConfig::CONFIG['libdir']
97
- RUBY_SITEDIR = RbConfig::CONFIG['sitedir']
98
+ RUBY_LIBDIR = RbConfig::CONFIG["libdir"]
99
+ RUBY_SITEDIR = RbConfig::CONFIG["sitedir"]
98
100
 
99
101
  def stability
100
102
  @stability ||= begin
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative('../explicit_require')
3
+ require_relative("../explicit_require")
4
4
 
5
5
  module Bootsnap
6
6
  module LoadPathCache
7
7
  module PathScanner
8
8
  REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
9
9
  NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
10
- ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
10
+ ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/.freeze
11
11
 
12
12
  BUNDLE_PATH = if Bootsnap.bundler?
13
13
  (Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
14
14
  else
15
- ''
15
+ ""
16
16
  end
17
17
 
18
18
  class << self
@@ -44,7 +44,8 @@ module Bootsnap
44
44
 
45
45
  def walk(absolute_dir_path, relative_dir_path, &block)
46
46
  Dir.foreach(absolute_dir_path) do |name|
47
- next if name.start_with?('.')
47
+ next if name.start_with?(".")
48
+
48
49
  relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
49
50
 
50
51
  absolute_path = "#{absolute_dir_path}/#{name}"
@@ -58,7 +59,7 @@ module Bootsnap
58
59
  end
59
60
  end
60
61
 
61
- if RUBY_VERSION >= '3.1'
62
+ if RUBY_VERSION >= "3.1"
62
63
  def os_path(path)
63
64
  path.freeze
64
65
  end
@@ -21,6 +21,7 @@ module Bootsnap
21
21
 
22
22
  def find_file(name)
23
23
  return File.realpath(name).freeze if File.exist?(name)
24
+
24
25
  CACHED_EXTENSIONS.each do |ext|
25
26
  filename = "#{name}#{ext}"
26
27
  return File.realpath(filename).freeze if File.exist?(filename)
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
- require_relative('../explicit_require')
3
2
 
4
- Bootsnap::ExplicitRequire.with_gems('msgpack') { require('msgpack') }
5
- Bootsnap::ExplicitRequire.from_rubylibdir('fileutils')
3
+ require_relative("../explicit_require")
4
+
5
+ Bootsnap::ExplicitRequire.with_gems("msgpack") { require("msgpack") }
6
6
 
7
7
  module Bootsnap
8
8
  module LoadPathCache
9
9
  class Store
10
+ VERSION_KEY = "__bootsnap_ruby_version__"
11
+ CURRENT_VERSION = "#{RUBY_REVISION}-#{RUBY_PLATFORM}".freeze # rubocop:disable Style/RedundantFreeze
12
+
10
13
  NestedTransactionError = Class.new(StandardError)
11
14
  SetOutsideTransactionNotAllowed = Class.new(StandardError)
12
15
 
@@ -23,6 +26,7 @@ module Bootsnap
23
26
 
24
27
  def fetch(key)
25
28
  raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
29
+
26
30
  v = get(key)
27
31
  unless v
28
32
  @dirty = true
@@ -34,6 +38,7 @@ module Bootsnap
34
38
 
35
39
  def set(key, value)
36
40
  raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
41
+
37
42
  if value != @data[key]
38
43
  @dirty = true
39
44
  @data[key] = value
@@ -42,6 +47,7 @@ module Bootsnap
42
47
 
43
48
  def transaction
44
49
  raise(NestedTransactionError) if @txn_mutex.owned?
50
+
45
51
  @txn_mutex.synchronize do
46
52
  begin
47
53
  yield
@@ -62,15 +68,20 @@ module Bootsnap
62
68
 
63
69
  def load_data
64
70
  @data = begin
65
- File.open(@store_path, encoding: Encoding::BINARY) do |io|
71
+ data = File.open(@store_path, encoding: Encoding::BINARY) do |io|
66
72
  MessagePack.load(io)
67
73
  end
74
+ if data.is_a?(Hash) && data[VERSION_KEY] == CURRENT_VERSION
75
+ data
76
+ else
77
+ default_data
78
+ end
68
79
  # handle malformed data due to upgrade incompatibility
69
80
  rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
70
- {}
81
+ default_data
71
82
  rescue ArgumentError => error
72
83
  if error.message =~ /negative array size/
73
- {}
84
+ default_data
74
85
  else
75
86
  raise
76
87
  end
@@ -78,9 +89,11 @@ module Bootsnap
78
89
  end
79
90
 
80
91
  def dump_data
92
+ require "fileutils" unless defined? FileUtils
93
+
81
94
  # Change contents atomically so other processes can't get invalid
82
95
  # caches if they read at an inopportune time.
83
- tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100000).to_i}.tmp"
96
+ tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100_000).to_i}.tmp"
84
97
  FileUtils.mkpath(File.dirname(tmp))
85
98
  exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
86
99
  # `encoding:` looks redundant wrt `binwrite`, but necessary on windows
@@ -93,6 +106,10 @@ module Bootsnap
93
106
  retry
94
107
  rescue SystemCallError
95
108
  end
109
+
110
+ def default_data
111
+ {VERSION_KEY => CURRENT_VERSION}
112
+ end
96
113
  end
97
114
  end
98
115
  end