bootsnap 1.1.8 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|