bootsnap 1.8.1 → 1.10.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.
@@ -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