bootsnap 1.1.8 → 1.4.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 +5 -5
- data/.travis.yml +23 -3
- data/CHANGELOG.md +35 -0
- data/README.jp.md +231 -0
- data/README.md +4 -1
- data/Rakefile +2 -1
- data/bin/ci +10 -0
- data/bin/console +3 -3
- data/bin/test-minimal-support +7 -0
- data/bootsnap.gemspec +15 -9
- data/dev.yml +1 -1
- data/ext/bootsnap/bootsnap.c +3 -22
- data/ext/bootsnap/extconf.rb +2 -1
- data/lib/bootsnap.rb +13 -6
- data/lib/bootsnap/bundler.rb +5 -3
- data/lib/bootsnap/compile_cache.rb +19 -4
- data/lib/bootsnap/compile_cache/iseq.rb +8 -8
- data/lib/bootsnap/compile_cache/yaml.rb +7 -7
- data/lib/bootsnap/explicit_require.rb +1 -1
- data/lib/bootsnap/load_path_cache.rb +28 -9
- data/lib/bootsnap/load_path_cache/cache.rb +24 -23
- data/lib/bootsnap/load_path_cache/change_observer.rb +34 -29
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +28 -3
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +46 -52
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +7 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +116 -0
- data/lib/bootsnap/load_path_cache/path.rb +5 -5
- data/lib/bootsnap/load_path_cache/path_scanner.rb +14 -18
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
- data/lib/bootsnap/load_path_cache/store.rb +8 -8
- data/lib/bootsnap/setup.rb +7 -13
- data/lib/bootsnap/version.rb +1 -1
- data/shipit.rubygems.yml +4 -0
- metadata +18 -9
data/ext/bootsnap/extconf.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require("mkmf")
|
2
2
|
$CFLAGS << ' -O3 '
|
3
3
|
$CFLAGS << ' -std=c99'
|
4
4
|
|
@@ -12,6 +12,7 @@ unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
|
|
12
12
|
|
13
13
|
$CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
|
14
14
|
$CFLAGS << ' -Wno-keyword-macro' # hiding return
|
15
|
+
$CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno
|
15
16
|
end
|
16
17
|
|
17
18
|
create_makefile("bootsnap/bootsnap")
|
data/lib/bootsnap.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
3
|
-
require_relative
|
4
|
-
require_relative
|
1
|
+
require_relative('bootsnap/version')
|
2
|
+
require_relative('bootsnap/bundler')
|
3
|
+
require_relative('bootsnap/load_path_cache')
|
4
|
+
require_relative('bootsnap/compile_cache')
|
5
5
|
|
6
6
|
module Bootsnap
|
7
7
|
InvalidConfiguration = Class.new(StandardError)
|
@@ -16,7 +16,7 @@ module Bootsnap
|
|
16
16
|
compile_cache_yaml: true
|
17
17
|
)
|
18
18
|
if autoload_paths_cache && !load_path_cache
|
19
|
-
raise
|
19
|
+
raise(InvalidConfiguration, "feature 'autoload_paths_cache' depends on feature 'load_path_cache'")
|
20
20
|
end
|
21
21
|
|
22
22
|
setup_disable_trace if disable_trace
|
@@ -35,6 +35,13 @@ module Bootsnap
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def self.setup_disable_trace
|
38
|
-
|
38
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
|
39
|
+
warn(
|
40
|
+
"from #{caller_locations(1, 1)[0]}: The 'disable_trace' method is not allowed with this Ruby version. " \
|
41
|
+
"current: #{RUBY_VERSION}, allowed version: < 2.5.0",
|
42
|
+
)
|
43
|
+
else
|
44
|
+
RubyVM::InstructionSequence.compile_option = { trace_instruction: false }
|
45
|
+
end
|
39
46
|
end
|
40
47
|
end
|
data/lib/bootsnap/bundler.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
module Bootsnap
|
2
|
-
|
2
|
+
extend(self)
|
3
3
|
|
4
4
|
def bundler?
|
5
|
+
return false unless defined?(::Bundler)
|
6
|
+
|
5
7
|
# Bundler environment variable
|
6
|
-
|
8
|
+
%w(BUNDLE_BIN_PATH BUNDLE_GEMFILE).each do |current|
|
7
9
|
return true if ENV.key?(current)
|
8
10
|
end
|
9
|
-
|
11
|
+
|
10
12
|
false
|
11
13
|
end
|
12
14
|
end
|
@@ -2,14 +2,29 @@ module Bootsnap
|
|
2
2
|
module CompileCache
|
3
3
|
def self.setup(cache_dir:, iseq:, yaml:)
|
4
4
|
if iseq
|
5
|
-
|
6
|
-
|
5
|
+
if supported?
|
6
|
+
require_relative('compile_cache/iseq')
|
7
|
+
Bootsnap::CompileCache::ISeq.install!(cache_dir)
|
8
|
+
elsif $VERBOSE
|
9
|
+
warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby")
|
10
|
+
end
|
7
11
|
end
|
8
12
|
|
9
13
|
if yaml
|
10
|
-
|
11
|
-
|
14
|
+
if supported?
|
15
|
+
require_relative('compile_cache/yaml')
|
16
|
+
Bootsnap::CompileCache::YAML.install!(cache_dir)
|
17
|
+
elsif $VERBOSE
|
18
|
+
warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
|
19
|
+
end
|
12
20
|
end
|
13
21
|
end
|
22
|
+
|
23
|
+
def self.supported?
|
24
|
+
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
|
25
|
+
RUBY_ENGINE == 'ruby' &&
|
26
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
|
27
|
+
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
28
|
+
end
|
14
29
|
end
|
15
30
|
end
|
@@ -1,25 +1,25 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require('bootsnap/bootsnap')
|
2
|
+
require('zlib')
|
3
3
|
|
4
4
|
module Bootsnap
|
5
5
|
module CompileCache
|
6
6
|
module ISeq
|
7
7
|
class << self
|
8
|
-
attr_accessor
|
8
|
+
attr_accessor(:cache_dir)
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.input_to_storage(_, path)
|
12
12
|
RubyVM::InstructionSequence.compile_file(path).to_binary
|
13
13
|
rescue SyntaxError
|
14
|
-
raise
|
14
|
+
raise(Uncompilable, 'syntax error')
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.storage_to_output(binary)
|
18
18
|
RubyVM::InstructionSequence.load_from_binary(binary)
|
19
19
|
rescue RuntimeError => e
|
20
20
|
if e.message == 'broken binary format'
|
21
|
-
STDERR.puts
|
22
|
-
|
21
|
+
STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
|
22
|
+
nil
|
23
23
|
else
|
24
24
|
raise
|
25
25
|
end
|
@@ -41,7 +41,7 @@ module Bootsnap
|
|
41
41
|
)
|
42
42
|
rescue RuntimeError => e
|
43
43
|
if e.message =~ /unmatched platform/
|
44
|
-
puts
|
44
|
+
puts("unmatched platform for file #{path}")
|
45
45
|
end
|
46
46
|
raise
|
47
47
|
end
|
@@ -62,7 +62,7 @@ module Bootsnap
|
|
62
62
|
Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
|
63
63
|
Bootsnap::CompileCache::ISeq.compile_option_updated
|
64
64
|
class << RubyVM::InstructionSequence
|
65
|
-
prepend
|
65
|
+
prepend(InstructionSequenceMixin)
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
@@ -1,21 +1,21 @@
|
|
1
|
-
require
|
1
|
+
require('bootsnap/bootsnap')
|
2
2
|
|
3
3
|
module Bootsnap
|
4
4
|
module CompileCache
|
5
5
|
module YAML
|
6
6
|
class << self
|
7
|
-
attr_accessor
|
7
|
+
attr_accessor(:msgpack_factory)
|
8
8
|
end
|
9
9
|
|
10
10
|
def self.input_to_storage(contents, _)
|
11
|
-
raise
|
11
|
+
raise(Uncompilable) if contents.index("!ruby/object")
|
12
12
|
obj = ::YAML.load(contents)
|
13
13
|
msgpack_factory.packer.write(obj).to_s
|
14
14
|
rescue NoMethodError, RangeError
|
15
15
|
# if the object included things that we can't serialize, fall back to
|
16
16
|
# Marshal. It's a bit slower, but can encode anything yaml can.
|
17
17
|
# NoMethodError is unexpected types; RangeError is Bignums
|
18
|
-
|
18
|
+
Marshal.dump(obj)
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.storage_to_output(data)
|
@@ -34,8 +34,8 @@ module Bootsnap
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def self.install!(cache_dir)
|
37
|
-
require
|
38
|
-
require
|
37
|
+
require('yaml')
|
38
|
+
require('msgpack')
|
39
39
|
|
40
40
|
# MessagePack serializes symbols as strings by default.
|
41
41
|
# We want them to roundtrip cleanly, so we use a custom factory.
|
@@ -48,7 +48,7 @@ module Bootsnap
|
|
48
48
|
klass.send(:define_method, :load_file) do |path|
|
49
49
|
Bootsnap::CompileCache::Native.fetch(
|
50
50
|
cache_dir,
|
51
|
-
path
|
51
|
+
path,
|
52
52
|
Bootsnap::CompileCache::YAML
|
53
53
|
)
|
54
54
|
end
|
@@ -21,32 +21,51 @@ module Bootsnap
|
|
21
21
|
CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
|
22
22
|
|
23
23
|
class << self
|
24
|
-
attr_reader
|
24
|
+
attr_reader(:load_path_cache, :autoload_paths_cache,
|
25
|
+
:loaded_features_index, :realpath_cache)
|
25
26
|
|
26
27
|
def setup(cache_path:, development_mode:, active_support: true)
|
28
|
+
unless supported?
|
29
|
+
warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
27
33
|
store = Store.new(cache_path)
|
28
34
|
|
35
|
+
@loaded_features_index = LoadedFeaturesIndex.new
|
36
|
+
@realpath_cache = RealpathCache.new
|
37
|
+
|
29
38
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
30
|
-
require_relative
|
39
|
+
require_relative('load_path_cache/core_ext/kernel_require')
|
40
|
+
require_relative('load_path_cache/core_ext/loaded_features')
|
31
41
|
|
32
42
|
if active_support
|
33
43
|
# this should happen after setting up the initial cache because it
|
34
44
|
# loads a lot of code. It's better to do after +require+ is optimized.
|
35
|
-
require
|
45
|
+
require('active_support/dependencies')
|
36
46
|
@autoload_paths_cache = Cache.new(
|
37
47
|
store,
|
38
48
|
::ActiveSupport::Dependencies.autoload_paths,
|
39
49
|
development_mode: development_mode
|
40
50
|
)
|
41
|
-
require_relative
|
51
|
+
require_relative('load_path_cache/core_ext/active_support')
|
42
52
|
end
|
43
53
|
end
|
54
|
+
|
55
|
+
def supported?
|
56
|
+
RUBY_ENGINE == 'ruby' &&
|
57
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd/
|
58
|
+
end
|
44
59
|
end
|
45
60
|
end
|
46
61
|
end
|
47
62
|
|
48
|
-
|
49
|
-
require_relative
|
50
|
-
require_relative
|
51
|
-
require_relative
|
52
|
-
require_relative
|
63
|
+
if Bootsnap::LoadPathCache.supported?
|
64
|
+
require_relative('load_path_cache/path_scanner')
|
65
|
+
require_relative('load_path_cache/path')
|
66
|
+
require_relative('load_path_cache/cache')
|
67
|
+
require_relative('load_path_cache/store')
|
68
|
+
require_relative('load_path_cache/change_observer')
|
69
|
+
require_relative('load_path_cache/loaded_features_index')
|
70
|
+
require_relative('load_path_cache/realpath_cache')
|
71
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative('../explicit_require')
|
2
2
|
|
3
3
|
module Bootsnap
|
4
4
|
module LoadPathCache
|
@@ -9,15 +9,15 @@ module Bootsnap
|
|
9
9
|
@development_mode = development_mode
|
10
10
|
@store = store
|
11
11
|
@mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
|
12
|
-
@path_obj = path_obj
|
12
|
+
@path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
|
13
13
|
@has_relative_paths = nil
|
14
14
|
reinitialize
|
15
15
|
end
|
16
16
|
|
17
|
-
#
|
18
|
-
# e.g. given "/a/b/c/d" exists, and the path is ["/a/b"],
|
19
|
-
# is
|
20
|
-
def
|
17
|
+
# What is the path item that contains the dir as child?
|
18
|
+
# e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], load_dir("c/d")
|
19
|
+
# is "/a/b".
|
20
|
+
def load_dir(dir)
|
21
21
|
reinitialize if stale?
|
22
22
|
@mutex.synchronize { @dirs[dir] }
|
23
23
|
end
|
@@ -56,7 +56,7 @@ module Bootsnap
|
|
56
56
|
# returns false as if it were already loaded; however, there is no
|
57
57
|
# file to find on disk. We've pre-built a list of these, and we
|
58
58
|
# return false if any of them is loaded.
|
59
|
-
raise
|
59
|
+
raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
|
60
60
|
|
61
61
|
# The feature wasn't found on our preliminary search through the index.
|
62
62
|
# We resolve this differently depending on what the extension was.
|
@@ -73,14 +73,21 @@ module Bootsnap
|
|
73
73
|
x = search_index(feature[0..-4] + DLEXT)
|
74
74
|
return x if x
|
75
75
|
if DLEXT2
|
76
|
-
search_index(feature[0..-4] + DLEXT2)
|
76
|
+
x = search_index(feature[0..-4] + DLEXT2)
|
77
|
+
return x if x
|
77
78
|
end
|
78
79
|
else
|
79
80
|
# other, unknown extension. For example, `.rake`. Since we haven't
|
80
81
|
# cached these, we legitimately need to run the load path search.
|
81
|
-
raise
|
82
|
+
raise(LoadPathCache::FallbackScan, '', [])
|
82
83
|
end
|
83
84
|
end
|
85
|
+
|
86
|
+
# In development mode, we don't want to confidently return failures for
|
87
|
+
# cases where the file doesn't appear to be on the load path. We should
|
88
|
+
# be able to detect newly-created files without rebooting the
|
89
|
+
# application.
|
90
|
+
raise(LoadPathCache::FallbackScan, '', []) if @development_mode
|
84
91
|
end
|
85
92
|
|
86
93
|
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
@@ -103,20 +110,12 @@ module Bootsnap
|
|
103
110
|
@mutex.synchronize { push_paths_locked(*paths) }
|
104
111
|
end
|
105
112
|
|
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
113
|
def reinitialize(path_obj = @path_obj)
|
115
114
|
@mutex.synchronize do
|
116
115
|
@path_obj = path_obj
|
117
116
|
ChangeObserver.register(self, @path_obj)
|
118
117
|
@index = {}
|
119
|
-
@dirs =
|
118
|
+
@dirs = {}
|
120
119
|
@generated_at = now
|
121
120
|
push_paths_locked(*@path_obj)
|
122
121
|
end
|
@@ -140,10 +139,11 @@ module Bootsnap
|
|
140
139
|
p = Path.new(path)
|
141
140
|
@has_relative_paths = true if p.relative?
|
142
141
|
next if p.non_directory?
|
142
|
+
expanded_path = p.expanded_path
|
143
143
|
entries, dirs = p.entries_and_dirs(@store)
|
144
144
|
# push -> low precedence -> set only if unset
|
145
|
-
dirs.each { |dir| @dirs[dir] ||=
|
146
|
-
entries.each { |rel| @index[rel] ||=
|
145
|
+
dirs.each { |dir| @dirs[dir] ||= path }
|
146
|
+
entries.each { |rel| @index[rel] ||= expanded_path }
|
147
147
|
end
|
148
148
|
end
|
149
149
|
end
|
@@ -153,10 +153,11 @@ module Bootsnap
|
|
153
153
|
paths.map(&:to_s).reverse_each do |path|
|
154
154
|
p = Path.new(path)
|
155
155
|
next if p.non_directory?
|
156
|
+
expanded_path = p.expanded_path
|
156
157
|
entries, dirs = p.entries_and_dirs(@store)
|
157
158
|
# unshift -> high precedence -> unconditional set
|
158
|
-
dirs.each { |dir| @dirs[dir] =
|
159
|
-
entries.each { |rel| @index[rel] =
|
159
|
+
dirs.each { |dir| @dirs[dir] = path }
|
160
|
+
entries.each { |rel| @index[rel] = expanded_path }
|
160
161
|
end
|
161
162
|
end
|
162
163
|
end
|
@@ -180,7 +181,7 @@ module Bootsnap
|
|
180
181
|
end
|
181
182
|
|
182
183
|
def try_index(f)
|
183
|
-
if p = @index[f]
|
184
|
+
if (p = @index[f])
|
184
185
|
p + '/' + f
|
185
186
|
end
|
186
187
|
end
|
@@ -1,37 +1,36 @@
|
|
1
1
|
module Bootsnap
|
2
2
|
module LoadPathCache
|
3
3
|
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
|
-
|
4
|
+
module ArrayMixin
|
9
5
|
# For each method that adds items to one end or another of the array
|
10
6
|
# (<<, push, unshift, concat), override that method to also notify the
|
11
7
|
# observer of the change.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
def <<(entry)
|
9
|
+
@lpc_observer.push_paths(self, entry.to_s)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(*entries)
|
14
|
+
@lpc_observer.push_paths(self, *entries.map(&:to_s))
|
15
|
+
super
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
push_without_lpc(*entries)
|
18
|
+
def unshift(*entries)
|
19
|
+
@lpc_observer.unshift_paths(self, *entries.map(&:to_s))
|
20
|
+
super
|
23
21
|
end
|
24
22
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
unshift_without_lpc(*entries)
|
23
|
+
def concat(entries)
|
24
|
+
@lpc_observer.push_paths(self, *entries.map(&:to_s))
|
25
|
+
super
|
29
26
|
end
|
30
27
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
# uniq! keeps the first occurance of each path, otherwise preserving
|
29
|
+
# order, preserving the effective load path
|
30
|
+
def uniq!(*args)
|
31
|
+
ret = super
|
32
|
+
@lpc_observer.reinitialize if block_given? || !args.empty?
|
33
|
+
ret
|
35
34
|
end
|
36
35
|
|
37
36
|
# For each method that modifies the array more aggressively, override
|
@@ -41,16 +40,22 @@ module Bootsnap
|
|
41
40
|
# accounting cost would be greater than the hit from these, since we
|
42
41
|
# actively discourage calling them.
|
43
42
|
%i(
|
44
|
-
collect! compact! delete delete_at delete_if fill flatten!
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
43
|
+
[]= clear collect! compact! delete delete_at delete_if fill flatten!
|
44
|
+
insert keep_if map! pop reject! replace reverse! rotate! select!
|
45
|
+
shift shuffle! slice! sort! sort_by!
|
46
|
+
).each do |method_name|
|
47
|
+
define_method(method_name) do |*args, &block|
|
48
|
+
ret = super(*args, &block)
|
49
|
+
@lpc_observer.reinitialize
|
50
|
+
ret
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
54
|
+
|
55
|
+
def self.register(observer, arr)
|
56
|
+
arr.instance_variable_set(:@lpc_observer, observer)
|
57
|
+
arr.extend(ArrayMixin)
|
58
|
+
end
|
54
59
|
end
|
55
60
|
end
|
56
61
|
end
|