bootsnap 1.6.0 → 1.18.3
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/CHANGELOG.md +218 -0
 - data/LICENSE.txt +1 -1
 - data/README.md +48 -22
 - data/exe/bootsnap +1 -1
 - data/ext/bootsnap/bootsnap.c +347 -145
 - data/ext/bootsnap/extconf.rb +29 -15
 - data/lib/bootsnap/bundler.rb +2 -1
 - data/lib/bootsnap/cli/worker_pool.rb +6 -1
 - data/lib/bootsnap/cli.rb +90 -53
 - data/lib/bootsnap/compile_cache/iseq.rb +52 -16
 - data/lib/bootsnap/compile_cache/json.rb +89 -0
 - data/lib/bootsnap/compile_cache/yaml.rb +285 -60
 - data/lib/bootsnap/compile_cache.rb +26 -17
 - data/lib/bootsnap/explicit_require.rb +4 -3
 - data/lib/bootsnap/load_path_cache/cache.rb +71 -35
 - data/lib/bootsnap/load_path_cache/change_observer.rb +23 -2
 - data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +26 -94
 - data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
 - data/lib/bootsnap/load_path_cache/loaded_features_index.rb +36 -25
 - data/lib/bootsnap/load_path_cache/path.rb +40 -18
 - data/lib/bootsnap/load_path_cache/path_scanner.rb +25 -7
 - data/lib/bootsnap/load_path_cache/store.rb +64 -24
 - data/lib/bootsnap/load_path_cache.rb +40 -38
 - data/lib/bootsnap/setup.rb +2 -36
 - data/lib/bootsnap/version.rb +2 -1
 - data/lib/bootsnap.rb +139 -36
 - metadata +8 -79
 - data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -107
 - data/lib/bootsnap/load_path_cache/realpath_cache.rb +0 -32
 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require_relative 
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative "../explicit_require"
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module Bootsnap
         
     | 
| 
       6 
6 
     | 
    
         
             
              module LoadPathCache
         
     | 
| 
         @@ -10,8 +10,8 @@ module Bootsnap 
     | 
|
| 
       10 
10 
     | 
    
         
             
                  def initialize(store, path_obj, development_mode: false)
         
     | 
| 
       11 
11 
     | 
    
         
             
                    @development_mode = development_mode
         
     | 
| 
       12 
12 
     | 
    
         
             
                    @store = store
         
     | 
| 
       13 
     | 
    
         
            -
                    @mutex =  
     | 
| 
       14 
     | 
    
         
            -
                    @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @mutex = Mutex.new
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
         
     | 
| 
       15 
15 
     | 
    
         
             
                    @has_relative_paths = nil
         
     | 
| 
       16 
16 
     | 
    
         
             
                    reinitialize
         
     | 
| 
       17 
17 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -24,19 +24,28 @@ module Bootsnap 
     | 
|
| 
       24 
24 
     | 
    
         
             
                    @mutex.synchronize { @dirs[dir] }
         
     | 
| 
       25 
25 
     | 
    
         
             
                  end
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
      
 27 
     | 
    
         
            +
                  TRUFFLERUBY_LIB_DIR_PREFIX = if RUBY_ENGINE == "truffleruby"
         
     | 
| 
      
 28 
     | 
    
         
            +
                    "#{File.join(RbConfig::CONFIG['libdir'], 'truffle')}#{File::SEPARATOR}"
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
       27 
31 
     | 
    
         
             
                  # { 'enumerator' => nil, 'enumerator.so' => nil, ... }
         
     | 
| 
       28 
32 
     | 
    
         
             
                  BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
         
     | 
| 
      
 33 
     | 
    
         
            +
                    if TRUFFLERUBY_LIB_DIR_PREFIX && feat.start_with?(TRUFFLERUBY_LIB_DIR_PREFIX)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      feat = feat.byteslice(TRUFFLERUBY_LIB_DIR_PREFIX.bytesize..-1)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
       29 
37 
     | 
    
         
             
                    # Builtin features are of the form 'enumerator.so'.
         
     | 
| 
       30 
38 
     | 
    
         
             
                    # All others include paths.
         
     | 
