bootsnap 1.4.0 → 1.7.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -0
  3. data/README.md +68 -13
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +285 -87
  6. data/ext/bootsnap/extconf.rb +20 -14
  7. data/lib/bootsnap/bundler.rb +1 -0
  8. data/lib/bootsnap/cli/worker_pool.rb +135 -0
  9. data/lib/bootsnap/cli.rb +246 -0
  10. data/lib/bootsnap/compile_cache/iseq.rb +24 -7
  11. data/lib/bootsnap/compile_cache/yaml.rb +113 -39
  12. data/lib/bootsnap/compile_cache.rb +15 -2
  13. data/lib/bootsnap/explicit_require.rb +1 -0
  14. data/lib/bootsnap/load_path_cache/cache.rb +44 -9
  15. data/lib/bootsnap/load_path_cache/change_observer.rb +5 -1
  16. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +30 -6
  17. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +11 -0
  18. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +43 -11
  19. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  20. data/lib/bootsnap/load_path_cache/path_scanner.rb +53 -27
  21. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  22. data/lib/bootsnap/load_path_cache/store.rb +28 -14
  23. data/lib/bootsnap/load_path_cache.rb +10 -16
  24. data/lib/bootsnap/setup.rb +2 -33
  25. data/lib/bootsnap/version.rb +2 -1
  26. data/lib/bootsnap.rb +91 -15
  27. metadata +17 -29
  28. data/.gitignore +0 -17
  29. data/.rubocop.yml +0 -20
  30. data/.travis.yml +0 -24
  31. data/CODE_OF_CONDUCT.md +0 -74
  32. data/CONTRIBUTING.md +0 -21
  33. data/Gemfile +0 -8
  34. data/README.jp.md +0 -231
  35. data/Rakefile +0 -12
  36. data/bin/ci +0 -10
  37. data/bin/console +0 -14
  38. data/bin/setup +0 -8
  39. data/bin/test-minimal-support +0 -7
  40. data/bin/testunit +0 -8
  41. data/bootsnap.gemspec +0 -45
  42. data/dev.yml +0 -10
  43. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -100
  44. data/shipit.rubygems.yml +0 -4
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative('../explicit_require')
2
4
 
3
5
  module Bootsnap
@@ -8,8 +10,8 @@ module Bootsnap
8
10
  def initialize(store, path_obj, development_mode: false)
9
11
  @development_mode = development_mode
10
12
  @store = store
11
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
12
- @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
13
+ @mutex = Mutex.new
14
+ @path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
13
15
  @has_relative_paths = nil
14
16
  reinitialize
15
17
  end
@@ -44,9 +46,9 @@ module Bootsnap
44
46
  # loadpath.
45
47
  def find(feature)
46
48
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
47
- feature = feature.to_s
49
+ feature = feature.to_s.freeze
48
50
  return feature if absolute_path?(feature)
49
- return File.expand_path(feature) if feature.start_with?('./')
51
+ return expand_path(feature) if feature.start_with?('./')
50
52
  @mutex.synchronize do
51
53
  x = search_index(feature)
52
54
  return x if x
@@ -65,7 +67,7 @@ module Bootsnap
65
67
  # native dynamic extension, e.g. .bundle or .so), we know it was a
66
68
  # failure and there's nothing more we can do to find the file.
67
69
  # no extension, .rb, (.bundle or .so)
68
- when '', *CACHED_EXTENSIONS # rubocop:disable Performance/CaseWhenSplat
70
+ when '', *CACHED_EXTENSIONS
69
71
  nil
70
72
  # Ruby allows specifying native extensions as '.so' even when DLEXT
71
73
  # is '.bundle'. This is where we handle that case.
@@ -142,7 +144,7 @@ module Bootsnap
142
144
  expanded_path = p.expanded_path
143
145
  entries, dirs = p.entries_and_dirs(@store)
144
146
  # push -> low precedence -> set only if unset
145
- dirs.each { |dir| @dirs[dir] ||= path }
147
+ dirs.each { |dir| @dirs[dir] ||= path }
146
148
  entries.each { |rel| @index[rel] ||= expanded_path }
147
149
  end
148
150
  end
@@ -162,6 +164,10 @@ module Bootsnap
162
164
  end
