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.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +103 -0
  3. data/README.md +47 -6
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +217 -88
  6. data/ext/bootsnap/extconf.rb +3 -1
  7. data/lib/bootsnap.rb +17 -8
  8. data/lib/bootsnap/bundler.rb +6 -3
  9. data/lib/bootsnap/cli.rb +246 -0
  10. data/lib/bootsnap/cli/worker_pool.rb +131 -0
  11. data/lib/bootsnap/compile_cache.rb +32 -4
  12. data/lib/bootsnap/compile_cache/iseq.rb +32 -15
  13. data/lib/bootsnap/compile_cache/yaml.rb +94 -40
  14. data/lib/bootsnap/explicit_require.rb +2 -1
  15. data/lib/bootsnap/load_path_cache.rb +35 -9
  16. data/lib/bootsnap/load_path_cache/cache.rb +48 -29
  17. data/lib/bootsnap/load_path_cache/change_observer.rb +36 -29
  18. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +39 -7
  19. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +70 -53
  20. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +18 -0
  21. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +148 -0
  22. data/lib/bootsnap/load_path_cache/path.rb +8 -7
  23. data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -39
  24. data/lib/bootsnap/load_path_cache/realpath_cache.rb +32 -0
  25. data/lib/bootsnap/load_path_cache/store.rb +20 -14
  26. data/lib/bootsnap/setup.rb +11 -13
  27. data/lib/bootsnap/version.rb +2 -1
  28. metadata +44 -45
  29. data/.gitignore +0 -17
  30. data/.rubocop.yml +0 -20
  31. data/.travis.yml +0 -4
  32. data/CODE_OF_CONDUCT.md +0 -74
  33. data/CONTRIBUTING.md +0 -21
  34. data/Gemfile +0 -8
  35. data/Rakefile +0 -11
  36. data/bin/console +0 -14
  37. data/bin/setup +0 -8
  38. data/bin/testunit +0 -8
  39. data/bootsnap.gemspec +0 -39
  40. data/dev.yml +0 -10
@@ -1,15 +1,43 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module CompileCache
4
+ Error = Class.new(StandardError)
5
+ PermissionError = Class.new(Error)
6
+
3
7
  def self.setup(cache_dir:, iseq:, yaml:)
4
8
  if iseq
5
- require_relative 'compile_cache/iseq'
6
- Bootsnap::CompileCache::ISeq.install!(cache_dir)
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
7
15
  end
8
16
 
9
17
  if yaml
10
- require_relative 'compile_cache/yaml'
11
- Bootsnap::CompileCache::YAML.install!(cache_dir)
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
12
24
  end
13
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), Windows (RubyInstaller2) and >= 2.3.0
38
+ RUBY_ENGINE == 'ruby' &&
39
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
40
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
41
+ end
14
42
  end
15
43
  end
@@ -1,31 +1,49 @@
1
- require 'bootsnap/bootsnap'
2
- require 'zlib'
1
+ # frozen_string_literal: true
2
+ require('bootsnap/bootsnap')
3
+ require('zlib')
3
4
 
4
5
  module Bootsnap
5
6
  module CompileCache
6
7
  module ISeq
7
8
  class << self
8
- attr_accessor :cache_dir
9
+ attr_accessor(:cache_dir)
9
10
  end
10
11
 
11
12
  def self.input_to_storage(_, path)
12
13
  RubyVM::InstructionSequence.compile_file(path).to_binary
13
14
  rescue SyntaxError
14
- raise Uncompilable, 'syntax error'
15
+ raise(Uncompilable, 'syntax error')
15
16
  end
16
17
 
17
- def self.storage_to_output(binary)
18
+ def self.storage_to_output(binary, _args)
18
19
  RubyVM::InstructionSequence.load_from_binary(binary)
19
20
  rescue RuntimeError => e
20
21
  if e.message == 'broken binary format'
21
- STDERR.puts "[Bootsnap::CompileCache] warning: rejecting broken binary"
22
- return nil
22
+ STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
23
+ nil
23
24
  else
24
25
  raise
25
26
  end
26
27
  end
27
28
 