| 
       31 
     | 
    
         
            -
                    next unless feat.size < 20 && !feat.include?( 
     | 
| 
      
 39 
     | 
    
         
            +
                    next unless feat.size < 20 && !feat.include?("/")
         
     | 
| 
       32 
40 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                    base = File.basename(feat,  
     | 
| 
      
 41 
     | 
    
         
            +
                    base = File.basename(feat, ".*") # enumerator.so -> enumerator
         
     | 
| 
       34 
42 
     | 
    
         
             
                    ext  = File.extname(feat) # .so
         
     | 
| 
       35 
43 
     | 
    
         | 
| 
       36 
44 
     | 
    
         
             
                    features[feat] = nil # enumerator.so
         
     | 
| 
       37 
45 
     | 
    
         
             
                    features[base] = nil # enumerator
         
     | 
| 
       38 
46 
     | 
    
         | 
| 
       39 
47 
     | 
    
         
             
                    next unless [DOT_SO, *DL_EXTENSIONS].include?(ext)
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
       40 
49 
     | 
    
         
             
                    DL_EXTENSIONS.each do |dl_ext|
         
     | 
| 
       41 
50 
     | 
    
         
             
                      features["#{base}#{dl_ext}"] = nil # enumerator.bundle
         
     | 
| 
       42 
51 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -47,8 +56,13 @@ module Bootsnap 
     | 
|
| 
       47 
56 
     | 
    
         
             
                  def find(feature)
         
     | 
| 
       48 
57 
     | 
    
         
             
                    reinitialize if (@has_relative_paths && dir_changed?) || stale?
         
     | 
| 
       49 
58 
     | 
    
         
             
                    feature = feature.to_s.freeze
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                    return  
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    return feature if Bootsnap.absolute_path?(feature)
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    if feature.start_with?("./", "../")
         
     | 
| 
      
 63 
     | 
    
         
            +
                      return expand_path(feature)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
       52 
66 
     | 
    
         
             
                    @mutex.synchronize do
         
     | 
| 
       53 
67 
     | 
    
         
             
                      x = search_index(feature)
         
     | 
| 
       54 
68 
     | 
    
         
             
                      return x if x
         
     | 
| 
         @@ -58,7 +72,7 @@ module Bootsnap 
     | 
|
| 
       58 
72 
     | 
    
         
             
                      # returns false as if it were already loaded; however, there is no
         
     | 
| 
       59 
73 
     | 
    
         
             
                      # file to find on disk. We've pre-built a list of these, and we
         
     | 
| 
       60 
74 
     | 
    
         
             
                      # return false if any of them is loaded.
         
     | 
| 
       61 
     | 
    
         
            -
                       
     | 
| 
      
 75 
     | 
    
         
            +
                      return false if BUILTIN_FEATURES.key?(feature)
         
     | 
| 
       62 
76 
     | 
    
         | 
| 
       63 
77 
     | 
    
         
             
                      # The feature wasn't found on our preliminary search through the index.
         
     | 
| 
       64 
78 
     | 
    
         
             
                      # We resolve this differently depending on what the extension was.
         
     | 
| 
         @@ -67,13 +81,14 @@ module Bootsnap 
     | 
|
| 
       67 
81 
     | 
    
         
             
                      # native dynamic extension, e.g. .bundle or .so), we know it was a
         
     | 
| 
       68 
82 
     | 
    
         
             
                      # failure and there's nothing more we can do to find the file.
         
     | 
| 
       69 
83 
     | 
    
         
             
                      # no extension, .rb, (.bundle or .so)
         
     | 
| 
       70 
     | 
    
         
            -
                      when  
     | 
| 
      
 84 
     | 
    
         
            +
                      when "", *CACHED_EXTENSIONS
         
     | 
| 
       71 
85 
     | 
    
         
             
                        nil
         
     | 
| 
       72 
86 
     | 
    
         
             
                      # Ruby allows specifying native extensions as '.so' even when DLEXT
         
     | 
| 
       73 
87 
     | 
    
         
             
                      # is '.bundle'. This is where we handle that case.
         
     | 
| 
       74 
88 
     | 
    
         
             
                      when DOT_SO
         
     | 
| 
       75 
89 
     | 
    
         
             
                        x = search_index(feature[0..-4] + DLEXT)
         
     | 
| 
       76 
90 
     | 
    
         
             
                        return x if x
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
       77 
92 
     | 
    
         
             
                        if DLEXT2
         
     | 
| 
       78 
93 
     | 
    
         
             
                          x = search_index(feature[0..-4] + DLEXT2)
         
     | 
| 
       79 
94 
     | 
    
         
             
                          return x if x
         
     | 
| 
         @@ -81,7 +96,7 @@ module Bootsnap 
     | 
|
| 
       81 
96 
     | 
    
         
             
                      else
         
     | 
| 
       82 
97 
     | 
    
         
             
                        # other, unknown extension. For example, `.rake`. Since we haven't
         
     | 
| 
       83 
98 
     | 
    
         
             
                        # cached these, we legitimately need to run the load path search.
         
     | 
| 
       84 
     | 
    
         
            -
                         
     | 
| 
      
 99 
     | 
    
         
            +
                        return FALLBACK_SCAN
         
     | 
| 
       85 
100 
     | 
    
         
             
                      end
         
     | 
| 
       86 
101 
     | 
    
         
             
                    end
         
     | 
| 
       87 
102 
     | 
    
         | 
| 
         @@ -89,33 +104,25 @@ module Bootsnap 
     | 
|
| 
       89 
104 
     | 
    
         
             
                    # cases where the file doesn't appear to be on the load path. We should
         
     | 
| 
       90 
105 
     | 
    
         
             
                    # be able to detect newly-created files without rebooting the
         
     | 
| 
       91 
106 
     | 
    
         
             
                    # application.
         
     | 
| 
       92 
     | 
    
         
            -
                     
     | 
| 
       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
         
     | 
| 
      
 107 
     | 
    
         
            +
                    return FALLBACK_SCAN if @development_mode
         
     | 
| 
       103 
108 
     | 
    
         
             
                  end
         
     | 
| 
       104 
109 
     | 
    
         | 
| 
       105 
110 
     | 
    
         
             
                  def unshift_paths(sender, *paths)
         
     | 
| 
       106 
111 
     | 
    
         
             
                    return unless sender == @path_obj
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
       107 
113 
     | 
    
         
             
                    @mutex.synchronize { unshift_paths_locked(*paths) }
         
     | 
| 
       108 
114 
     | 
    
         
             
                  end
         
     | 
| 
       109 
115 
     | 
    
         | 
| 
       110 
116 
     | 
    
         
             
                  def push_paths(sender, *paths)
         
     | 
| 
       111 
117 
     | 
    
         
             
                    return unless sender == @path_obj
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
       112 
119 
     | 
    
         
             
                    @mutex.synchronize { push_paths_locked(*paths) }
         
     | 
| 
       113 
120 
     | 
    
         
             
                  end
         
     | 
| 
       114 
121 
     | 
    
         | 
| 
       115 
122 
     | 
    
         
             
                  def reinitialize(path_obj = @path_obj)
         
     | 
| 
       116 
123 
     | 
    
         
             
                    @mutex.synchronize do
         
     | 
| 
       117 
124 
     | 
    
         
             
                      @path_obj = path_obj
         
     | 
| 
       118 
     | 
    
         
            -
                      ChangeObserver.register( 
     | 
| 
      
 125 
     | 
    
         
            +
                      ChangeObserver.register(@path_obj, self)
         
     | 
| 
       119 
126 
     | 
    
         
             
                      @index = {}
         
     | 
| 
       120 
127 
     | 
    
         
             
                      @dirs = {}
         
     | 
| 
       121 
128 
     | 
    
         
             
                      @generated_at = now
         
     | 
| 
         @@ -141,6 +148,9 @@ module Bootsnap 
     | 
|
| 
       141 
148 
     | 
    
         
             
                        p = Path.new(path)
         
     | 
| 
       142 
149 
     | 
    
         
             
                        @has_relative_paths = true if p.relative?
         
     | 
| 
       143 
150 
     | 
    
         
             
                        next if p.non_directory?
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                        p = p.to_realpath
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
       144 
154 
     | 
    
         
             
                        expanded_path = p.expanded_path
         
     | 
| 
       145 
155 
     | 
    
         
             
                        entries, dirs = p.entries_and_dirs(@store)
         
     | 
| 
       146 
156 
     | 
    
         
             
                        # push -> low precedence -> set only if unset
         
     | 
| 
         @@ -155,6 +165,9 @@ module Bootsnap 
     | 
|
| 
       155 
165 
     | 
    
         
             
                      paths.map(&:to_s).reverse_each do |path|
         
     | 
| 
       156 
166 
     | 
    
         
             
                        p = Path.new(path)
         
     | 
| 
       157 
167 
     | 
    
         
             
                        next if p.non_directory?
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                        p = p.to_realpath
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
       158 
171 
     | 
    
         
             
                        expanded_path = p.expanded_path
         
     | 
| 
       159 
172 
     | 
    
         
             
                        entries, dirs = p.entries_and_dirs(@store)
         
     | 
| 
       160 
173 
     | 
    
         
             
                        # unshift -> high precedence -> unconditional set
         
     | 
| 
         @@ -177,31 +190,54 @@ module Bootsnap 
     | 
|
| 
       177 
190 
     | 
    
         
             
                  end
         
     | 
| 
       178 
191 
     | 
    
         | 
| 
       179 
192 
     | 
    
         
             
                  if DLEXT2
         
     | 
| 
       180 
     | 
    
         
            -
                    def search_index( 
     | 
| 
       181 
     | 
    
         
            -
                      try_index( 
     | 
| 
      
 193 
     | 
    
         
            +
                    def search_index(feature)
         
     | 
| 
      
 194 
     | 
    
         
            +
                      try_index(feature + DOT_RB) ||
         
     | 
| 
      
 195 
     | 
    
         
            +
                        try_index(feature + DLEXT) ||
         
     | 
| 
      
 196 
     | 
    
         
            +
                        try_index(feature + DLEXT2) ||
         
     | 
| 
      
 197 
     | 
    
         
            +
                        try_index(feature)
         
     | 
| 
       182 
198 
     | 
    
         
             
                    end
         
     | 
| 
       183 
199 
     | 
    
         | 
| 
       184 
     | 
    
         
            -
                    def maybe_append_extension( 
     | 
| 
       185 
     | 
    
         
            -
                      try_ext( 
     | 
| 
      
 200 
     | 
    
         
            +
                    def maybe_append_extension(feature)
         
     | 
| 
      
 201 
     | 
    
         
            +
                      try_ext(feature + DOT_RB) ||
         
     | 
| 
      
 202 
     | 
    
         
            +
                        try_ext(feature + DLEXT) ||
         
     | 
| 
      
 203 
     | 
    
         
            +
                        try_ext(feature + DLEXT2) ||
         
     | 
| 
      
 204 
     | 
    
         
            +
                        feature
         
     | 
| 
       186 
205 
     | 
    
         
             
                    end
         
     | 
| 
       187 
206 
     | 
    
         
             
                  else
         
     | 
| 
       188 
     | 
    
         
            -
                    def search_index( 
     | 
| 
       189 
     | 
    
         
            -
                      try_index( 
     | 
| 
      
 207 
     | 
    
         
            +
                    def search_index(feature)
         
     | 
| 
      
 208 
     | 
    
         
            +
                      try_index(feature + DOT_RB) || try_index(feature + DLEXT) || try_index(feature)
         
     | 
| 
       190 
209 
     | 
    
         
             
                    end
         
     | 
| 
       191 
210 
     | 
    
         | 
| 
       192 
     | 
    
         
            -
                    def maybe_append_extension( 
     | 
| 
       193 
     | 
    
         
            -
                      try_ext( 
     | 
| 
      
 211 
     | 
    
         
            +
                    def maybe_append_extension(feature)
         
     | 
| 
      
 212 
     | 
    
         
            +
                      try_ext(feature + DOT_RB) || try_ext(feature + DLEXT) || feature
         
     | 
| 
       194 
213 
     | 
    
         
             
                    end
         
     | 
| 
       195 
214 
     | 
    
         
             
                  end
         
     | 
| 
       196 
215 
     | 
    
         | 
| 
       197 
     | 
    
         
            -
                   
     | 
| 
       198 
     | 
    
         
            -
             
     | 
| 
       199 
     | 
    
         
            -
             
     | 
| 
      
 216 
     | 
    
         
            +
                  s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
         
     | 
| 
      
 217 
     | 
    
         
            +
                  if s.respond_to?(:-@)
         
     | 
| 
      
 218 
     | 
    
         
            +
                    if ((-s).equal?(s) && (-s.dup).equal?(s)) || RUBY_VERSION >= "2.7"
         
     | 
| 
      
 219 
     | 
    
         
            +
                      def try_index(feature)
         
     | 
| 
      
 220 
     | 
    
         
            +
                        if (path = @index[feature])
         
     | 
| 
      
 221 
     | 
    
         
            +
                          -File.join(path, feature).freeze
         
     | 
| 
      
 222 
     | 
    
         
            +
                        end
         
     | 
| 
      
 223 
     | 
    
         
            +
                      end
         
     | 
| 
      
 224 
     | 
    
         
            +
                    else
         
     | 
| 
      
 225 
     | 
    
         
            +
                      def try_index(feature)
         
     | 
| 
      
 226 
     | 
    
         
            +
                        if (path = @index[feature])
         
     | 
| 
      
 227 
     | 
    
         
            +
                          -File.join(path, feature).untaint
         
     | 
| 
      
 228 
     | 
    
         
            +
                        end
         
     | 
| 
      
 229 
     | 
    
         
            +
                      end
         
     | 
| 
      
 230 
     | 
    
         
            +
                    end
         
     | 
| 
      
 231 
     | 
    
         
            +
                  else
         
     | 
| 
      
 232 
     | 
    
         
            +
                    def try_index(feature)
         
     | 
| 
      
 233 
     | 
    
         
            +
                      if (path = @index[feature])
         
     | 
| 
      
 234 
     | 
    
         
            +
                        File.join(path, feature)
         
     | 
| 
      
 235 
     | 
    
         
            +
                      end
         
     | 
| 
       200 
236 
     | 
    
         
             
                    end
         
     | 
| 
       201 
237 
     | 
    
         
             
                  end
         
     | 
| 
       202 
238 
     | 
    
         | 
| 
       203 
     | 
    
         
            -
                  def try_ext( 
     | 
| 
       204 
     | 
    
         
            -
                     
     | 
| 
      
 239 
     | 
    
         
            +
                  def try_ext(feature)
         
     | 
| 
      
 240 
     | 
    
         
            +
                    feature if File.exist?(feature)
         
     | 
| 
       205 
241 
     | 
    
         
             
                  end
         
     | 
| 
       206 
242 
     | 
    
         
             
                end
         
     | 
| 
       207 
243 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,4 +1,5 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       2 
3 
     | 
    
         
             
            module Bootsnap
         
     | 
| 
       3 
4 
     | 
    
         
             
              module LoadPathCache
         
     | 
| 
       4 
5 
     | 
    
         
             
                module ChangeObserver
         
     | 
| 
         @@ -15,11 +16,13 @@ module Bootsnap 
     | 
|
| 
       15 
16 
     | 
    
         
             
                      @lpc_observer.push_paths(self, *entries.map(&:to_s))
         
     | 
| 
       16 
17 
     | 
    
         
             
                      super
         
     | 
| 
       17 
18 
     | 
    
         
             
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                    alias_method :append, :push
         
     | 
| 
       18 
20 
     | 
    
         | 
| 
       19 
21 
     | 
    
         
             
                    def unshift(*entries)
         
     | 
| 
       20 
22 
     | 
    
         
             
                      @lpc_observer.unshift_paths(self, *entries.map(&:to_s))
         
     | 
| 
       21 
23 
     | 
    
         
             
                      super
         
     | 
| 
       22 
24 
     | 
    
         
             
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                    alias_method :prepend, :unshift
         
     | 
| 
       23 
26 
     | 
    
         | 
| 
       24 
27 
     | 
    
         
             
                    def concat(entries)
         
     | 
| 
       25 
28 
     | 
    
         
             
                      @lpc_observer.push_paths(self, *entries.map(&:to_s))
         
     | 
| 
         @@ -51,12 +54,30 @@ module Bootsnap 
     | 
|
| 
       51 
54 
     | 
    
         
             
                        ret
         
     | 
| 
       52 
55 
     | 
    
         
             
                      end
         
     | 
| 
       53 
56 
     | 
    
         
             
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    def dup
         
     | 
| 
      
 59 
     | 
    
         
            +
                      [] + self
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    alias_method :clone, :dup
         
     | 
| 
       54 
63 
     | 
    
         
             
                  end
         
     | 
| 
       55 
64 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
                  def self.register( 
     | 
| 
      
 65 
     | 
    
         
            +
                  def self.register(arr, observer)
         
     | 
| 
       57 
66 
     | 
    
         
             
                    return if arr.frozen? # can't register observer, but no need to.
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
       58 
68 
     | 
    
         
             
                    arr.instance_variable_set(:@lpc_observer, observer)
         
     | 
| 
       59 
     | 
    
         
            -
                     
     | 
| 
      
 69 
     | 
    
         
            +
                    ArrayMixin.instance_methods.each do |method_name|
         
     | 
| 
      
 70 
     | 
    
         
            +
                      arr.singleton_class.send(:define_method, method_name, ArrayMixin.instance_method(method_name))
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  def self.unregister(arr)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    return unless arr.instance_variable_defined?(:@lpc_observer) && arr.instance_variable_get(:@lpc_observer)
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    ArrayMixin.instance_methods.each do |method_name|
         
     | 
| 
      
 78 
     | 
    
         
            +
                      arr.singleton_class.send(:remove_method, method_name)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
                    arr.instance_variable_set(:@lpc_observer, nil)
         
     | 
| 
       60 
81 
     | 
    
         
             
                  end
         
     | 
| 
       61 
82 
     | 
    
         
             
                end
         
     | 
| 
       62 
83 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,105 +1,37 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
     | 
    
         
            -
            module Bootsnap
         
     | 
| 
       3 
     | 
    
         
            -
              module LoadPathCache
         
     | 
| 
       4 
     | 
    
         
            -
                module CoreExt
         
     | 
| 
       5 
     | 
    
         
            -
                  def self.make_load_error(path)
         
     | 
| 
       6 
     | 
    
         
            -
                    err = LoadError.new(+"cannot load such file -- #{path}")
         
     | 
| 
       7 
     | 
    
         
            -
                    err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
         
     | 
| 
       8 
     | 
    
         
            -
                    err.define_singleton_method(:path) { path }
         
     | 
| 
       9 
     | 
    
         
            -
                    err
         
     | 
| 
       10 
     | 
    
         
            -
                  end
         
     | 
| 
       11 
     | 
    
         
            -
                end
         
     | 
| 
       12 
     | 
    
         
            -
              end
         
     | 
| 
       13 
     | 
    
         
            -
            end
         
     | 
| 
       14 
2 
     | 
    
         | 
| 
       15 
3 
     | 
    
         
             
            module Kernel
         
     | 
| 
       16 
     | 
    
         
            -
               
     | 
| 
      
 4 
     | 
    
         
            +
              alias_method :require_without_bootsnap, :require
         
     | 
| 
       17 
5 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
              alias_method 
     | 
| 
      
 6 
     | 
    
         
            +
              alias_method :require, :require # Avoid method redefinition warnings
         
     | 
| 
       19 
7 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
               
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
                Bootsnap::LoadPathCache.loaded_features_index.register(path, resolved) do
         
     | 
| 
       23 
     | 
    
         
            -
                  require_without_bootsnap(resolved || path)
         
     | 
| 
       24 
     | 
    
         
            -
                end
         
     | 
| 
       25 
     | 
    
         
            -
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
              def require(path) # rubocop:disable Lint/DuplicateMethods
         
     | 
| 
      
 9 
     | 
    
         
            +
                return require_without_bootsnap(path) unless Bootsnap::LoadPathCache.enabled?
         
     | 
| 
       26 
10 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
                return false if Bootsnap::LoadPathCache.loaded_features_index.key?( 
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path))
         
     | 
| 
       31 
     | 
    
         
            -
                  return require_with_bootsnap_lfi(path, resolved)
         
     | 
| 
       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)
         
     | 
| 
       38 
     | 
    
         
            -
              rescue Bootsnap::LoadPathCache::ReturnFalse
         
     | 
| 
       39 
     | 
    
         
            -
                false
         
     | 
| 
       40 
     | 
    
         
            -
              rescue Bootsnap::LoadPathCache::FallbackScan
         
     | 
| 
       41 
     | 
    
         
            -
                fallback = true
         
     | 
| 
       42 
     | 
    
         
            -
              ensure
         
     | 
| 
       43 
     | 
    
         
            -
                if fallback
         
     | 
| 
       44 
     | 
    
         
            -
                  require_with_bootsnap_lfi(path)
         
     | 
| 
       45 
     | 
    
         
            -
                end
         
     | 
| 
       46 
     | 
    
         
            -
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
                string_path = Bootsnap.rb_get_path(path)
         
     | 
| 
      
 12 
     | 
    
         
            +
                return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
         
     | 
| 
       47 
13 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
                 
     | 
| 
       59 
     | 
    
         
            -
                  return  
     | 
| 
       60 
     | 
    
         
            -
                 
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
       62 
     | 
    
         
            -
                 
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
                   
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       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)
         
     | 
| 
       71 
     | 
    
         
            -
              rescue Bootsnap::LoadPathCache::ReturnFalse
         
     | 
| 
       72 
     | 
    
         
            -
                false
         
     | 
| 
       73 
     | 
    
         
            -
              rescue Bootsnap::LoadPathCache::FallbackScan
         
     | 
| 
       74 
     | 
    
         
            -
                fallback = true
         
     | 
| 
       75 
     | 
    
         
            -
              ensure
         
     | 
| 
       76 
     | 
    
         
            -
                if fallback
         
     | 
| 
       77 
     | 
    
         
            -
                  load_without_bootsnap(path, wrap)
         
     | 
| 
      
 14 
     | 
    
         
            +
                resolved = Bootsnap::LoadPathCache.load_path_cache.find(string_path)
         
     | 
| 
      
 15 
     | 
    
         
            +
                if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  if (cursor = Bootsnap::LoadPathCache.loaded_features_index.cursor(string_path))
         
     | 
| 
      
 17 
     | 
    
         
            +
                    ret = require_without_bootsnap(path)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    resolved = Bootsnap::LoadPathCache.loaded_features_index.identify(string_path, cursor)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    return ret
         
     | 
| 
      
 21 
     | 
    
         
            +
                  else
         
     | 
| 
      
 22 
     | 
    
         
            +
                    return require_without_bootsnap(path)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                elsif false == resolved
         
     | 
| 
      
 25 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 26 
     | 
    
         
            +
                elsif resolved.nil?
         
     | 
| 
      
 27 
     | 
    
         
            +
                  return require_without_bootsnap(path)
         
     | 
| 
      
 28 
     | 
    
         
            +
                else
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # Note that require registers to $LOADED_FEATURES while load does not.
         
     | 
| 
      
 30 
     | 
    
         
            +
                  ret = require_without_bootsnap(resolved)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  return ret
         
     | 
| 
       78 
33 
     | 
    
         
             
                end
         
     | 
| 
       79 
34 
     | 
    
         
             
              end
         
     | 
| 
       80 
     | 
    
         
            -
            end
         
     | 
| 
       81 
35 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
              alias_method(:autoload_without_bootsnap, :autoload)
         
     | 
| 
       84 
     | 
    
         
            -
              def autoload(const, path)
         
     | 
| 
       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)
         
     | 
| 
       96 
     | 
    
         
            -
              rescue Bootsnap::LoadPathCache::ReturnFalse
         
     | 
| 
       97 
     | 
    
         
            -
                false
         
     | 
| 
       98 
     | 
    
         
            -
              rescue Bootsnap::LoadPathCache::FallbackScan
         
     | 
| 
       99 
     | 
    
         
            -
                fallback = true
         
     | 
| 
       100 
     | 
    
         
            -
              ensure
         
     | 
| 
       101 
     | 
    
         
            -
                if fallback
         
     | 
| 
       102 
     | 
    
         
            -
                  autoload_without_bootsnap(const, path)
         
     | 
| 
       103 
     | 
    
         
            -
                end
         
     | 
| 
       104 
     | 
    
         
            -
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
              private :require
         
     | 
| 
       105 
37 
     | 
    
         
             
            end
         
     | 
| 
         @@ -26,20 +26,21 @@ module Bootsnap 
     | 
|
| 
       26 
26 
     | 
    
         
             
                class LoadedFeaturesIndex
         
     | 
| 
       27 
27 
     | 
    
         
             
                  def initialize
         
     | 
| 
       28 
28 
     | 
    
         
             
                    @lfi = {}
         
     | 
| 
       29 
     | 
    
         
            -
                    @mutex =  
     | 
| 
      
 29 
     | 
    
         
            +
                    @mutex = Mutex.new
         
     | 
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
                    # In theory the user could mutate $LOADED_FEATURES and invalidate our
         
     | 
| 
       32 
     | 
    
         
            -
                    # cache. If this ever comes up in practice  
     | 
| 
       33 
     | 
    
         
            -
                    # enterprising reader, feels inclined to solve this problem  
     | 
| 
      
 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 
34 
     | 
    
         
             
                    # parallel the work done with ChangeObserver on $LOAD_PATH to mirror
         
     | 
| 
       35 
35 
     | 
    
         
             
                    # updates to our @lfi.
         
     | 
| 
       36 
36 
     | 
    
         
             
                    $LOADED_FEATURES.each do |feat|
         
     | 
| 
       37 
37 
     | 
    
         
             
                      hash = feat.hash
         
     | 
| 
       38 
38 
     | 
    
         
             
                      $LOAD_PATH.each do |lpe|
         
     | 
| 
       39 
39 
     | 
    
         
             
                        next unless feat.start_with?(lpe)
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
       40 
41 
     | 
    
         
             
                        # /a/b/lib/my/foo.rb
         
     | 
| 
       41 
42 
     | 
    
         
             
                        #          ^^^^^^^^^
         
     | 
| 
       42 
     | 
    
         
            -
                        short = feat[(lpe.length + 1) 
     | 
| 
      
 43 
     | 
    
         
            +
                        short = feat[(lpe.length + 1)..]
         
     | 
| 
       43 
44 
     | 
    
         
             
                        stripped = strip_extension_if_elidable(short)
         
     | 
| 
       44 
45 
     | 
    
         
             
                        @lfi[short] = hash
         
     | 
| 
       45 
46 
     | 
    
         
             
                        @lfi[stripped] = hash
         
     | 
| 
         @@ -58,9 +59,9 @@ module Bootsnap 
     | 
|
| 
       58 
59 
     | 
    
         
             
                  end
         
     | 
| 
       59 
60 
     | 
    
         | 
| 
       60 
61 
     | 
    
         
             
                  def purge_multi(features)
         
     | 
| 
       61 
     | 
    
         
            -
                    rejected_hashes = features. 
     | 
| 
      
 62 
     | 
    
         
            +
                    rejected_hashes = features.each_with_object({}) { |f, h| h[f.hash] = true }
         
     | 
| 
       62 
63 
     | 
    
         
             
                    @mutex.synchronize do
         
     | 
| 
       63 
     | 
    
         
            -
                      @lfi.reject! { |_, hash| rejected_hashes. 
     | 
| 
      
 64 
     | 
    
         
            +
                      @lfi.reject! { |_, hash| rejected_hashes.key?(hash) }
         
     | 
| 
       64 
65 
     | 
    
         
             
                    end
         
     | 
| 
       65 
66 
     | 
    
         
             
                  end
         
     | 
| 
       66 
67 
     | 
    
         | 
| 
         @@ -68,11 +69,30 @@ module Bootsnap 
     | 
|
| 
       68 
69 
     | 
    
         
             
                    @mutex.synchronize { @lfi.key?(feature) }
         
     | 
| 
       69 
70 
     | 
    
         
             
                  end
         
     | 
| 
       70 
71 
     | 
    
         | 
| 
      
 72 
     | 
    
         
            +
                  def cursor(short)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    unless Bootsnap.absolute_path?(short.to_s)
         
     | 
| 
      
 74 
     | 
    
         
            +
                      $LOADED_FEATURES.size
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  def identify(short, cursor)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    $LOADED_FEATURES[cursor..].detect do |feat|
         
     | 
| 
      
 80 
     | 
    
         
            +
                      offset = 0
         
     | 
| 
      
 81 
     | 
    
         
            +
                      while (offset = feat.index(short, offset))
         
     | 
| 
      
 82 
     | 
    
         
            +
                        if feat.index(".", offset + 1) && !feat.index("/", offset + 2)
         
     | 
| 
      
 83 
     | 
    
         
            +
                          break true
         
     | 
| 
      
 84 
     | 
    
         
            +
                        else
         
     | 
| 
      
 85 
     | 
    
         
            +
                          offset += 1
         
     | 
| 
      
 86 
     | 
    
         
            +
                        end
         
     | 
| 
      
 87 
     | 
    
         
            +
                      end
         
     | 
| 
      
 88 
     | 
    
         
            +
                    end
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
       71 
91 
     | 
    
         
             
                  # There is a relatively uncommon case where we could miss adding an
         
     | 
| 
       72 
92 
     | 
    
         
             
                  # entry:
         
     | 
| 
       73 
93 
     | 
    
         
             
                  #
         
     | 
| 
       74 
94 
     | 
    
         
             
                  # If the user asked for e.g. `require 'bundler'`, and we went through the
         
     | 
| 
       75 
     | 
    
         
            -
                  # ` 
     | 
| 
      
 95 
     | 
    
         
            +
                  # `FALLBACK_SCAN` pathway in `kernel_require.rb` and therefore did not
         
     | 
| 
       76 
96 
     | 
    
         
             
                  # pass `long` (the full expanded absolute path), then we did are not able
         
     | 
| 
       77 
97 
     | 
    
         
             
                  # to confidently add the `bundler.rb` form to @lfi.
         
     | 
| 
       78 
98 
     | 
    
         
             
                  #
         
     | 
| 
         @@ -82,15 +102,8 @@ module Bootsnap 
     | 
|
| 
       82 
102 
     | 
    
         
             
                  #    not quite right; or
         
     | 
| 
       83 
103 
     | 
    
         
             
                  # 2. Inspect $LOADED_FEATURES upon return from yield to find the matching
         
     | 
| 
       84 
104 
     | 
    
         
             
                  #    entry.
         
     | 
| 
       85 
     | 
    
         
            -
                  def register(short, long 
     | 
| 
       86 
     | 
    
         
            -
                    if  
     | 
| 
       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
         
     | 
| 
      
 105 
     | 
    
         
            +
                  def register(short, long)
         
     | 
| 
      
 106 
     | 
    
         
            +
                    return if Bootsnap.absolute_path?(short)
         
     | 
| 
       94 
107 
     | 
    
         | 
| 
       95 
108 
     | 
    
         
             
                    hash = long.hash
         
     | 
| 
       96 
109 
     | 
    
         | 
| 
         @@ -109,13 +122,11 @@ module Bootsnap 
     | 
|
| 
       109 
122 
     | 
    
         
             
                      @lfi[short] = hash
         
     | 
| 
       110 
123 
     | 
    
         
             
                      (@lfi[altname] = hash) if altname
         
     | 
| 
       111 
124 
     | 
    
         
             
                    end
         
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
                    ret
         
     | 
| 
       114 
125 
     | 
    
         
             
                  end
         
     | 
| 
       115 
126 
     | 
    
         | 
| 
       116 
127 
     | 
    
         
             
                  private
         
     | 
| 
       117 
128 
     | 
    
         | 
| 
       118 
     | 
    
         
            -
                  STRIP_EXTENSION = /\.[^.] 
     | 
| 
      
 129 
     | 
    
         
            +
                  STRIP_EXTENSION = /\.[^.]*?$/.freeze
         
     | 
| 
       119 
130 
     | 
    
         
             
                  private_constant(:STRIP_EXTENSION)
         
     | 
| 
       120 
131 
     | 
    
         | 
| 
       121 
132 
     | 
    
         
             
                  # Might Ruby automatically search for this extension if
         
     | 
| 
         @@ -132,15 +143,15 @@ module Bootsnap 
     | 
|
| 
       132 
143 
     | 
    
         
             
                  # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
         
     | 
| 
       133 
144 
     | 
    
         
             
                  #
         
     | 
| 
       134 
145 
     | 
    
         
             
                  # See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
         
     | 
| 
       135 
     | 
    
         
            -
                  def extension_elidable?( 
     | 
| 
       136 
     | 
    
         
            -
                     
     | 
| 
      
 146 
     | 
    
         
            +
                  def extension_elidable?(feature)
         
     | 
| 
      
 147 
     | 
    
         
            +
                    feature.to_s.end_with?(".rb", ".so", ".o", ".dll", ".dylib")
         
     | 
| 
       137 
148 
     | 
    
         
             
                  end
         
     | 
| 
       138 
149 
     | 
    
         | 
| 
       139 
     | 
    
         
            -
                  def strip_extension_if_elidable( 
     | 
| 
       140 
     | 
    
         
            -
                    if extension_elidable?( 
     | 
| 
       141 
     | 
    
         
            -
                       
     | 
| 
      
 150 
     | 
    
         
            +
                  def strip_extension_if_elidable(feature)
         
     | 
| 
      
 151 
     | 
    
         
            +
                    if extension_elidable?(feature)
         
     | 
| 
      
 152 
     | 
    
         
            +
                      feature.sub(STRIP_EXTENSION, "")
         
     | 
| 
       142 
153 
     | 
    
         
             
                    else
         
     | 
| 
       143 
     | 
    
         
            -
                       
     | 
| 
      
 154 
     | 
    
         
            +
                      feature
         
     | 
| 
       144 
155 
     | 
    
         
             
                    end
         
     | 
| 
       145 
156 
     | 
    
         
             
                  end
         
     | 
| 
       146 
157 
     | 
    
         
             
                end
         
     | 
| 
         @@ -1,5 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative "path_scanner"
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
5 
     | 
    
         
             
            module Bootsnap
         
     | 
| 
       5 
6 
     | 
    
         
             
              module LoadPathCache
         
     | 
| 
         @@ -20,14 +21,32 @@ module Bootsnap 
     | 
|
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         
             
                  attr_reader(:path)
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                  def initialize(path)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def initialize(path, real: false)
         
     | 
| 
       24 
25 
     | 
    
         
             
                    @path = path.to_s.freeze
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @real = real
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def to_realpath
         
     | 
| 
      
 30 
     | 
    
         
            +
                    return self if @real
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    realpath = begin
         
     | 
| 
      
 33 
     | 
    
         
            +
                      File.realpath(path)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    rescue Errno::ENOENT
         
     | 
| 
      
 35 
     | 
    
         
            +
                      return self
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    if realpath == path
         
     | 
| 
      
 39 
     | 
    
         
            +
                      @real = true
         
     | 
| 
      
 40 
     | 
    
         
            +
                      self
         
     | 
| 
      
 41 
     | 
    
         
            +
                    else
         
     | 
| 
      
 42 
     | 
    
         
            +
                      Path.new(realpath, real: true)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
       25 
44 
     | 
    
         
             
                  end
         
     | 
| 
       26 
45 
     | 
    
         | 
| 
       27 
46 
     | 
    
         
             
                  # True if the path exists, but represents a non-directory object
         
     | 
| 
       28 
47 
     | 
    
         
             
                  def non_directory?
         
     | 
| 
       29 
48 
     | 
    
         
             
                    !File.stat(path).directory?
         
     | 
| 
       30 
     | 
    
         
            -
                  rescue Errno::ENOENT, Errno::ENOTDIR
         
     | 
| 
      
 49 
     | 
    
         
            +
                  rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
         
     | 
| 
       31 
50 
     | 
    
         
             
                    false
         
     | 
| 
       32 
51 
     | 
    
         
             
                  end
         
     | 
| 
       33 
52 
     | 
    
         | 
| 
         @@ -43,6 +62,7 @@ module Bootsnap 
     | 
|
| 
       43 
62 
     | 
    
         
             
                      # set to zero anyway, just in case we change the stability heuristics.
         
     | 
| 
       44 
63 
     | 
    
         
             
                      _, entries, dirs = store.get(expanded_path)
         
     | 
| 
       45 
64 
     | 
    
         
             
                      return [entries, dirs] if entries # cache hit
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
       46 
66 
     | 
    
         
             
                      entries, dirs = scan!
         
     | 
| 
       47 
67 
     | 
    
         
             
                      store.set(expanded_path, [0, entries, dirs])
         
     | 
| 
       48 
68 
     | 
    
         
             
                      return [entries, dirs]
         
     | 
| 
         @@ -60,7 +80,11 @@ module Bootsnap 
     | 
|
| 
       60 
80 
     | 
    
         
             
                  end
         
     | 
| 
       61 
81 
     | 
    
         | 
| 
       62 
82 
     | 
    
         
             
                  def expanded_path
         
     | 
| 
       63 
     | 
    
         
            -
                     
     | 
| 
      
 83 
     | 
    
         
            +
                    if @real
         
     | 
| 
      
 84 
     | 
    
         
            +
                      path
         
     | 
| 
      
 85 
     | 
    
         
            +
                    else
         
     | 
| 
      
 86 
     | 
    
         
            +
                      @expanded_path ||= File.expand_path(path).freeze
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end
         
     | 
| 
       64 
88 
     | 
    
         
             
                  end
         
     | 
| 
       65 
89 
     | 
    
         | 
| 
       66 
90 
     | 
    
         
             
                  private
         
     | 
| 
         @@ -77,7 +101,7 @@ module Bootsnap 
     | 
|
| 
       77 
101 
     | 
    
         
             
                    ["", *dirs].each do |dir|
         
     | 
| 
       78 
102 
     | 
    
         
             
                      curr = begin
         
     | 
| 
       79 
103 
     | 
    
         
             
                        File.mtime("#{path}/#{dir}").to_i
         
     | 
| 
       80 
     | 
    
         
            -
                             rescue Errno::ENOENT, Errno::ENOTDIR
         
     | 
| 
      
 104 
     | 
    
         
            +
                             rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
         
     | 
| 
       81 
105 
     | 
    
         
             
                               -1
         
     | 
| 
       82 
106 
     | 
    
         
             
                      end
         
     | 
| 
       83 
107 
     | 
    
         
             
                      max = curr if curr > max
         
     | 
| 
         @@ -92,21 +116,19 @@ module Bootsnap 
     | 
|
| 
       92 
116 
     | 
    
         
             
                  VOLATILE = :volatile
         
     | 
| 
       93 
117 
     | 
    
         | 
| 
       94 
118 
     | 
    
         
             
                  # Built-in ruby lib stuff doesn't change, but things can occasionally be
         
     | 
| 
       95 
     | 
    
         
            -
                  # installed into sitedir, which generally lives under  
     | 
| 
       96 
     | 
    
         
            -
                  RUBY_LIBDIR  = RbConfig::CONFIG[ 
     | 
| 
       97 
     | 
    
         
            -
                  RUBY_SITEDIR = RbConfig::CONFIG[ 
     | 
| 
      
 119 
     | 
    
         
            +
                  # installed into sitedir, which generally lives under rubylibdir.
         
     | 
| 
      
 120 
     | 
    
         
            +
                  RUBY_LIBDIR  = RbConfig::CONFIG["rubylibdir"]
         
     | 
| 
      
 121 
     | 
    
         
            +
                  RUBY_SITEDIR = RbConfig::CONFIG["sitedir"]
         
     | 
| 
       98 
122 
     | 
    
         | 
| 
       99 
123 
     | 
    
         
             
                  def stability
         
     | 
| 
       100 
     | 
    
         
            -
                    @stability ||=  
     | 
| 
       101 
     | 
    
         
            -
                       
     | 
| 
       102 
     | 
    
         
            -
             
     | 
| 
       103 
     | 
    
         
            -
                       
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
                       
     | 
| 
       106 
     | 
    
         
            -
             
     | 
| 
       107 
     | 
    
         
            -
                       
     | 
| 
       108 
     | 
    
         
            -
                        VOLATILE
         
     | 
| 
       109 
     | 
    
         
            -
                      end
         
     | 
| 
      
 124 
     | 
    
         
            +
                    @stability ||= if Gem.path.detect { |p| expanded_path.start_with?(p.to_s) }
         
     | 
| 
      
 125 
     | 
    
         
            +
                      STABLE
         
     | 
| 
      
 126 
     | 
    
         
            +
                    elsif Bootsnap.bundler? && expanded_path.start_with?(Bundler.bundle_path.to_s)
         
     | 
| 
      
 127 
     | 
    
         
            +
                      STABLE
         
     | 
| 
      
 128 
     | 
    
         
            +
                    elsif expanded_path.start_with?(RUBY_LIBDIR) && !expanded_path.start_with?(RUBY_SITEDIR)
         
     | 
| 
      
 129 
     | 
    
         
            +
                      STABLE
         
     | 
| 
      
 130 
     | 
    
         
            +
                    else
         
     | 
| 
      
 131 
     | 
    
         
            +
                      VOLATILE
         
     | 
| 
       110 
132 
     | 
    
         
             
                    end
         
     | 
| 
       111 
133 
     | 
    
         
             
                  end
         
     | 
| 
       112 
134 
     | 
    
         
             
                end
         
     |