163
165
  end
164
166
 
167
+ def expand_path(feature)
168
+ maybe_append_extension(File.expand_path(feature))
169
+ end
170
+
165
171
  def stale?
166
172
  @development_mode && @generated_at + AGE_THRESHOLD < now
167
173
  end
@@ -174,17 +180,46 @@ module Bootsnap
174
180
  def search_index(f)
175
181
  try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
176
182
  end
183
+
184
+ def maybe_append_extension(f)
185
+ try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
186
+ end
177
187
  else
178
188
  def search_index(f)
179
189
  try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
180
190
  end
191
+
192
+ def maybe_append_extension(f)
193
+ try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
194
+ end
181
195
  end
182
196
 
183
- def try_index(f)
184
- if (p = @index[f])
185
- p + '/' + f
197
+ s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
198
+ 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)
203
+ end
204
+ end
205
+ else
206
+ def try_index(f)
207
+ if (p = @index[f])
208
+ -File.join(p, f).untaint
209
+ end
210
+ end
211
+ end
212
+ else
213
+ def try_index(f)
214
+ if (p = @index[f])
215
+ File.join(p, f)
216
+ end
186
217
  end
187
218
  end
219
+
220
+ def try_ext(f)
221
+ f if File.exist?(f)
222
+ end
188
223
  end
189
224
  end
190
225
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module ChangeObserver
@@ -14,18 +15,20 @@ module Bootsnap
14
15
  @lpc_observer.push_paths(self, *entries.map(&:to_s))
15
16
  super
16
17
  end
18
+ alias_method :append, :push
17
19
 
18
20
  def unshift(*entries)
19
21
  @lpc_observer.unshift_paths(self, *entries.map(&:to_s))
20
22
  super
21
23
  end
24
+ alias_method :prepend, :unshift
22
25
 
23
26
  def concat(entries)
24
27
  @lpc_observer.push_paths(self, *entries.map(&:to_s))
25
28
  super
26
29
  end
27
30
 
28
- # uniq! keeps the first occurance of each path, otherwise preserving
31
+ # uniq! keeps the first occurrence of each path, otherwise preserving
29
32
  # order, preserving the effective load path
30
33
  def uniq!(*args)
31
34
  ret = super
@@ -53,6 +56,7 @@ module Bootsnap
53
56
  end
54
57
 
55
58
  def self.register(observer, arr)
59
+ return if arr.frozen? # can't register observer, but no need to.
56
60
  arr.instance_variable_set(:@lpc_observer, observer)
57
61
  arr.extend(ArrayMixin)
58
62
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module CoreExt
4
5
  def self.make_load_error(path)
5
- err = LoadError.new("cannot load such file -- #{path}")
6
+ err = LoadError.new(+"cannot load such file -- #{path}")
7
+ err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
6
8
  err.define_singleton_method(:path) { path }
7
9
  err
8
10
  end
@@ -30,16 +32,24 @@ module Kernel
30
32
  end
31
33
 
32
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)
33
38
  rescue Bootsnap::LoadPathCache::ReturnFalse
34
39
  false
35
40
  rescue Bootsnap::LoadPathCache::FallbackScan
36
- require_with_bootsnap_lfi(path)
41
+ fallback = true
42
+ ensure
43
+ if fallback
44
+ require_with_bootsnap_lfi(path)
45
+ end
37
46
  end
38
47
 
39
48
  alias_method(:require_relative_without_bootsnap, :require_relative)
40
49
  def require_relative(path)
50
+ location = caller_locations(1..1).first
41
51
  realpath = Bootsnap::LoadPathCache.realpath_cache.call(
42
- caller_locations(1..1).first.absolute_path, path
52
+ location.absolute_path || location.path, path
43
53
  )
44
54
  require(realpath)
45
55
  end
@@ -51,15 +61,22 @@ module Kernel
51
61
  end
52
62
 
53
63
  # load also allows relative paths from pwd even when not in $:
54
- if File.exist?(relative = File.expand_path(path))
64
+ if File.exist?(relative = File.expand_path(path).freeze)
55
65
  return load_without_bootsnap(relative, wrap)
56
66
  end
57
67
 
58
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)
59
72
  rescue Bootsnap::LoadPathCache::ReturnFalse
