bootsnap 1.1.8-java → 1.5.0-java

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,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module ExplicitRequire
3
4
  ARCHDIR = RbConfig::CONFIG['archdir']
@@ -5,7 +6,7 @@ module Bootsnap
5
6
  DLEXT = RbConfig::CONFIG['DLEXT']
6
7
 
7
8
  def self.from_self(feature)
8
- require_relative "../#{feature}"
9
+ require_relative("../#{feature}")
9
10
  end
10
11
 
11
12
  def self.from_rubylibdir(feature)
@@ -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,32 +28,51 @@ 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
31
+ attr_reader(:load_path_cache, :autoload_paths_cache,
32
+ :loaded_features_index, :realpath_cache)
25
33
 
26
34
  def setup(cache_path:, development_mode:, active_support: true)
35
+ unless supported?
36
+ warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
37
+ return
38
+ end
39
+
27
40
  store = Store.new(cache_path)
28
41
 
42
+ @loaded_features_index = LoadedFeaturesIndex.new
43
+ @realpath_cache = RealpathCache.new
44
+
29
45
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
30
- require_relative 'load_path_cache/core_ext/kernel_require'
46
+ require_relative('load_path_cache/core_ext/kernel_require')
47
+ require_relative('load_path_cache/core_ext/loaded_features')
31
48
 
32
49
  if active_support
33
50
  # this should happen after setting up the initial cache because it
34
51
  # loads a lot of code. It's better to do after +require+ is optimized.
35
- require 'active_support/dependencies'
52
+ require('active_support/dependencies')
36
53
  @autoload_paths_cache = Cache.new(
37
54
  store,
38
55
  ::ActiveSupport::Dependencies.autoload_paths,
39
56
  development_mode: development_mode
40
57
  )
41
- require_relative 'load_path_cache/core_ext/active_support'
58
+ require_relative('load_path_cache/core_ext/active_support')
42
59
  end
43
60
  end
61
+
62
+ def supported?
63
+ RUBY_ENGINE == 'ruby' &&
64
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
65
+ end
44
66
  end
45
67
  end
46
68
  end
47
69
 
48
- require_relative 'load_path_cache/path_scanner'
49
- require_relative 'load_path_cache/path'
50
- require_relative 'load_path_cache/cache'
51
- require_relative 'load_path_cache/store'
52
- require_relative 'load_path_cache/change_observer'
70
+ if Bootsnap::LoadPathCache.supported?
71
+ require_relative('load_path_cache/path_scanner')
72
+ require_relative('load_path_cache/path')
73
+ require_relative('load_path_cache/cache')
74
+ require_relative('load_path_cache/store')
75
+ require_relative('load_path_cache/change_observer')
76
+ require_relative('load_path_cache/loaded_features_index')
77
+ require_relative('load_path_cache/realpath_cache')
78
+ end
@@ -1,4 +1,6 @@
1
- require_relative '../explicit_require'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative('../explicit_require')
2
4
 
3
5
  module Bootsnap
4
6
  module LoadPathCache
@@ -9,15 +11,15 @@ module Bootsnap
9
11
  @development_mode = development_mode
10
12
  @store = store
11
13
  @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
12
- @path_obj = path_obj
14
+ @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
13
15
  @has_relative_paths = nil
14
16
  reinitialize
15
17
  end
16
18
 
17
- # Does this directory exist as a child of one of the path items?
18
- # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], has_dir?("c/d")
19
- # is true.
20
- def has_dir?(dir)
19
+ # What is the path item that contains the dir as child?
20
+ # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], load_dir("c/d")
21
+ # is "/a/b".
22
+ def load_dir(dir)
21
23
  reinitialize if stale?
22
24
  @mutex.synchronize { @dirs[dir] }
23
25
  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
@@ -56,7 +58,7 @@ module Bootsnap
56
58
  # returns false as if it were already loaded; however, there is no
57
59
  # file to find on disk. We've pre-built a list of these, and we
58
60
  # return false if any of them is loaded.