28
- def self.input_to_output(_)
29
+ def self.fetch(path, cache_dir: ISeq.cache_dir)
30
+ Bootsnap::CompileCache::Native.fetch(
31
+ cache_dir,
32
+ path.to_s,
33
+ Bootsnap::CompileCache::ISeq,
34
+ nil,
35
+ )
36
+ end
37
+
38
+ def self.precompile(path, cache_dir: ISeq.cache_dir)
39
+ Bootsnap::CompileCache::Native.precompile(
40
+ cache_dir,
41
+ path.to_s,
42
+ Bootsnap::CompileCache::ISeq,
43
+ )
44
+ end
45
+
46
+ def self.input_to_output(_data, _kwargs)
29
47
  nil # ruby handles this
30
48
  end
31
49
 
@@ -34,14 +52,12 @@ module Bootsnap
34
52
  # Having coverage enabled prevents iseq dumping/loading.
35
53
  return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
36
54
 
37
- Bootsnap::CompileCache::Native.fetch(
38
- Bootsnap::CompileCache::ISeq.cache_dir,
39
- path.to_s,
40
- Bootsnap::CompileCache::ISeq
41
- )
55
+ Bootsnap::CompileCache::ISeq.fetch(path.to_s)
56
+ rescue Errno::EACCES
57
+ Bootsnap::CompileCache.permission_error(path)
42
58
  rescue RuntimeError => e
43
59
  if e.message =~ /unmatched platform/
44
- puts "unmatched platform for file #{path}"
60
+ puts("unmatched platform for file #{path}")
45
61
  end
46
62
  raise
47
63
  end
@@ -57,12 +73,13 @@ module Bootsnap
57
73
  crc = Zlib.crc32(option.inspect)
58
74
  Bootsnap::CompileCache::Native.compile_option_crc32 = crc
59
75
  end
76
+ compile_option_updated
60
77
 
61
78
  def self.install!(cache_dir)
62
79
  Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
63
80
  Bootsnap::CompileCache::ISeq.compile_option_updated
64
81
  class << RubyVM::InstructionSequence
65
- prepend InstructionSequenceMixin
82
+ prepend(InstructionSequenceMixin)
66
83
  end
67
84
  end
68
85
  end
@@ -1,58 +1,112 @@
1
- require 'bootsnap/bootsnap'
1
+ # frozen_string_literal: true
2
+ require('bootsnap/bootsnap')
2
3
 
3
4
  module Bootsnap
4
5
  module CompileCache
5
6
  module YAML
6
7
  class << self
7
- attr_accessor :msgpack_factory
8
- end
9
-
10
- def self.input_to_storage(contents, _)
11
- raise Uncompilable if contents.index("!ruby/object")
12
- obj = ::YAML.load(contents)
13
- msgpack_factory.packer.write(obj).to_s
14
- rescue NoMethodError, RangeError
15
- # if the object included things that we can't serialize, fall back to
16
- # Marshal. It's a bit slower, but can encode anything yaml can.
17
- # NoMethodError is unexpected types; RangeError is Bignums
18
- return Marshal.dump(obj)
19
- end
8
+ attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
20
9
 
21
- def self.storage_to_output(data)
22
- # This could have a meaning in messagepack, and we're being a little lazy
23
- # about it. -- but a leading 0x04 would indicate the contents of the YAML
24
- # is a positive integer, which is rare, to say the least.
25
- if data[0] == 0x04.chr && data[1] == 0x08.chr
26
- Marshal.load(data)
27
- else
28
- msgpack_factory.unpacker.feed(data).read
10
+ def input_to_storage(contents, _)
11
+ raise(Uncompilable) if contents.index("!ruby/object")
12
+ obj = ::YAML.load(contents)
13
+ msgpack_factory.dump(obj)
14
+ rescue NoMethodError, RangeError
15
+ # The object included things that we can't serialize
16
+ raise(Uncompilable)
29
17
  end
30
- end
31
-
32
- def self.input_to_output(data)
33
- ::YAML.load(data)
34
- end
35
18
 
