bootsnap 1.11.1 → 1.18.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kernel
4
- module_function
4
+ alias_method :require_without_bootsnap, :require
5
5
 
6
- alias_method(:require_without_bootsnap, :require)
6
+ alias_method :require, :require # Avoid method redefinition warnings
7
+
8
+ def require(path) # rubocop:disable Lint/DuplicateMethods
9
+ return require_without_bootsnap(path) unless Bootsnap::LoadPathCache.enabled?
7
10
 
8
- def require(path)
9
11
  string_path = Bootsnap.rb_get_path(path)
10
12
  return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
11
13
 
@@ -22,9 +24,7 @@ module Kernel
22
24
  elsif false == resolved
23
25
  return false
24
26
  elsif resolved.nil?
25
- error = LoadError.new(+"cannot load such file -- #{path}")
26
- error.instance_variable_set(:@path, path)
27
- raise error
27
+ return require_without_bootsnap(path)
28
28
  else
29
29
  # Note that require registers to $LOADED_FEATURES while load does not.
30
30
  ret = require_without_bootsnap(resolved)
@@ -33,33 +33,5 @@ module Kernel
33
33
  end
34
34
  end
35
35
 
36
- alias_method(:load_without_bootsnap, :load)
37
- def load(path, wrap = false)
38
- if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path), try_extensions: false))
39
- load_without_bootsnap(resolved, wrap)
40
- else
41
- load_without_bootsnap(path, wrap)
42
- end
43
- end
44
- end
45
-
46
- class Module
47
- alias_method(:autoload_without_bootsnap, :autoload)
48
- def autoload(const, path)
49
- # NOTE: This may defeat LoadedFeaturesIndex, but it's not immediately
50
- # obvious how to make it work. This feels like a pretty niche case, unclear
51
- # if it will ever burn anyone.
52
- #
53
- # The challenge is that we don't control the point at which the entry gets
54
- # added to $LOADED_FEATURES and won't be able to hook that modification
55
- # since it's done in C-land.
56
- resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path))
57
- if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
58
- autoload_without_bootsnap(const, path)
59
- elsif resolved == false
60
- return false
61
- else
62
- autoload_without_bootsnap(const, resolved || path)
63
- end
64
- end
36
+ private :require
65
37
  end
@@ -40,7 +40,7 @@ module Bootsnap
40
40
 
41
41
  # /a/b/lib/my/foo.rb
42
42
  # ^^^^^^^^^
43
- short = feat[(lpe.length + 1)..-1]
43
+ short = feat[(lpe.length + 1)..]
44
44
  stripped = strip_extension_if_elidable(short)
45
45
  @lfi[short] = hash
46
46
  @lfi[stripped] = hash
@@ -76,7 +76,7 @@ module Bootsnap
76
76
  end
77
77
 
78
78
  def identify(short, cursor)
79
- $LOADED_FEATURES[cursor..-1].detect do |feat|
79
+ $LOADED_FEATURES[cursor..].detect do |feat|
80
80
  offset = 0
81
81
  while (offset = feat.index(short, offset))
82
82
  if feat.index(".", offset + 1) && !feat.index("/", offset + 2)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative("path_scanner")
3
+ require_relative "path_scanner"
4
4
 
5
5
  module Bootsnap
6
6
  module LoadPathCache
@@ -35,18 +35,18 @@ module Bootsnap
35
35
  return self
36
36
  end
37
37
 
38
- if realpath != path
39
- Path.new(realpath, real: true)
40
- else
38
+ if realpath == path
41
39
  @real = true
42
40
  self
41
+ else
42
+ Path.new(realpath, real: true)
43
43
  end
44
44
  end
45
45
 
46
46
  # True if the path exists, but represents a non-directory object
47
47
  def non_directory?
48
48
  !File.stat(path).directory?
49
- rescue Errno::ENOENT, Errno::ENOTDIR
49
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
50
50
  false
51
51
  end
52
52
 
@@ -101,7 +101,7 @@ module Bootsnap
101
101
  ["", *dirs].each do |dir|
102
102
  curr = begin
103
103
  File.mtime("#{path}/#{dir}").to_i
104
- rescue Errno::ENOENT, Errno::ENOTDIR
104
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
105
105
  -1
106
106
  end
107
107
  max = curr if curr > max
@@ -116,21 +116,19 @@ module Bootsnap
116
116
  VOLATILE = :volatile
