bootsnap 1.4.6
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 +7 -0
- data/.github/CODEOWNERS +2 -0
- data/.github/probots.yml +2 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +20 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +122 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +21 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.jp.md +231 -0
- data/README.md +304 -0
- data/Rakefile +13 -0
- data/bin/ci +10 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/test-minimal-support +7 -0
- data/bin/testunit +8 -0
- data/bootsnap.gemspec +46 -0
- data/dev.yml +10 -0
- data/ext/bootsnap/bootsnap.c +829 -0
- data/ext/bootsnap/bootsnap.h +6 -0
- data/ext/bootsnap/extconf.rb +19 -0
- data/lib/bootsnap.rb +48 -0
- data/lib/bootsnap/bundler.rb +15 -0
- data/lib/bootsnap/compile_cache.rb +43 -0
- data/lib/bootsnap/compile_cache/iseq.rb +73 -0
- data/lib/bootsnap/compile_cache/yaml.rb +63 -0
- data/lib/bootsnap/explicit_require.rb +50 -0
- data/lib/bootsnap/load_path_cache.rb +78 -0
- data/lib/bootsnap/load_path_cache/cache.rb +208 -0
- data/lib/bootsnap/load_path_cache/change_observer.rb +63 -0
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +107 -0
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +93 -0
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +18 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +148 -0
- data/lib/bootsnap/load_path_cache/path.rb +114 -0
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -0
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
- data/lib/bootsnap/load_path_cache/store.rb +90 -0
- data/lib/bootsnap/setup.rb +39 -0
- data/lib/bootsnap/version.rb +4 -0
- data/shipit.rubygems.yml +0 -0
- metadata +174 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require("mkmf")
|
3
|
+
$CFLAGS << ' -O3 '
|
4
|
+
$CFLAGS << ' -std=c99'
|
5
|
+
|
6
|
+
# ruby.h has some -Wpedantic fails in some cases
|
7
|
+
# (e.g. https://github.com/Shopify/bootsnap/issues/15)
|
8
|
+
unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
|
9
|
+
$CFLAGS << ' -Wall'
|
10
|
+
$CFLAGS << ' -Werror'
|
11
|
+
$CFLAGS << ' -Wextra'
|
12
|
+
$CFLAGS << ' -Wpedantic'
|
13
|
+
|
14
|
+
$CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
|
15
|
+
$CFLAGS << ' -Wno-keyword-macro' # hiding return
|
16
|
+
$CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno
|
17
|
+
end
|
18
|
+
|
19
|
+
create_makefile("bootsnap/bootsnap")
|
data/lib/bootsnap.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative('bootsnap/version')
|
3
|
+
require_relative('bootsnap/bundler')
|
4
|
+
require_relative('bootsnap/load_path_cache')
|
5
|
+
require_relative('bootsnap/compile_cache')
|
6
|
+
|
7
|
+
module Bootsnap
|
8
|
+
InvalidConfiguration = Class.new(StandardError)
|
9
|
+
|
10
|
+
def self.setup(
|
11
|
+
cache_dir:,
|
12
|
+
development_mode: true,
|
13
|
+
load_path_cache: true,
|
14
|
+
autoload_paths_cache: true,
|
15
|
+
disable_trace: false,
|
16
|
+
compile_cache_iseq: true,
|
17
|
+
compile_cache_yaml: true
|
18
|
+
)
|
19
|
+
if autoload_paths_cache && !load_path_cache
|
20
|
+
raise(InvalidConfiguration, "feature 'autoload_paths_cache' depends on feature 'load_path_cache'")
|
21
|
+
end
|
22
|
+
|
23
|
+
setup_disable_trace if disable_trace
|
24
|
+
|
25
|
+
Bootsnap::LoadPathCache.setup(
|
26
|
+
cache_path: cache_dir + '/bootsnap-load-path-cache',
|
27
|
+
development_mode: development_mode,
|
28
|
+
active_support: autoload_paths_cache
|
29
|
+
) if load_path_cache
|
30
|
+
|
31
|
+
Bootsnap::CompileCache.setup(
|
32
|
+
cache_dir: cache_dir + '/bootsnap-compile-cache',
|
33
|
+
iseq: compile_cache_iseq,
|
34
|
+
yaml: compile_cache_yaml
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.setup_disable_trace
|
39
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
|
40
|
+
warn(
|
41
|
+
"from #{caller_locations(1, 1)[0]}: The 'disable_trace' method is not allowed with this Ruby version. " \
|
42
|
+
"current: #{RUBY_VERSION}, allowed version: < 2.5.0",
|
43
|
+
)
|
44
|
+
else
|
45
|
+
RubyVM::InstructionSequence.compile_option = { trace_instruction: false }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Bootsnap
|
3
|
+
extend(self)
|
4
|
+
|
5
|
+
def bundler?
|
6
|
+
return false unless defined?(::Bundler)
|
7
|
+
|
8
|
+
# Bundler environment variable
|
9
|
+
%w(BUNDLE_BIN_PATH BUNDLE_GEMFILE).each do |current|
|
10
|
+
return true if ENV.key?(current)
|
11
|
+
end
|
12
|
+
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Bootsnap
|
3
|
+
module CompileCache
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
PermissionError = Class.new(Error)
|
6
|
+
|
7
|
+
def self.setup(cache_dir:, iseq:, yaml:)
|
8
|
+
if iseq
|
9
|
+
if supported?
|
10
|
+
require_relative('compile_cache/iseq')
|
11
|
+
Bootsnap::CompileCache::ISeq.install!(cache_dir)
|
12
|
+
elsif $VERBOSE
|
13
|
+
warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
if yaml
|
18
|
+
if supported?
|
19
|
+
require_relative('compile_cache/yaml')
|
20
|
+
Bootsnap::CompileCache::YAML.install!(cache_dir)
|
21
|
+
elsif $VERBOSE
|
22
|
+
warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.permission_error(path)
|
28
|
+
cpath = Bootsnap::CompileCache::ISeq.cache_dir
|
29
|
+
raise(
|
30
|
+
PermissionError,
|
31
|
+
"bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
|
32
|
+
"(or, less likely, doesn't have permission to read '#{path}')",
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.supported?
|
37
|
+
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
|
38
|
+
RUBY_ENGINE == 'ruby' &&
|
39
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
|
40
|
+
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require('bootsnap/bootsnap')
|
3
|
+
require('zlib')
|
4
|
+
|
5
|
+
module Bootsnap
|
6
|
+
module CompileCache
|
7
|
+
module ISeq
|
8
|
+
class << self
|
9
|
+
attr_accessor(:cache_dir)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.input_to_storage(_, path)
|
13
|
+
RubyVM::InstructionSequence.compile_file(path).to_binary
|
14
|
+
rescue SyntaxError
|
15
|
+
raise(Uncompilable, 'syntax error')
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.storage_to_output(binary)
|
19
|
+
RubyVM::InstructionSequence.load_from_binary(binary)
|
20
|
+
rescue RuntimeError => e
|
21
|
+
if e.message == 'broken binary format'
|
22
|
+
STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.input_to_output(_)
|
30
|
+
nil # ruby handles this
|
31
|
+
end
|
32
|
+
|
33
|
+
module InstructionSequenceMixin
|
34
|
+
def load_iseq(path)
|
35
|
+
# Having coverage enabled prevents iseq dumping/loading.
|
36
|
+
return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
|
37
|
+
|
38
|
+
Bootsnap::CompileCache::Native.fetch(
|
39
|
+
Bootsnap::CompileCache::ISeq.cache_dir,
|
40
|
+
path.to_s,
|
41
|
+
Bootsnap::CompileCache::ISeq
|
42
|
+
)
|
43
|
+
rescue Errno::EACCES
|
44
|
+
Bootsnap::CompileCache.permission_error(path)
|
45
|
+
rescue RuntimeError => e
|
46
|
+
if e.message =~ /unmatched platform/
|
47
|
+
puts("unmatched platform for file #{path}")
|
48
|
+
end
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
|
52
|
+
def compile_option=(hash)
|
53
|
+
super(hash)
|
54
|
+
Bootsnap::CompileCache::ISeq.compile_option_updated
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.compile_option_updated
|
59
|
+
option = RubyVM::InstructionSequence.compile_option
|
60
|
+
crc = Zlib.crc32(option.inspect)
|
61
|
+
Bootsnap::CompileCache::Native.compile_option_crc32 = crc
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.install!(cache_dir)
|
65
|
+
Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
|
66
|
+
Bootsnap::CompileCache::ISeq.compile_option_updated
|
67
|
+
class << RubyVM::InstructionSequence
|
68
|
+
prepend(InstructionSequenceMixin)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require('bootsnap/bootsnap')
|
3
|
+
|
4
|
+
module Bootsnap
|
5
|
+
module CompileCache
|
6
|
+
module YAML
|
7
|
+
class << self
|
8
|
+
attr_accessor(:msgpack_factory)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.input_to_storage(contents, _)
|
12
|
+
raise(Uncompilable) if contents.index("!ruby/object")
|
13
|
+
obj = ::YAML.load(contents)
|
14
|
+
msgpack_factory.packer.write(obj).to_s
|
15
|
+
rescue NoMethodError, RangeError
|
16
|
+
# if the object included things that we can't serialize, fall back to
|
17
|
+
# Marshal. It's a bit slower, but can encode anything yaml can.
|
18
|
+
# NoMethodError is unexpected types; RangeError is Bignums
|
19
|
+
Marshal.dump(obj)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.storage_to_output(data)
|
23
|
+
# This could have a meaning in messagepack, and we're being a little lazy
|
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
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.input_to_output(data)
|
34
|
+
::YAML.load(data)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.install!(cache_dir)
|
38
|
+
require('yaml')
|
39
|
+
require('msgpack')
|
40
|
+
|
41
|
+
# MessagePack serializes symbols as strings by default.
|
42
|
+
# We want them to roundtrip cleanly, so we use a custom factory.
|
43
|
+
# see: https://github.com/msgpack/msgpack-ruby/pull/122
|
44
|
+
factory = MessagePack::Factory.new
|
45
|
+
factory.register_type(0x00, Symbol)
|
46
|
+
Bootsnap::CompileCache::YAML.msgpack_factory = factory
|
47
|
+
|
48
|
+
klass = class << ::YAML; self; end
|
49
|
+
klass.send(:define_method, :load_file) do |path|
|
50
|
+
begin
|
51
|
+
Bootsnap::CompileCache::Native.fetch(
|
52
|
+
cache_dir,
|
53
|
+
path,
|
54
|
+
Bootsnap::CompileCache::YAML
|
55
|
+
)
|
56
|
+
rescue Errno::EACCES
|
57
|
+
Bootsnap::CompileCache.permission_error(path)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Bootsnap
|
3
|
+
module ExplicitRequire
|
4
|
+
ARCHDIR = RbConfig::CONFIG['archdir']
|
5
|
+
RUBYLIBDIR = RbConfig::CONFIG['rubylibdir']
|
6
|
+
DLEXT = RbConfig::CONFIG['DLEXT']
|
7
|
+
|
8
|
+
def self.from_self(feature)
|
9
|
+
require_relative("../#{feature}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from_rubylibdir(feature)
|
13
|
+
require(File.join(RUBYLIBDIR, "#{feature}.rb"))
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_archdir(feature)
|
17
|
+
require(File.join(ARCHDIR, "#{feature}.#{DLEXT}"))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Given a set of gems, run a block with the LOAD_PATH narrowed to include
|
21
|
+
# only core ruby source paths and these gems -- that is, roughly,
|
22
|
+
# temporarily remove all gems not listed in this call from the LOAD_PATH.
|
23
|
+
#
|
24
|
+
# This is useful before bootsnap is fully-initialized to load gems that it
|
25
|
+
# depends on, without forcing full LOAD_PATH traversals.
|
26
|
+
def self.with_gems(*gems)
|
27
|
+
orig = $LOAD_PATH.dup
|
28
|
+
$LOAD_PATH.clear
|
29
|
+
gems.each do |gem|
|
30
|
+
pat = %r{
|
31
|
+
/
|
32
|
+
(gems|extensions/[^/]+/[^/]+) # "gems" or "extensions/x64_64-darwin16/2.3.0"
|
33
|
+
/
|
34
|
+
#{Regexp.escape(gem)}-(\h{12}|(\d+\.)) # msgpack-1.2.3 or msgpack-1234567890ab
|
35
|
+
}x
|
36
|
+
$LOAD_PATH.concat(orig.grep(pat))
|
37
|
+
end
|
38
|
+
$LOAD_PATH << ARCHDIR
|
39
|
+
$LOAD_PATH << RUBYLIBDIR
|
40
|
+
begin
|
41
|
+
yield
|
42
|
+
rescue LoadError
|
43
|
+
$LOAD_PATH.replace(orig)
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
ensure
|
47
|
+
$LOAD_PATH.replace(orig)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bootsnap
|
4
|
+
module LoadPathCache
|
5
|
+
ReturnFalse = Class.new(StandardError)
|
6
|
+
FallbackScan = Class.new(StandardError)
|
7
|
+
|
8
|
+
DOT_RB = '.rb'
|
9
|
+
DOT_SO = '.so'
|
10
|
+
SLASH = '/'
|
11
|
+
|
12
|
+
# If a NameError happens several levels deep, don't re-handle it
|
13
|
+
# all the way up the chain: mark it once and bubble it up without
|
14
|
+
# more retries.
|
15
|
+
ERROR_TAG_IVAR = :@__bootsnap_rescued
|
16
|
+
|
17
|
+
DL_EXTENSIONS = ::RbConfig::CONFIG
|
18
|
+
.values_at('DLEXT', 'DLEXT2')
|
19
|
+
.reject { |ext| !ext || ext.empty? }
|
20
|
+
.map { |ext| ".#{ext}" }
|
21
|
+
.freeze
|
22
|
+
DLEXT = DL_EXTENSIONS[0]
|
23
|
+
# This is nil on linux and darwin, but I think it's '.o' on some other
|
24
|
+
# platform. I'm not really sure which, but it seems better to replicate
|
25
|
+
# ruby's semantics as faithfully as possible.
|
26
|
+
DLEXT2 = DL_EXTENSIONS[1]
|
27
|
+
|
28
|
+
CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
|
29
|
+
|
30
|
+
class << self
|
31
|
+
attr_reader(:load_path_cache, :autoload_paths_cache,
|
32
|
+
:loaded_features_index, :realpath_cache)
|
33
|
+
|
34
|
+
def setup(cache_path:, development_mode:, active_support: true)
|
35
|
+
unless supported?
|
36
|
+
warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
store = Store.new(cache_path)
|
41
|
+
|
42
|
+
@loaded_features_index = LoadedFeaturesIndex.new
|
43
|
+
@realpath_cache = RealpathCache.new
|
44
|
+
|
45
|
+
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
46
|
+
require_relative('load_path_cache/core_ext/kernel_require')
|
47
|
+
require_relative('load_path_cache/core_ext/loaded_features')
|
48
|
+
|
49
|
+
if active_support
|
50
|
+
# this should happen after setting up the initial cache because it
|
51
|
+
# loads a lot of code. It's better to do after +require+ is optimized.
|
52
|
+
require('active_support/dependencies')
|
53
|
+
@autoload_paths_cache = Cache.new(
|
54
|
+
store,
|
55
|
+
::ActiveSupport::Dependencies.autoload_paths,
|
56
|
+
development_mode: development_mode
|
57
|
+
)
|
58
|
+
require_relative('load_path_cache/core_ext/active_support')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def supported?
|
63
|
+
RUBY_ENGINE == 'ruby' &&
|
64
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd/
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if Bootsnap::LoadPathCache.supported?
|
71
|
+
require_relative('load_path_cache/path_scanner')
|
72
|
+
require_relative('load_path_cache/path')
|
73
|
+
require_relative('load_path_cache/cache')
|
74
|
+
require_relative('load_path_cache/store')
|
75
|
+
require_relative('load_path_cache/change_observer')
|
76
|
+
require_relative('load_path_cache/loaded_features_index')
|
77
|
+
require_relative('load_path_cache/realpath_cache')
|
78
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative('../explicit_require')
|
4
|
+
|
5
|
+
module Bootsnap
|
6
|
+
module LoadPathCache
|
7
|
+
class Cache
|
8
|
+
AGE_THRESHOLD = 30 # seconds
|
9
|
+
|
10
|
+
def initialize(store, path_obj, development_mode: false)
|
11
|
+
@development_mode = development_mode
|
12
|
+
@store = store
|
13
|
+
@mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
|
14
|
+
@path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
|
15
|
+
@has_relative_paths = nil
|
16
|
+
reinitialize
|
17
|
+
end
|
18
|
+
|
19
|
+
# What is the path item that contains the dir as child?
|
20
|
+
# e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], load_dir("c/d")
|
21
|
+
# is "/a/b".
|
22
|
+
def load_dir(dir)
|
23
|
+
reinitialize if stale?
|
24
|
+
@mutex.synchronize { @dirs[dir] }
|
25
|
+
end
|
26
|
+
|
27
|
+
# { 'enumerator' => nil, 'enumerator.so' => nil, ... }
|
28
|
+
BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
|
29
|
+
# Builtin features are of the form 'enumerator.so'.
|
30
|
+
# All others include paths.
|
31
|
+
next unless feat.size < 20 && !feat.include?('/')
|
32
|
+
|
33
|
+
base = File.basename(feat, '.*') # enumerator.so -> enumerator
|
34
|
+
ext = File.extname(feat) # .so
|
35
|
+
|
36
|
+
features[feat] = nil # enumerator.so
|
37
|
+
features[base] = nil # enumerator
|
38
|
+
|
39
|
+
next unless [DOT_SO, *DL_EXTENSIONS].include?(ext)
|
40
|
+
DL_EXTENSIONS.each do |dl_ext|
|
41
|
+
features["#{base}#{dl_ext}"] = nil # enumerator.bundle
|
42
|
+
end
|
43
|
+
end.freeze
|
44
|
+
|
45
|
+
# Try to resolve this feature to an absolute path without traversing the
|
46
|
+
# loadpath.
|
47
|
+
def find(feature)
|
48
|
+
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
49
|
+
feature = feature.to_s
|
50
|
+
return feature if absolute_path?(feature)
|
51
|
+
return expand_path(feature) if feature.start_with?('./')
|
52
|
+
@mutex.synchronize do
|
53
|
+
x = search_index(feature)
|
54
|
+
return x if x
|
55
|
+
|
56
|
+
# Ruby has some built-in features that require lies about.
|
57
|
+
# For example, 'enumerator' is built in. If you require it, ruby
|
58
|
+
# returns false as if it were already loaded; however, there is no
|
59
|
+
# file to find on disk. We've pre-built a list of these, and we
|
60
|
+
# return false if any of them is loaded.
|
61
|
+
raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
|
62
|
+
|
63
|
+
# The feature wasn't found on our preliminary search through the index.
|
64
|
+
# We resolve this differently depending on what the extension was.
|
65
|
+
case File.extname(feature)
|
66
|
+
# If the extension was one of the ones we explicitly cache (.rb and the
|
67
|
+
# native dynamic extension, e.g. .bundle or .so), we know it was a
|
68
|
+
# failure and there's nothing more we can do to find the file.
|
69
|
+
# no extension, .rb, (.bundle or .so)
|
70
|
+
when '', *CACHED_EXTENSIONS
|
71
|
+
nil
|
72
|
+
# Ruby allows specifying native extensions as '.so' even when DLEXT
|
73
|
+
# is '.bundle'. This is where we handle that case.
|
74
|
+
when DOT_SO
|
75
|
+
x = search_index(feature[0..-4] + DLEXT)
|
76
|
+
return x if x
|
77
|
+
if DLEXT2
|
78
|
+
x = search_index(feature[0..-4] + DLEXT2)
|
79
|
+
return x if x
|
80
|
+
end
|
81
|
+
else
|
82
|
+
# other, unknown extension. For example, `.rake`. Since we haven't
|
83
|
+
# cached these, we legitimately need to run the load path search.
|
84
|
+
raise(LoadPathCache::FallbackScan, '', [])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# In development mode, we don't want to confidently return failures for
|
89
|
+
# cases where the file doesn't appear to be on the load path. We should
|
90
|
+
# be able to detect newly-created files without rebooting the
|
91
|
+
# application.
|
92
|
+
raise(LoadPathCache::FallbackScan, '', []) if @development_mode
|
93
|
+
end
|
94
|
+
|
95
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
96
|
+
def absolute_path?(path)
|
97
|
+
path[1] == ':'
|
98
|
+
end
|
99
|
+
else
|
100
|
+
def absolute_path?(path)
|
101
|
+
path.start_with?(SLASH)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def unshift_paths(sender, *paths)
|
106
|
+
return unless sender == @path_obj
|
107
|
+
@mutex.synchronize { unshift_paths_locked(*paths) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def push_paths(sender, *paths)
|
111
|
+
return unless sender == @path_obj
|
112
|
+
@mutex.synchronize { push_paths_locked(*paths) }
|
113
|
+
end
|
114
|
+
|
115
|
+
def reinitialize(path_obj = @path_obj)
|
116
|
+
@mutex.synchronize do
|
117
|
+
@path_obj = path_obj
|
118
|
+
ChangeObserver.register(self, @path_obj)
|
119
|
+
@index = {}
|
120
|
+
@dirs = {}
|
121
|
+
@generated_at = now
|
122
|
+
push_paths_locked(*@path_obj)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def dir_changed?
|
129
|
+
@prev_dir ||= Dir.pwd
|
130
|
+
if @prev_dir == Dir.pwd
|
131
|
+
false
|
132
|
+
else
|
133
|
+
@prev_dir = Dir.pwd
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def push_paths_locked(*paths)
|
139
|
+
@store.transaction do
|
140
|
+
paths.map(&:to_s).each do |path|
|
141
|
+
p = Path.new(path)
|
142
|
+
@has_relative_paths = true if p.relative?
|
143
|
+
next if p.non_directory?
|
144
|
+
expanded_path = p.expanded_path
|
145
|
+
entries, dirs = p.entries_and_dirs(@store)
|
146
|
+
# push -> low precedence -> set only if unset
|
147
|
+
dirs.each { |dir| @dirs[dir] ||= path }
|
148
|
+
entries.each { |rel| @index[rel] ||= expanded_path }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def unshift_paths_locked(*paths)
|
154
|
+
@store.transaction do
|
155
|
+
paths.map(&:to_s).reverse_each do |path|
|
156
|
+
p = Path.new(path)
|
157
|
+
next if p.non_directory?
|
158
|
+
expanded_path = p.expanded_path
|
159
|
+
entries, dirs = p.entries_and_dirs(@store)
|
160
|
+
# unshift -> high precedence -> unconditional set
|
161
|
+
dirs.each { |dir| @dirs[dir] = path }
|
162
|
+
entries.each { |rel| @index[rel] = expanded_path }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def expand_path(feature)
|
168
|
+
maybe_append_extension(File.expand_path(feature))
|
169
|
+
end
|
170
|
+
|
171
|
+
def stale?
|
172
|
+
@development_mode && @generated_at + AGE_THRESHOLD < now
|
173
|
+
end
|
174
|
+
|
175
|
+
def now
|
176
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
if DLEXT2
|
180
|
+
def search_index(f)
|
181
|
+
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
|
182
|
+
end
|
183
|
+
|
184
|
+
def maybe_append_extension(f)
|
185
|
+
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
|
186
|
+
end
|
187
|
+
else
|
188
|
+
def search_index(f)
|
189
|
+
try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
|
190
|
+
end
|
191
|
+
|
192
|
+
def maybe_append_extension(f)
|
193
|
+
try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def try_index(f)
|
198
|
+
if (p = @index[f])
|
199
|
+
p + '/' + f
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def try_ext(f)
|
204
|
+
f if File.exist?(f)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|