60
73
  false
61
74
  rescue Bootsnap::LoadPathCache::FallbackScan
62
- load_without_bootsnap(path, wrap)
75
+ fallback = true
76
+ ensure
77
+ if fallback
78
+ load_without_bootsnap(path, wrap)
79
+ end
63
80
  end
64
81
  end
65
82
 
@@ -74,9 +91,16 @@ class Module
74
91
  # added to $LOADED_FEATURES and won't be able to hook that modification
75
92
  # since it's done in C-land.
76
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)
77
97
  rescue Bootsnap::LoadPathCache::ReturnFalse
78
98
  false
79
99
  rescue Bootsnap::LoadPathCache::FallbackScan
80
- autoload_without_bootsnap(const, path)
100
+ fallback = true
101
+ ensure
102
+ if fallback
103
+ autoload_without_bootsnap(const, path)
104
+ end
81
105
  end
82
106
  end
@@ -1,7 +1,18 @@
1
+ # frozen_string_literal: true
1
2
  class << $LOADED_FEATURES
2
3
  alias_method(:delete_without_bootsnap, :delete)
3
4
  def delete(key)
4
5
  Bootsnap::LoadPathCache.loaded_features_index.purge(key)
5
6
  delete_without_bootsnap(key)
6
7
  end
8
+
9
+ alias_method(:reject_without_bootsnap!, :reject!)
10
+ def reject!(&block)
11
+ backup = dup
12
+
13
+ # FIXME: if no block is passed we'd need to return a decorated iterator
14
+ reject_without_bootsnap!(&block)
15
+
16
+ Bootsnap::LoadPathCache.loaded_features_index.purge_multi(backup - self)
17
+ end
7
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bootsnap
2
4
  module LoadPathCache
3
5
  # LoadedFeaturesIndex partially mirrors an internal structure in ruby that
@@ -24,7 +26,7 @@ module Bootsnap
24
26
  class LoadedFeaturesIndex
25
27
  def initialize
26
28
  @lfi = {}
27
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
29
+ @mutex = Mutex.new
28
30
 
29
31
  # In theory the user could mutate $LOADED_FEATURES and invalidate our
30
32
  # cache. If this ever comes up in practice — or if you, the
@@ -38,7 +40,7 @@ module Bootsnap
38
40
  # /a/b/lib/my/foo.rb
39
41
  # ^^^^^^^^^
40
42
  short = feat[(lpe.length + 1)..-1]
41
- stripped = strip_extension(short)
43
+ stripped = strip_extension_if_elidable(short)
42
44
  @lfi[short] = hash
43
45
  @lfi[stripped] = hash
44
46
  end
@@ -55,6 +57,13 @@ module Bootsnap
55
57
  end
56
58
  end
57
59
 
60
+ def purge_multi(features)
61
+ rejected_hashes = features.each_with_object({}) { |f, h| h[f.hash] = true }
62
+ @mutex.synchronize do
63
+ @lfi.reject! { |_, hash| rejected_hashes.key?(hash) }
64
+ end
65
+ end
66
+
58
67
  def key?(feature)
59
68
  @mutex.synchronize { @lfi.key?(feature) }
60
69
  end
@@ -85,13 +94,14 @@ module Bootsnap
85
94
 
86
95
  hash = long.hash
87
96
 
88
- # do we have 'bundler' or 'bundler.rb'?
89
- altname = if File.extname(short) != ''
90
- # strip the path from 'bundler.rb' -> 'bundler'
91
- strip_extension(short)
92
- elsif long && (ext = File.extname(long))
93
- # get the extension from the expanded path if given
94
- # 'bundler' + '.rb'
97
+ # Do we have a filename with an elidable extension, e.g.,
98
+ # 'bundler.rb', or 'libgit2.so'?
99
+ altname = if extension_elidable?(short)
100
+ # Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
101
+ strip_extension_if_elidable(short)
102
+ elsif long && (ext = File.extname(long.freeze))
103
+ # We already know the extension of the actual file this
104
+ # resolves to, so put that back on.
95
105
  short + ext
96
106
  end
97
107
 
@@ -108,8 +118,30 @@ module Bootsnap
108
118
  STRIP_EXTENSION = /\.[^.]*?$/
