bootsnap 1.4.4 → 1.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +129 -0
- data/README.md +46 -15
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +276 -87
- data/ext/bootsnap/extconf.rb +20 -14
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/cli/worker_pool.rb +135 -0
- data/lib/bootsnap/cli.rb +281 -0
- data/lib/bootsnap/compile_cache/iseq.rb +51 -11
- data/lib/bootsnap/compile_cache/json.rb +79 -0
- data/lib/bootsnap/compile_cache/yaml.rb +141 -39
- data/lib/bootsnap/compile_cache.rb +14 -4
- data/lib/bootsnap/explicit_require.rb +1 -0
- data/lib/bootsnap/load_path_cache/cache.rb +47 -26
- data/lib/bootsnap/load_path_cache/change_observer.rb +4 -1
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -20
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +51 -15
- data/lib/bootsnap/load_path_cache/path.rb +3 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -26
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +39 -15
- data/lib/bootsnap/load_path_cache.rb +3 -16
- data/lib/bootsnap/setup.rb +2 -36
- data/lib/bootsnap/version.rb +2 -1
- data/lib/bootsnap.rb +106 -17
- metadata +18 -32
- data/.github/CODEOWNERS +0 -2
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -21
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/README.jp.md +0 -231
- data/Rakefile +0 -12
- data/bin/ci +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -45
- data/dev.yml +0 -10
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -106
- data/shipit.rubygems.yml +0 -0
@@ -1,61 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require('bootsnap/bootsnap')
|
2
3
|
|
3
4
|
module Bootsnap
|
4
5
|
module CompileCache
|
5
6
|
module YAML
|
6
7
|
class << self
|
7
|
-
attr_accessor(:msgpack_factory)
|
8
|
-
end
|
8
|
+
attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# NoMethodError is unexpected types; RangeError is Bignums
|
18
|
-
Marshal.dump(obj)
|
19
|
-
end
|
10
|
+
def input_to_storage(contents, _)
|
11
|
+
obj = strict_load(contents)
|
12
|
+
msgpack_factory.dump(obj)
|
13
|
+
rescue NoMethodError, RangeError
|
14
|
+
# The object included things that we can't serialize
|
15
|
+
raise(Uncompilable)
|
16
|
+
end
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
18
|
+
def storage_to_output(data, kwargs)
|
19
|
+
if kwargs && kwargs.key?(:symbolize_names)
|
20
|
+
kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
|
21
|
+
end
|
22
|
+
msgpack_factory.load(data, kwargs)
|
23
|
+
end
|
24
|
+
|
25
|
+
def input_to_output(data, kwargs)
|
26
|
+
if ::YAML.respond_to?(:unsafe_load)
|
27
|
+
::YAML.unsafe_load(data, **(kwargs || {}))
|
28
|
+
else
|
29
|
+
::YAML.load(data, **(kwargs || {}))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def strict_load(payload, *args)
|
34
|
+
ast = ::YAML.parse(payload)
|
35
|
+
return ast unless ast
|
36
|
+
strict_visitor.create(*args).visit(ast)
|
37
|
+
end
|
38
|
+
ruby2_keywords :strict_load if respond_to?(:ruby2_keywords, true)
|
39
|
+
|
40
|
+
def precompile(path, cache_dir: YAML.cache_dir)
|
41
|
+
Bootsnap::CompileCache::Native.precompile(
|
42
|
+
cache_dir,
|
43
|
+
path.to_s,
|
44
|
+
Bootsnap::CompileCache::YAML,
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def install!(cache_dir)
|
49
|
+
self.cache_dir = cache_dir
|
50
|
+
init!
|
51
|
+
::YAML.singleton_class.prepend(Patch)
|
52
|
+
end
|
53
|
+
|
54
|
+
def init!
|
55
|
+
require('yaml')
|
56
|
+
require('msgpack')
|
57
|
+
require('date')
|
58
|
+
|
59
|
+
if Patch.method_defined?(:unsafe_load_file) && !::YAML.respond_to?(:unsafe_load_file)
|
60
|
+
Patch.send(:remove_method, :unsafe_load_file)
|
61
|
+
end
|
62
|
+
if Patch.method_defined?(:load_file) && ::YAML::VERSION >= '4'
|
63
|
+
Patch.send(:remove_method, :load_file)
|
64
|
+
end
|
65
|
+
|
66
|
+
# MessagePack serializes symbols as strings by default.
|
67
|
+
# We want them to roundtrip cleanly, so we use a custom factory.
|
68
|
+
# see: https://github.com/msgpack/msgpack-ruby/pull/122
|
69
|
+
factory = MessagePack::Factory.new
|
70
|
+
factory.register_type(0x00, Symbol)
|
71
|
+
|
72
|
+
if defined? MessagePack::Timestamp
|
73
|
+
factory.register_type(
|
74
|
+
MessagePack::Timestamp::TYPE, # or just -1
|
75
|
+
Time,
|
76
|
+
packer: MessagePack::Time::Packer,
|
77
|
+
unpacker: MessagePack::Time::Unpacker
|
78
|
+
)
|
79
|
+
|
80
|
+
marshal_fallback = {
|
81
|
+
packer: ->(value) { Marshal.dump(value) },
|
82
|
+
unpacker: ->(payload) { Marshal.load(payload) },
|
83
|
+
}
|
84
|
+
{
|
85
|
+
Date => 0x01,
|
86
|
+
Regexp => 0x02,
|
87
|
+
}.each do |type, code|
|
88
|
+
factory.register_type(code, type, marshal_fallback)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
self.msgpack_factory = factory
|
93
|
+
|
94
|
+
self.supported_options = []
|
95
|
+
params = ::YAML.method(:load).parameters
|
96
|
+
if params.include?([:key, :symbolize_names])
|
97
|
+
self.supported_options << :symbolize_names
|
98
|
+
end
|
99
|
+
if params.include?([:key, :freeze])
|
100
|
+
if factory.load(factory.dump('yaml'), freeze: true).frozen?
|
101
|
+
self.supported_options << :freeze
|
102
|
+
end
|
103
|
+
end
|
104
|
+
self.supported_options.freeze
|
29
105
|
end
|
30
|
-
end
|
31
106
|
|
32
|
-
|
33
|
-
|
107
|
+
def strict_visitor
|
108
|
+
self::NoTagsVisitor ||= Class.new(Psych::Visitors::ToRuby) do
|
109
|
+
def visit(target)
|
110
|
+
if target.tag
|
111
|
+
raise Uncompilable, "YAML tags are not supported: #{target.tag}"
|
112
|
+
end
|
113
|
+
super
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
34
117
|
end
|
35
118
|
|
36
|
-
|
37
|
-
|
38
|
-
|
119
|
+
module Patch
|
120
|
+
def load_file(path, *args)
|
121
|
+
return super if args.size > 1
|
122
|
+
if kwargs = args.first
|
123
|
+
return super unless kwargs.is_a?(Hash)
|
124
|
+
return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
|
125
|
+
end
|
126
|
+
|
127
|
+
begin
|
128
|
+
::Bootsnap::CompileCache::Native.fetch(
|
129
|
+
Bootsnap::CompileCache::YAML.cache_dir,
|
130
|
+
File.realpath(path),
|
131
|
+
::Bootsnap::CompileCache::YAML,
|
132
|
+
kwargs,
|
133
|
+
)
|
134
|
+
rescue Errno::EACCES
|
135
|
+
::Bootsnap::CompileCache.permission_error(path)
|
136
|
+
end
|
137
|
+
end
|
39
138
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
139
|
+
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
140
|
+
|
141
|
+
def unsafe_load_file(path, *args)
|
142
|
+
return super if args.size > 1
|
143
|
+
if kwargs = args.first
|
144
|
+
return super unless kwargs.is_a?(Hash)
|
145
|
+
return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
|
146
|
+
end
|
46
147
|
|
47
|
-
klass = class << ::YAML; self; end
|
48
|
-
klass.send(:define_method, :load_file) do |path|
|
49
148
|
begin
|
50
|
-
Bootsnap::CompileCache::Native.fetch(
|
51
|
-
cache_dir,
|
52
|
-
path,
|
53
|
-
Bootsnap::CompileCache::YAML
|
149
|
+
::Bootsnap::CompileCache::Native.fetch(
|
150
|
+
Bootsnap::CompileCache::YAML.cache_dir,
|
151
|
+
File.realpath(path),
|
152
|
+
::Bootsnap::CompileCache::YAML,
|
153
|
+
kwargs,
|
54
154
|
)
|
55
155
|
rescue Errno::EACCES
|
56
|
-
Bootsnap::CompileCache.permission_error(path)
|
156
|
+
::Bootsnap::CompileCache.permission_error(path)
|
57
157
|
end
|
58
158
|
end
|
159
|
+
|
160
|
+
ruby2_keywords :unsafe_load_file if respond_to?(:ruby2_keywords, true)
|
59
161
|
end
|
60
162
|
end
|
61
163
|
end
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module CompileCache
|
3
4
|
Error = Class.new(StandardError)
|
4
5
|
PermissionError = Class.new(Error)
|
5
6
|
|
6
|
-
def self.setup(cache_dir:, iseq:, yaml:)
|
7
|
+
def self.setup(cache_dir:, iseq:, yaml:, json:)
|
7
8
|
if iseq
|
8
9
|
if supported?
|
9
10
|
require_relative('compile_cache/iseq')
|
@@ -21,6 +22,15 @@ module Bootsnap
|
|
21
22
|
warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
|
22
23
|
end
|
23
24
|
end
|
25
|
+
|
26
|
+
if json
|
27
|
+
if supported?
|
28
|
+
require_relative('compile_cache/json')
|
29
|
+
Bootsnap::CompileCache::JSON.install!(cache_dir)
|
30
|
+
elsif $VERBOSE
|
31
|
+
warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
|
32
|
+
end
|
33
|
+
end
|
24
34
|
end
|
25
35
|
|
26
36
|
def self.permission_error(path)
|
@@ -28,14 +38,14 @@ module Bootsnap
|
|
28
38
|
raise(
|
29
39
|
PermissionError,
|
30
40
|
"bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
|
31
|
-
"(or, less likely, doesn't have
|
41
|
+
"(or, less likely, doesn't have permission to read '#{path}')",
|
32
42
|
)
|
33
43
|
end
|
34
44
|
|
35
45
|
def self.supported?
|
36
|
-
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
|
46
|
+
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
|
37
47
|
RUBY_ENGINE == 'ruby' &&
|
38
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
|
48
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
|
39
49
|
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
40
50
|
end
|
41
51
|
end
|
@@ -10,8 +10,8 @@ module Bootsnap
|
|
10
10
|
def initialize(store, path_obj, development_mode: false)
|
11
11
|
@development_mode = development_mode
|
12
12
|
@store = store
|
13
|
-
@mutex =
|
14
|
-
@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) }
|
15
15
|
@has_relative_paths = nil
|
16
16
|
reinitialize
|
17
17
|
end
|
@@ -44,14 +44,20 @@ module Bootsnap
|
|
44
44
|
|
45
45
|
# Try to resolve this feature to an absolute path without traversing the
|
46
46
|
# loadpath.
|
47
|
-
def find(feature)
|
47
|
+
def find(feature, try_extensions: true)
|
48
48
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
49
|
-
feature = feature.to_s
|
50
|
-
|
51
|
-
return
|
49
|
+
feature = feature.to_s.freeze
|
50
|
+
|
51
|
+
return feature if Bootsnap.absolute_path?(feature)
|
52
|
+
|
53
|
+
if feature.start_with?('./', '../')
|
54
|
+
return try_extensions ? expand_path(feature) : File.expand_path(feature).freeze
|
55
|
+
end
|
56
|
+
|
52
57
|
@mutex.synchronize do
|
53
|
-
x = search_index(feature)
|
58
|
+
x = search_index(feature, try_extensions: try_extensions)
|
54
59
|
return x if x
|
60
|
+
return unless try_extensions
|
55
61
|
|
56
62
|
# Ruby has some built-in features that require lies about.
|
57
63
|
# For example, 'enumerator' is built in. If you require it, ruby
|
@@ -67,7 +73,7 @@ module Bootsnap
|
|
67
73
|
# native dynamic extension, e.g. .bundle or .so), we know it was a
|
68
74
|
# failure and there's nothing more we can do to find the file.
|
69
75
|
# no extension, .rb, (.bundle or .so)
|
70
|
-
when '', *CACHED_EXTENSIONS
|
76
|
+
when '', *CACHED_EXTENSIONS
|
71
77
|
nil
|
72
78
|
# Ruby allows specifying native extensions as '.so' even when DLEXT
|
73
79
|
# is '.bundle'. This is where we handle that case.
|
@@ -92,16 +98,6 @@ module Bootsnap
|
|
92
98
|
raise(LoadPathCache::FallbackScan, '', []) if @development_mode
|
93
99
|
end
|
94
100
|
|
95
|
-
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
96
|
-
def absolute_path?(path)
|
97
|
-
path[1] == ':'
|
98
|
-
end
|
99
|
-
else
|
100
|
-
def absolute_path?(path)
|
101
|
-
path.start_with?(SLASH)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
101
|
def unshift_paths(sender, *paths)
|
106
102
|
return unless sender == @path_obj
|
107
103
|
@mutex.synchronize { unshift_paths_locked(*paths) }
|
@@ -144,7 +140,7 @@ module Bootsnap
|
|
144
140
|
expanded_path = p.expanded_path
|
145
141
|
entries, dirs = p.entries_and_dirs(@store)
|
146
142
|
# push -> low precedence -> set only if unset
|
147
|
-
dirs.each { |dir| @dirs[dir]
|
143
|
+
dirs.each { |dir| @dirs[dir] ||= path }
|
148
144
|
entries.each { |rel| @index[rel] ||= expanded_path }
|
149
145
|
end
|
150
146
|
end
|
@@ -177,16 +173,24 @@ module Bootsnap
|
|
177
173
|
end
|
178
174
|
|
179
175
|
if DLEXT2
|
180
|
-
def search_index(f)
|
181
|
-
|
176
|
+
def search_index(f, try_extensions: true)
|
177
|
+
if try_extensions
|
178
|
+
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
|
179
|
+
else
|
180
|
+
try_index(f)
|
181
|
+
end
|
182
182
|
end
|
183
183
|
|
184
184
|
def maybe_append_extension(f)
|
185
185
|
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
|
186
186
|
end
|
187
187
|
else
|
188
|
-
def search_index(f)
|
189
|
-
|
188
|
+
def search_index(f, try_extensions: true)
|
189
|
+
if try_extensions
|
190
|
+
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
|
191
|
+
else
|
192
|
+
try_index(f)
|
193
|
+
end
|
190
194
|
end
|
191
195
|
|
192
196
|
def maybe_append_extension(f)
|
@@ -194,9 +198,26 @@ module Bootsnap
|
|
194
198
|
end
|
195
199
|
end
|
196
200
|
|
197
|
-
|
198
|
-
|
199
|
-
|
201
|
+
s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
|
202
|
+
if s.respond_to?(:-@)
|
203
|
+
if (-s).equal?(s) && (-s.dup).equal?(s) || RUBY_VERSION >= '2.7'
|
204
|
+
def try_index(f)
|
205
|
+
if (p = @index[f])
|
206
|
+
-(File.join(p, f).freeze)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
else
|
210
|
+
def try_index(f)
|
211
|
+
if (p = @index[f])
|
212
|
+
-File.join(p, f).untaint
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
else
|
217
|
+
def try_index(f)
|
218
|
+
if (p = @index[f])
|
219
|
+
File.join(p, f)
|
220
|
+
end
|
200
221
|
end
|
201
222
|
end
|
202
223
|
|
@@ -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
|
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
|
@@ -1,8 +1,9 @@
|
|
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}")
|
6
7
|
err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
7
8
|
err.define_singleton_method(:path) { path }
|
8
9
|
err
|
@@ -37,36 +38,29 @@ module Kernel
|
|
37
38
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
38
39
|
false
|
39
40
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
40
|
-
|
41
|
+
fallback = true
|
42
|
+
ensure
|
43
|
+
if fallback
|
44
|
+
require_with_bootsnap_lfi(path)
|
45
|
+
end
|
41
46
|
end
|
42
47
|
|
43
48
|
alias_method(:require_relative_without_bootsnap, :require_relative)
|
44
49
|
def require_relative(path)
|
50
|
+
location = caller_locations(1..1).first
|
45
51
|
realpath = Bootsnap::LoadPathCache.realpath_cache.call(
|
46
|
-
|
52
|
+
location.absolute_path || location.path, path
|
47
53
|
)
|
48
54
|
require(realpath)
|
49
55
|
end
|
50
56
|
|
51
57
|
alias_method(:load_without_bootsnap, :load)
|
52
58
|
def load(path, wrap = false)
|
53
|
-
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# load also allows relative paths from pwd even when not in $:
|
58
|
-
if File.exist?(relative = File.expand_path(path))
|
59
|
-
return load_without_bootsnap(relative, wrap)
|
59
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path, try_extensions: false))
|
60
|
+
load_without_bootsnap(resolved, wrap)
|
61
|
+
else
|
62
|
+
load_without_bootsnap(path, wrap)
|
60
63
|
end
|
61
|
-
|
62
|
-
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
63
|
-
rescue LoadError => e
|
64
|
-
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
65
|
-
raise(e)
|
66
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
67
|
-
false
|
68
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
69
|
-
load_without_bootsnap(path, wrap)
|
70
64
|
end
|
71
65
|
end
|
72
66
|
|
@@ -87,6 +81,10 @@ class Module
|
|
87
81
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
88
82
|
false
|
89
83
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
90
|
-
|
84
|
+
fallback = true
|
85
|
+
ensure
|
86
|
+
if fallback
|
87
|
+
autoload_without_bootsnap(const, path)
|
88
|
+
end
|
91
89
|
end
|
92
90
|
end
|
@@ -26,7 +26,7 @@ module Bootsnap
|
|
26
26
|
class LoadedFeaturesIndex
|
27
27
|
def initialize
|
28
28
|
@lfi = {}
|
29
|
-
@mutex =
|
29
|
+
@mutex = Mutex.new
|
30
30
|
|
31
31
|
# In theory the user could mutate $LOADED_FEATURES and invalidate our
|
32
32
|
# cache. If this ever comes up in practice — or if you, the
|
@@ -40,7 +40,7 @@ module Bootsnap
|
|
40
40
|
# /a/b/lib/my/foo.rb
|
41
41
|
# ^^^^^^^^^
|
42
42
|
short = feat[(lpe.length + 1)..-1]
|
43
|
-
stripped =
|
43
|
+
stripped = strip_extension_if_elidable(short)
|
44
44
|
@lfi[short] = hash
|
45
45
|
@lfi[stripped] = hash
|
46
46
|
end
|
@@ -58,9 +58,9 @@ module Bootsnap
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def purge_multi(features)
|
61
|
-
rejected_hashes = features.
|
61
|
+
rejected_hashes = features.each_with_object({}) { |f, h| h[f.hash] = true }
|
62
62
|
@mutex.synchronize do
|
63
|
-
@lfi.reject! { |_, hash| rejected_hashes.
|
63
|
+
@lfi.reject! { |_, hash| rejected_hashes.key?(hash) }
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
@@ -83,24 +83,38 @@ module Bootsnap
|
|
83
83
|
# 2. Inspect $LOADED_FEATURES upon return from yield to find the matching
|
84
84
|
# entry.
|
85
85
|
def register(short, long = nil)
|
86
|
+
# Absolute paths are not a concern.
|
87
|
+
if Bootsnap.absolute_path?(short.to_s)
|
88
|
+
return yield
|
89
|
+
end
|
90
|
+
|
86
91
|
if long.nil?
|
87
|
-
pat = %r{/#{Regexp.escape(short)}(\.[^/]+)?$}
|
88
92
|
len = $LOADED_FEATURES.size
|
89
93
|
ret = yield
|
90
|
-
long = $LOADED_FEATURES[len..-1].detect
|
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
|
91
104
|
else
|
92
105
|
ret = yield
|
93
106
|
end
|
94
107
|
|
95
108
|
hash = long.hash
|
96
109
|
|
97
|
-
#
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
#
|
110
|
+
# Do we have a filename with an elidable extension, e.g.,
|
111
|
+
# 'bundler.rb', or 'libgit2.so'?
|
112
|
+
altname = if extension_elidable?(short)
|
113
|
+
# Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
|
114
|
+
strip_extension_if_elidable(short)
|
115
|
+
elsif long && (ext = File.extname(long.freeze))
|
116
|
+
# We already know the extension of the actual file this
|
117
|
+
# resolves to, so put that back on.
|
104
118
|
short + ext
|
105
119
|
end
|
106
120
|
|
@@ -117,8 +131,30 @@ module Bootsnap
|
|
117
131
|
STRIP_EXTENSION = /\.[^.]*?$/
|
118
132
|
private_constant(:STRIP_EXTENSION)
|
119
133
|
|
120
|
-
|
121
|
-
|
134
|
+
# Might Ruby automatically search for this extension if
|
135
|
+
# someone tries to 'require' the file without it? E.g. Ruby
|
136
|
+
# will implicitly try 'x.rb' if you ask for 'x'.
|
137
|
+
#
|
138
|
+
# This is complex and platform-dependent, and the Ruby docs are a little
|
139
|
+
# handwavy about what will be tried when and in what order.
|
140
|
+
# So optimistically pretend that all known elidable extensions
|
141
|
+
# will be tried on all platforms, and that people are unlikely
|
142
|
+
# to name files in a way that assumes otherwise.
|
143
|
+
# (E.g. It's unlikely that someone will know that their code
|
144
|
+
# will _never_ run on MacOS, and therefore think they can get away
|
145
|
+
# with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
|
146
|
+
#
|
147
|
+
# See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
|
148
|
+
def extension_elidable?(f)
|
149
|
+
f.to_s.end_with?('.rb', '.so', '.o', '.dll', '.dylib')
|
150
|
+
end
|
151
|
+
|
152
|
+
def strip_extension_if_elidable(f)
|
153
|
+
if extension_elidable?(f)
|
154
|
+
f.sub(STRIP_EXTENSION, '')
|
155
|
+
else
|
156
|
+
f
|
157
|
+
end
|
122
158
|
end
|
123
159
|
end
|
124
160
|
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
|