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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +89 -0
- data/README.md +46 -6
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +125 -87
- data/ext/bootsnap/extconf.rb +3 -1
- data/lib/bootsnap.rb +15 -6
- data/lib/bootsnap/bundler.rb +6 -3
- data/lib/bootsnap/cli.rb +136 -0
- data/lib/bootsnap/compile_cache.rb +32 -4
- data/lib/bootsnap/compile_cache/iseq.rb +25 -16
- data/lib/bootsnap/compile_cache/yaml.rb +75 -42
- data/lib/bootsnap/explicit_require.rb +2 -1
- data/lib/bootsnap/load_path_cache.rb +35 -9
- data/lib/bootsnap/load_path_cache/cache.rb +48 -29
- data/lib/bootsnap/load_path_cache/change_observer.rb +36 -29
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +39 -7
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +70 -53
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +18 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +148 -0
- data/lib/bootsnap/load_path_cache/path.rb +8 -7
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -39
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
- data/lib/bootsnap/load_path_cache/store.rb +20 -14
- data/lib/bootsnap/setup.rb +11 -13
- data/lib/bootsnap/version.rb +2 -1
- metadata +25 -28
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -4
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/Rakefile +0 -11
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -39
- data/dev.yml +0 -10
@@ -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
|
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
|
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
|
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
|
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
|
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
|
-
|
49
|
-
require_relative
|
50
|
-
require_relative
|
51
|
-
require_relative
|
52
|
-
require_relative
|
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
|
-
|
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
|
-
#
|
18
|
-
# e.g. given "/a/b/c/d" exists, and the path is ["/a/b"],
|
19
|
-
# is
|
20
|
-
def
|
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
|
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
|
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
|
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
|
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 =
|
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]
|
146
|
-
entries.each { |rel| @index[rel] ||=
|
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] =
|
159
|
-
entries.each { |rel| @index[rel] =
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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!
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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.
|
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
|
-
|
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
|
104
|
+
prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods)
|
73
105
|
end
|
74
106
|
end
|
75
107
|
end
|