bootsnap 1.20.1 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19e8b61a552b7f3c9bb4425cbbd58581aff5fed55607ca105b84bc414bb2dbed
4
- data.tar.gz: c9aed95261640936c810231f33392b909039d8f09b33883f0e9082b0cf7ead9f
3
+ metadata.gz: 4e0ad269f816c24dc901b6544acb59c5ebd65b22432f39ee69bc3387b9c2b04d
4
+ data.tar.gz: 700f525d90d4e77421ce83e38f8731981a48cc82a8b9a937c29f661972f78d7f
5
5
  SHA512:
6
- metadata.gz: 65607260e60599f4d0ed4c30bf43bc2b42a8e22611fcba4f1cd3abb3563a1158556e22cb78e05ba16af5c55b97ecf88d45a63c2a65d9f28082bc0d75ec759174
7
- data.tar.gz: 0ac04f4093bddc1457fbd3dc9b14083570c36f6cf0f19ab9ac62f93112944502b32f483e01c11b2c112349ca4c2811480a12e3112ed2ddbe8cb0e4f35977928a
6
+ metadata.gz: f5859d7fd4b81f1969c55e12b33a957b34b1dc2825d03bc3f7ab60a57d34153cbcdda7a60626ea758fd66877b1b74a58d17b276aa2055f41086488d6ced5317b
7
+ data.tar.gz: c09f2cd48da0ba3bb5e9d3c995ffb86b8b16aec987c311162fc79ca648a66f355e4051d6ae4c5ee486d5ad2d2bc3af32090e15776e1f45129c1ea8c8e6c811ed
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.23.0
4
+
5
+ * Require Ruby 2.7.
6
+ * Fix support for absolute paths in `BOOTSNAP_IGNORE_DIRECTORIES`.
7
+
8
+ # 1.22.0
9
+
10
+ * Better fix for the `opendir` crash.
11
+ * Add `bootsnap/rake` for cleaning the bootsnap cache as part of `rake clobber`.
12
+
13
+ # 1.21.1
14
+
15
+ * Prevent a Ruby crash while scanning load path if `opendir` fails without setting `errno`.
16
+ According to the C spec this should not happen, but according to user reports, it did.
17
+
18
+ # 1.21.0
19
+
20
+ * Fix the `require` decorator to handle `Bootsnap.unload_cache!` being called.
21
+ * Minor optimization: Eagerly clear cache buffers to appease the GC.
22
+
3
23
  # 1.20.1
4
24
 
5
25
  * Handle broken symlinks in load path scanning code.
@@ -21,6 +21,10 @@
21
21
  #include <sys/stat.h>
22
22
  #include <dirent.h>
23
23
 
24
+ #ifndef RBIMPL_ATTR_NORETURN
25
+ #define RBIMPL_ATTR_NORETURN()
26
+ #endif
27
+
24
28
  #ifdef __APPLE__
25
29
  // The symbol is present, however not in the headers
26
30
  // See: https://github.com/rails/bootsnap/issues/470
@@ -152,22 +156,37 @@ bs_rb_get_path(VALUE self, VALUE fname)
152
156
  }
153
157
 
154
158
  #ifdef HAVE_FSTATAT
159
+
160
+ RBIMPL_ATTR_NORETURN()
161
+ static void
162
+ bs_syserr_fail_path(const char *func_name, int n, VALUE path)
163
+ {
164
+ rb_syserr_fail_str(n, rb_sprintf("%s @ %s", func_name, RSTRING_PTR(path)));
165
+ }
166
+
167
+ RBIMPL_ATTR_NORETURN()
168
+ static void
169
+ bs_syserr_fail_dir_entry(const char *func_name, int n, VALUE dir, const char *d_name)
170
+ {
171
+ rb_syserr_fail_str(n, rb_sprintf("%s @ %s/%s", func_name, RSTRING_PTR(dir), d_name));
172
+ }
173
+
155
174
  static VALUE
156
175
  bs_rb_scan_dir(VALUE self, VALUE abspath)