59
- raise LoadPathCache::ReturnFalse if BUILTIN_FEATURES.key?(feature)
61
+ raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
60
62
 
61
63
  # The feature wasn't found on our preliminary search through the index.
62
64
  # We resolve this differently depending on what the extension was.
@@ -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.
@@ -73,14 +75,21 @@ module Bootsnap
73
75
  x = search_index(feature[0..-4] + DLEXT)
74
76
  return x if x
75
77
  if DLEXT2
76
- search_index(feature[0..-4] + DLEXT2)
78
+ x = search_index(feature[0..-4] + DLEXT2)
79
+ return x if x
77
80
  end
78
81
  else
79
82
  # other, unknown extension. For example, `.rake`. Since we haven't
80
83
  # cached these, we legitimately need to run the load path search.
81
- raise LoadPathCache::FallbackScan
84
+ raise(LoadPathCache::FallbackScan, '', [])
82
85
  end
83
86
  end
87
+
88
+ # In development mode, we don't want to confidently return failures for
89
+ # cases where the file doesn't appear to be on the load path. We should
90
+ # be able to detect newly-created files without rebooting the
91
+ # application.
92
+ raise(LoadPathCache::FallbackScan, '', []) if @development_mode
84
93
  end
85
94
 
86
95
  if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
@@ -103,20 +112,12 @@ module Bootsnap
103
112
  @mutex.synchronize { push_paths_locked(*paths) }
104
113
  end
105
114
 
106
- def each_requirable
107
- @mutex.synchronize do
108
- @index.each do |rel, entry|
109
- yield "#{entry}/#{rel}"
110
- end
111
- end
112
- end
113
-
114
115
  def reinitialize(path_obj = @path_obj)
115
116
  @mutex.synchronize do
116
117
  @path_obj = path_obj
117
118
  ChangeObserver.register(self, @path_obj)
118
119
  @index = {}
119
- @dirs = Hash.new(false)
120
+ @dirs = {}
120
121
  @generated_at = now
121
122
  push_paths_locked(*@path_obj)
122
123
  end
@@ -140,10 +141,11 @@ module Bootsnap
140
141
  p = Path.new(path)
141
142
  @has_relative_paths = true if p.relative?
142
143
  next if p.non_directory?
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] ||= true }
146
- entries.each { |rel| @index[rel] ||= p.expanded_path }
147
+ dirs.each { |dir| @dirs[dir] ||= path }
148
+ entries.each { |rel| @index[rel] ||= expanded_path }
147
149
  end
148
150
  end
149
151
  end
@@ -153,14 +155,19 @@ module Bootsnap
153
155
  paths.map(&:to_s).reverse_each do |path|
154
156
  p = Path.new(path)
155
157
  next if p.non_directory?
158
+ expanded_path = p.expanded_path
156
159
  entries, dirs = p.entries_and_dirs(@store)
157
160
  # unshift -> high precedence -> unconditional set
158
- dirs.each { |dir| @dirs[dir] = true }
159
- entries.each { |rel| @index[rel] = p.expanded_path }
161
+ dirs.each { |dir| @dirs[dir] = path }
162
+ entries.each { |rel| @index[rel] = expanded_path }
160
163
  end
161
164
  end
162
165
  end
163
166
 
167
+ def expand_path(feature)
168
+ maybe_append_extension(File.expand_path(feature))
169
+ end
170
+
164
171
  def stale?
165
172
  @development_mode && @generated_at + AGE_THRESHOLD < now
166
173
  end
@@ -171,19 +178,31 @@ module Bootsnap
171
178
 
172
179
  if DLEXT2
173
180
  def search_index(f)
174
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
181
+ try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
182
+ end
183
+
184
+ def maybe_append_extension(f)
185
+ try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
175
186
  end
176
187
  else
177
188
  def search_index(f)
178
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
189
+ try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
190
+ end
191
+
192
+ def maybe_append_extension(f)
193
+ try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
179
194
  end
180
195
  end
181
196
 
182
197
  def try_index(f)
