bootsnap 1.1.8-java → 1.6.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/CHANGELOG.md +103 -0
- data/README.md +47 -6
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +217 -88
- data/ext/bootsnap/extconf.rb +3 -1
- data/lib/bootsnap.rb +17 -8
- data/lib/bootsnap/bundler.rb +6 -3
- data/lib/bootsnap/cli.rb +246 -0
- data/lib/bootsnap/cli/worker_pool.rb +131 -0
- data/lib/bootsnap/compile_cache.rb +32 -4
- data/lib/bootsnap/compile_cache/iseq.rb +32 -15
- data/lib/bootsnap/compile_cache/yaml.rb +94 -40
- data/lib/bootsnap/explicit_require.rb +2 -1
- data/lib/bootsnap/load_path_cache.rb +35 -9
- data/lib/bootsnap/load_path_cache/cache.rb +48 -29
- data/lib/bootsnap/load_path_cache/change_observer.rb +36 -29
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +39 -7
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +70 -53
- 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 +8 -7
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -39
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
- data/lib/bootsnap/load_path_cache/store.rb +20 -14
- data/lib/bootsnap/setup.rb +11 -13
- data/lib/bootsnap/version.rb +2 -1
- metadata +44 -45
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -4
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -8
- data/Rakefile +0 -11
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -39
- data/dev.yml +0 -10
@@ -1,37 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module LoadPathCache
|
3
4
|
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
|
-
|
5
|
+
module ArrayMixin
|
9
6
|
# For each method that adds items to one end or another of the array
|
10
7
|
# (<<, push, unshift, concat), override that method to also notify the
|
11
8
|
# observer of the change.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
def <<(entry)
|
10
|
+
@lpc_observer.push_paths(self, entry.to_s)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def push(*entries)
|
15
|
+
@lpc_observer.push_paths(self, *entries.map(&:to_s))
|
16
|
+
super
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
push_without_lpc(*entries)
|
19
|
+
def unshift(*entries)
|
20
|
+
@lpc_observer.unshift_paths(self, *entries.map(&:to_s))
|
21
|
+
super
|
23
22
|
end
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
unshift_without_lpc(*entries)
|
24
|
+
def concat(entries)
|
25
|
+
@lpc_observer.push_paths(self, *entries.map(&:to_s))
|
26
|
+
super
|
29
27
|
end
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
# uniq! keeps the first occurrence of each path, otherwise preserving
|
30
|
+
# order, preserving the effective load path
|
31
|
+
def uniq!(*args)
|
32
|
+
ret = super
|
33
|
+
@lpc_observer.reinitialize if block_given? || !args.empty?
|
34
|
+
ret
|
35
35
|
end
|
36
36
|
|
37
37
|
# For each method that modifies the array more aggressively, override
|
@@ -41,16 +41,23 @@ module Bootsnap
|
|
41
41
|
# accounting cost would be greater than the hit from these, since we
|
42
42
|
# actively discourage calling them.
|
43
43
|
%i(
|
44
|
-
collect! compact! delete delete_at delete_if fill flatten!
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
[]= clear collect! compact! delete delete_at delete_if fill flatten!
|
45
|
+
insert keep_if map! pop reject! replace reverse! rotate! select!
|
46
|
+
shift shuffle! slice! sort! sort_by!
|
47
|
+
).each do |method_name|
|
48
|
+
define_method(method_name) do |*args, &block|
|
49
|
+
ret = super(*args, &block)
|
50
|
+
@lpc_observer.reinitialize
|
51
|
+
ret
|
51
52
|
end
|
52
53
|
end
|
53
54
|
end
|
55
|
+
|
56
|
+
def self.register(observer, arr)
|
57
|
+
return if arr.frozen? # can't register observer, but no need to.
|
58
|
+
arr.instance_variable_set(:@lpc_observer, observer)
|
59
|
+
arr.extend(ArrayMixin)
|
60
|
+
end
|
54
61
|
end
|
55
62
|
end
|
56
63
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module LoadPathCache
|
3
4
|
module CoreExt
|
@@ -10,6 +11,14 @@ module Bootsnap
|
|
10
11
|
Thread.current[:without_bootsnap_cache] = prev
|
11
12
|
end
|
12
13
|
|
14
|
+
def self.allow_bootsnap_retry(allowed)
|
15
|
+
prev = Thread.current[:without_bootsnap_retry] || false
|
16
|
+
Thread.current[:without_bootsnap_retry] = !allowed
|
17
|
+
yield
|
18
|
+
ensure
|
19
|
+
Thread.current[:without_bootsnap_retry] = prev
|
20
|
+
end
|
21
|
+
|
13
22
|
module ClassMethods
|
14
23
|
def autoload_paths=(o)
|
15
24
|
super
|
@@ -22,17 +31,25 @@ module Bootsnap
|
|
22
31
|
Bootsnap::LoadPathCache.autoload_paths_cache.find(path)
|
23
32
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
24
33
|
nil # doesn't really apply here
|
34
|
+
rescue Bootsnap::LoadPathCache::FallbackScan
|
35
|
+
nil # doesn't really apply here
|
25
36
|
end
|
26
37
|
end
|
27
38
|
|
28
39
|
def autoloadable_module?(path_suffix)
|
29
|
-
Bootsnap::LoadPathCache.autoload_paths_cache.
|
40
|
+
Bootsnap::LoadPathCache.autoload_paths_cache.load_dir(path_suffix)
|
30
41
|
end
|
31
42
|
|
32
43
|
def remove_constant(const)
|
33
44
|
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
34
45
|
end
|
35
46
|
|
47
|
+
def require_or_load(*)
|
48
|
+
CoreExt::ActiveSupport.allow_bootsnap_retry(true) do
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
36
53
|
# If we can't find a constant using the patched implementation of
|
37
54
|
# search_for_file, try again with the default implementation.
|
38
55
|
#
|
@@ -40,16 +57,26 @@ module Bootsnap
|
|
40
57
|
# behaviour. The gymnastics here are a bit awkward, but it prevents
|
41
58
|
# 200+ lines of monkeypatches.
|
42
59
|
def load_missing_constant(from_mod, const_name)
|
43
|
-
|
60
|
+
CoreExt::ActiveSupport.allow_bootsnap_retry(false) do
|
61
|
+
super
|
62
|
+
end
|
44
63
|
rescue NameError => e
|
64
|
+
raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
|
65
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
66
|
+
|
67
|
+
# This function can end up called recursively, we only want to
|
68
|
+
# retry at the top-level.
|
69
|
+
raise(e) if Thread.current[:without_bootsnap_retry]
|
70
|
+
# If we already had cache disabled, there's no use retrying
|
71
|
+
raise(e) if Thread.current[:without_bootsnap_cache]
|
45
72
|
# NoMethodError is a NameError, but we only want to handle actual
|
46
73
|
# NameError instances.
|
47
|
-
raise unless e.class == NameError
|
74
|
+
raise(e) unless e.class == NameError
|
48
75
|
# We can only confidently handle cases when *this* constant fails
|
49
76
|
# to load, not other constants referred to by it.
|
50
|
-
raise unless e.name == const_name
|
77
|
+
raise(e) unless e.name == const_name
|
51
78
|
# If the constant was actually loaded, something else went wrong?
|
52
|
-
raise if from_mod.const_defined?(const_name)
|
79
|
+
raise(e) if from_mod.const_defined?(const_name)
|
53
80
|
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
54
81
|
end
|
55
82
|
|
@@ -57,7 +84,12 @@ module Bootsnap
|
|
57
84
|
# reiterate it with version polymorphism here...
|
58
85
|
def depend_on(*)
|
59
86
|
super
|
60
|
-
rescue LoadError
|
87
|
+
rescue LoadError => e
|
88
|
+
raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
|
89
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
90
|
+
|
91
|
+
# If we already had cache disabled, there's no use retrying
|
92
|
+
raise(e) if Thread.current[:without_bootsnap_cache]
|
61
93
|
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
62
94
|
end
|
63
95
|
end
|
@@ -69,7 +101,7 @@ end
|
|
69
101
|
module ActiveSupport
|
70
102
|
module Dependencies
|
71
103
|
class << self
|
72
|
-
prepend
|
104
|
+
prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods)
|
73
105
|
end
|
74
106
|
end
|
75
107
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module LoadPathCache
|
3
4
|
module CoreExt
|
4
5
|
def self.make_load_error(path)
|
5
|
-
err = LoadError.new("cannot load such file -- #{path}")
|
6
|
+
err = LoadError.new(+"cannot load such file -- #{path}")
|
7
|
+
err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
6
8
|
err.define_singleton_method(:path) { path }
|
7
9
|
err
|
8
10
|
end
|
@@ -11,78 +13,93 @@ module Bootsnap
|
|
11
13
|
end
|
12
14
|
|
13
15
|
module Kernel
|
14
|
-
|
15
|
-
def require(path)
|
16
|
-
if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
|
17
|
-
require_without_cache(resolved)
|
18
|
-
else
|
19
|
-
raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
|
20
|
-
end
|
21
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
22
|
-
return false
|
23
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
24
|
-
require_without_cache(path)
|
25
|
-
end
|
16
|
+
module_function # rubocop:disable Style/ModuleFunction
|
26
17
|
|
27
|
-
alias_method
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
relative = File.expand_path(path)
|
34
|
-
if File.exist?(File.expand_path(path))
|
35
|
-
return load_without_cache(relative, wrap)
|
36
|
-
end
|
37
|
-
raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
|
18
|
+
alias_method(:require_without_bootsnap, :require)
|
19
|
+
|
20
|
+
# Note that require registers to $LOADED_FEATURES while load does not.
|
21
|
+
def require_with_bootsnap_lfi(path, resolved = nil)
|
22
|
+
Bootsnap::LoadPathCache.loaded_features_index.register(path, resolved) do
|
23
|
+
require_without_bootsnap(resolved || path)
|
38
24
|
end
|
39
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
40
|
-
return false
|
41
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
42
|
-
load_without_cache(path, wrap)
|
43
25
|
end
|
44
|
-
end
|
45
26
|
|
46
|
-
class << Kernel
|
47
|
-
alias_method :require_without_cache, :require
|
48
27
|
def require(path)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
28
|
+
return false if Bootsnap::LoadPathCache.loaded_features_index.key?(path)
|
29
|
+
|
30
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
|
31
|
+
return require_with_bootsnap_lfi(path, resolved)
|
53
32
|
end
|
33
|
+
|
34
|
+
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
35
|
+
rescue LoadError => e
|
36
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
37
|
+
raise(e)
|
54
38
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
55
|
-
|
39
|
+
false
|
56
40
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
57
|
-
|
41
|
+
fallback = true
|
42
|
+
ensure
|
43
|
+
if fallback
|
44
|
+
require_with_bootsnap_lfi(path)
|
45
|
+
end
|
58
46
|
end
|
59
47
|
|
60
|
-
alias_method
|
48
|
+
alias_method(:require_relative_without_bootsnap, :require_relative)
|
49
|
+
def require_relative(path)
|
50
|
+
realpath = Bootsnap::LoadPathCache.realpath_cache.call(
|
51
|
+
caller_locations(1..1).first.absolute_path, path
|
52
|
+
)
|
53
|
+
require(realpath)
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method(:load_without_bootsnap, :load)
|
61
57
|
def load(path, wrap = false)
|
62
|
-
if resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
|
63
|
-
|
64
|
-
else
|
65
|
-
# load also allows relative paths from pwd even when not in $:
|
66
|
-
relative = File.expand_path(path)
|
67
|
-
if File.exist?(relative)
|
68
|
-
return load_without_cache(relative, wrap)
|
69
|
-
end
|
70
|
-
raise Bootsnap::LoadPathCache::CoreExt.make_load_error(path)
|
58
|
+
if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
|
59
|
+
return load_without_bootsnap(resolved, wrap)
|
71
60
|
end
|
61
|
+
|
62
|
+
# load also allows relative paths from pwd even when not in $:
|
63
|
+
if File.exist?(relative = File.expand_path(path).freeze)
|
64
|
+
return load_without_bootsnap(relative, wrap)
|
65
|
+
end
|
66
|
+
|
67
|
+
raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path))
|
68
|
+
rescue LoadError => e
|
69
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
70
|
+
raise(e)
|
72
71
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
73
|
-
|
72
|
+
false
|
74
73
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
75
|
-
|
74
|
+
fallback = true
|
75
|
+
ensure
|
76
|
+
if fallback
|
77
|
+
load_without_bootsnap(path, wrap)
|
78
|
+
end
|
76
79
|
end
|
77
80
|
end
|
78
81
|
|
79
82
|
class Module
|
80
|
-
alias_method
|
83
|
+
alias_method(:autoload_without_bootsnap, :autoload)
|
81
84
|
def autoload(const, path)
|
82
|
-
|
85
|
+
# NOTE: This may defeat LoadedFeaturesIndex, but it's not immediately
|
86
|
+
# obvious how to make it work. This feels like a pretty niche case, unclear
|
87
|
+
# if it will ever burn anyone.
|
88
|
+
#
|
89
|
+
# The challenge is that we don't control the point at which the entry gets
|
90
|
+
# added to $LOADED_FEATURES and won't be able to hook that modification
|
91
|
+
# since it's done in C-land.
|
92
|
+
autoload_without_bootsnap(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path)
|
93
|
+
rescue LoadError => e
|
94
|
+
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
95
|
+
raise(e)
|
83
96
|
rescue Bootsnap::LoadPathCache::ReturnFalse
|
84
|
-
|
97
|
+
false
|
85
98
|
rescue Bootsnap::LoadPathCache::FallbackScan
|
86
|
-
|
99
|
+
fallback = true
|
100
|
+
ensure
|
101
|
+
if fallback
|
102
|
+
autoload_without_bootsnap(const, path)
|
103
|
+
end
|
87
104
|
end
|
88
105
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class << $LOADED_FEATURES
|
3
|
+
alias_method(:delete_without_bootsnap, :delete)
|
4
|
+
def delete(key)
|
5
|
+
Bootsnap::LoadPathCache.loaded_features_index.purge(key)
|
6
|
+
delete_without_bootsnap(key)
|
7
|
+
end
|
8
|
+
|
9
|
+
alias_method(:reject_without_bootsnap!, :reject!)
|
10
|
+
def reject!(&block)
|
11
|
+
backup = dup
|
12
|
+
|
13
|
+
# FIXME: if no block is passed we'd need to return a decorated iterator
|
14
|
+
reject_without_bootsnap!(&block)
|
15
|
+
|
16
|
+
Bootsnap::LoadPathCache.loaded_features_index.purge_multi(backup - self)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bootsnap
|
4
|
+
module LoadPathCache
|
5
|
+
# LoadedFeaturesIndex partially mirrors an internal structure in ruby that
|
6
|
+
# we can't easily obtain an interface to.
|
7
|
+
#
|
8
|
+
# This works around an issue where, without bootsnap, *ruby* knows that it
|
9
|
+
# has already required a file by its short name (e.g. require 'bundler') if
|
10
|
+
# a new instance of bundler is added to the $LOAD_PATH which resolves to a
|
11
|
+
# different absolute path. This class makes bootsnap smart enough to
|
12
|
+
# realize that it has already loaded 'bundler', and not just
|
13
|
+
# '/path/to/bundler'.
|
14
|
+
#
|
15
|
+
# If you disable LoadedFeaturesIndex, you can see the problem this solves by:
|
16
|
+
#
|
17
|
+
# 1. `require 'a'`
|
18
|
+
# 2. Prepend a new $LOAD_PATH element containing an `a.rb`
|
19
|
+
# 3. `require 'a'`
|
20
|
+
#
|
21
|
+
# Ruby returns false from step 3.
|
22
|
+
# With bootsnap but with no LoadedFeaturesIndex, this loads two different
|
23
|
+
# `a.rb`s.
|
24
|
+
# With bootsnap and with LoadedFeaturesIndex, this skips the second load,
|
25
|
+
# returning false like ruby.
|
26
|
+
class LoadedFeaturesIndex
|
27
|
+
def initialize
|
28
|
+
@lfi = {}
|
29
|
+
@mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
|
30
|
+
|
31
|
+
# In theory the user could mutate $LOADED_FEATURES and invalidate our
|
32
|
+
# cache. If this ever comes up in practice — or if you, the
|
33
|
+
# enterprising reader, feels inclined to solve this problem — we could
|
34
|
+
# parallel the work done with ChangeObserver on $LOAD_PATH to mirror
|
35
|
+
# updates to our @lfi.
|
36
|
+
$LOADED_FEATURES.each do |feat|
|
37
|
+
hash = feat.hash
|
38
|
+
$LOAD_PATH.each do |lpe|
|
39
|
+
next unless feat.start_with?(lpe)
|
40
|
+
# /a/b/lib/my/foo.rb
|
41
|
+
# ^^^^^^^^^
|
42
|
+
short = feat[(lpe.length + 1)..-1]
|
43
|
+
stripped = strip_extension_if_elidable(short)
|
44
|
+
@lfi[short] = hash
|
45
|
+
@lfi[stripped] = hash
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# We've optimized for initialize and register to be fast, and purge to be tolerable.
|
51
|
+
# If access patterns make this not-okay, we can lazy-invert the LFI on
|
52
|
+
# first purge and work from there.
|
53
|
+
def purge(feature)
|
54
|
+
@mutex.synchronize do
|
55
|
+
feat_hash = feature.hash
|
56
|
+
@lfi.reject! { |_, hash| hash == feat_hash }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def purge_multi(features)
|
61
|
+
rejected_hashes = features.map(&:hash).to_set
|
62
|
+
@mutex.synchronize do
|
63
|
+
@lfi.reject! { |_, hash| rejected_hashes.include?(hash) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def key?(feature)
|
68
|
+
@mutex.synchronize { @lfi.key?(feature) }
|
69
|
+
end
|
70
|
+
|
71
|
+
# There is a relatively uncommon case where we could miss adding an
|
72
|
+
# entry:
|
73
|
+
#
|
74
|
+
# If the user asked for e.g. `require 'bundler'`, and we went through the
|
75
|
+
# `FallbackScan` pathway in `kernel_require.rb` and therefore did not
|
76
|
+
# pass `long` (the full expanded absolute path), then we did are not able
|
77
|
+
# to confidently add the `bundler.rb` form to @lfi.
|
78
|
+
#
|
79
|
+
# We could either:
|
80
|
+
#
|
81
|
+
# 1. Just add `bundler.rb`, `bundler.so`, and so on, which is close but
|
82
|
+
# not quite right; or
|
83
|
+
# 2. Inspect $LOADED_FEATURES upon return from yield to find the matching
|
84
|
+
# entry.
|
85
|
+
def register(short, long = nil)
|
86
|
+
if long.nil?
|
87
|
+
pat = %r{/#{Regexp.escape(short)}(\.[^/]+)?$}
|
88
|
+
len = $LOADED_FEATURES.size
|
89
|
+
ret = yield
|
90
|
+
long = $LOADED_FEATURES[len..-1].detect { |feat| feat =~ pat }
|
91
|
+
else
|
92
|
+
ret = yield
|
93
|
+
end
|
94
|
+
|
95
|
+
hash = long.hash
|
96
|
+
|
97
|
+
# Do we have a filename with an elidable extension, e.g.,
|
98
|
+
# 'bundler.rb', or 'libgit2.so'?
|
99
|
+
altname = if extension_elidable?(short)
|
100
|
+
# Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
|
101
|
+
strip_extension_if_elidable(short)
|
102
|
+
elsif long && (ext = File.extname(long.freeze))
|
103
|
+
# We already know the extension of the actual file this
|
104
|
+
# resolves to, so put that back on.
|
105
|
+
short + ext
|
106
|
+
end
|
107
|
+
|
108
|
+
@mutex.synchronize do
|
109
|
+
@lfi[short] = hash
|
110
|
+
(@lfi[altname] = hash) if altname
|
111
|
+
end
|
112
|
+
|
113
|
+
ret
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
STRIP_EXTENSION = /\.[^.]*?$/
|
119
|
+
private_constant(:STRIP_EXTENSION)
|
120
|
+
|
121
|
+
# Might Ruby automatically search for this extension if
|
122
|
+
# someone tries to 'require' the file without it? E.g. Ruby
|
123
|
+
# will implicitly try 'x.rb' if you ask for 'x'.
|
124
|
+
#
|
125
|
+
# This is complex and platform-dependent, and the Ruby docs are a little
|
126
|
+
# handwavy about what will be tried when and in what order.
|
127
|
+
# So optimistically pretend that all known elidable extensions
|
128
|
+
# will be tried on all platforms, and that people are unlikely
|
129
|
+
# to name files in a way that assumes otherwise.
|
130
|
+
# (E.g. It's unlikely that someone will know that their code
|
131
|
+
# will _never_ run on MacOS, and therefore think they can get away
|
132
|
+
# with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
|
133
|
+
#
|
134
|
+
# See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
|
135
|
+
def extension_elidable?(f)
|
136
|
+
f.to_s.end_with?('.rb', '.so', '.o', '.dll', '.dylib')
|
137
|
+
end
|
138
|
+
|
139
|
+
def strip_extension_if_elidable(f)
|
140
|
+
if extension_elidable?(f)
|
141
|
+
f.sub(STRIP_EXTENSION, '')
|
142
|
+
else
|
143
|
+
f
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|