117
117
 
118
118
  # Built-in ruby lib stuff doesn't change, but things can occasionally be
119
- # installed into sitedir, which generally lives under libdir.
120
- RUBY_LIBDIR = RbConfig::CONFIG["libdir"]
119
+ # installed into sitedir, which generally lives under rubylibdir.
120
+ RUBY_LIBDIR = RbConfig::CONFIG["rubylibdir"]
121
121
  RUBY_SITEDIR = RbConfig::CONFIG["sitedir"]
122
122
 
123
123
  def stability
124
- @stability ||= begin
125
- if Gem.path.detect { |p| expanded_path.start_with?(p.to_s) }
126
- STABLE
127
- elsif Bootsnap.bundler? && expanded_path.start_with?(Bundler.bundle_path.to_s)
128
- STABLE
129
- elsif expanded_path.start_with?(RUBY_LIBDIR) && !expanded_path.start_with?(RUBY_SITEDIR)
130
- STABLE
131
- else
132
- VOLATILE
133
- 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
134
132
  end
135
133
  end
136
134
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative("../explicit_require")
3
+ require_relative "../explicit_require"
4
4
 
5
5
  module Bootsnap
6
6
  module LoadPathCache
@@ -15,7 +15,11 @@ module Bootsnap
15
15
  ""
16
16
  end
17
17
 
18
+ @ignored_directories = %w(node_modules)
19
+
18
20
  class << self
21
+ attr_accessor :ignored_directories
22
+
19
23
  def call(path)
20
24
  path = File.expand_path(path.to_s).freeze
21
25
  return [[], []] unless File.directory?(path)
@@ -50,6 +54,8 @@ module Bootsnap
50
54
 
51
55
  absolute_path = "#{absolute_dir_path}/#{name}"
52
56
  if File.directory?(absolute_path)
57
+ next if ignored_directories.include?(name) || ignored_directories.include?(absolute_path)
58
+
53
59
  if yield relative_path, absolute_path, true
54
60
  walk(absolute_path, relative_path, &block)
55
61
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative("../explicit_require")
3
+ require_relative "../explicit_require"
4
4
 
5
- Bootsnap::ExplicitRequire.with_gems("msgpack") { require("msgpack") }
5
+ Bootsnap::ExplicitRequire.with_gems("msgpack") { require "msgpack" }
6
6
 
7
7
  module Bootsnap
8
8
  module LoadPathCache
@@ -13,10 +13,11 @@ module Bootsnap
13
13
  NestedTransactionError = Class.new(StandardError)
14
14
  SetOutsideTransactionNotAllowed = Class.new(StandardError)
15
15
 
16
- def initialize(store_path)
16
+ def initialize(store_path, readonly: false)
17
17
  @store_path = store_path
18
18
  @txn_mutex = Mutex.new
19
19
  @dirty = false
20
+ @readonly = readonly
20
21
  load_data
21
22
  end
22
23
 
@@ -49,11 +50,9 @@ module Bootsnap
49
50
  raise(NestedTransactionError) if @txn_mutex.owned?
50
51
 
51
52
  @txn_mutex.synchronize do
52
- begin
53
- yield
54
- ensure
55
- commit_transaction
56
- end
53
+ yield
54
+ ensure
55
+ commit_transaction
57
56
  end
58
57
  end
59
58
 
@@ -65,7 +64,7 @@ module Bootsnap
65
64
  end
66
65
 
67
66
  def commit_transaction
68
- if @dirty
67
+ if @dirty && !@readonly
69
68
  dump_data
70
69
  @dirty = false
71
70
  end
@@ -121,11 +120,9 @@ module Bootsnap
121
120
  path = File.dirname(path)
122
121
  end
123
122
  stack.reverse_each do |dir|
124
- begin
125
- Dir.mkdir(dir)
126
- rescue SystemCallError
127
- raise unless File.directory?(dir)
128
- end
123
+ Dir.mkdir(dir)
124
+ rescue SystemCallError
125
+ raise unless File.directory?(dir)
129
126
  end
130
127
  end
131
128
  end
@@ -21,37 +21,60 @@ module Bootsnap
21
21
 
22
22
  CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
23
23
 
24
+ @enabled = false
25
+
24
26
  class << self
25
- attr_reader(:load_path_cache, :loaded_features_index)
27
+ attr_reader(:load_path_cache, :loaded_features_index, :enabled)
28
+ alias_method :enabled?, :enabled
29
+ remove_method(:enabled)
26
30
 
