bootsnap 1.9.4 → 1.11.0
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 +4 -4
- data/CHANGELOG.md +63 -3
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/exe/bootsnap +1 -1
- data/ext/bootsnap/bootsnap.c +47 -36
- data/ext/bootsnap/extconf.rb +13 -11
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli/worker_pool.rb +1 -0
- data/lib/bootsnap/cli.rb +49 -49
- data/lib/bootsnap/compile_cache/iseq.rb +16 -11
- data/lib/bootsnap/compile_cache/json.rb +18 -9
- data/lib/bootsnap/compile_cache/yaml.rb +264 -77
- data/lib/bootsnap/compile_cache.rb +11 -7
- data/lib/bootsnap/explicit_require.rb +4 -3
- data/lib/bootsnap/load_path_cache/cache.rb +46 -30
- data/lib/bootsnap/load_path_cache/change_observer.rb +2 -0
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +31 -56
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +32 -34
- data/lib/bootsnap/load_path_cache/path.rb +29 -5
- data/lib/bootsnap/load_path_cache/path_scanner.rb +6 -5
- data/lib/bootsnap/load_path_cache/store.rb +29 -11
- data/lib/bootsnap/load_path_cache.rb +16 -24
- data/lib/bootsnap/setup.rb +2 -1
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +111 -99
- metadata +6 -77
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative(
|
|
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,
|
|
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
|
|
@@ -50,7 +51,7 @@ module Bootsnap
|
|
|
50
51
|
|
|
51
52
|
return feature if Bootsnap.absolute_path?(feature)
|
|
52
53
|
|
|
53
|
-
if feature.start_with?(
|
|
54
|
+
if feature.start_with?("./", "../")
|
|
54
55
|
return try_extensions ? expand_path(feature) : File.expand_path(feature).freeze
|
|
55
56
|
end
|
|
56
57
|
|
|
@@ -64,7 +65,7 @@ module Bootsnap
|
|
|
64
65
|
# returns false as if it were already loaded; however, there is no
|
|
65
66
|
# file to find on disk. We've pre-built a list of these, and we
|
|
66
67
|
# return false if any of them is loaded.
|
|
67
|
-
|
|
68
|
+
return false if BUILTIN_FEATURES.key?(feature)
|
|
68
69
|
|
|
69
70
|
# The feature wasn't found on our preliminary search through the index.
|
|
70
71
|
# We resolve this differently depending on what the extension was.
|
|
@@ -73,13 +74,14 @@ module Bootsnap
|
|
|
73
74
|
# native dynamic extension, e.g. .bundle or .so), we know it was a
|
|
74
75
|
# failure and there's nothing more we can do to find the file.
|
|
75
76
|
# no extension, .rb, (.bundle or .so)
|
|
76
|
-
when
|
|
77
|
+
when "", *CACHED_EXTENSIONS
|
|
77
78
|
nil
|
|
78
79
|
# Ruby allows specifying native extensions as '.so' even when DLEXT
|
|
79
80
|
# is '.bundle'. This is where we handle that case.
|
|
80
81
|
when DOT_SO
|
|
81
82
|
x = search_index(feature[0..-4] + DLEXT)
|
|
82
83
|
return x if x
|
|
84
|
+
|
|
83
85
|
if DLEXT2
|
|
84
86
|
x = search_index(feature[0..-4] + DLEXT2)
|
|
85
87
|
return x if x
|
|
@@ -87,7 +89,7 @@ module Bootsnap
|
|
|
87
89
|
else
|
|
88
90
|
# other, unknown extension. For example, `.rake`. Since we haven't
|
|
89
91
|
# cached these, we legitimately need to run the load path search.
|
|
90
|
-
|
|
92
|
+
return FALLBACK_SCAN
|
|
91
93
|
end
|
|
92
94
|
end
|
|
93
95
|
|
|
@@ -95,16 +97,18 @@ module Bootsnap
|
|
|
95
97
|
# cases where the file doesn't appear to be on the load path. We should
|
|
96
98
|
# be able to detect newly-created files without rebooting the
|
|
97
99
|
# application.
|
|
98
|
-
|
|
100
|
+
return FALLBACK_SCAN if @development_mode
|
|
99
101
|
end
|
|
100
102
|
|
|
101
103
|
def unshift_paths(sender, *paths)
|
|
102
104
|
return unless sender == @path_obj
|
|
105
|
+
|
|
103
106
|
@mutex.synchronize { unshift_paths_locked(*paths) }
|
|
104
107
|
end
|
|
105
108
|
|
|
106
109
|
def push_paths(sender, *paths)
|
|
107
110
|
return unless sender == @path_obj
|
|
111
|
+
|
|
108
112
|
@mutex.synchronize { push_paths_locked(*paths) }
|
|
109
113
|
end
|
|
110
114
|
|
|
@@ -137,6 +141,9 @@ module Bootsnap
|
|
|
137
141
|
p = Path.new(path)
|
|
138
142
|
@has_relative_paths = true if p.relative?
|
|
139
143
|
next if p.non_directory?
|
|
144
|
+
|
|
145
|
+
p = p.to_realpath
|
|
146
|
+
|
|
140
147
|
expanded_path = p.expanded_path
|
|
141
148
|
entries, dirs = p.entries_and_dirs(@store)
|
|
142
149
|
# push -> low precedence -> set only if unset
|
|
@@ -151,6 +158,9 @@ module Bootsnap
|
|
|
151
158
|
paths.map(&:to_s).reverse_each do |path|
|
|
152
159
|
p = Path.new(path)
|
|
153
160
|
next if p.non_directory?
|
|
161
|
+
|
|
162
|
+
p = p.to_realpath
|
|
163
|
+
|
|
154
164
|
expanded_path = p.expanded_path
|
|
155
165
|
entries, dirs = p.entries_and_dirs(@store)
|
|
156
166
|
# unshift -> high precedence -> unconditional set
|
|
@@ -173,56 +183,62 @@ module Bootsnap
|
|
|
173
183
|
end
|
|
174
184
|
|
|
175
185
|
if DLEXT2
|
|
176
|
-
def search_index(
|
|
186
|
+
def search_index(feature, try_extensions: true)
|
|
177
187
|
if try_extensions
|
|
178
|
-
try_index(
|
|
188
|
+
try_index(feature + DOT_RB) ||
|
|
189
|
+
try_index(feature + DLEXT) ||
|
|
190
|
+
try_index(feature + DLEXT2) ||
|
|
191
|
+
try_index(feature)
|
|
179
192
|
else
|
|
180
|
-
try_index(
|
|
193
|
+
try_index(feature)
|
|
181
194
|
end
|
|
182
195
|
end
|
|
183
196
|
|
|
184
|
-
def maybe_append_extension(
|
|
185
|
-
try_ext(
|
|
197
|
+
def maybe_append_extension(feature)
|
|
198
|
+
try_ext(feature + DOT_RB) ||
|
|
199
|
+
try_ext(feature + DLEXT) ||
|
|
200
|
+
try_ext(feature + DLEXT2) ||
|
|
201
|
+
feature
|
|
186
202
|
end
|
|
187
203
|
else
|
|
188
|
-
def search_index(
|
|
204
|
+
def search_index(feature, try_extensions: true)
|
|
189
205
|
if try_extensions
|
|
190
|
-
try_index(
|
|
206
|
+
try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
|
|
191
207
|
else
|
|
192
|
-
try_index(
|
|
208
|
+
try_index(feature)
|
|
193
209
|
end
|
|
194
210
|
end
|
|
195
211
|
|
|
196
|
-
def maybe_append_extension(
|
|
197
|
-
try_ext(
|
|
212
|
+
def maybe_append_extension(feature)
|
|
213
|
+
try_ext(feature + DOT_RB) || try_ext(feature + DLEXT) || feature
|
|
198
214
|
end
|
|
199
215
|
end
|
|
200
216
|
|
|
201
217
|
s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
|
|
202
218
|
if s.respond_to?(:-@)
|
|
203
|
-
if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >=
|
|
204
|
-
def try_index(
|
|
205
|
-
if (
|
|
206
|
-
-
|
|
219
|
+
if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= "2.7"
|
|
220
|
+
def try_index(feature)
|
|
221
|
+
if (path = @index[feature])
|
|
222
|
+
-File.join(path, feature).freeze
|
|
207
223
|
end
|
|
208
224
|
end
|
|
209
225
|
else
|
|
210
|
-
def try_index(
|
|
211
|
-
if (
|
|
212
|
-
-File.join(
|
|
226
|
+
def try_index(feature)
|
|
227
|
+
if (path = @index[feature])
|
|
228
|
+
-File.join(path, feature).untaint
|
|
213
229
|
end
|
|
214
230
|
end
|
|
215
231
|
end
|
|
216
232
|
else
|
|
217
|
-
def try_index(
|
|
218
|
-
if (
|
|
219
|
-
File.join(
|
|
233
|
+
def try_index(feature)
|
|
234
|
+
if (path = @index[feature])
|
|
235
|
+
File.join(path, feature)
|
|
220
236
|
end
|
|
221
237
|
end
|
|
222
238
|
end
|
|
223
239
|
|
|
224
|
-
def try_ext(
|
|
225
|
-
|
|
240
|
+
def try_ext(feature)
|
|
241
|
+
feature if File.exist?(feature)
|
|
226
242
|
end
|
|
227
243
|
end
|
|
228
244
|
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,62 +1,41 @@
|
|
|
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
|
|
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
|
-
|
|
9
|
+
string_path = Bootsnap.rb_get_path(path)
|
|
10
|
+
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
|
|
29
11
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
|
48
|
-
alias_method(:require_relative_without_bootsnap, :require_relative)
|
|
49
|
-
def require_relative(path)
|
|
50
|
-
location = caller_locations(1..1).first
|
|
51
|
-
realpath = Bootsnap::LoadPathCache.realpath_cache.call(
|
|
52
|
-
location.absolute_path || location.path, path
|
|
53
|
-
)
|
|
54
|
-
require(realpath)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
36
|
alias_method(:load_without_bootsnap, :load)
|
|
58
37
|
def load(path, wrap = false)
|
|
59
|
-
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path, try_extensions: false))
|
|
38
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path), try_extensions: false))
|
|
60
39
|
load_without_bootsnap(resolved, wrap)
|
|
61
40
|
else
|
|
62
41
|
load_without_bootsnap(path, wrap)
|
|
@@ -74,17 +53,13 @@ class Module
|
|
|
74
53
|
# The challenge is that we don't control the point at which the entry gets
|
|
75
54
|
# added to $LOADED_FEATURES and won't be able to hook that modification
|
|
76
55
|
# since it's done in C-land.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
|
80
|
-
raise(e)
|
|
81
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
82
|
-
false
|
|
83
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
84
|
-
fallback = true
|
|
85
|
-
ensure
|
|
86
|
-
if fallback
|
|
56
|
+
resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path))
|
|
57
|
+
if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
|
|
87
58
|
autoload_without_bootsnap(const, path)
|
|
59
|
+
elsif resolved == false
|
|
60
|
+
return false
|
|
61
|
+
else
|
|
62
|
+
autoload_without_bootsnap(const, resolved || path)
|
|
88
63
|
end
|
|
89
64
|
end
|
|
90
65
|
end
|
|
@@ -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
|
|
33
|
-
# enterprising reader, feels inclined to solve this problem
|
|
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
|
-
# `
|
|
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,28 +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
|
|
86
|
-
|
|
87
|
-
if Bootsnap.absolute_path?(short.to_s)
|
|
88
|
-
return yield
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
if long.nil?
|
|
92
|
-
len = $LOADED_FEATURES.size
|
|
93
|
-
ret = yield
|
|
94
|
-
long = $LOADED_FEATURES[len..-1].detect do |feat|
|
|
95
|
-
offset = 0
|
|
96
|
-
while offset = feat.index(short, offset)
|
|
97
|
-
if feat.index(".", offset + 1) && !feat.index("/", offset + 2)
|
|
98
|
-
break true
|
|
99
|
-
else
|
|
100
|
-
offset += 1
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
else
|
|
105
|
-
ret = yield
|
|
106
|
-
end
|
|
105
|
+
def register(short, long)
|
|
106
|
+
return if Bootsnap.absolute_path?(short)
|
|
107
107
|
|
|
108
108
|
hash = long.hash
|
|
109
109
|
|
|
@@ -122,13 +122,11 @@ module Bootsnap
|
|
|
122
122
|
@lfi[short] = hash
|
|
123
123
|
(@lfi[altname] = hash) if altname
|
|
124
124
|
end
|
|
125
|
-
|
|
126
|
-
ret
|
|
127
125
|
end
|
|
128
126
|
|
|
129
127
|
private
|
|
130
128
|
|
|
131
|
-
STRIP_EXTENSION = /\.[^.]
|
|
129
|
+
STRIP_EXTENSION = /\.[^.]*?$/.freeze
|
|
132
130
|
private_constant(:STRIP_EXTENSION)
|
|
133
131
|
|
|
134
132
|
# Might Ruby automatically search for this extension if
|
|
@@ -145,15 +143,15 @@ module Bootsnap
|
|
|
145
143
|
# with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
|
|
146
144
|
#
|
|
147
145
|
# See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
|
|
148
|
-
def extension_elidable?(
|
|
149
|
-
|
|
146
|
+
def extension_elidable?(feature)
|
|
147
|
+
feature.to_s.end_with?(".rb", ".so", ".o", ".dll", ".dylib")
|
|
150
148
|
end
|
|
151
149
|
|
|
152
|
-
def strip_extension_if_elidable(
|
|
153
|
-
if extension_elidable?(
|
|
154
|
-
|
|
150
|
+
def strip_extension_if_elidable(feature)
|
|
151
|
+
if extension_elidable?(feature)
|
|
152
|
+
feature.sub(STRIP_EXTENSION, "")
|
|
155
153
|
else
|
|
156
|
-
|
|
154
|
+
feature
|
|
157
155
|
end
|
|
158
156
|
end
|
|
159
157
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
require_relative("path_scanner")
|
|
3
4
|
|
|
4
5
|
module Bootsnap
|
|
5
6
|
module LoadPathCache
|
|
@@ -20,8 +21,26 @@ module Bootsnap
|
|
|
20
21
|
|
|
21
22
|
attr_reader(:path)
|
|
22
23
|
|
|
23
|
-
def initialize(path)
|
|
24
|
+
def initialize(path, real: false)
|
|
24
25
|
@path = path.to_s.freeze
|
|
26
|
+
@real = real
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_realpath
|
|
30
|
+
return self if @real
|
|
31
|
+
|
|
32
|
+
realpath = begin
|
|
33
|
+
File.realpath(path)
|
|
34
|
+
rescue Errno::ENOENT
|
|
35
|
+
return self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if realpath != path
|
|
39
|
+
Path.new(realpath, real: true)
|
|
40
|
+
else
|
|
41
|
+
@real = true
|
|
42
|
+
self
|
|
43
|
+
end
|
|
25
44
|
end
|
|
26
45
|
|
|
27
46
|
# True if the path exists, but represents a non-directory object
|
|
@@ -43,6 +62,7 @@ module Bootsnap
|
|
|
43
62
|
# set to zero anyway, just in case we change the stability heuristics.
|
|
44
63
|
_, entries, dirs = store.get(expanded_path)
|
|
45
64
|
return [entries, dirs] if entries # cache hit
|
|
65
|
+
|
|
46
66
|
entries, dirs = scan!
|
|
47
67
|
store.set(expanded_path, [0, entries, dirs])
|
|
48
68
|
return [entries, dirs]
|
|
@@ -60,7 +80,11 @@ module Bootsnap
|
|
|
60
80
|
end
|
|
61
81
|
|
|
62
82
|
def expanded_path
|
|
63
|
-
|
|
83
|
+
if @real
|
|
84
|
+
path
|
|
85
|
+
else
|
|
86
|
+
@expanded_path ||= File.expand_path(path).freeze
|
|
87
|
+
end
|
|
64
88
|
end
|
|
65
89
|
|
|
66
90
|
private
|
|
@@ -93,8 +117,8 @@ module Bootsnap
|
|
|
93
117
|
|
|
94
118
|
# Built-in ruby lib stuff doesn't change, but things can occasionally be
|
|
95
119
|
# installed into sitedir, which generally lives under libdir.
|
|
96
|
-
RUBY_LIBDIR = RbConfig::CONFIG[
|
|
97
|
-
RUBY_SITEDIR = RbConfig::CONFIG[
|
|
120
|
+
RUBY_LIBDIR = RbConfig::CONFIG["libdir"]
|
|
121
|
+
RUBY_SITEDIR = RbConfig::CONFIG["sitedir"]
|
|
98
122
|
|
|
99
123
|
def stability
|
|
100
124
|
@stability ||= begin
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative(
|
|
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 >=
|
|
62
|
+
if RUBY_VERSION >= "3.1"
|
|
62
63
|
def os_path(path)
|
|
63
64
|
path.freeze
|
|
64
65
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require_relative('../explicit_require')
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
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 =
|
|
11
|
-
CURRENT_VERSION = "#{RUBY_REVISION}-#{RUBY_PLATFORM}".freeze
|
|
10
|
+
VERSION_KEY = "__bootsnap_ruby_version__"
|
|
11
|
+
CURRENT_VERSION = "#{RUBY_REVISION}-#{RUBY_PLATFORM}".freeze # rubocop:disable Style/RedundantFreeze
|
|
12
12
|
|
|
13
13
|
NestedTransactionError = Class.new(StandardError)
|
|
14
14
|
SetOutsideTransactionNotAllowed = Class.new(StandardError)
|
|
@@ -26,6 +26,7 @@ module Bootsnap
|
|
|
26
26
|
|
|
27
27
|
def fetch(key)
|
|
28
28
|
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
|
29
|
+
|
|
29
30
|
v = get(key)
|
|
30
31
|
unless v
|
|
31
32
|
@dirty = true
|
|
@@ -37,6 +38,7 @@ module Bootsnap
|
|
|
37
38
|
|
|
38
39
|
def set(key, value)
|
|
39
40
|
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
|
41
|
+
|
|
40
42
|
if value != @data[key]
|
|
41
43
|
@dirty = true
|
|
42
44
|
@data[key] = value
|
|
@@ -45,6 +47,7 @@ module Bootsnap
|
|
|
45
47
|
|
|
46
48
|
def transaction
|
|
47
49
|
raise(NestedTransactionError) if @txn_mutex.owned?
|
|
50
|
+
|
|
48
51
|
@txn_mutex.synchronize do
|
|
49
52
|
begin
|
|
50
53
|
yield
|
|
@@ -66,7 +69,7 @@ module Bootsnap
|
|
|
66
69
|
def load_data
|
|
67
70
|
@data = begin
|
|
68
71
|
data = File.open(@store_path, encoding: Encoding::BINARY) do |io|
|
|
69
|
-
MessagePack.load(io)
|
|
72
|
+
MessagePack.load(io, freeze: true)
|
|
70
73
|
end
|
|
71
74
|
if data.is_a?(Hash) && data[VERSION_KEY] == CURRENT_VERSION
|
|
72
75
|
data
|
|
@@ -88,22 +91,37 @@ module Bootsnap
|
|
|
88
91
|
def dump_data
|
|
89
92
|
# Change contents atomically so other processes can't get invalid
|
|
90
93
|
# caches if they read at an inopportune time.
|
|
91
|
-
tmp = "#{@store_path}.#{Process.pid}.#{(rand *
|
|
92
|
-
|
|
94
|
+
tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100_000).to_i}.tmp"
|
|
95
|
+
mkdir_p(File.dirname(tmp))
|
|
93
96
|
exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
|
|
94
97
|
# `encoding:` looks redundant wrt `binwrite`, but necessary on windows
|
|
95
98
|
# because binary is part of mode.
|
|
96
99
|
File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
|
|
97
|
-
MessagePack.dump(@data, io
|
|
100
|
+
MessagePack.dump(@data, io)
|
|
98
101
|
end
|
|
99
|
-
|
|
102
|
+
File.rename(tmp, @store_path)
|
|
100
103
|
rescue Errno::EEXIST
|
|
101
104
|
retry
|
|
102
105
|
rescue SystemCallError
|
|
103
106
|
end
|
|
104
107
|
|
|
105
108
|
def default_data
|
|
106
|
-
{
|
|
109
|
+
{VERSION_KEY => CURRENT_VERSION}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def mkdir_p(path)
|
|
113
|
+
stack = []
|
|
114
|
+
until File.directory?(path)
|
|
115
|
+
stack.push path
|
|
116
|
+
path = File.dirname(path)
|
|
117
|
+
end
|
|
118
|
+
stack.reverse_each do |dir|
|
|
119
|
+
begin
|
|
120
|
+
Dir.mkdir(dir)
|
|
121
|
+
rescue SystemCallError
|
|
122
|
+
raise unless File.directory?(dir)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
107
125
|
end
|
|
108
126
|
end
|
|
109
127
|
end
|