157
176
  {
158
177
  Check_Type(abspath, T_STRING);
159
178
 
160
- DIR *dirp = opendir(RSTRING_PTR(abspath));
161
-
162
179
  VALUE dirs = rb_ary_new();
163
180
  VALUE requirables = rb_ary_new();
164
181
  VALUE result = rb_ary_new_from_args(2, requirables, dirs);
165
182
 
183
+ DIR *dirp = opendir(RSTRING_PTR(abspath));
166
184
  if (dirp == NULL) {
167
185
  if (errno == ENOTDIR || errno == ENOENT) {
168
186
  return result;
169
187
  }
170
- rb_sys_fail("opendir");
188
+
189
+ bs_syserr_fail_path("opendir", errno, abspath);
171
190
  return Qundef;
172
191
  }
173
192
 
@@ -175,8 +194,12 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
175
194
  struct stat st;
176
195
  int dfd = -1;
177
196
 
178
- errno = 0;
179
- while ((entry = readdir(dirp))) {
197
+ while (1) {
198
+ errno = 0;
199
+
200
+ entry = readdir(dirp);
201
+ if (entry == NULL) break;
202
+
180
203
  if (entry->d_name[0] == '.') continue;
181
204
 
182
205
  if (RB_UNLIKELY(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) {
@@ -185,17 +208,19 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
185
208
  if (dfd < 0) {
186
209
  dfd = dirfd(dirp);
187
210
  if (dfd < 0) {
188
- rb_sys_fail("dirfd");
211
+ int err = errno;
212
+ closedir(dirp);
213
+ bs_syserr_fail_path("dirfd", err, abspath);
189
214
  return Qundef;
190
215
  }
191
216
  }
192
217
 
193
218
  if (fstatat(dfd, entry->d_name, &st, 0)) {
194
- if (errno == ENOENT) {
195
- // Broken symlinK
196
- continue;
197
- }
198
- rb_sys_fail("fstatat");
219
+ if (errno == ENOENT) continue; // Broken symlink
220
+
221
+ int err = errno;
222
+ closedir(dirp);
223
+ bs_syserr_fail_dir_entry("fstatat", err, abspath, entry->d_name);
199
224
  return Qundef;
200
225
  }
201
226
 
@@ -225,10 +250,15 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
225
250
  }
226
251
  }
227
252
 
228
- if (closedir(dirp)) {
229
- rb_sys_fail("closedir");
253
+ if (errno) {
254
+ int err = errno;
255
+ closedir(dirp);
256
+ bs_syserr_fail_path("readdir", err, abspath);
257
+ } else if (closedir(dirp)) {
258
+ bs_syserr_fail_path("closedir", errno, abspath);
230
259
  return Qundef;
231
260
  }
261
+
232
262
  return result;
233
263
  }
234
264
  #endif
@@ -50,7 +50,9 @@ module Bootsnap
50
50
  end
51
51
 
52
52
  def self.storage_to_output(binary, _args)
53
- RubyVM::InstructionSequence.load_from_binary(binary)
53
+ iseq = RubyVM::InstructionSequence.load_from_binary(binary)
54
+ binary.clear
55
+ iseq
54
56
  rescue RuntimeError => error
55
57
  if error.message == "broken binary format"
56
58
  $stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
@@ -95,7 +97,7 @@ module Bootsnap
95
97
  end
96
98
 
97
99
  def compile_option=(hash)
98
- super(hash)
100
+ super
99
101
  Bootsnap::CompileCache::ISeq.compile_option_updated
100
102
  end
101
103
  end
@@ -170,7 +170,9 @@ module Bootsnap
170
170
  unpacker = CompileCache::YAML.msgpack_factory.unpacker(kwargs)
171
171
  unpacker.feed(data)
172
172
  _safe_loaded = unpacker.unpack
173
- unpacker.unpack
173
+ result = unpacker.unpack
174
+ data.clear
175
+ result
174
176
  end
175
177
 
176
178
  def input_to_output(data, kwargs)
@@ -5,7 +5,7 @@ module Kernel
5
5
 
6
6
  alias_method :require, :require # Avoid method redefinition warnings
7
7
 
8
- def require(path) # rubocop:disable Lint/DuplicateMethods
8
+ def require(path)
9
9
  return require_without_bootsnap(path) unless Bootsnap::LoadPathCache.enabled?
10
10
 
11
11
  string_path = Bootsnap.rb_get_path(path)
@@ -15,8 +15,11 @@ module Kernel
15
15
  if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
16
16
  if (cursor = Bootsnap::LoadPathCache.loaded_features_index.cursor(string_path))
17
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)
18
+
19
+ # The file we required may have unloaded the cache
20
+ resolved = Bootsnap::LoadPathCache.loaded_features_index&.identify(string_path, cursor)
21
+ Bootsnap::LoadPathCache.loaded_features_index&.register(string_path, resolved)
22
+
20
23
  return ret
21
24
  else
22
25
  return require_without_bootsnap(path)
@@ -28,7 +31,9 @@ module Kernel
28
31
  else
29
32
  # Note that require registers to $LOADED_FEATURES while load does not.
30
33
  ret = require_without_bootsnap(resolved)
31
- Bootsnap::LoadPathCache.loaded_features_index.register(string_path, resolved)
34
+
35
+ # The file we required may have unloaded the cache
36
+ Bootsnap::LoadPathCache.loaded_features_index&.register(string_path, resolved)
32
37
  return ret
33
38
  end
34
39
  end
@@ -18,21 +18,12 @@ module Bootsnap
18
18
  class << self
19
19
  attr_accessor :ignored_directories
20
20
 
21
- def ruby_call(path)
22
- path = File.expand_path(path.to_s).freeze
23
- return [] unless File.directory?(path)
24
-
25
- # If the bundle path is a descendent of this path, we do additional
26
- # checks to prevent recursing into the bundle path as we recurse
27
- # through this path. We don't want to scan the bundle path because
28
- # anything useful in it will be present on other load path items.
29
- #
30
- # This can happen if, for example, the user adds '.' to the load path,
31
- # and the bundle path is '.bundle'.
32
- contains_bundle_path = BUNDLE_PATH.start_with?(path)
21
+ def ruby_call(root_path)
22
+ root_path, contains_bundle_path, ignored_abs_paths, ignored_dir_names = prepare_scan(root_path)
23
+ return [] unless File.directory?(root_path)
33
24
 
34
25
  requirables = []
35
- walk(path, nil) do |relative_path, absolute_path, is_directory|
26
+ walk(root_path, nil, ignored_abs_paths, ignored_dir_names) do |relative_path, absolute_path, is_directory|
36
27
  if is_directory
37
28
  !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
38
29
  elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
@@ -42,25 +33,6 @@ module Bootsnap
42
33
  requirables
43
34
  end
44
35
 
45
- def walk(absolute_dir_path, relative_dir_path, &block)
46
- Dir.foreach(absolute_dir_path) do |name|
47
- next if name.start_with?(".")
48
-
49
- relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
50
-
51
- absolute_path = "#{absolute_dir_path}/#{name}"
52
- if File.directory?(absolute_path)
53
- next if ignored_directories.include?(name) || ignored_directories.include?(absolute_path)
54
-
55
- if yield relative_path, absolute_path, true
56
- walk(absolute_path, relative_path, &block)
57
- end
58
- else
59
- yield relative_path, absolute_path, false
60
- end
61
- end
62
- end
63
-
64
36
  if RUBY_ENGINE == "ruby" && RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
65
37
  require "bootsnap/bootsnap"
66
38
  end
@@ -69,33 +41,35 @@ module Bootsnap
69
41
  def native_call(root_path)
70
42
  # NOTE: if https://bugs.ruby-lang.org/issues/21800 is accepted we should be able
71
43
  # to have similar performance with pure Ruby
72
-
73
- # If the bundle path is a descendent of this path, we do additional
74
- # checks to prevent recursing into the bundle path as we recurse
75
- # through this path. We don't want to scan the bundle path because
76
- # anything useful in it will be present on other load path items.
77
- #
78
- # This can happen if, for example, the user adds '.' to the load path,
79
- # and the bundle path is '.bundle'.
80
- contains_bundle_path = BUNDLE_PATH.start_with?(root_path)
44
+ root_path, contains_bundle_path, ignored_abs_paths, ignored_dir_names = prepare_scan(root_path)
81
45
 
82
46
  all_requirables, queue = Native.scan_dir(root_path)
83
47
  all_requirables.each(&:freeze)
84
48
 
85
49
  queue.reject! do |dir|
86
- ignored_directories.include?(dir) ||
87
- (contains_bundle_path && dir.start_with?(BUNDLE_PATH))
50
+ if ignored_dir_names&.include?(dir)
51
+ true
52
+ elsif ignored_abs_paths || contains_bundle_path
53
+ absolute_dir = File.join(root_path, dir)
54
+ ignored_abs_paths&.include?(absolute_dir) ||
55
+ (contains_bundle_path && absolute_dir.start_with?(BUNDLE_PATH))
56
+ end
88
57
  end
89
58
 
90
- while (path = queue.pop)
91
- requirables, dirs = Native.scan_dir(File.join(root_path, path))
92
- dirs.reject! { |dir| ignored_directories.include?(dir) }
93
- dirs.map! { |f| File.join(path, f).freeze }
94
- requirables.map! { |f| File.join(path, f).freeze }
95
-
96
- if contains_bundle_path
97
- dirs.reject! { |dir| dir.start_with?(BUNDLE_PATH) }
59
+ while (relative_path = queue.pop)
60
+ absolute_base = File.join(root_path, relative_path)
61
+ requirables, dirs = Native.scan_dir(absolute_base)
62
+ dirs.reject! do |dir|
63
+ if ignored_dir_names&.include?(dir)
64
+ true
65
+ elsif ignored_abs_paths || contains_bundle_path
66
+ absolute_dir = File.join(absolute_base, dir)
67
+ ignored_abs_paths&.include?(absolute_dir) ||
68
+ (contains_bundle_path && absolute_dir.start_with?(BUNDLE_PATH))
69
+ end
98
70
  end
71
+ dirs.map! { |f| File.join(relative_path, f).freeze }
72
+ requirables.map! { |f| File.join(relative_path, f).freeze }
99
73
 
100
74
  all_requirables.concat(requirables)
101
75
  queue.concat(dirs)
@@ -107,6 +81,46 @@ module Bootsnap
107
81
  else
108
82
  alias_method :call, :ruby_call
109
83
  end
84
+
85
+ private
86
+
87
+ def prepare_scan(root_path)
88
+ root_path = File.expand_path(root_path.to_s).freeze
89
+
90
+ # If the bundle path is a descendent of this path, we do additional
91
+ # checks to prevent recursing into the bundle path as we recurse
92
+ # through this path. We don't want to scan the bundle path because
93
+ # anything useful in it will be present on other load path items.
94
+ #
95
+ # This can happen if, for example, the user adds '.' to the load path,
96
+ # and the bundle path is '.bundle'.
97
+ contains_bundle_path = BUNDLE_PATH.start_with?(root_path)
98
+
99
+ ignored_abs_paths, ignored_dir_names = ignored_directories.partition { |p| File.absolute_path?(p) }
100
+ ignored_abs_paths = nil if ignored_abs_paths.empty?
101
+ ignored_dir_names = nil if ignored_dir_names.empty?
102
+
103
+ [root_path, contains_bundle_path, ignored_abs_paths, ignored_dir_names]
104
+ end
105
+
106
+ def walk(absolute_dir_path, relative_dir_path, ignored_abs_paths, ignored_dir_names, &block)
107
+ Dir.foreach(absolute_dir_path) do |name|
108
+ next if name.start_with?(".")
109
+
110
+ relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
111
+
112
+ absolute_path = File.join(absolute_dir_path, name)
113
+ if File.directory?(absolute_path)
114
+ next if ignored_dir_names&.include?(name) || ignored_abs_paths&.include?(absolute_path)
115
+
116
+ if yield relative_path, absolute_path, true
117
+ walk(absolute_path, relative_path, ignored_abs_paths, ignored_dir_names, &block)
118
+ end
119
+ else
120
+ yield relative_path, absolute_path, false
121
+ end
122
+ end
123
+ end
110
124
  end
111
125
  end
112
126
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bootsnap"
4
+ require "rake/clean"
5
+
6
+ # Typically, should have been set up prior to requiring rake integration.
7
+ # But allow for a streamlined ::default_setup
8
+ require "bootsnap/setup" if Bootsnap.cache_dir.nil?
9
+
10
+ if Bootsnap.cache_dir
11
+ CLEAN.include Bootsnap.cache_dir
12
+ else
13
+ abort "Bootsnap must be set-up prior to rake integration"
14
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- VERSION = "1.20.1"
4
+ VERSION = "1.23.0"
5
5
  end
data/lib/bootsnap.rb CHANGED
@@ -2,14 +2,12 @@
2
2
 
3
3
  require_relative "bootsnap/version"
4
4
  require_relative "bootsnap/bundler"
5
- require_relative "bootsnap/compile_cache"
6
- require_relative "bootsnap/load_path_cache"
7
5
 
8
6
  module Bootsnap
9
7
  InvalidConfiguration = Class.new(StandardError)
10
8
 
11
9
  class << self
12
- attr_reader :logger
10
+ attr_reader :cache_dir, :logger
13
11
 
14
12
  def log_stats!
15
13
  stats = {hit: 0, revalidated: 0, miss: 0, stale: 0}
@@ -60,9 +58,11 @@ module Bootsnap
60
58
  warn("Bootsnap.setup `compile_cache_json` argument is deprecated and has no effect")
61
59
  end
62
60
 
61
+ @cache_dir = "#{cache_dir}/bootsnap"
62
+
63
63
  if load_path_cache
64
64
  Bootsnap::LoadPathCache.setup(
65
- cache_path: "#{cache_dir}/bootsnap/load-path-cache",
65
+ cache_path: "#{@cache_dir}/load-path-cache",
66
66
  development_mode: development_mode,
67
67
  ignore_directories: ignore_directories,
68
68
  readonly: readonly,
@@ -70,7 +70,7 @@ module Bootsnap
70
70
  end
71
71
 
72
72
  Bootsnap::CompileCache.setup(
73
- cache_dir: "#{cache_dir}/bootsnap/compile-cache",
73
+ cache_dir: "#{@cache_dir}/compile-cache",
74
74
  iseq: compile_cache_iseq,
75
75
  yaml: compile_cache_yaml,
76
76
  readonly: readonly,
@@ -150,7 +150,7 @@ module Bootsnap
150
150
  end
151
151
 
152
152
  # Allow the C extension to redefine `rb_get_path` without warning.
153
- alias_method :rb_get_path, :rb_get_path # rubocop:disable Lint/DuplicateMethods
153
+ alias_method :rb_get_path, :rb_get_path
154
154
 
155
155
  private
156
156
 
@@ -164,3 +164,6 @@ module Bootsnap
164
164
  end
165
165
  end
166
166
  end
167
+
168
+ require_relative "bootsnap/compile_cache"
169
+ require_relative "bootsnap/load_path_cache"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.1
4
+ version: 1.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -55,6 +55,7 @@ files:
55
55
  - lib/bootsnap/load_path_cache/path.rb
56
56
  - lib/bootsnap/load_path_cache/path_scanner.rb
57
57
  - lib/bootsnap/load_path_cache/store.rb
58
+ - lib/bootsnap/rake.rb
58
59
  - lib/bootsnap/setup.rb
59
60
  - lib/bootsnap/version.rb
60
61
  homepage: https://github.com/rails/bootsnap
@@ -72,7 +73,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
73
  requirements:
73
74
  - - ">="
74
75
  - !ruby/object:Gem::Version
75
- version: 2.6.0
76
+ version: 2.7.0
76
77
  required_rubygems_version: !ruby/object:Gem::Requirement
77
78
  requirements:
78
79
  - - ">="