183
- if p = @index[f]
184
- p + '/' + f
198
+ if (p = @index[f])
199
+ "#{p}/#{f}"
185
200
  end
186
201
  end
202
+
203
+ def try_ext(f)
204
+ f if File.exist?(f)
205
+ end
187
206
  end
188
207
  end
189
208
  end
@@ -1,37 +1,37 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module ChangeObserver
4
- def self.register(observer, arr)
5
- # Re-overriding these methods on an array that already has them would
6
- # cause StackOverflowErrors
7
- return if arr.respond_to?(:push_without_lpc)
8
-
5
+ module ArrayMixin
9
6
  # For each method that adds items to one end or another of the array
10
7
  # (<<, push, unshift, concat), override that method to also notify the
11
8
  # observer of the change.
12
- sc = arr.singleton_class
13
- sc.send(:alias_method, :shovel_without_lpc, :<<)
14
- arr.define_singleton_method(:<<) do |entry|
15
- observer.push_paths(self, entry.to_s)
16
- shovel_without_lpc(entry)
9
+ def <<(entry)
10
+ @lpc_observer.push_paths(self, entry.to_s)
11
+ super
12
+ end
13
+
14
+ def push(*entries)
15
+ @lpc_observer.push_paths(self, *entries.map(&:to_s))
16
+ super
17
17
  end
18
18
 
19
- sc.send(:alias_method, :push_without_lpc, :push)
20
- arr.define_singleton_method(:push) do |*entries|
21
- observer.push_paths(self, *entries.map(&:to_s))
22
- push_without_lpc(*entries)
19
+ def unshift(*entries)
20
+ @lpc_observer.unshift_paths(self, *entries.map(&:to_s))
21
+ super
23
22
  end
24
23
 
25
- sc.send(:alias_method, :unshift_without_lpc, :unshift)
26
- arr.define_singleton_method(:unshift) do |*entries|
27
- observer.unshift_paths(self, *entries.map(&:to_s))
28
- unshift_without_lpc(*entries)
24
+ def concat(entries)
25
+ @lpc_observer.push_paths(self, *entries.map(&:to_s))
26
+ super
29
27
  end
30
28
 
31
- sc.send(:alias_method, :concat_without_lpc, :concat)
32
- arr.define_singleton_method(:concat) do |entries|
33
- observer.push_paths(self, *entries.map(&:to_s))
34
- concat_without_lpc(entries)
29
+ # uniq! keeps the first occurrence of each path, otherwise preserving
30
+ # order, preserving the effective load path
31
+ def uniq!(*args)
32
+ ret = super
33
+ @lpc_observer.reinitialize if block_given? || !args.empty?
34
+ ret
35
35
  end
36
36
 
37
37
  # For each method that modifies the array more aggressively, override
@@ -41,16 +41,23 @@ module Bootsnap
41
41
  # accounting cost would be greater than the hit from these, since we
42
42
  # actively discourage calling them.
43
43
  %i(
44
- collect! compact! delete delete_at delete_if fill flatten! insert map!
45
- reject! reverse! select! shuffle! shift slice! sort! sort_by!
46
- ).each do |meth|
47
- sc.send(:alias_method, :"#{meth}_without_lpc", meth)
48
- arr.define_singleton_method(meth) do |*a|
49
- send(:"#{meth}_without_lpc", *a)
50
- observer.reinitialize
44
+ []= clear collect! compact! delete delete_at delete_if fill flatten!
45
+ insert keep_if map! pop reject! replace reverse! rotate! select!
46
+ shift shuffle! slice! sort! sort_by!
47
+ ).each do |method_name|
48
+ define_method(method_name) do |*args, &block|
49
+ ret = super(*args, &block)
50
+ @lpc_observer.reinitialize
51
+ ret
51
52
  end
52
53
  end
53
54
  end
55
+
56
+ def self.register(observer, arr)
57
+ return if arr.frozen? # can't register observer, but no need to.
58
+ arr.instance_variable_set(:@lpc_observer, observer)
59
+ arr.extend(ArrayMixin)
60
+ end
54
61
  end