27
- def setup(cache_path:, development_mode:)
31
+ def setup(cache_path:, development_mode:, ignore_directories:, readonly: false)
28
32
  unless supported?
29
33
  warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
30
34
  return
31
35
  end
32
36
 
33
- store = Store.new(cache_path)
37
+ store = Store.new(cache_path, readonly: readonly)
34
38
 
35
39
  @loaded_features_index = LoadedFeaturesIndex.new
36
40
 
41
+ PathScanner.ignored_directories = ignore_directories if ignore_directories
37
42
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
38
- require_relative("load_path_cache/core_ext/kernel_require")
39
- require_relative("load_path_cache/core_ext/loaded_features")
43
+ @enabled = true
44
+ require_relative "load_path_cache/core_ext/kernel_require"
45
+ require_relative "load_path_cache/core_ext/loaded_features"
46
+ end
47
+
48
+ def unload!
49
+ @enabled = false
50
+ @loaded_features_index = nil
51
+ @realpath_cache = nil
52
+ @load_path_cache = nil
53
+ ChangeObserver.unregister($LOAD_PATH) if supported?
40
54
  end
41
55
 
42
56
  def supported?
43
- RUBY_ENGINE == "ruby" &&
44
- RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
57
+ if RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
58
+ case RUBY_ENGINE
59
+ when "truffleruby"
60
+ # https://github.com/oracle/truffleruby/issues/3131
61
+ RUBY_ENGINE_VERSION >= "23.1.0"
62
+ when "ruby"
63
+ true
64
+ else
65
+ false
66
+ end
67
+ end
45
68
  end
46
69
  end
47
70
  end
48
71
  end
49
72
 
50
73
  if Bootsnap::LoadPathCache.supported?
51
- require_relative("load_path_cache/path_scanner")
52
- require_relative("load_path_cache/path")
53
- require_relative("load_path_cache/cache")
54
- require_relative("load_path_cache/store")
55
- require_relative("load_path_cache/change_observer")
56
- require_relative("load_path_cache/loaded_features_index")
74
+ require_relative "load_path_cache/path_scanner"
75
+ require_relative "load_path_cache/path"
76
+ require_relative "load_path_cache/cache"
77
+ require_relative "load_path_cache/store"
78
+ require_relative "load_path_cache/change_observer"
79
+ require_relative "load_path_cache/loaded_features_index"
57
80
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative("../bootsnap")
3
+ require_relative "../bootsnap"
4
4
 
5
5
  Bootsnap.default_setup
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- VERSION = "1.11.1"
4
+ VERSION = "1.18.3"
5
5
  end
data/lib/bootsnap.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative("bootsnap/version")
4
- require_relative("bootsnap/bundler")
5
- require_relative("bootsnap/load_path_cache")
6
- require_relative("bootsnap/compile_cache")
3
+ require_relative "bootsnap/version"
4
+ require_relative "bootsnap/bundler"
5
+ require_relative "bootsnap/load_path_cache"
6
+ require_relative "bootsnap/compile_cache"
7
7
 
8
8
  module Bootsnap
9
9
  InvalidConfiguration = Class.new(StandardError)
@@ -11,6 +11,16 @@ module Bootsnap
11
11
  class << self
12
12
  attr_reader :logger
13
13
 
14
+ def log_stats!
15
+ stats = {hit: 0, revalidated: 0, miss: 0, stale: 0}
16
+ self.instrumentation = ->(event, _path) { stats[event] += 1 }
17
+ Kernel.at_exit do
18
+ stats.each do |event, count|
19
+ $stderr.puts "bootsnap #{event}: #{count}"
20
+ end
21
+ end
22
+ end
23
+
14
24
  def log!
15
25
  self.logger = $stderr.method(:puts)
16
26
  end
@@ -18,9 +28,9 @@ module Bootsnap
18
28
  def logger=(logger)
19
29
  @logger = logger
20
30
  self.instrumentation = if logger.respond_to?(:debug)
21
- ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") }
31
+ ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") unless event == :hit }
22
32
  else
23
- ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") }
33
+ ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") unless event == :hit }
24
34
  end
25
35
  end
26
36
 
@@ -39,48 +49,34 @@ module Bootsnap
39
49
  cache_dir:,
40
50
  development_mode: true,
41
51
  load_path_cache: true,
