bootsnap 1.4.6 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +14 -1
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +64 -41
- data/lib/bootsnap.rb +1 -0
- data/lib/bootsnap/cli.rb +136 -0
- data/lib/bootsnap/compile_cache.rb +2 -2
- data/lib/bootsnap/compile_cache/iseq.rb +14 -8
- data/lib/bootsnap/compile_cache/yaml.rb +66 -38
- data/lib/bootsnap/load_path_cache.rb +1 -1
- data/lib/bootsnap/load_path_cache/cache.rb +6 -6
- data/lib/bootsnap/load_path_cache/change_observer.rb +1 -1
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +16 -4
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +2 -2
- data/lib/bootsnap/load_path_cache/path.rb +2 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +39 -26
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +1 -1
- data/lib/bootsnap/version.rb +1 -1
- metadata +13 -27
- 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 -9
- data/README.jp.md +0 -231
- data/Rakefile +0 -13
- data/bin/ci +0 -10
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -46
- data/dev.yml +0 -10
- data/shipit.rubygems.yml +0 -0
@@ -34,9 +34,9 @@ module Bootsnap
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def self.supported?
|
37
|
-
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
|
37
|
+
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
|
38
38
|
RUBY_ENGINE == 'ruby' &&
|
39
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
|
39
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
|
40
40
|
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
41
41
|
end
|
42
42
|
end
|
@@ -9,13 +9,13 @@ module Bootsnap
|
|
9
9
|
attr_accessor(:cache_dir)
|
10
10
|
end
|
11
11
|
|
12
|
-
def self.input_to_storage(_, path)
|
12
|
+
def self.input_to_storage(_, path, _args)
|
13
13
|
RubyVM::InstructionSequence.compile_file(path).to_binary
|
14
14
|
rescue SyntaxError
|
15
15
|
raise(Uncompilable, 'syntax error')
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.storage_to_output(binary)
|
18
|
+
def self.storage_to_output(binary, _args)
|
19
19
|
RubyVM::InstructionSequence.load_from_binary(binary)
|
20
20
|
rescue RuntimeError => e
|
21
21
|
if e.message == 'broken binary format'
|
@@ -26,7 +26,16 @@ module Bootsnap
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
def self.
|
29
|
+
def self.fetch(path, cache_dir: ISeq.cache_dir)
|
30
|
+
Bootsnap::CompileCache::Native.fetch(
|
31
|
+
cache_dir,
|
32
|
+
path.to_s,
|
33
|
+
Bootsnap::CompileCache::ISeq,
|
34
|
+
nil,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.input_to_output(_, _)
|
30
39
|
nil # ruby handles this
|
31
40
|
end
|
32
41
|
|
@@ -35,11 +44,7 @@ module Bootsnap
|
|
35
44
|
# Having coverage enabled prevents iseq dumping/loading.
|
36
45
|
return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
|
37
46
|
|
38
|
-
Bootsnap::CompileCache::
|
39
|
-
Bootsnap::CompileCache::ISeq.cache_dir,
|
40
|
-
path.to_s,
|
41
|
-
Bootsnap::CompileCache::ISeq
|
42
|
-
)
|
47
|
+
Bootsnap::CompileCache::ISeq.fetch(path.to_s)
|
43
48
|
rescue Errno::EACCES
|
44
49
|
Bootsnap::CompileCache.permission_error(path)
|
45
50
|
rescue RuntimeError => e
|
@@ -60,6 +65,7 @@ module Bootsnap
|
|
60
65
|
crc = Zlib.crc32(option.inspect)
|
61
66
|
Bootsnap::CompileCache::Native.compile_option_crc32 = crc
|
62
67
|
end
|
68
|
+
compile_option_updated
|
63
69
|
|
64
70
|
def self.install!(cache_dir)
|
65
71
|
Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
|
@@ -5,56 +5,84 @@ module Bootsnap
|
|
5
5
|
module CompileCache
|
6
6
|
module YAML
|
7
7
|
class << self
|
8
|
-
attr_accessor(:msgpack_factory)
|
9
|
-
end
|
8
|
+
attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
10
|
+
def input_to_storage(contents, _, kwargs)
|
11
|
+
raise(Uncompilable) if contents.index("!ruby/object")
|
12
|
+
obj = ::YAML.load(contents, **(kwargs || {}))
|
13
|
+
msgpack_factory.dump(obj)
|
14
|
+
rescue NoMethodError, RangeError
|
15
|
+
# if the object included things that we can't serialize, fall back to
|
16
|
+
# Marshal. It's a bit slower, but can encode anything yaml can.
|
17
|
+
# NoMethodError is unexpected types; RangeError is Bignums
|
18
|
+
Marshal.dump(obj)
|
19
|
+
end
|
20
|
+
|
21
|
+
def storage_to_output(data, kwargs)
|
22
|
+
# This could have a meaning in messagepack, and we're being a little lazy
|
23
|
+
# about it. -- but a leading 0x04 would indicate the contents of the YAML
|
24
|
+
# is a positive integer, which is rare, to say the least.
|
25
|
+
if data[0] == 0x04.chr && data[1] == 0x08.chr
|
26
|
+
Marshal.load(data)
|
27
|
+
else
|
28
|
+
msgpack_factory.load(data, **(kwargs || {}))
|
29
|
+
end
|
30
|
+
end
|
21
31
|
|
22
|
-
|
23
|
-
|
24
|
-
# about it. -- but a leading 0x04 would indicate the contents of the YAML
|
25
|
-
# is a positive integer, which is rare, to say the least.
|
26
|
-
if data[0] == 0x04.chr && data[1] == 0x08.chr
|
27
|
-
Marshal.load(data)
|
28
|
-
else
|
29
|
-
msgpack_factory.unpacker.feed(data).read
|
32
|
+
def input_to_output(data, kwargs)
|
33
|
+
::YAML.load(data, **(kwargs || {}))
|
30
34
|
end
|
31
|
-
end
|
32
35
|
|
33
|
-
|
34
|
-
|
36
|
+
def install!(cache_dir)
|
37
|
+
self.cache_dir = cache_dir
|
38
|
+
init!
|
39
|
+
::YAML.singleton_class.prepend(Patch)
|
40
|
+
end
|
41
|
+
|
42
|
+
def init!
|
43
|
+
require('yaml')
|
44
|
+
require('msgpack')
|
45
|
+
|
46
|
+
# MessagePack serializes symbols as strings by default.
|
47
|
+
# We want them to roundtrip cleanly, so we use a custom factory.
|
48
|
+
# see: https://github.com/msgpack/msgpack-ruby/pull/122
|
49
|
+
factory = MessagePack::Factory.new
|
50
|
+
factory.register_type(0x00, Symbol)
|
51
|
+
self.msgpack_factory = factory
|
52
|
+
|
53
|
+
self.supported_options = []
|
54
|
+
params = ::YAML.method(:load).parameters
|
55
|
+
if params.include?([:key, :symbolize_names])
|
56
|
+
self.supported_options << :symbolize_names
|
57
|
+
end
|
58
|
+
if params.include?([:key, :freeze])
|
59
|
+
if factory.load(factory.dump('yaml'), freeze: true).frozen?
|
60
|
+
self.supported_options << :freeze
|
61
|
+
end
|
62
|
+
end
|
63
|
+
self.supported_options.freeze
|
64
|
+
end
|
35
65
|
end
|
36
66
|
|
37
|
-
|
38
|
-
|
39
|
-
require('msgpack')
|
67
|
+
module Patch
|
68
|
+
extend self
|
40
69
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
70
|
+
def load_file(path, *args)
|
71
|
+
return super if args.size > 1
|
72
|
+
if kwargs = args.first
|
73
|
+
return super unless kwargs.is_a?(Hash)
|
74
|
+
return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
|
75
|
+
end
|
47
76
|
|
48
|
-
klass = class << ::YAML; self; end
|
49
|
-
klass.send(:define_method, :load_file) do |path|
|
50
77
|
begin
|
51
|
-
Bootsnap::CompileCache::Native.fetch(
|
52
|
-
cache_dir,
|
78
|
+
::Bootsnap::CompileCache::Native.fetch(
|
79
|
+
Bootsnap::CompileCache::YAML.cache_dir,
|
53
80
|
path,
|
54
|
-
Bootsnap::CompileCache::YAML
|
81
|
+
::Bootsnap::CompileCache::YAML,
|
82
|
+
kwargs,
|
55
83
|
)
|
56
84
|
rescue Errno::EACCES
|
57
|
-
Bootsnap::CompileCache.permission_error(path)
|
85
|
+
::Bootsnap::CompileCache.permission_error(path)
|
58
86
|
end
|
59
87
|
end
|
60
88
|
end
|
@@ -46,7 +46,7 @@ module Bootsnap
|
|
46
46
|
# loadpath.
|
47
47
|
def find(feature)
|
48
48
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
49
|
-
feature = feature.to_s
|
49
|
+
feature = feature.to_s.freeze
|
50
50
|
return feature if absolute_path?(feature)
|
51
51
|
return expand_path(feature) if feature.start_with?('./')
|
52
52
|
@mutex.synchronize do
|
@@ -178,25 +178,25 @@ module Bootsnap
|
|
178
178
|
|
179
179
|
if DLEXT2
|
180
180
|
def search_index(f)
|
181
|
-
try_index(f
|
181
|
+
try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
|
182
182
|
end
|
183
183
|
|
184
184
|
def maybe_append_extension(f)
|
185
|
-
try_ext(f
|
185
|
+
try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
|
186
186
|
end
|
187
187
|
else
|
188
188
|
def search_index(f)
|
189
|
-
try_index(f
|
189
|
+
try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
|
190
190
|
end
|
191
191
|
|
192
192
|
def maybe_append_extension(f)
|
193
|
-
try_ext(f
|
193
|
+
try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
197
|
def try_index(f)
|
198
198
|
if (p = @index[f])
|
199
|
-
p
|
199
|
+
"#{p}/#{f}"
|
200
200
|
end
|
201
201
|
end
|
202
202
|
|
@@ -26,7 +26,7 @@ module Bootsnap
|
|
26
26
|
super
|
27
27
|
end
|
28
28
|
|
29
|
-
# uniq! keeps the first
|
29
|
+
# uniq! keeps the first occurrence of each path, otherwise preserving
|
30
30
|
# order, preserving the effective load path
|
31
31
|
def uniq!(*args)
|
32
32
|
ret = super
|
@@ -38,7 +38,11 @@ module Kernel
|
|
38
38
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
39
39
|
false
|
40
40
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
41
|
-
|
41
|
+
fallback = true
|
42
|
+
ensure
|
43
|
+
if fallback
|
44
|
+
require_with_bootsnap_lfi(path)
|
45
|
+
end
|
42
46
|
end
|
43
47
|
|
44
48
|
alias_method(:require_relative_without_bootsnap, :require_relative)
|
@@ -56,7 +60,7 @@ module Kernel
|
|
56
60
|
end
|
57
61
|
|
58
62
|
# load also allows relative paths from pwd even when not in $:
|
59
|
-
if File.exist?(relative = File.expand_path(path))
|
63
|
+
if File.exist?(relative = File.expand_path(path).freeze)
|
60
64
|
return load_without_bootsnap(relative, wrap)
|
61
65
|
end
|
62
66
|
|
@@ -67,7 +71,11 @@ module Kernel
|
|
67
71
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
68
72
|
false
|
69
73
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
70
|
-
|
74
|
+
fallback = true
|
75
|
+
ensure
|
76
|
+
if fallback
|
77
|
+
load_without_bootsnap(path, wrap)
|
78
|
+
end
|
71
79
|
end
|
72
80
|
end
|
73
81
|
|
@@ -88,6 +96,10 @@ class Module
|
|
88
96
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
89
97
|
false
|
90
98
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
91
|
-
|
99
|
+
fallback = true
|
100
|
+
ensure
|
101
|
+
if fallback
|
102
|
+
autoload_without_bootsnap(const, path)
|
103
|
+
end
|
92
104
|
end
|
93
105
|
end
|
@@ -99,7 +99,7 @@ module Bootsnap
|
|
99
99
|
altname = if extension_elidable?(short)
|
100
100
|
# Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
|
101
101
|
strip_extension_if_elidable(short)
|
102
|
-
elsif long && (ext = File.extname(long))
|
102
|
+
elsif long && (ext = File.extname(long.freeze))
|
103
103
|
# We already know the extension of the actual file this
|
104
104
|
# resolves to, so put that back on.
|
105
105
|
short + ext
|
@@ -129,7 +129,7 @@ module Bootsnap
|
|
129
129
|
# to name files in a way that assumes otherwise.
|
130
130
|
# (E.g. It's unlikely that someone will know that their code
|
131
131
|
# will _never_ run on MacOS, and therefore think they can get away
|
132
|
-
# with
|
132
|
+
# with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
|
133
133
|
#
|
134
134
|
# See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
|
135
135
|
def extension_elidable?(f)
|
@@ -21,7 +21,7 @@ module Bootsnap
|
|
21
21
|
attr_reader(:path)
|
22
22
|
|
23
23
|
def initialize(path)
|
24
|
-
@path = path.to_s
|
24
|
+
@path = path.to_s.freeze
|
25
25
|
end
|
26
26
|
|
27
27
|
# True if the path exists, but represents a non-directory object
|
@@ -60,7 +60,7 @@ module Bootsnap
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def expanded_path
|
63
|
-
File.expand_path(path)
|
63
|
+
File.expand_path(path).freeze
|
64
64
|
end
|
65
65
|
|
66
66
|
private
|
@@ -5,7 +5,6 @@ require_relative('../explicit_require')
|
|
5
5
|
module Bootsnap
|
6
6
|
module LoadPathCache
|
7
7
|
module PathScanner
|
8
|
-
ALL_FILES = "/{,**/*/**/}*"
|
9
8
|
REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
|
10
9
|
NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
|
11
10
|
ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
|
@@ -16,34 +15,48 @@ module Bootsnap
|
|
16
15
|
''
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
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 << relative_path
|
37
|
+
!contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
|
38
|
+
elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
|
39
|
+
requirables << relative_path
|
40
|
+
end
|
43
41
|
end
|
42
|
+
[requirables, dirs]
|
44
43
|
end
|
45
44
|
|
46
|
-
|
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 ? "#{relative_dir_path}/#{name}" : name.freeze
|
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
|
58
|
+
end
|
59
|
+
end
|
47
60
|
end
|
48
61
|
end
|
49
62
|
end
|
@@ -15,15 +15,15 @@ module Bootsnap
|
|
15
15
|
|
16
16
|
def realpath(caller_location, path)
|
17
17
|
base = File.dirname(caller_location)
|
18
|
-
|
19
|
-
|
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
|
-
|
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
|
@@ -64,7 +64,7 @@ module Bootsnap
|
|
64
64
|
def load_data
|
65
65
|
@data = begin
|
66
66
|
MessagePack.load(File.binread(@store_path))
|
67
|
-
# handle malformed data due to upgrade
|
67
|
+
# handle malformed data due to upgrade incompatibility
|
68
68
|
rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
|
69
69
|
{}
|
70
70
|
rescue ArgumentError => e
|
data/lib/bootsnap/version.rb
CHANGED