55
62
  end
56
63
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module CoreExt
@@ -10,6 +11,14 @@ module Bootsnap
10
11
  Thread.current[:without_bootsnap_cache] = prev
11
12
  end
12
13
 
14
+ def self.allow_bootsnap_retry(allowed)
15
+ prev = Thread.current[:without_bootsnap_retry] || false
16
+ Thread.current[:without_bootsnap_retry] = !allowed
17
+ yield
18
+ ensure
19
+ Thread.current[:without_bootsnap_retry] = prev
20
+ end
21
+
13
22
  module ClassMethods
14
23
  def autoload_paths=(o)
15
24
  super
@@ -22,17 +31,25 @@ module Bootsnap
22
31
  Bootsnap::LoadPathCache.autoload_paths_cache.find(path)
23
32
  rescue Bootsnap::LoadPathCache::ReturnFalse
24
33
  nil # doesn't really apply here
34
+ rescue Bootsnap::LoadPathCache::FallbackScan
35
+ nil # doesn't really apply here
25
36
  end
26
37
  end
27
38
 
28
39
  def autoloadable_module?(path_suffix)
29
- Bootsnap::LoadPathCache.autoload_paths_cache.has_dir?(path_suffix)
40
+ Bootsnap::LoadPathCache.autoload_paths_cache.load_dir(path_suffix)
30
41
  end
31
42
 
32
43
  def remove_constant(const)
33
44
  CoreExt::ActiveSupport.without_bootsnap_cache { super }
34
45
  end
35
46
 
47
+ def require_or_load(*)
48
+ CoreExt::ActiveSupport.allow_bootsnap_retry(true) do
49
+ super
50
+ end
51
+ end
52
+
36
53
  # If we can't find a constant using the patched implementation of
37
54
  # search_for_file, try again with the default implementation.
38
55
  #
@@ -40,16 +57,26 @@ module Bootsnap
40
57
  # behaviour. The gymnastics here are a bit awkward, but it prevents
41
58
  # 200+ lines of monkeypatches.
42
59
  def load_missing_constant(from_mod, const_name)
43
- super
60
+ CoreExt::ActiveSupport.allow_bootsnap_retry(false) do
61
+ super
62
+ end
44
63
  rescue NameError => e
64
+ raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
65
+ e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
66
+
67
+ # This function can end up called recursively, we only want to
68
+ # retry at the top-level.
69
+ raise(e) if Thread.current[:without_bootsnap_retry]
70
+ # If we already had cache disabled, there's no use retrying
71
+ raise(e) if Thread.current[:without_bootsnap_cache]
45
72
  # NoMethodError is a NameError, but we only want to handle actual
46
73
  # NameError instances.
47
- raise unless e.class == NameError
74
+ raise(e) unless e.class == NameError
48
75
  # We can only confidently handle cases when *this* constant fails
49
76
  # to load, not other constants referred to by it.
50
- raise unless e.name == const_name
77
+ raise(e) unless e.name == const_name
51
78
  # If the constant was actually loaded, something else went wrong?
52
- raise if from_mod.const_defined?(const_name)
79
+ raise(e) if from_mod.const_defined?(const_name)
53
80
  CoreExt::ActiveSupport.without_bootsnap_cache { super }
54
81
  end
55
82
 
@@ -57,7 +84,12 @@ module Bootsnap
57
84
  # reiterate it with version polymorphism here...
58
85
  def depend_on(*)
59
86
  super
60
- rescue LoadError
87
+ rescue LoadError => e
88
+ raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
89
+ e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
90
+
91
+ # If we already had cache disabled, there's no use retrying
92
+ raise(e) if Thread.current[:without_bootsnap_cache]
61
93
  CoreExt::ActiveSupport.without_bootsnap_cache { super }
62
94
  end
63
95
  end
@@ -69,7 +101,7 @@ end
69
101
  module ActiveSupport
70
102
  module Dependencies
71
103
  class << self
72
- prepend Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods
104
+ prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods)
73
105
  end
74
106
  end
75
107
  end