36
- def self.install!(cache_dir)
37
- require 'yaml'
38
- require 'msgpack'
19
+ def storage_to_output(data, kwargs)
20
+ if kwargs && kwargs.key?(:symbolize_names)
21
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
22
+ end
23
+ msgpack_factory.load(data, kwargs)
24
+ end
39
25
 
40
- # MessagePack serializes symbols as strings by default.
41
- # We want them to roundtrip cleanly, so we use a custom factory.
42
- # see: https://github.com/msgpack/msgpack-ruby/pull/122
43
- factory = MessagePack::Factory.new
44
- factory.register_type(0x00, Symbol)
45
- Bootsnap::CompileCache::YAML.msgpack_factory = factory
26
+ def input_to_output(data, kwargs)
27
+ ::YAML.load(data, **(kwargs || {}))
28
+ end
46
29
 
47
- klass = class << ::YAML; self; end
48
- klass.send(:define_method, :load_file) do |path|
49
- Bootsnap::CompileCache::Native.fetch(
30
+ def precompile(path, cache_dir: YAML.cache_dir)
31
+ Bootsnap::CompileCache::Native.precompile(
50
32
  cache_dir,
51
33
  path.to_s,
52
- Bootsnap::CompileCache::YAML
34
+ Bootsnap::CompileCache::YAML,
35
+ )
36
+ end
37
+
38
+ def install!(cache_dir)
39
+ self.cache_dir = cache_dir
40
+ init!
41
+ ::YAML.singleton_class.prepend(Patch)
42
+ end
43
+
44
+ def init!
45
+ require('yaml')
46
+ require('msgpack')
47
+ require('date')
48
+
49
+ # MessagePack serializes symbols as strings by default.
50
+ # We want them to roundtrip cleanly, so we use a custom factory.
51
+ # see: https://github.com/msgpack/msgpack-ruby/pull/122
52
+ factory = MessagePack::Factory.new
53
+ factory.register_type(0x00, Symbol)
54
+ factory.register_type(
55
+ MessagePack::Timestamp::TYPE, # or just -1
56
+ Time,
57
+ packer: MessagePack::Time::Packer,
58
+ unpacker: MessagePack::Time::Unpacker
53
59
  )
60
+
61
+ marshal_fallback = {
62
+ packer: ->(value) { Marshal.dump(value) },
63
+ unpacker: ->(payload) { Marshal.load(payload) },
64
+ }
65
+ {
66
+ Date => 0x01,
67
+ Regexp => 0x02,
68
+ }.each do |type, code|
69
+ factory.register_type(code, type, marshal_fallback)
70
+ end
71
+
72
+ self.msgpack_factory = factory
73
+
74
+ self.supported_options = []
75
+ params = ::YAML.method(:load).parameters
76
+ if params.include?([:key, :symbolize_names])
77
+ self.supported_options << :symbolize_names
78
+ end
79
+ if params.include?([:key, :freeze])
80
+ if factory.load(factory.dump('yaml'), freeze: true).frozen?
81
+ self.supported_options << :freeze
82
+ end
83
+ end
84
+ self.supported_options.freeze
54
85
  end
55
86
  end
87
+
88
+ module Patch
89
+ def load_file(path, *args)
90
+ return super if args.size > 1
91
+ if kwargs = args.first
92
+ return super unless kwargs.is_a?(Hash)
93
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
94
+ end
95
+
96
+ begin
97
+ ::Bootsnap::CompileCache::Native.fetch(
98
+ Bootsnap::CompileCache::YAML.cache_dir,
99
+ path,
100
+ ::Bootsnap::CompileCache::YAML,
101
+ kwargs,
102
+ )
103
+ rescue Errno::EACCES
104
+ ::Bootsnap::CompileCache.permission_error(path)
105
+ end
106
+ end
107
+
108
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
109
+ end
56
110
  end
57
111
  end
58
112
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module ExplicitRequire
3
4
  ARCHDIR = RbConfig::CONFIG['archdir']
@@ -5,7 +6,7 @@ module Bootsnap
5
6
  DLEXT = RbConfig::CONFIG['DLEXT']
6
7
 
7
8
  def self.from_self(feature)
8
- require_relative "../#{feature}"
9
+ require_relative("../#{feature}")
9
10
  end
10
11
 
11
12
  def self.from_rubylibdir(feature)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bootsnap
2
4
  module LoadPathCache
3
5
  ReturnFalse = Class.new(StandardError)
@@ -7,6 +9,11 @@ module Bootsnap
7
9
  DOT_SO = '.so'
8
10
  SLASH = '/'
9
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
+
10
17
  DL_EXTENSIONS = ::RbConfig::CONFIG
11
18
  .values_at('DLEXT', 'DLEXT2')
12
19
  .reject { |ext| !ext || ext.empty? }
@@ -21,32 +28,51 @@ module Bootsnap
21
28
  CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
22
29
 
23
30
  class << self
24
- attr_reader :load_path_cache, :autoload_paths_cache
31
+ attr_reader(:load_path_cache, :autoload_paths_cache,
32
+ :loaded_features_index, :realpath_cache)
25
33
 
26
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
+
27
40
  store = Store.new(cache_path)
28
41
 
42
+ @loaded_features_index = LoadedFeaturesIndex.new
43
+ @realpath_cache = RealpathCache.new
44
+
29
45
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
30
- require_relative 'load_path_cache/core_ext/kernel_require'
46
+ require_relative('load_path_cache/core_ext/kernel_require')
47
+ require_relative('load_path_cache/core_ext/loaded_features')
31
48
 
32
49
  if active_support
33
50
  # this should happen after setting up the initial cache because it
34
51
  # loads a lot of code. It's better to do after +require+ is optimized.
35
- require 'active_support/dependencies'
52
+ require('active_support/dependencies')
36
53
  @autoload_paths_cache = Cache.new(
37
54
  store,
38
55
  ::ActiveSupport::Dependencies.autoload_paths,
39
56
  development_mode: development_mode
40
57
  )
41
- require_relative 'load_path_cache/core_ext/active_support'
58
+ require_relative('load_path_cache/core_ext/active_support')
42
59
  end
43
60
  end
61
+
62
+ def supported?
63
+ RUBY_ENGINE == 'ruby' &&
64
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
65
+ end
44
66
  end
45
67
  end
46
68
  end
47
69
 
48
- require_relative 'load_path_cache/path_scanner'
49
- require_relative 'load_path_cache/path'
50
- require_relative 'load_path_cache/cache'
51
- require_relative 'load_path_cache/store'
52
- require_relative 'load_path_cache/change_observer'
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
@@ -1,4 +1,6 @@
1
- require_relative '../explicit_require'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative('../explicit_require')
2
4
 
3
5
  module Bootsnap
4
6
  module LoadPathCache
@@ -9,15 +11,15 @@ module Bootsnap
9
11
  @development_mode = development_mode
10
12
  @store = store
11
13
  @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
12
- @path_obj = path_obj
14
+ @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
13
15
  @has_relative_paths = nil
14
16
  reinitialize
15
17
  end
16
18
 
17
- # Does this directory exist as a child of one of the path items?
18
- # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], has_dir?("c/d")
19
- # is true.
20
- def has_dir?(dir)
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)
21
23
  reinitialize if stale?
