bootsnap 1.4.0 → 1.7.7

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.
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