42
- autoload_paths_cache: nil,
43
- disable_trace: nil,
52
+ ignore_directories: nil,
53
+ readonly: false,
54
+ revalidation: false,
44
55
  compile_cache_iseq: true,
45
56
  compile_cache_yaml: true,
46
57
  compile_cache_json: true
47
58
  )
48
- unless autoload_paths_cache.nil?
49
- warn "[DEPRECATED] Bootsnap's `autoload_paths_cache:` option is deprecated and will be removed. " \
50
- "If you use Zeitwerk this option is useless, and if you are still using the classic autoloader " \
51
- "upgrading is recommended."
52
- end
53
-
54
- unless disable_trace.nil?
55
- warn "[DEPRECATED] Bootsnap's `disable_trace:` option is deprecated and will be removed. " \
56
- "If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended."
57
- end
58
-
59
- if compile_cache_iseq && !iseq_cache_supported?
60
- warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommened " \
61
- "to turn `compile_cache_iseq` off on Ruby 2.5"
62
- end
63
-
64
59
  if load_path_cache
65
60
  Bootsnap::LoadPathCache.setup(
66
- cache_path: cache_dir + "/bootsnap/load-path-cache",
61
+ cache_path: "#{cache_dir}/bootsnap/load-path-cache",
67
62
  development_mode: development_mode,
63
+ ignore_directories: ignore_directories,
64
+ readonly: readonly,
68
65
  )
69
66
  end
70
67
 
71
68
  Bootsnap::CompileCache.setup(
72
- cache_dir: cache_dir + "/bootsnap/compile-cache",
69
+ cache_dir: "#{cache_dir}/bootsnap/compile-cache",
73
70
  iseq: compile_cache_iseq,
74
71
  yaml: compile_cache_yaml,
75
72
  json: compile_cache_json,
73
+ readonly: readonly,
74
+ revalidation: revalidation,
76
75
  )
77
76
  end
78
77
 
79
- def iseq_cache_supported?
80
- return @iseq_cache_supported if defined? @iseq_cache_supported
81
-
82
- ruby_version = Gem::Version.new(RUBY_VERSION)
83
- @iseq_cache_supported = ruby_version < Gem::Version.new("2.5.0") || ruby_version >= Gem::Version.new("2.6.0")
78
+ def unload_cache!
79
+ LoadPathCache.unload!
84
80
  end
85
81
 
86
82
  def default_setup
@@ -109,17 +105,25 @@ module Bootsnap
109
105
  cache_dir = File.join(app_root, "tmp", "cache")
110
106
  end
111
107
 
108
+ ignore_directories = if ENV.key?("BOOTSNAP_IGNORE_DIRECTORIES")
109
+ ENV["BOOTSNAP_IGNORE_DIRECTORIES"].split(",")
110
+ end
111
+
112
112
  setup(
113
113
  cache_dir: cache_dir,
114
114
  development_mode: development_mode,
115
115
  load_path_cache: !ENV["DISABLE_BOOTSNAP_LOAD_PATH_CACHE"],
116
- compile_cache_iseq: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"] && iseq_cache_supported?,
116
+ compile_cache_iseq: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
117
117
  compile_cache_yaml: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
118
118
  compile_cache_json: !ENV["DISABLE_BOOTSNAP_COMPILE_CACHE"],
119
+ readonly: !!ENV["BOOTSNAP_READONLY"],
120
+ ignore_directories: ignore_directories,
119
121
  )
120
122
 
121
123
  if ENV["BOOTSNAP_LOG"]
122
124
  log!
125
+ elsif ENV["BOOTSNAP_STATS"]
126
+ log_stats!
123
127
  end
124
128
  end
125
129
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.1
4
+ version: 1.18.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-08 00:00:00.000000000 Z
11
+ date: 2024-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -76,14 +76,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
76
  requirements:
77
77
  - - ">="
78
78
  - !ruby/object:Gem::Version
79
- version: 2.4.0
79
+ version: 2.6.0
80
80
  required_rubygems_version: !ruby/object:Gem::Requirement
81
81
  requirements:
82
82
  - - ">="
83
83
  - !ruby/object:Gem::Version
84
84
  version: '0'
85
85
  requirements: []
86
- rubygems_version: 3.2.20
86
+ rubygems_version: 3.5.5
87
87
  signing_key:
88
88
  specification_version: 4
89
89
  summary: Boot large ruby/rails apps faster