22
24
  @mutex.synchronize { @dirs[dir] }
23
25
  end
@@ -44,9 +46,9 @@ module Bootsnap
44
46
  # loadpath.
45
47
  def find(feature)
46
48
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
47
- feature = feature.to_s
49
+ feature = feature.to_s.freeze
48
50
  return feature if absolute_path?(feature)
49
- return File.expand_path(feature) if feature.start_with?('./')
51
+ return expand_path(feature) if feature.start_with?('./')
50
52
  @mutex.synchronize do
51
53
  x = search_index(feature)
52
54
  return x if x
@@ -56,7 +58,7 @@ module Bootsnap
56
58
  # returns false as if it were already loaded; however, there is no
57
59
  # file to find on disk. We've pre-built a list of these, and we
58
60
  # return false if any of them is loaded.
59
- raise LoadPathCache::ReturnFalse if BUILTIN_FEATURES.key?(feature)
61
+ raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
60
62
 
61
63
  # The feature wasn't found on our preliminary search through the index.
62
64
  # We resolve this differently depending on what the extension was.
@@ -65,7 +67,7 @@ module Bootsnap
65
67
  # native dynamic extension, e.g. .bundle or .so), we know it was a
66
68
  # failure and there's nothing more we can do to find the file.
67
69
  # no extension, .rb, (.bundle or .so)