109
119
  private_constant(:STRIP_EXTENSION)
110
120
 
111
- def strip_extension(f)
112
- f.sub(STRIP_EXTENSION, '')
121
+ # Might Ruby automatically search for this extension if
122
+ # someone tries to 'require' the file without it? E.g. Ruby
123
+ # will implicitly try 'x.rb' if you ask for 'x'.
124
+ #
125
+ # This is complex and platform-dependent, and the Ruby docs are a little
126
+ # handwavy about what will be tried when and in what order.
127
+ # So optimistically pretend that all known elidable extensions
128
+ # will be tried on all platforms, and that people are unlikely
129
+ # to name files in a way that assumes otherwise.
130
+ # (E.g. It's unlikely that someone will know that their code
131
+ # will _never_ run on MacOS, and therefore think they can get away
132
+ # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
133
+ #
134
+ # 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')
137
+ end
138
+
139
+ def strip_extension_if_elidable(f)
140
+ if extension_elidable?(f)
141
+ f.sub(STRIP_EXTENSION, '')
142
+ else
143
+ f
144
+ end
113
145
  end
114
146
  end
115
147
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative('path_scanner')
2
3
 
3
4
  module Bootsnap
@@ -20,7 +21,7 @@ module Bootsnap
20
21
  attr_reader(:path)
21
22
 
22
23
  def initialize(path)
23
- @path = path.to_s
24
+ @path = path.to_s.freeze
24
25
  end
25
26
 
26
27
  # True if the path exists, but represents a non-directory object
@@ -59,7 +60,7 @@ module Bootsnap
59
60
  end
60
61
 
61
62
  def expanded_path
62
- File.expand_path(path)
63
+ File.expand_path(path).freeze
63
64
  end
64
65
 
65
66
  private
@@ -1,9 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative('../explicit_require')
2
4
 
3
5
  module Bootsnap
4
6
  module LoadPathCache
5
7
  module PathScanner
6
- ALL_FILES = "/{,**/*/**/}*"
7
8
  REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
8
9
  NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
9
10
  ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
@@ -11,37 +12,62 @@ module Bootsnap
11
12
  BUNDLE_PATH = if Bootsnap.bundler?
