bootsnap 1.3.0 → 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 +4 -4
- data/.travis.yml +13 -2
- data/CHANGELOG.md +22 -0
- data/README.jp.md +8 -6
- data/Rakefile +2 -2
- 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 +2 -0
- data/ext/bootsnap/extconf.rb +2 -1
- data/lib/bootsnap/bundler.rb +5 -3
- data/lib/bootsnap/compile_cache/iseq.rb +8 -8
- data/lib/bootsnap/compile_cache/yaml.rb +7 -7
- data/lib/bootsnap/compile_cache.rb +19 -4
- data/lib/bootsnap/explicit_require.rb +1 -1
- data/lib/bootsnap/load_path_cache/cache.rb +23 -22
- 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 +12 -62
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +7 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +29 -8
- 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/store.rb +8 -8
- data/lib/bootsnap/load_path_cache.rb +25 -12
- data/lib/bootsnap/setup.rb +7 -13
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +13 -6
- metadata +14 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40694ca2512ff7861584a4f9cee05e424c3d9319f3b36c7f4f45f9196d2d9f19
|
|
4
|
+
data.tar.gz: '087c4f2dafdd51a6a3d0a69d31aadabfe6ef90bf2ab12b861f2a288d7b16c04a'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ecbabde06c5f871702a000128e4dbc15d60fe0e16ab7adb4a8bb40b1c3b5b5f54b39c6927dc5aea9e49242d7e7f5deb596c42c44f8c2b8f998f835ec5b058b47
|
|
7
|
+
data.tar.gz: d566ffe7c3048f57c3c4fcd925d5e8ff5a722fc2622628e26571361e7a0fbdb5cbe8c724d45f2fc8ba09d0de614c5fad87f1875ecc99a08a34aab3afa7665fe1
|
data/.travis.yml
CHANGED
|
@@ -8,6 +8,17 @@ os:
|
|
|
8
8
|
rvm:
|
|
9
9
|
- ruby-2.4
|
|
10
10
|
- ruby-2.5
|
|
11
|
+
- ruby-head
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
matrix:
|
|
14
|
+
allow_failures:
|
|
15
|
+
- rvm: ruby-head
|
|
16
|
+
include:
|
|
17
|
+
- rvm: jruby
|
|
18
|
+
os: linux
|
|
19
|
+
env: MINIMAL_SUPPORT=1
|
|
20
|
+
- rvm: truffleruby
|
|
21
|
+
os: linux
|
|
22
|
+
env: MINIMAL_SUPPORT=1
|
|
23
|
+
|
|
24
|
+
script: bin/ci
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
# 1.4.0
|
|
2
|
+
|
|
3
|
+
* When running in development mode, always fall back to a full path scan on LoadError, making
|
|
4
|
+
bootsnap more able to detect newly-created files. (#230)
|
|
5
|
+
* Respect `$LOADED_FEATURES.delete` in order to support code reloading, for integration with
|
|
6
|
+
Zeitwerk. (#230)
|
|
7
|
+
* Minor performance improvement: flow-control exceptions no longer generate backtraces.
|
|
8
|
+
* Better support for requiring from environments where some features are not supported (especially
|
|
9
|
+
JRuby). (#226)k
|
|
10
|
+
* More robust handling of OS errors when creating files. (#225)
|
|
11
|
+
|
|
12
|
+
# 1.3.2
|
|
13
|
+
|
|
14
|
+
* Fix Spring + Bootsnap incompatibility when there are files with similar names.
|
|
15
|
+
* Fix `YAML.load_file` monkey patch to keep accepting File objects as arguments.
|
|
16
|
+
* Fix the API for `ActiveSupport::Dependencies#autoloadable_module?`.
|
|
17
|
+
* Some performance improvements.
|
|
18
|
+
|
|
19
|
+
# 1.3.1
|
|
20
|
+
|
|
21
|
+
* Change load path scanning to more correctly follow symlinks.
|
|
22
|
+
|
|
1
23
|
# 1.3.0
|
|
2
24
|
|
|
3
25
|
* Handle cases where load path entries are symlinked (https://github.com/Shopify/bootsnap/pull/136)
|
data/README.jp.md
CHANGED
|
@@ -12,7 +12,7 @@ Bootsnap は RubyVM におけるバイトコード生成やファイルルック
|
|
|
12
12
|
|
|
13
13
|
## 使用方法
|
|
14
14
|
|
|
15
|
-
この gem は
|
|
15
|
+
この gem は macOS と Linux で作動します。まずは、`bootsnap` を `Gemfile` に追加します:
|
|
16
16
|
|
|
17
17
|
```ruby
|
|
18
18
|
gem 'bootsnap', require: false
|
|
@@ -24,6 +24,8 @@ Rails を使用している場合は、以下のコードを、`config/boot.rb`
|
|
|
24
24
|
require 'bootsnap/setup'
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
単に `gem 'bootsnap', require: 'bootsnap/setup'` と指定することも技術的には可能ですが、最大限のパフォーマンス改善を得るためには Bootsnap をできるだけ早く読み込むことが重要です。
|
|
28
|
+
|
|
27
29
|
この require の仕組みは[こちら](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb)で確認できます。
|
|
28
30
|
|
|
29
31
|
Rails を使用していない場合、または、より多くの設定を変更したい場合は、以下のコードを `require 'bundler/setup'` の直後に追加してください(早く読み込まれるほど、より多くのものを最適化することができます)。
|
|
@@ -56,18 +58,18 @@ Bootsnapのすべての機能はセットアップ時の設定に従って開発
|
|
|
56
58
|
Bootsnap は、処理に時間のかかるメソッドの結果をキャッシュすることで最適化しています。これは、大きく分けて2つのカテゴリに分けられます。
|
|
57
59
|
|
|
58
60
|
* [Path Pre-Scanning](#path-pre-scanning)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
* `Kernel#require` と `Kernel#load` を `$LOAD_PATH` フルスキャンを行わないように変更します。
|
|
62
|
+
* `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` を `ActiveSupport::Dependencies.autoload_paths` のフルスキャンを行わないようにオーバーライドします。
|
|
61
63
|
* [Compilation caching](#compilation-caching)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
* Ruby バイトコードのコンパイル結果をキャッシュするためのメソッド `RubyVM::InstructionSequence.load_iseq` が実装されています。
|
|
65
|
+
* `YAML.load_file` を YAML オブジェクトのロード結果を MessagePack でキャッシュするように変更します。 MessagePack でサポートされていないタイプが使われている場合は Marshal が使われます。
|
|
64
66
|
|
|
65
67
|
### Path Pre-Scanning
|
|
66
68
|
|
|
67
69
|
_(このライブラリは [bootscale](https://github.com/byroot/bootscale) という別のライブラリを元に開発されました)_
|
|
68
70
|
|
|
69
71
|
Bootsnap の初期化時、あるいはパス(例えば、`$LOAD_PATH`)の変更時に、`Bootsnap::LoadPathCache` がキャッシュから必要なエントリーのリストを読み込みます。または、必要に応じてフルスキャンを実行し結果をキャッシュします。
|
|
70
|
-
その後、たとえば `require 'foo'` を評価する場合, Ruby は `$LOAD_PATH` `['x', 'y', ...]` のすべてのエントリーを繰り返し評価することで `x/foo.rb`, `y/foo.rb` などを探索します。これに対して Bootsnap は、キャッシュされた
|
|
72
|
+
その後、たとえば `require 'foo'` を評価する場合, Ruby は `$LOAD_PATH` `['x', 'y', ...]` のすべてのエントリーを繰り返し評価することで `x/foo.rb`, `y/foo.rb` などを探索します。これに対して Bootsnap は、キャッシュされた require 可能なファイルと `$LOAD_PATH` を見ることで、Rubyが最終的に選択するであろうパスで置き換えます。
|
|
71
73
|
|
|
72
74
|
この動作によって生成された syscall を見ると、最終的な結果は以前なら次のようになります。
|
|
73
75
|
|
data/Rakefile
CHANGED
data/bin/ci
ADDED
data/bin/console
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require("bundler/setup")
|
|
4
|
+
require("bootsnap")
|
|
5
5
|
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
|
@@ -10,5 +10,5 @@ require "bootsnap"
|
|
|
10
10
|
# require "pry"
|
|
11
11
|
# Pry.start
|
|
12
12
|
|
|
13
|
-
require
|
|
13
|
+
require("irb")
|
|
14
14
|
IRB.start(__FILE__)
|
data/bootsnap.gemspec
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
require
|
|
4
|
+
require('bootsnap/version')
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "bootsnap"
|
|
@@ -15,10 +15,16 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
spec.description = spec.summary
|
|
16
16
|
spec.homepage = "https://github.com/Shopify/bootsnap"
|
|
17
17
|
|
|
18
|
-
spec.
|
|
18
|
+
spec.metadata = {
|
|
19
|
+
'bug_tracker_uri' => 'https://github.com/Shopify/bootsnap/issues',
|
|
20
|
+
'changelog_uri' => 'https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md',
|
|
21
|
+
'source_code_uri' => 'https://github.com/Shopify/bootsnap',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
spec.files = %x(git ls-files -z).split("\x0").reject do |f|
|
|
19
25
|
f.match(%r{^(test|spec|features)/})
|
|
20
26
|
end
|
|
21
|
-
spec.require_paths =
|
|
27
|
+
spec.require_paths = %w(lib)
|
|
22
28
|
|
|
23
29
|
spec.required_ruby_version = '>= 2.0.0'
|
|
24
30
|
|
|
@@ -29,11 +35,11 @@ Gem::Specification.new do |spec|
|
|
|
29
35
|
spec.extensions = ['ext/bootsnap/extconf.rb']
|
|
30
36
|
end
|
|
31
37
|
|
|
32
|
-
spec.add_development_dependency
|
|
33
|
-
spec.add_development_dependency
|
|
34
|
-
spec.add_development_dependency
|
|
35
|
-
spec.add_development_dependency
|
|
36
|
-
spec.add_development_dependency
|
|
38
|
+
spec.add_development_dependency("bundler")
|
|
39
|
+
spec.add_development_dependency('rake', '~> 10.0')
|
|
40
|
+
spec.add_development_dependency('rake-compiler', '~> 0')
|
|
41
|
+
spec.add_development_dependency("minitest", "~> 5.0")
|
|
42
|
+
spec.add_development_dependency("mocha", "~> 1.2")
|
|
37
43
|
|
|
38
|
-
spec.add_runtime_dependency
|
|
44
|
+
spec.add_runtime_dependency("msgpack", "~> 1.0")
|
|
39
45
|
end
|
data/dev.yml
CHANGED
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -269,6 +269,8 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
|
269
269
|
static VALUE
|
|
270
270
|
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
271
271
|
{
|
|
272
|
+
FilePathValue(path_v);
|
|
273
|
+
|
|
272
274
|
Check_Type(cachedir_v, T_STRING);
|
|
273
275
|
Check_Type(path_v, T_STRING);
|
|
274
276
|
|
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/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
|
|
@@ -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
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative('../explicit_require')
|
|
2
2
|
|
|
3
3
|
module Bootsnap
|
|
4
4
|
module LoadPathCache
|
|
@@ -14,10 +14,10 @@ module Bootsnap
|
|
|
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
|
|
@@ -10,6 +10,14 @@ module Bootsnap
|
|
|
10
10
|
Thread.current[:without_bootsnap_cache] = prev
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def self.allow_bootsnap_retry(allowed)
|
|
14
|
+
prev = Thread.current[:without_bootsnap_retry] || false
|
|
15
|
+
Thread.current[:without_bootsnap_retry] = !allowed
|
|
16
|
+
yield
|
|
17
|
+
ensure
|
|
18
|
+
Thread.current[:without_bootsnap_retry] = prev
|
|
19
|
+
end
|
|
20
|
+
|
|
13
21
|
module ClassMethods
|
|
14
22
|
def autoload_paths=(o)
|
|
15
23
|
super
|
|
@@ -22,17 +30,25 @@ module Bootsnap
|
|
|
22
30
|
Bootsnap::LoadPathCache.autoload_paths_cache.find(path)
|
|
23
31
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
24
32
|
nil # doesn't really apply here
|
|
33
|
+
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
34
|
+
nil # doesn't really apply here
|
|
25
35
|
end
|
|
26
36
|
end
|
|
27
37
|
|
|
28
38
|
def autoloadable_module?(path_suffix)
|
|
29
|
-
Bootsnap::LoadPathCache.autoload_paths_cache.
|
|
39
|
+
Bootsnap::LoadPathCache.autoload_paths_cache.load_dir(path_suffix)
|
|
30
40
|
end
|
|
31
41
|
|
|
32
42
|
def remove_constant(const)
|
|
33
43
|
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
|
34
44
|
end
|
|
35
45
|
|
|
46
|
+
def require_or_load(*)
|
|
47
|
+
CoreExt::ActiveSupport.allow_bootsnap_retry(true) do
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
36
52
|
# If we can't find a constant using the patched implementation of
|
|
37
53
|
# search_for_file, try again with the default implementation.
|
|
38
54
|
#
|
|
@@ -40,8 +56,15 @@ module Bootsnap
|
|
|
40
56
|
# behaviour. The gymnastics here are a bit awkward, but it prevents
|
|
41
57
|
# 200+ lines of monkeypatches.
|
|
42
58
|
def load_missing_constant(from_mod, const_name)
|
|
43
|
-
|
|
59
|
+
CoreExt::ActiveSupport.allow_bootsnap_retry(false) do
|
|
60
|
+
super
|
|
61
|
+
end
|
|
44
62
|
rescue NameError => e
|
|
63
|
+
# This function can end up called recursively, we only want to
|
|
64
|
+
# retry at the top-level.
|
|
65
|
+
raise if Thread.current[:without_bootsnap_retry]
|
|
66
|
+
# If we already had cache disabled, there's no use retrying
|
|
67
|
+
raise if Thread.current[:without_bootsnap_cache]
|
|
45
68
|
# NoMethodError is a NameError, but we only want to handle actual
|
|
46
69
|
# NameError instances.
|
|
47
70
|
raise unless e.class == NameError
|
|
@@ -58,6 +81,8 @@ module Bootsnap
|
|
|
58
81
|
def depend_on(*)
|
|
59
82
|
super
|
|
60
83
|
rescue LoadError
|
|
84
|
+
# If we already had cache disabled, there's no use retrying
|
|
85
|
+
raise if Thread.current[:without_bootsnap_cache]
|
|
61
86
|
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
|
62
87
|
end
|
|
63
88
|
end
|
|
@@ -69,7 +94,7 @@ end
|
|
|
69
94
|
module ActiveSupport
|
|
70
95
|
module Dependencies
|
|
71
96
|
class << self
|
|
72
|
-
prepend
|
|
97
|
+
prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods)
|
|
73
98
|
end
|
|
74
99
|
end
|
|
75
100
|
end
|
|
@@ -11,9 +11,9 @@ module Bootsnap
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
module Kernel
|
|
14
|
-
|
|
14
|
+
module_function # rubocop:disable Style/ModuleFunction
|
|
15
15
|
|
|
16
|
-
alias_method
|
|
16
|
+
alias_method(:require_without_bootsnap, :require)
|
|
17
17
|
|
|
18
18
|
# Note that require registers to $LOADED_FEATURES while load does not.
|
|
19
19
|
def require_with_bootsnap_lfi(path, resolved = nil)
|
|
@@ -25,18 +25,18 @@ module Kernel
|
|
|
25
25
|
def require(path)
|
|
26
26
|
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(path)
|
|
27
27
|
|
|
28
|
-
if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
|
|
28
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
|
|
29
29
|
return require_with_bootsnap_lfi(path, resolved)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
raise
|
|
32
|
+
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
|
33
33
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
34
|
-
|
|
34
|
+
false
|
|
35
35
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
36
36
|
require_with_bootsnap_lfi(path)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
alias_method
|
|
39
|
+
alias_method(:require_relative_without_bootsnap, :require_relative)
|
|
40
40
|
def require_relative(path)
|
|
41
41
|
realpath = Bootsnap::LoadPathCache.realpath_cache.call(
|
|
42
42
|
caller_locations(1..1).first.absolute_path, path
|
|
@@ -44,9 +44,9 @@ module Kernel
|
|
|
44
44
|
require(realpath)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
alias_method
|
|
47
|
+
alias_method(:load_without_bootsnap, :load)
|
|
48
48
|
def load(path, wrap = false)
|
|
49
|
-
if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
|
|
49
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
|
|
50
50
|
return load_without_bootsnap(resolved, wrap)
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -55,66 +55,16 @@ module Kernel
|
|
|
55
55
|
return load_without_bootsnap(relative, wrap)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
raise
|
|
58
|
+
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
|
59
59
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
60
|
-
|
|
61
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
62
|
-
load_without_bootsnap(path, wrap)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
class << Kernel
|
|
67
|
-
alias_method :require_without_bootsnap, :require
|
|
68
|
-
|
|
69
|
-
def require_with_bootsnap_lfi(path, resolved = nil)
|
|
70
|
-
Bootsnap::LoadPathCache.loaded_features_index.register(path, resolved) do
|
|
71
|
-
require_without_bootsnap(resolved || path)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def require(path)
|
|
76
|
-
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(path)
|
|
77
|
-
|
|
78
|
-
if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
|
|
79
|
-
return require_with_bootsnap_lfi(path, resolved)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
|
|
83
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
84
|
-
return false
|
|
85
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
86
|
-
require_with_bootsnap_lfi(path)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
alias_method :require_relative_without_bootsnap, :require_relative
|
|
90
|
-
def require_relative(path)
|
|
91
|
-
realpath = Bootsnap::LoadPathCache.realpath_cache.call(
|
|
92
|
-
caller_locations(1..1).first.absolute_path, path
|
|
93
|
-
)
|
|
94
|
-
require(realpath)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
alias_method :load_without_bootsnap, :load
|
|
98
|
-
def load(path, wrap = false)
|
|
99
|
-
if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
|
|
100
|
-
return load_without_bootsnap(resolved, wrap)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# load also allows relative paths from pwd even when not in $:
|
|
104
|
-
if File.exist?(relative = File.expand_path(path))
|
|
105
|
-
return load_without_bootsnap(relative, wrap)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
|
|
109
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
110
|
-
return false
|
|
60
|
+
false
|
|
111
61
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
112
62
|
load_without_bootsnap(path, wrap)
|
|
113
63
|
end
|
|
114
64
|
end
|
|
115
65
|
|
|
116
66
|
class Module
|
|
117
|
-
alias_method
|
|
67
|
+
alias_method(:autoload_without_bootsnap, :autoload)
|
|
118
68
|
def autoload(const, path)
|
|
119
69
|
# NOTE: This may defeat LoadedFeaturesIndex, but it's not immediately
|
|
120
70
|
# obvious how to make it work. This feels like a pretty niche case, unclear
|
|
@@ -125,7 +75,7 @@ class Module
|
|
|
125
75
|
# since it's done in C-land.
|
|
126
76
|
autoload_without_bootsnap(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path)
|
|
127
77
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
|
128
|
-
|
|
78
|
+
false
|
|
129
79
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
|
130
80
|
autoload_without_bootsnap(const, path)
|
|
131
81
|
end
|
|
@@ -32,17 +32,29 @@ module Bootsnap
|
|
|
32
32
|
# parallel the work done with ChangeObserver on $LOAD_PATH to mirror
|
|
33
33
|
# updates to our @lfi.
|
|
34
34
|
$LOADED_FEATURES.each do |feat|
|
|
35
|
+
hash = feat.hash
|
|
35
36
|
$LOAD_PATH.each do |lpe|
|
|
36
37
|
next unless feat.start_with?(lpe)
|
|
37
38
|
# /a/b/lib/my/foo.rb
|
|
38
39
|
# ^^^^^^^^^
|
|
39
40
|
short = feat[(lpe.length + 1)..-1]
|
|
40
|
-
|
|
41
|
-
@lfi[
|
|
41
|
+
stripped = strip_extension(short)
|
|
42
|
+
@lfi[short] = hash
|
|
43
|
+
@lfi[stripped] = hash
|
|
42
44
|
end
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
47
|
|
|
48
|
+
# We've optimized for initialize and register to be fast, and purge to be tolerable.
|
|
49
|
+
# If access patterns make this not-okay, we can lazy-invert the LFI on
|
|
50
|
+
# first purge and work from there.
|
|
51
|
+
def purge(feature)
|
|
52
|
+
@mutex.synchronize do
|
|
53
|
+
feat_hash = feature.hash
|
|
54
|
+
@lfi.reject! { |_, hash| hash == feat_hash }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
46
58
|
def key?(feature)
|
|
47
59
|
@mutex.synchronize { @lfi.key?(feature) }
|
|
48
60
|
end
|
|
@@ -62,21 +74,30 @@ module Bootsnap
|
|
|
62
74
|
# 2. Inspect $LOADED_FEATURES upon return from yield to find the matching
|
|
63
75
|
# entry.
|
|
64
76
|
def register(short, long = nil)
|
|
65
|
-
|
|
77
|
+
if long.nil?
|
|
78
|
+
pat = %r{/#{Regexp.escape(short)}(\.[^/]+)?$}
|
|
79
|
+
len = $LOADED_FEATURES.size
|
|
80
|
+
ret = yield
|
|
81
|
+
long = $LOADED_FEATURES[len..-1].detect { |feat| feat =~ pat }
|
|
82
|
+
else
|
|
83
|
+
ret = yield
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
hash = long.hash
|
|
66
87
|
|
|
67
88
|
# do we have 'bundler' or 'bundler.rb'?
|
|
68
89
|
altname = if File.extname(short) != ''
|
|
69
90
|
# strip the path from 'bundler.rb' -> 'bundler'
|
|
70
91
|
strip_extension(short)
|
|
71
|
-
elsif long && ext = File.extname(long)
|
|
92
|
+
elsif long && (ext = File.extname(long))
|
|
72
93
|
# get the extension from the expanded path if given
|
|
73
94
|
# 'bundler' + '.rb'
|
|
74
95
|
short + ext
|
|
75
96
|
end
|
|
76
97
|
|
|
77
98
|
@mutex.synchronize do
|
|
78
|
-
@lfi[short] =
|
|
79
|
-
(@lfi[altname] =
|
|
99
|
+
@lfi[short] = hash
|
|
100
|
+
(@lfi[altname] = hash) if altname
|
|
80
101
|
end
|
|
81
102
|
|
|
82
103
|
ret
|
|
@@ -84,8 +105,8 @@ module Bootsnap
|
|
|
84
105
|
|
|
85
106
|
private
|
|
86
107
|
|
|
87
|
-
STRIP_EXTENSION =
|
|
88
|
-
private_constant
|
|
108
|
+
STRIP_EXTENSION = /\.[^.]*?$/
|
|
109
|
+
private_constant(:STRIP_EXTENSION)
|
|
89
110
|
|
|
90
111
|
def strip_extension(f)
|
|
91
112
|
f.sub(STRIP_EXTENSION, '')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative('path_scanner')
|
|
2
2
|
|
|
3
3
|
module Bootsnap
|
|
4
4
|
module LoadPathCache
|
|
@@ -17,7 +17,7 @@ module Bootsnap
|
|
|
17
17
|
stability == VOLATILE
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
attr_reader
|
|
20
|
+
attr_reader(:path)
|
|
21
21
|
|
|
22
22
|
def initialize(path)
|
|
23
23
|
@path = path.to_s
|
|
@@ -26,7 +26,7 @@ module Bootsnap
|
|
|
26
26
|
# True if the path exists, but represents a non-directory object
|
|
27
27
|
def non_directory?
|
|
28
28
|
!File.stat(path).directory?
|
|
29
|
-
rescue Errno::ENOENT
|
|
29
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
|
30
30
|
false
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -76,8 +76,8 @@ module Bootsnap
|
|
|
76
76
|
["", *dirs].each do |dir|
|
|
77
77
|
curr = begin
|
|
78
78
|
File.mtime("#{path}/#{dir}").to_i
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
|
80
|
+
-1
|
|
81
81
|
end
|
|
82
82
|
max = curr if curr > max
|
|
83
83
|
end
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative('../explicit_require')
|
|
2
2
|
|
|
3
3
|
module Bootsnap
|
|
4
4
|
module LoadPathCache
|
|
5
5
|
module PathScanner
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
# * `/*{.rb,.so,/}` - It matches requirable files, directories and
|
|
10
|
-
# symlinks to directories at given path.
|
|
11
|
-
# * `/*/**/*{.rb,.so,/}` - It matches requirable files and
|
|
12
|
-
# subdirectories in any (even symlinked) directory at given path at
|
|
13
|
-
# any directory tree depth.
|
|
14
|
-
#
|
|
15
|
-
REQUIRABLES_AND_DIRS = "/{,*/**/}*{#{DOT_RB},#{DL_EXTENSIONS.join(',')},/}"
|
|
6
|
+
ALL_FILES = "/{,**/*/**/}*"
|
|
7
|
+
REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
|
|
16
8
|
NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
|
|
17
9
|
ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
|
|
11
|
+
BUNDLE_PATH = if Bootsnap.bundler?
|
|
12
|
+
(Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
|
|
13
|
+
else
|
|
14
|
+
''.freeze
|
|
15
|
+
end
|
|
20
16
|
|
|
21
17
|
def self.call(path)
|
|
22
18
|
path = path.to_s
|
|
@@ -34,13 +30,13 @@ module Bootsnap
|
|
|
34
30
|
dirs = []
|
|
35
31
|
requirables = []
|
|
36
32
|
|
|
37
|
-
Dir.glob(path +
|
|
33
|
+
Dir.glob(path + ALL_FILES).each do |absolute_path|
|
|
38
34
|
next if contains_bundle_path && absolute_path.start_with?(BUNDLE_PATH)
|
|
39
|
-
relative_path = absolute_path.slice
|
|
35
|
+
relative_path = absolute_path.slice(relative_slice)
|
|
40
36
|
|
|
41
|
-
if
|
|
42
|
-
dirs << relative_path
|
|
43
|
-
|
|
37
|
+
if File.directory?(absolute_path)
|
|
38
|
+
dirs << relative_path
|
|
39
|
+
elsif REQUIRABLE_EXTENSIONS.include?(File.extname(relative_path))
|
|
44
40
|
requirables << relative_path
|
|
45
41
|
end
|
|
46
42
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative('../explicit_require')
|
|
2
2
|
|
|
3
|
-
Bootsnap::ExplicitRequire.with_gems('msgpack') { require
|
|
3
|
+
Bootsnap::ExplicitRequire.with_gems('msgpack') { require('msgpack') }
|
|
4
4
|
Bootsnap::ExplicitRequire.from_rubylibdir('fileutils')
|
|
5
5
|
|
|
6
6
|
module Bootsnap
|
|
@@ -21,7 +21,7 @@ module Bootsnap
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def fetch(key)
|
|
24
|
-
raise
|
|
24
|
+
raise(SetOutsideTransactionNotAllowed) unless @in_txn
|
|
25
25
|
v = get(key)
|
|
26
26
|
unless v
|
|
27
27
|
@dirty = true
|
|
@@ -32,7 +32,7 @@ module Bootsnap
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def set(key, value)
|
|
35
|
-
raise
|
|
35
|
+
raise(SetOutsideTransactionNotAllowed) unless @in_txn
|
|
36
36
|
if value != @data[key]
|
|
37
37
|
@dirty = true
|
|
38
38
|
@data[key] = value
|
|
@@ -40,7 +40,7 @@ module Bootsnap
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def transaction
|
|
43
|
-
raise
|
|
43
|
+
raise(NestedTransactionError) if @in_txn
|
|
44
44
|
@in_txn = true
|
|
45
45
|
yield
|
|
46
46
|
ensure
|
|
@@ -60,9 +60,9 @@ module Bootsnap
|
|
|
60
60
|
def load_data
|
|
61
61
|
@data = begin
|
|
62
62
|
MessagePack.load(File.binread(@store_path))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
# handle malformed data due to upgrade incompatability
|
|
64
|
+
rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
|
|
65
|
+
{}
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
@@ -21,38 +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
|
|
25
|
-
|
|
24
|
+
attr_reader(:load_path_cache, :autoload_paths_cache,
|
|
25
|
+
:loaded_features_index, :realpath_cache)
|
|
26
26
|
|
|
27
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
|
+
|
|
28
33
|
store = Store.new(cache_path)
|
|
29
34
|
|
|
30
35
|
@loaded_features_index = LoadedFeaturesIndex.new
|
|
31
36
|
@realpath_cache = RealpathCache.new
|
|
32
37
|
|
|
33
38
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
|
34
|
-
require_relative
|
|
39
|
+
require_relative('load_path_cache/core_ext/kernel_require')
|
|
40
|
+
require_relative('load_path_cache/core_ext/loaded_features')
|
|
35
41
|
|
|
36
42
|
if active_support
|
|
37
43
|
# this should happen after setting up the initial cache because it
|
|
38
44
|
# loads a lot of code. It's better to do after +require+ is optimized.
|
|
39
|
-
require
|
|
45
|
+
require('active_support/dependencies')
|
|
40
46
|
@autoload_paths_cache = Cache.new(
|
|
41
47
|
store,
|
|
42
48
|
::ActiveSupport::Dependencies.autoload_paths,
|
|
43
49
|
development_mode: development_mode
|
|
44
50
|
)
|
|
45
|
-
require_relative
|
|
51
|
+
require_relative('load_path_cache/core_ext/active_support')
|
|
46
52
|
end
|
|
47
53
|
end
|
|
54
|
+
|
|
55
|
+
def supported?
|
|
56
|
+
RUBY_ENGINE == 'ruby' &&
|
|
57
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd/
|
|
58
|
+
end
|
|
48
59
|
end
|
|
49
60
|
end
|
|
50
61
|
end
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
require_relative
|
|
54
|
-
require_relative
|
|
55
|
-
require_relative
|
|
56
|
-
require_relative
|
|
57
|
-
require_relative
|
|
58
|
-
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
|
data/lib/bootsnap/setup.rb
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative('../bootsnap')
|
|
2
2
|
|
|
3
3
|
env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
|
|
4
4
|
development_mode = ['', nil, 'development'].include?(env)
|
|
5
5
|
|
|
6
|
-
# only enable on 'ruby' (MRI), POSIX (darin, linux, *bsd), and >= 2.3.0
|
|
7
|
-
enable_cc =
|
|
8
|
-
RUBY_ENGINE == 'ruby' &&
|
|
9
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
|
|
10
|
-
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
|
11
|
-
|
|
12
6
|
cache_dir = ENV['BOOTSNAP_CACHE_DIR']
|
|
13
7
|
unless cache_dir
|
|
14
8
|
config_dir_frame = caller.detect do |line|
|
|
@@ -16,11 +10,11 @@ unless cache_dir
|
|
|
16
10
|
end
|
|
17
11
|
|
|
18
12
|
unless config_dir_frame
|
|
19
|
-
$stderr.puts
|
|
20
|
-
$stderr.puts
|
|
21
|
-
$stderr.puts
|
|
13
|
+
$stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
|
|
14
|
+
$stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
|
|
15
|
+
$stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
|
|
22
16
|
|
|
23
|
-
raise
|
|
17
|
+
raise("couldn't infer bootsnap cache directory")
|
|
24
18
|
end
|
|
25
19
|
|
|
26
20
|
path = config_dir_frame.split(/:\d+:/).first
|
|
@@ -36,6 +30,6 @@ Bootsnap.setup(
|
|
|
36
30
|
load_path_cache: true,
|
|
37
31
|
autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
|
|
38
32
|
disable_trace: false,
|
|
39
|
-
compile_cache_iseq:
|
|
40
|
-
compile_cache_yaml:
|
|
33
|
+
compile_cache_iseq: true,
|
|
34
|
+
compile_cache_yaml: true,
|
|
41
35
|
)
|
data/lib/bootsnap/version.rb
CHANGED
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
|
metadata
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bootsnap
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Burke Libbey
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2019-02-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '0'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rake
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -113,8 +113,10 @@ files:
|
|
|
113
113
|
- README.jp.md
|
|
114
114
|
- README.md
|
|
115
115
|
- Rakefile
|
|
116
|
+
- bin/ci
|
|
116
117
|
- bin/console
|
|
117
118
|
- bin/setup
|
|
119
|
+
- bin/test-minimal-support
|
|
118
120
|
- bin/testunit
|
|
119
121
|
- bootsnap.gemspec
|
|
120
122
|
- dev.yml
|
|
@@ -132,6 +134,7 @@ files:
|
|
|
132
134
|
- lib/bootsnap/load_path_cache/change_observer.rb
|
|
133
135
|
- lib/bootsnap/load_path_cache/core_ext/active_support.rb
|
|
134
136
|
- lib/bootsnap/load_path_cache/core_ext/kernel_require.rb
|
|
137
|
+
- lib/bootsnap/load_path_cache/core_ext/loaded_features.rb
|
|
135
138
|
- lib/bootsnap/load_path_cache/loaded_features_index.rb
|
|
136
139
|
- lib/bootsnap/load_path_cache/path.rb
|
|
137
140
|
- lib/bootsnap/load_path_cache/path_scanner.rb
|
|
@@ -143,7 +146,10 @@ files:
|
|
|
143
146
|
homepage: https://github.com/Shopify/bootsnap
|
|
144
147
|
licenses:
|
|
145
148
|
- MIT
|
|
146
|
-
metadata:
|
|
149
|
+
metadata:
|
|
150
|
+
bug_tracker_uri: https://github.com/Shopify/bootsnap/issues
|
|
151
|
+
changelog_uri: https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md
|
|
152
|
+
source_code_uri: https://github.com/Shopify/bootsnap
|
|
147
153
|
post_install_message:
|
|
148
154
|
rdoc_options: []
|
|
149
155
|
require_paths:
|
|
@@ -159,8 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
159
165
|
- !ruby/object:Gem::Version
|
|
160
166
|
version: '0'
|
|
161
167
|
requirements: []
|
|
162
|
-
|
|
163
|
-
rubygems_version: 2.7.6
|
|
168
|
+
rubygems_version: 3.0.2
|
|
164
169
|
signing_key:
|
|
165
170
|
specification_version: 4
|
|
166
171
|
summary: Boot large ruby/rails apps faster
|