68
- when '', *CACHED_EXTENSIONS # rubocop:disable Performance/CaseWhenSplat
70
+ when '', *CACHED_EXTENSIONS
69
71
  nil
70
72
  # Ruby allows specifying native extensions as '.so' even when DLEXT
71
73
  # is '.bundle'. This is where we handle that case.
@@ -73,14 +75,21 @@ module Bootsnap
73
75
  x = search_index(feature[0..-4] + DLEXT)
74
76
  return x if x
75
77
  if DLEXT2
76
- search_index(feature[0..-4] + DLEXT2)
78
+ x = search_index(feature[0..-4] + DLEXT2)
79
+ return x if x
77
80
  end
78
81
  else
79
82
  # other, unknown extension. For example, `.rake`. Since we haven't
80
83
  # cached these, we legitimately need to run the load path search.
81
- raise LoadPathCache::FallbackScan
84
+ raise(LoadPathCache::FallbackScan, '', [])
82
85
  end
83
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
84
93
  end
85
94
 
86
95
  if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
@@ -103,20 +112,12 @@ module Bootsnap
103
112
  @mutex.synchronize { push_paths_locked(*paths) }
104
113
  end
105
114
 
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
115
  def reinitialize(path_obj = @path_obj)
115
116
  @mutex.synchronize do
116
117
  @path_obj = path_obj
117
118
  ChangeObserver.register(self, @path_obj)
118
119
  @index = {}
119
- @dirs = Hash.new(false)
120
+ @dirs = {}
120
121
  @generated_at = now
121
122
  push_paths_locked(*@path_obj)
122
123
  end
@@ -140,10 +141,11 @@ module Bootsnap
140
141
  p = Path.new(path)
141
142
  @has_relative_paths = true if p.relative?
142
143
  next if p.non_directory?
144
+ expanded_path = p.expanded_path
143
145
  entries, dirs = p.entries_and_dirs(@store)
144
146
  # push -> low precedence -> set only if unset
145
- dirs.each { |dir| @dirs[dir] ||= true }
146
- entries.each { |rel| @index[rel] ||= p.expanded_path }
147
+ dirs.each { |dir| @dirs[dir] ||= path }
148
+ entries.each { |rel| @index[rel] ||= expanded_path }
147
149
  end
148
150
  end
149
151
  end
@@ -153,14 +155,19 @@ module Bootsnap
153
155
  paths.map(&:to_s).reverse_each do |path|
154
156
  p = Path.new(path)
155
157
  next if p.non_directory?
158
+ expanded_path = p.expanded_path
156
159
  entries, dirs = p.entries_and_dirs(@store)
157
160
  # unshift -> high precedence -> unconditional set
158
- dirs.each { |dir| @dirs[dir] = true }
159
- entries.each { |rel| @index[rel] = p.expanded_path }
161
+ dirs.each { |dir| @dirs[dir] = path }
162
+ entries.each { |rel| @index[rel] = expanded_path }
160
163
  end
161
164
  end
162
165
  end
163
166
 
167
+ def expand_path(feature)
168
+ maybe_append_extension(File.expand_path(feature))
169
+ end
170
+
164
171
  def stale?
165
172
  @development_mode && @generated_at + AGE_THRESHOLD < now
166
173
  end
@@ -171,19 +178,31 @@ module Bootsnap
171
178
 
172
179
  if DLEXT2
173
180
  def search_index(f)
174
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_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
175
186
  end
176
187
  else
177
188
  def search_index(f)
178
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_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
179
194
  end
180
195
  end
181
196
 
182
197
  def try_index(f)
183
- if p = @index[f]
184
- p + '/' + f
198
+ if (p = @index[f])
199
+ "#{p}/#{f}"
185
200
  end
186
201
  end
202
+
203
+ def try_ext(f)
204
+ f if File.exist?(f)
205
+ end
187
206
  end
188
207
  end
189
208
  end