12
13
  (Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
13
14
  else
14
- ''.freeze
15
+ ''
15
16
  end
16
17
 
17
- def self.call(path)
18
- path = path.to_s
19
-
20
- relative_slice = (path.size + 1)..-1
21
- # If the bundle path is a descendent of this path, we do additional
22
- # checks to prevent recursing into the bundle path as we recurse
23
- # through this path. We don't want to scan the bundle path because
24
- # anything useful in it will be present on other load path items.
25
- #
26
- # This can happen if, for example, the user adds '.' to the load path,
27
- # and the bundle path is '.bundle'.
28
- contains_bundle_path = BUNDLE_PATH.start_with?(path)
29
-
30
- dirs = []
31
- requirables = []
32
-
33
- Dir.glob(path + ALL_FILES).each do |absolute_path|
34
- next if contains_bundle_path && absolute_path.start_with?(BUNDLE_PATH)
35
- relative_path = absolute_path.slice(relative_slice)
36
-
37
- if File.directory?(absolute_path)
38
- dirs << relative_path
39
- elsif REQUIRABLE_EXTENSIONS.include?(File.extname(relative_path))
40
- requirables << relative_path
18
+ class << self
19
+ def call(path)
20
+ path = File.expand_path(path.to_s).freeze
21
+ return [[], []] unless File.directory?(path)
22
+
23
+ # If the bundle path is a descendent of this path, we do additional
24
+ # checks to prevent recursing into the bundle path as we recurse
25
+ # through this path. We don't want to scan the bundle path because
26
+ # anything useful in it will be present on other load path items.
27
+ #
28
+ # This can happen if, for example, the user adds '.' to the load path,
29
+ # and the bundle path is '.bundle'.
30
+ contains_bundle_path = BUNDLE_PATH.start_with?(path)
31
+
32
+ dirs = []
33
+ requirables = []
34
+ walk(path, nil) do |relative_path, absolute_path, is_directory|
35
+ if is_directory
36
+ dirs << os_path(relative_path)
37
+ !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
38
+ elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
39
+ requirables << os_path(relative_path)
40
+ end
41
+ end
42
+ [requirables, dirs]
43
+ end
44
+
45
+ def walk(absolute_dir_path, relative_dir_path, &block)
46
+ Dir.foreach(absolute_dir_path) do |name|
47
+ next if name.start_with?('.')
48
+ relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
49
+
50
+ absolute_path = "#{absolute_dir_path}/#{name}"
51
+ if File.directory?(absolute_path)
52
+ if yield relative_path, absolute_path, true
53
+ walk(absolute_path, relative_path, &block)
54
+ end
55
+ else
56
+ yield relative_path, absolute_path, false
57
+ end
41
58
  end
42
59
  end
43
60
 
44
- [requirables, dirs]
61
+ if RUBY_VERSION >= '3.1'
62
+ def os_path(path)
63
+ path.freeze
64
+ end
65
+ else
66
+ def os_path(path)
67
+ path.force_encoding(Encoding::US_ASCII) if path.ascii_only?
68
+ path.freeze
69
+ end
70
+ end
45
71
  end
46
72
  end
47
73
  end
@@ -15,15 +15,15 @@ module Bootsnap
15
15
 
16
16
  def realpath(caller_location, path)
17
17
  base = File.dirname(caller_location)
18
- file = find_file(File.expand_path(path, base))
19
- dir = File.dirname(file)
20
- File.join(dir, File.basename(file))
18
+ abspath = File.expand_path(path, base).freeze
19
+ find_file(abspath)
21
20
  end
22
21
 
23
22
  def find_file(name)
24
- ['', *CACHED_EXTENSIONS].each do |ext|
23
+ return File.realpath(name).freeze if File.exist?(name)
24
+ CACHED_EXTENSIONS.each do |ext|
25
25
  filename = "#{name}#{ext}"
26
- return File.realpath(filename) if File.exist?(filename)
26
+ return File.realpath(filename).freeze if File.exist?(filename)
27
27
  end
28
28
  name
29
29
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative('../explicit_require')
2
3
 
3
4
  Bootsnap::ExplicitRequire.with_gems('msgpack') { require('msgpack') }
@@ -11,7 +12,7 @@ module Bootsnap
11
12
 
12
13
  def initialize(store_path)
13
14
  @store_path = store_path
14
- @in_txn = false
15
+ @txn_mutex = Mutex.new
15
16
  @dirty = false
16
17
  load_data
17
18
  end
@@ -21,7 +22,7 @@ module Bootsnap
21
22
  end
22
23
 
23
24
  def fetch(key)
24
- raise(SetOutsideTransactionNotAllowed) unless @in_txn
25
+ raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
25
26
  v = get(key)
26
27
  unless v
27
28
  @dirty = true
@@ -32,7 +33,7 @@ module Bootsnap
32
33
  end
33
34
 
34
35
  def set(key, value)
35
- raise(SetOutsideTransactionNotAllowed) unless @in_txn
36
+ raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
36
37
  if value != @data[key]
37
38
  @dirty = true
38
39
  @data[key] = value
@@ -40,12 +41,14 @@ module Bootsnap
40
41
  end
41
42
 
42
43
  def transaction
43
- raise(NestedTransactionError) if @in_txn
44
- @in_txn = true
45
- yield
46
- ensure
47
- commit_transaction
48
- @in_txn = false
44
+ raise(NestedTransactionError) if @txn_mutex.owned?
45
+ @txn_mutex.synchronize do
46
+ begin
47
+ yield
48
+ ensure
49
+ commit_transaction
50
+ end
51
+ end
49
52
  end
50
53
 
51
54
  private
@@ -59,10 +62,18 @@ module Bootsnap
59
62
 
60
63
  def load_data
61
64
  @data = begin
62
- MessagePack.load(File.binread(@store_path))
63
- # handle malformed data due to upgrade incompatability
64
- rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
65
- {}
65
+ File.open(@store_path, encoding: Encoding::BINARY) do |io|
66
+ MessagePack.load(io)
67
+ end
68
+ # handle malformed data due to upgrade incompatibility
69
+ rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
70
+ {}
71
+ rescue ArgumentError => error
72
+ if error.message =~ /negative array size/
73
+ {}
74
+ else
75
+ raise
76
+ end
66
77
  end
67
78
  end
68
79
 
@@ -74,10 +85,13 @@ module Bootsnap
74
85
  exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
75
86
  # `encoding:` looks redundant wrt `binwrite`, but necessary on windows
76
87
  # because binary is part of mode.
77
- File.binwrite(tmp, MessagePack.dump(@data), mode: exclusive_write, encoding: Encoding::BINARY)
88
+ File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
89
+ MessagePack.dump(@data, io, freeze: true)
90
+ end
78
91
  FileUtils.mv(tmp, @store_path)
79
92
  rescue Errno::EEXIST
80
93
  retry
94
+ rescue SystemCallError
81
95
  end
82
96
  end
83
97
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bootsnap
2
4
  module LoadPathCache
3
5
  ReturnFalse = Class.new(StandardError)
@@ -7,6 +9,11 @@ module Bootsnap
7
9
  DOT_SO = '.so'
8
10
  SLASH = '/'
9
11
 
12
+ # If a NameError happens several levels deep, don't re-handle it
13
+ # all the way up the chain: mark it once and bubble it up without
14
+ # more retries.
15
+ ERROR_TAG_IVAR = :@__bootsnap_rescued
16
+
10
17
  DL_EXTENSIONS = ::RbConfig::CONFIG
11
18
  .values_at('DLEXT', 'DLEXT2')
12
19
  .reject { |ext| !ext || ext.empty? }
@@ -21,10 +28,9 @@ module Bootsnap
21
28
  CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
22
29
 
23
30
  class << self
24
- attr_reader(:load_path_cache, :autoload_paths_cache,
25
- :loaded_features_index, :realpath_cache)
31
+ attr_reader(:load_path_cache, :loaded_features_index, :realpath_cache)
26
32
 
27
- def setup(cache_path:, development_mode:, active_support: true)
33
+ def setup(cache_path:, development_mode:)
28
34
  unless supported?
29
35
  warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
30
36
  return
@@ -38,23 +44,11 @@ module Bootsnap
38
44
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
39
45
  require_relative('load_path_cache/core_ext/kernel_require')
40
46
  require_relative('load_path_cache/core_ext/loaded_features')
41
-
42
- if active_support
43
- # this should happen after setting up the initial cache because it
44
- # loads a lot of code. It's better to do after +require+ is optimized.
45
- require('active_support/dependencies')
46
- @autoload_paths_cache = Cache.new(
47
- store,
48
- ::ActiveSupport::Dependencies.autoload_paths,
49
- development_mode: development_mode
50
- )
51
- require_relative('load_path_cache/core_ext/active_support')
52
- end
53
47
  end
54
48
 
55
49
  def supported?
56
50
  RUBY_ENGINE == 'ruby' &&
57
- RUBY_PLATFORM =~ /darwin|linux|bsd/
51
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
58
52
  end
59
53
  end
60
54
  end
@@ -1,35 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative('../bootsnap')
2
3
 
3
- env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
4
- development_mode = ['', nil, 'development'].include?(env)
5
-
6
- cache_dir = ENV['BOOTSNAP_CACHE_DIR']
7
- unless cache_dir
8
- config_dir_frame = caller.detect do |line|
9
- line.include?('/config/')
10
- end
11
-
12
- unless config_dir_frame
13
- $stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
14
- $stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
15
- $stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
16
-
17
- raise("couldn't infer bootsnap cache directory")
18
- end
19
-
20
- path = config_dir_frame.split(/:\d+:/).first
21
- path = File.dirname(path) until File.basename(path) == 'config'
22
- app_root = File.dirname(path)
23
-
24
- cache_dir = File.join(app_root, 'tmp', 'cache')
25
- end
26
-
27
- Bootsnap.setup(
28
- cache_dir: cache_dir,
29
- development_mode: development_mode,
30
- load_path_cache: true,
31
- autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
32
- disable_trace: false,
33
- compile_cache_iseq: true,
34
- compile_cache_yaml: true,
35
- )
4
+ Bootsnap.default_setup
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
- VERSION = "1.4.0"
3
+ VERSION = "1.7.7"
3
4
  end