bootsnap 1.18.6 → 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: 962bb65ac06446a7f6a1bace4373f552c46f198cedbf32fa1584a531c7f96f48
4
- data.tar.gz: fc84432aa3a728562b7a3d0b29d2edf74a2ee39a888129e56882e8c061571a2d
3
+ metadata.gz: 4e0ad269f816c24dc901b6544acb59c5ebd65b22432f39ee69bc3387b9c2b04d
4
+ data.tar.gz: 700f525d90d4e77421ce83e38f8731981a48cc82a8b9a937c29f661972f78d7f
5
5
  SHA512:
6
- metadata.gz: 18daeec63113ff6eefc97058720d2e9d1703da5e2cc5d1a7e69db3d3d927a89ac156164138c113bcd44dd1fe4f49e7177b4f552c4839872fa11a6385e7413655
7
- data.tar.gz: a8b29f7ea54243953887da78816599bec864240650072e53496446146190c9be8f54a2539cd29f4aebcccb0cbcacdad2dfd2ae187e61b6dcad0ea91fe6aeddcc
6
+ metadata.gz: f5859d7fd4b81f1969c55e12b33a957b34b1dc2825d03bc3f7ab60a57d34153cbcdda7a60626ea758fd66877b1b74a58d17b276aa2055f41086488d6ced5317b
7
+ data.tar.gz: c09f2cd48da0ba3bb5e9d3c995ffb86b8b16aec987c311162fc79ca648a66f355e4051d6ae4c5ee486d5ad2d2bc3af32090e15776e1f45129c1ea8c8e6c811ed
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
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
+
23
+ # 1.20.1
24
+
25
+ * Handle broken symlinks in load path scanning code.
26
+ Should fix `Errno::ENOENT fstatat` issues some users have encountered after upgrading to 1.20.0.
27
+
28
+ # 1.20.0
29
+
30
+ * Optimized load path scanning with a C extension. Should be about 2x faster on supported platforms.
31
+
32
+ # 1.19.0
33
+
34
+ * Remove JSON parsing cache. Recent versions of the `json` gem are as fast as `msgpack` if not faster.
35
+
36
+ # 1.18.6
37
+
3
38
  * Fix cgroup CPU limits detection in CLI.
4
39
 
5
40
  # 1.18.5
@@ -103,7 +138,7 @@
103
138
 
104
139
  * Get rid of the `Kernel.require_relative` decorator by resolving `$LOAD_PATH` members to their real path.
105
140
  This way we handle symlinks in `$LOAD_PATH` much more efficiently. See #402 for the detailed explanation.
106
-
141
+
107
142
  * Drop support for Ruby 2.3 (to allow getting rid of the `Kernel.require_relative` decorator).
108
143
 
109
144
  # 1.10.3
@@ -229,7 +264,7 @@
229
264
  * Adds an instrumentation API to monitor cache misses.
230
265
  * Allow to control the behavior of `require 'bootsnap/setup'` using environment variables.
231
266
  * Deprecate the `disable_trace` option.
232
- * Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
267
+ * Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
233
268
 
234
269
  # 1.6.0
235
270
 
@@ -249,12 +284,12 @@
249
284
 
250
285
  # 1.4.9
251
286
 
252
- * [Windows support](https://github.com/Shopify/bootsnap/pull/319)
253
- * [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
287
+ * [Windows support](https://github.com/rails/bootsnap/pull/319)
288
+ * [Fix potential crash](https://github.com/rails/bootsnap/pull/322)
254
289
 
255
290
  # 1.4.8
256
291
 
257
- * [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
292
+ * [Prevent FallbackScan from polluting exception cause](https://github.com/rails/bootsnap/pull/314)
258
293
 
259
294
  # 1.4.7
260
295
 
@@ -267,7 +302,7 @@
267
302
  required if a different file with the same name was already being required
268
303
 
269
304
  Example:
270
-
305
+
271
306
  require 'foo'
272
307
  require 'foo.en'
273
308
 
@@ -321,7 +356,7 @@
321
356
 
322
357
  # 1.3.0
323
358
 
324
- * Handle cases where load path entries are symlinked (https://github.com/Shopify/bootsnap/pull/136)
359
+ * Handle cases where load path entries are symlinked (https://github.com/rails/bootsnap/pull/136)
325
360
 
326
361
  # 1.2.1
327
362
 
data/LICENSE.txt CHANGED
@@ -1,6 +1,7 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017-present Shopify, Inc.
3
+ Copyright (c) 2017-2025 Shopify, Inc.
4
+ Copyright (c) 2025-present Rails Foundation
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Bootsnap [![Actions Status](https://github.com/Shopify/bootsnap/workflows/ci/badge.svg)](https://github.com/Shopify/bootsnap/actions)
1
+ # Bootsnap [![Actions Status](https://github.com/rails/bootsnap/workflows/ci/badge.svg)](https://github.com/rails/bootsnap/actions)
2
2
 
3
3
  Bootsnap is a library that plugs into Ruby, with optional support for `YAML` and `JSON`,
4
4
  to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
@@ -41,7 +41,7 @@ getting progressively slower, this is almost certainly the cause.**
41
41
  It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
42
42
  important to load Bootsnap as early as possible to get maximum performance improvement.
43
43
 
44
- You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/setup.rb).
44
+ You can see how this require works [here](https://github.com/rails/bootsnap/blob/main/lib/bootsnap/setup.rb).
45
45
 
46
46
  If you are not using Rails, or if you are but want more control over things, add this to your
47
47
  application setup immediately after `require 'bundler/setup'` (i.e. as early as possible: the sooner
@@ -57,13 +57,12 @@ Bootsnap.setup(
57
57
  load_path_cache: true, # Optimize the LOAD_PATH with a cache
58
58
  compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
59
59
  compile_cache_yaml: true, # Compile YAML into a cache
60
- compile_cache_json: true, # Compile JSON into a cache
61
60
  readonly: true, # Use the caches but don't update them on miss or stale entries.
62
61
  )
63
62
  ```
64
63
 
65
64
  **Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
66
- 'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
65
+ 'bootsnap')` using [this trick](https://github.com/rails/bootsnap/wiki/Bootlib::Require). This
67
66
  will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
68
67
 
69
68
  Note: Bootsnap and [Spring](https://github.com/rails/spring) are orthogonal tools. While Bootsnap
@@ -170,7 +169,7 @@ The only directories considered "stable" are things under the Ruby install prefi
170
169
  "volatile".
171
170
 
172
171
  In addition to the [`Bootsnap::LoadPathCache::Cache`
173
- source](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/load_path_cache/cache.rb),
172
+ source](https://github.com/rails/bootsnap/blob/main/lib/bootsnap/load_path_cache/cache.rb),
174
173
  this diagram may help clarify how entry resolution works:
175
174
 
176
175
  ![How path searching works](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png)
@@ -334,6 +333,20 @@ Example:
334
333
  $ bundle exec bootsnap precompile --gemfile app/ lib/ config/
335
334
  ```
336
335
 
336
+ ## Known issues
337
+
338
+ ### QEMU environments
339
+
340
+ When building cross-platform Docker images, QEMU is often used for emulation and can be the source of a limitation that causes forked processes to hang. While Bootsnap includes automatic detection for this issue (as of [PR #501](https://github.com/rails/bootsnap/pull/501)), the detection may not always be sufficient.
341
+
342
+ If you encounter hangs during precompilation in QEMU-based environments (such as when using Docker buildx for cross-platform builds), you can work around this by disabling parallelization with the `-j 0` option:
343
+
344
+ ```bash
345
+ $ bundle exec bootsnap precompile -j 0 --gemfile app/ lib/ config/
346
+ ```
347
+
348
+ See [Issue #495](https://github.com/rails/bootsnap/issues/495) for more details about this QEMU-related issue.
349
+
337
350
  ## When not to use Bootsnap
338
351
 
339
352
  *Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
@@ -11,7 +11,6 @@
11
11
  * here.
12
12
  */
13
13
 
14
- #include "bootsnap.h"
15
14
  #include "ruby.h"
16
15
  #include <stdint.h>
17
16
  #include <stdbool.h>
@@ -20,10 +19,15 @@
20
19
  #include <fcntl.h>
21
20
  #include <unistd.h>
22
21
  #include <sys/stat.h>
22
+ #include <dirent.h>
23
+
24
+ #ifndef RBIMPL_ATTR_NORETURN
25
+ #define RBIMPL_ATTR_NORETURN()
26
+ #endif
23
27
 
24
28
  #ifdef __APPLE__
25
29
  // The symbol is present, however not in the headers
26
- // See: https://github.com/Shopify/bootsnap/issues/470
30
+ // See: https://github.com/rails/bootsnap/issues/470
27
31
  extern int fdatasync(int);
28
32
  #endif
29
33
 
@@ -96,7 +100,6 @@ static mode_t current_umask;
96
100
 
97
101
  /* Bootsnap::CompileCache::{Native, Uncompilable} */
98
102
  static VALUE rb_mBootsnap;
99
- static VALUE rb_mBootsnap_CompileCache;
100
103
  static VALUE rb_mBootsnap_CompileCache_Native;
101
104
  static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
102
105
  static ID instrumentation_method;
@@ -152,6 +155,114 @@ bs_rb_get_path(VALUE self, VALUE fname)
152
155
  return rb_get_path(fname);
153
156
  }
154
157
 
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
+
174
+ static VALUE
175
+ bs_rb_scan_dir(VALUE self, VALUE abspath)
176
+ {
177
+ Check_Type(abspath, T_STRING);
178
+
179
+ VALUE dirs = rb_ary_new();
180
+ VALUE requirables = rb_ary_new();
181
+ VALUE result = rb_ary_new_from_args(2, requirables, dirs);
182
+
183
+ DIR *dirp = opendir(RSTRING_PTR(abspath));
184
+ if (dirp == NULL) {
185
+ if (errno == ENOTDIR || errno == ENOENT) {
186
+ return result;
187
+ }
188
+
189
+ bs_syserr_fail_path("opendir", errno, abspath);
190
+ return Qundef;
191
+ }
192
+
193
+ struct dirent *entry;
194
+ struct stat st;
195
+ int dfd = -1;
196
+
197
+ while (1) {
198
+ errno = 0;
199
+
200
+ entry = readdir(dirp);
201
+ if (entry == NULL) break;
202
+
203
+ if (entry->d_name[0] == '.') continue;
204
+
205
+ if (RB_UNLIKELY(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) {
206
+ // Note: the original implementation of LoadPathCache did follow symlink.
207
+ // So this is replicated here, but I'm not sure it's a good idea.
208
+ if (dfd < 0) {
209
+ dfd = dirfd(dirp);
210
+ if (dfd < 0) {
211
+ int err = errno;
212
+ closedir(dirp);
213
+ bs_syserr_fail_path("dirfd", err, abspath);
214
+ return Qundef;
215
+ }
216
+ }
217
+
218
+ if (fstatat(dfd, entry->d_name, &st, 0)) {
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);
224
+ return Qundef;
225
+ }
226
+
227
+ if (S_ISREG(st.st_mode)) {
228
+ entry->d_type = DT_REG;
229
+ } else if (S_ISDIR(st.st_mode)) {
230
+ entry->d_type = DT_DIR;
231
+ }
232
+ }
233
+
234
+ if (entry->d_type == DT_DIR) {
235
+ rb_ary_push(dirs, rb_utf8_str_new_cstr(entry->d_name));
236
+ continue;
237
+ } else if (entry->d_type == DT_REG) {
238
+ size_t len = strlen(entry->d_name);
239
+ bool is_requirable = (
240
+ // Comparing 4B allows compiler to optimize this into a single 32b integer comparison.
241
+ (len > 3 && memcmp(entry->d_name + (len - 3), ".rb", 4) == 0)
242
+ || (len > DLEXT_MAXLEN && memcmp(entry->d_name + (len - DLEXT_MAXLEN), DLEXT, DLEXT_MAXLEN + 1) == 0)
243
+ #ifdef DLEXT2
244
+ || (len > DLEXT2_MAXLEN && memcmp(entry->d_name + (len - DLEXT2_MAXLEN), DLEXT2, DLEXT2_MAXLEN + 1) == 0)
245
+ #endif
246
+ );
247
+ if (is_requirable) {
248
+ rb_ary_push(requirables, rb_utf8_str_new(entry->d_name, len));
249
+ }
250
+ }
251
+ }
252
+
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);
259
+ return Qundef;
260
+ }
261
+
262
+ return result;
263
+ }
264
+ #endif
265
+
155
266
  /*
156
267
  * Ruby C extensions are initialized by calling Init_<extname>.
157
268
  *
@@ -166,7 +277,14 @@ Init_bootsnap(void)
166
277
 
167
278
  rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
168
279
 
169
- rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
280
+ #ifdef HAVE_FSTATAT
281
+ VALUE rb_mBootsnap_LoadPathCache = rb_define_module_under(rb_mBootsnap, "LoadPathCache");
282
+ VALUE rb_mBootsnap_LoadPathCache_Native = rb_define_module_under(rb_mBootsnap_LoadPathCache, "Native");
283
+
284
+ rb_define_singleton_method(rb_mBootsnap_LoadPathCache_Native, "scan_dir", bs_rb_scan_dir, 1);
285
+ #endif
286
+
287
+ VALUE rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
170
288
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
171
289
  rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
172
290
  rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
@@ -4,6 +4,7 @@ require "mkmf"
4
4
 
5
5
  if %w[ruby truffleruby].include?(RUBY_ENGINE)
6
6
  have_func "fdatasync", "unistd.h"
7
+ have_func "fstatat", "sys/stat.h"
7
8
 
8
9
  unless RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
9
10
  append_cppflags ["-D_GNU_SOURCE"] # Needed of O_NOATIME
@@ -12,7 +13,7 @@ if %w[ruby truffleruby].include?(RUBY_ENGINE)
12
13
  append_cflags ["-O3", "-std=c99"]
13
14
 
14
15
  # ruby.h has some -Wpedantic fails in some cases
15
- # (e.g. https://github.com/Shopify/bootsnap/issues/15)
16
+ # (e.g. https://github.com/rails/bootsnap/issues/15)
16
17
  unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
17
18
  append_cflags([
18
19
  "-Wall",
@@ -59,7 +59,7 @@ module Bootsnap
59
59
  def fork_defunct?
60
60
  return true unless ::Process.respond_to?(:fork)
61
61
 
62
- # Ref: https://github.com/Shopify/bootsnap/issues/495
62
+ # Ref: https://github.com/rails/bootsnap/issues/495
63
63
  # The second forked process will hang on some QEMU environments
64
64
  r, w = IO.pipe
65
65
  pids = 2.times.map do
data/lib/bootsnap/cli.rb CHANGED
@@ -20,7 +20,7 @@ module Bootsnap
20
20
 
21
21
  attr_reader :cache_dir, :argv
22
22
 
23
- attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :json, :jobs
23
+ attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :jobs
24
24
 
25
25
  def initialize(argv)
26
26
  @argv = argv
@@ -31,7 +31,6 @@ module Bootsnap
31
31
  self.jobs = nil
32
32
  self.iseq = true
33
33
  self.yaml = true
34
- self.json = true
35
34
  end
36
35
 
37
36
  def precompile_command(*sources)
@@ -42,21 +41,18 @@ module Bootsnap
42
41
  cache_dir: cache_dir,
43
42
  iseq: iseq,
44
43
  yaml: yaml,
45
- json: json,
46
44
  revalidation: true,
47
45
  )
48
46
 
49
47
  @work_pool = WorkerPool.create(size: jobs, jobs: {
50
48
  ruby: method(:precompile_ruby),
51
49
  yaml: method(:precompile_yaml),
52
- json: method(:precompile_json),
53
50
  })
54
51
  @work_pool.spawn
55
52
 
56
53
  main_sources = sources.map { |d| File.expand_path(d) }
57
54
  precompile_ruby_files(main_sources)
58
55
  precompile_yaml_files(main_sources)
59
- precompile_json_files(main_sources)
60
56
 
61
57
  if compile_gemfile
62
58
  # Gems that include JSON or YAML files usually don't put them in `lib/`.
@@ -70,7 +66,6 @@ module Bootsnap
70
66
 
71
67
  precompile_ruby_files(gem_paths, exclude: gem_exclude)
72
68
  precompile_yaml_files(gem_paths, exclude: gem_exclude)
73
- precompile_json_files(gem_paths, exclude: gem_exclude)
74
69
  end
75
70
 
76
71
  if (exitstatus = @work_pool.shutdown)
@@ -145,29 +140,6 @@ module Bootsnap
145
140
  end
146
141
  end
147
142
 
148
- def precompile_json_files(load_paths, exclude: self.exclude)
149
- return unless json
150
-
151
- load_paths.each do |path|
152
- if !exclude || !exclude.match?(path)
153
- list_files(path, "**/*.json").each do |json_file|
154
- # We ignore hidden files to not match the various .config.json files
155
- if !File.basename(json_file).start_with?(".") && (!exclude || !exclude.match?(json_file))
156
- @work_pool.push(:json, json_file)
157
- end
158
- end
159
- end
160
- end
161
- end
162
-
163
- def precompile_json(*json_files)
164
- Array(json_files).each do |json_file|
165
- if CompileCache::JSON.precompile(json_file) && verbose
166
- $stderr.puts(json_file)
167
- end
168
- end
169
- end
170
-
171
143
  def precompile_ruby_files(load_paths, exclude: self.exclude)
172
144
  return unless iseq
173
145
 
@@ -276,9 +248,9 @@ module Bootsnap
276
248
  opts.on("--no-yaml", help) { self.yaml = false }
277
249
 
278
250
  help = <<~HELP
279
- Disable JSON precompilation.
251
+ Disable JSON precompilation. Deprecated.
280
252
  HELP
281
- opts.on("--no-json", help) { self.json = false }
253
+ opts.on("--no-json", help) { $stderr.puts("The --no-json option is deprecated and now a noop.") }
282
254
  end
283
255
  end
284
256
  end
@@ -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)
@@ -9,7 +9,11 @@ module Bootsnap
9
9
 
10
10
  Error = Class.new(StandardError)
11
11
 
12
- def self.setup(cache_dir:, iseq:, yaml:, json:, readonly: false, revalidation: false)
12
+ def self.setup(cache_dir:, iseq:, yaml:, json: (json_unset = true), readonly: false, revalidation: false)
13
+ unless json_unset
14
+ warn("Bootsnap::CompileCache.setup `json` argument is deprecated and has no effect")
15
+ end
16
+
13
17
  if iseq
14
18
  if supported?
15
19
  require_relative "compile_cache/iseq"
@@ -28,15 +32,6 @@ module Bootsnap
28
32
  end
29
33
  end
30
34
 
31
- if json
32
- if supported?
33
- require_relative "compile_cache/json"
34
- Bootsnap::CompileCache::JSON.install!(cache_dir)
35
- elsif $VERBOSE
36
- warn("[bootsnap/setup] JSON parsing caching is not supported on this implementation of Ruby")
37
- end
38
- end
39
-
40
35
  if supported? && defined?(Bootsnap::CompileCache::Native)
41
36
  Bootsnap::CompileCache::Native.readonly = readonly
42
37
  Bootsnap::CompileCache::Native.revalidation = revalidation
@@ -11,19 +11,19 @@ module Bootsnap
11
11
  @development_mode = development_mode
12
12
  @store = store
13
13
  @mutex = Mutex.new
14
- @path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
14
+ @path_obj = path_obj.map! do |f|
15
+ if File.exist?(f)
16
+ File.realpath(f).freeze
17
+ elsif f.frozen?
18
+ f
19
+ else
20
+ f.dup.freeze
21
+ end
22
+ end
15
23
  @has_relative_paths = nil
16
24
  reinitialize
17
25
  end
18
26
 
19
- # What is the path item that contains the dir as child?
20
- # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], load_dir("c/d")
21
- # is "/a/b".
22
- def load_dir(dir)
23
- reinitialize if stale?
24
- @mutex.synchronize { @dirs[dir] }
25
- end
26
-
27
27
  TRUFFLERUBY_LIB_DIR_PREFIX = if RUBY_ENGINE == "truffleruby"
28
28
  "#{File.join(RbConfig::CONFIG['libdir'], 'truffle')}#{File::SEPARATOR}"
29
29
  end
@@ -124,7 +124,6 @@ module Bootsnap
124
124
  @path_obj = path_obj
125
125
  ChangeObserver.register(@path_obj, self)
126
126
  @index = {}
127
- @dirs = {}
128
127
  @generated_at = now
129
128
  push_paths_locked(*@path_obj)
130
129
  end
@@ -152,9 +151,8 @@ module Bootsnap
152
151
  p = p.to_realpath
153
152
 
154
153
  expanded_path = p.expanded_path
155
- entries, dirs = p.entries_and_dirs(@store)
154
+ entries = p.entries(@store)
156
155
  # push -> low precedence -> set only if unset
157
- dirs.each { |dir| @dirs[dir] ||= path }
158
156
  entries.each { |rel| @index[rel] ||= expanded_path }
159
157
  end
160
158
  end
@@ -169,9 +167,8 @@ module Bootsnap
169
167
  p = p.to_realpath
170
168
 
171
169
  expanded_path = p.expanded_path
172
- entries, dirs = p.entries_and_dirs(@store)
170
+ entries = p.entries(@store)
173
171
  # unshift -> high precedence -> unconditional set
174
- dirs.each { |dir| @dirs[dir] = path }
175
172
  entries.each { |rel| @index[rel] = expanded_path }
176
173
  end
177
174
  end
@@ -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
@@ -54,29 +54,28 @@ module Bootsnap
54
54
  !path.start_with?(SLASH)
55
55
  end
56
56
 
57
- # Return a list of all the requirable files and all of the subdirectories
58
- # of this +Path+.
59
- def entries_and_dirs(store)
57
+ # Return a list of all the requirable files of this +Path+.
58
+ def entries(store)
60
59
  if stable?
61
60
  # the cached_mtime field is unused for 'stable' paths, but is
62
61
  # set to zero anyway, just in case we change the stability heuristics.
63
- _, entries, dirs = store.get(expanded_path)
64
- return [entries, dirs] if entries # cache hit
62
+ _, entries, = store.get(expanded_path)
63
+ return entries if entries # cache hit
65
64
 
66
- entries, dirs = scan!
67
- store.set(expanded_path, [0, entries, dirs])
68
- return [entries, dirs]
65
+ entries = PathScanner.call(expanded_path)
66
+ store.set(expanded_path, [0, entries])
67
+ return entries
69
68
  end
70
69
 
71
- cached_mtime, entries, dirs = store.get(expanded_path)
70
+ cached_mtime, entries = store.get(expanded_path)
72
71
 
73
- current_mtime = latest_mtime(expanded_path, dirs || [])
74
- return [[], []] if current_mtime == -1 # path does not exist
75
- return [entries, dirs] if cached_mtime == current_mtime
72
+ current_mtime = latest_mtime(expanded_path, entries || [])
73
+ return [] if current_mtime == -1 # path does not exist
74
+ return entries if cached_mtime == current_mtime
76
75
 
77
- entries, dirs = scan!
78
- store.set(expanded_path, [current_mtime, entries, dirs])
79
- [entries, dirs]
76
+ entries = PathScanner.call(expanded_path)
77
+ store.set(expanded_path, [current_mtime, entries])
78
+ entries
80
79
  end
81
80
 
82
81
  def expanded_path
@@ -89,23 +88,31 @@ module Bootsnap
89
88
 
90
89
  private
91
90
 
92
- def scan! # (expensive) returns [entries, dirs]
93
- PathScanner.call(expanded_path)
94
- end
95
-
96
91
  # last time a directory was modified in this subtree. +dirs+ should be a
97
92
  # list of relative paths to directories under +path+. e.g. for /a/b and
98
93
  # /a/b/c, pass ('/a/b', ['c'])
99
- def latest_mtime(path, dirs)
100
- max = -1
101
- ["", *dirs].each do |dir|
102
- curr = begin
103
- File.mtime("#{path}/#{dir}").to_i
104
- rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
105
- -1
94
+ def latest_mtime(root_path, entries)
95
+ max = begin
96
+ File.mtime(root_path).to_i
97
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
98
+ -1
99
+ end
100
+
101
+ visited = {"." => true}
102
+
103
+ entries.each do |relpath|
104
+ dirname = File.dirname(relpath)
105
+ visited[dirname] ||= begin
106
+ begin
107
+ current = File.mtime(File.join(root_path, dirname)).to_i
108
+ max = current if current > max
109
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EINVAL
110
+ # ignore
111
+ end
112
+ true
106
113
  end
107
- max = curr if curr > max
108
114
  end
115
+
109
116
  max
110
117
  end
111
118
 
@@ -6,8 +6,6 @@ module Bootsnap
6
6
  module LoadPathCache
7
7
  module PathScanner
8
8
  REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
9
- NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
10
- ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/.freeze
11
9
 
12
10
  BUNDLE_PATH = if Bootsnap.bundler?
13
11
  (Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
@@ -20,9 +18,74 @@ module Bootsnap
20
18
  class << self
21
19
  attr_accessor :ignored_directories
22
20
 
23
- def call(path)
24
- path = File.expand_path(path.to_s).freeze
25
- return [[], []] unless File.directory?(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)
24
+
25
+ requirables = []
26
+ walk(root_path, nil, ignored_abs_paths, ignored_dir_names) do |relative_path, absolute_path, is_directory|
27
+ if is_directory
28
+ !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
29
+ elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
30
+ requirables << relative_path.freeze
31
+ end
32
+ end
33
+ requirables
34
+ end
35
+
36
+ if RUBY_ENGINE == "ruby" && RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
37
+ require "bootsnap/bootsnap"
38
+ end
39
+
40
+ if defined?(Native.scan_dir)
41
+ def native_call(root_path)
42
+ # NOTE: if https://bugs.ruby-lang.org/issues/21800 is accepted we should be able
43
+ # to have similar performance with pure Ruby
44
+ root_path, contains_bundle_path, ignored_abs_paths, ignored_dir_names = prepare_scan(root_path)
45
+
46
+ all_requirables, queue = Native.scan_dir(root_path)
47
+ all_requirables.each(&:freeze)
48
+
49
+ queue.reject! do |dir|
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
57
+ end
58
+
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
70
+ end
71
+ dirs.map! { |f| File.join(relative_path, f).freeze }
72
+ requirables.map! { |f| File.join(relative_path, f).freeze }
73
+
74
+ all_requirables.concat(requirables)
75
+ queue.concat(dirs)
76
+ end
77
+
78
+ all_requirables
79
+ end
80
+ alias_method :call, :native_call
81
+ else
82
+ alias_method :call, :ruby_call
83
+ end
84
+
85
+ private
86
+
87
+ def prepare_scan(root_path)
88
+ root_path = File.expand_path(root_path.to_s).freeze
26
89
 
27
90
  # If the bundle path is a descendent of this path, we do additional
28
91
  # checks to prevent recursing into the bundle path as we recurse
@@ -31,50 +94,33 @@ module Bootsnap
31
94
  #
32
95
  # This can happen if, for example, the user adds '.' to the load path,
33
96
  # and the bundle path is '.bundle'.
34
- contains_bundle_path = BUNDLE_PATH.start_with?(path)
97
+ contains_bundle_path = BUNDLE_PATH.start_with?(root_path)
35
98
 
36
- dirs = []
37
- requirables = []
38
- walk(path, nil) do |relative_path, absolute_path, is_directory|
39
- if is_directory
40
- dirs << os_path(relative_path)
41
- !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
42
- elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
43
- requirables << os_path(relative_path)
44
- end
45
- end
46
- [requirables, dirs]
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]
47
104
  end
48
105
 
49
- def walk(absolute_dir_path, relative_dir_path, &block)
106
+ def walk(absolute_dir_path, relative_dir_path, ignored_abs_paths, ignored_dir_names, &block)
50
107
  Dir.foreach(absolute_dir_path) do |name|
51
108
  next if name.start_with?(".")
52
109
 
53
110
  relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
54
111
 
55
- absolute_path = "#{absolute_dir_path}/#{name}"
112
+ absolute_path = File.join(absolute_dir_path, name)
56
113
  if File.directory?(absolute_path)
57
- next if ignored_directories.include?(name) || ignored_directories.include?(absolute_path)
114
+ next if ignored_dir_names&.include?(name) || ignored_abs_paths&.include?(absolute_path)
58
115
 
59
116
  if yield relative_path, absolute_path, true
60
- walk(absolute_path, relative_path, &block)
117
+ walk(absolute_path, relative_path, ignored_abs_paths, ignored_dir_names, &block)
61
118
  end
62
119
  else
63
120
  yield relative_path, absolute_path, false
64
121
  end
65
122
  end
66
123
  end
67
-
68
- if RUBY_VERSION >= "3.1"
69
- def os_path(path)
70
- path.freeze
71
- end
72
- else
73
- def os_path(path)
74
- path.force_encoding(Encoding::US_ASCII) if path.ascii_only?
75
- path.freeze
76
- end
77
- end
78
124
  end
79
125
  end
80
126
  end
@@ -8,7 +8,7 @@ module Bootsnap
8
8
  module LoadPathCache
9
9
  class Store
10
10
  VERSION_KEY = "__bootsnap_ruby_version__"
11
- CURRENT_VERSION = "#{RUBY_REVISION}-#{RUBY_PLATFORM}".freeze # rubocop:disable Style/RedundantFreeze
11
+ CURRENT_VERSION = "#{VERSION}-#{RUBY_REVISION}-#{RUBY_PLATFORM}".freeze # rubocop:disable Style/RedundantFreeze
12
12
 
13
13
  NestedTransactionError = Class.new(StandardError)
14
14
  SetOutsideTransactionNotAllowed = Class.new(StandardError)
@@ -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.18.6"
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/load_path_cache"
6
- require_relative "bootsnap/compile_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}
@@ -54,11 +52,17 @@ module Bootsnap
54
52
  revalidation: false,
55
53
  compile_cache_iseq: true,
56
54
  compile_cache_yaml: true,
57
- compile_cache_json: true
55
+ compile_cache_json: (compile_cache_json_unset = true)
58
56
  )
57
+ unless compile_cache_json_unset
58
+ warn("Bootsnap.setup `compile_cache_json` argument is deprecated and has no effect")
59
+ end
60
+
61
+ @cache_dir = "#{cache_dir}/bootsnap"
62
+
59
63
  if load_path_cache
60
64
  Bootsnap::LoadPathCache.setup(
61
- cache_path: "#{cache_dir}/bootsnap/load-path-cache",
65
+ cache_path: "#{@cache_dir}/load-path-cache",
62
66
  development_mode: development_mode,
63
67
  ignore_directories: ignore_directories,
64
68
  readonly: readonly,
@@ -66,10 +70,9 @@ module Bootsnap
66
70
  end
67
71
 
68
72
  Bootsnap::CompileCache.setup(
69
- cache_dir: "#{cache_dir}/bootsnap/compile-cache",
73
+ cache_dir: "#{@cache_dir}/compile-cache",
70
74
  iseq: compile_cache_iseq,
71
75
  yaml: compile_cache_yaml,
72
- json: compile_cache_json,
73
76
  readonly: readonly,
74
77
  revalidation: revalidation,
75
78
  )
@@ -115,7 +118,6 @@ module Bootsnap
115
118
  load_path_cache: enabled?("BOOTSNAP_LOAD_PATH_CACHE"),
116
119
  compile_cache_iseq: enabled?("BOOTSNAP_COMPILE_CACHE"),
117
120
  compile_cache_yaml: enabled?("BOOTSNAP_COMPILE_CACHE"),
118
- compile_cache_json: enabled?("BOOTSNAP_COMPILE_CACHE"),
119
121
  readonly: bool_env("BOOTSNAP_READONLY"),
120
122
  revalidation: bool_env("BOOTSNAP_REVALIDATE"),
121
123
  ignore_directories: ignore_directories,
@@ -148,7 +150,7 @@ module Bootsnap
148
150
  end
149
151
 
150
152
  # Allow the C extension to redefine `rb_get_path` without warning.
151
- alias_method :rb_get_path, :rb_get_path # rubocop:disable Lint/DuplicateMethods
153
+ alias_method :rb_get_path, :rb_get_path
152
154
 
153
155
  private
154
156
 
@@ -162,3 +164,6 @@ module Bootsnap
162
164
  end
163
165
  end
164
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.18.6
4
+ version: 1.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -37,7 +37,6 @@ files:
37
37
  - README.md
38
38
  - exe/bootsnap
39
39
  - ext/bootsnap/bootsnap.c
40
- - ext/bootsnap/bootsnap.h
41
40
  - ext/bootsnap/extconf.rb
42
41
  - lib/bootsnap.rb
43
42
  - lib/bootsnap/bundler.rb
@@ -45,7 +44,6 @@ files:
45
44
  - lib/bootsnap/cli/worker_pool.rb
46
45
  - lib/bootsnap/compile_cache.rb
47
46
  - lib/bootsnap/compile_cache/iseq.rb
48
- - lib/bootsnap/compile_cache/json.rb
49
47
  - lib/bootsnap/compile_cache/yaml.rb
50
48
  - lib/bootsnap/explicit_require.rb
51
49
  - lib/bootsnap/load_path_cache.rb
@@ -57,15 +55,16 @@ files:
57
55
  - lib/bootsnap/load_path_cache/path.rb
58
56
  - lib/bootsnap/load_path_cache/path_scanner.rb
59
57
  - lib/bootsnap/load_path_cache/store.rb
58
+ - lib/bootsnap/rake.rb
60
59
  - lib/bootsnap/setup.rb
61
60
  - lib/bootsnap/version.rb
62
- homepage: https://github.com/Shopify/bootsnap
61
+ homepage: https://github.com/rails/bootsnap
63
62
  licenses:
64
63
  - MIT
65
64
  metadata:
66
- bug_tracker_uri: https://github.com/Shopify/bootsnap/issues
67
- changelog_uri: https://github.com/Shopify/bootsnap/blob/main/CHANGELOG.md
68
- source_code_uri: https://github.com/Shopify/bootsnap
65
+ bug_tracker_uri: https://github.com/rails/bootsnap/issues
66
+ changelog_uri: https://github.com/rails/bootsnap/blob/main/CHANGELOG.md
67
+ source_code_uri: https://github.com/rails/bootsnap
69
68
  allowed_push_host: https://rubygems.org
70
69
  rdoc_options: []
71
70
  require_paths:
@@ -74,14 +73,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
74
73
  requirements:
75
74
  - - ">="
76
75
  - !ruby/object:Gem::Version
77
- version: 2.6.0
76
+ version: 2.7.0
78
77
  required_rubygems_version: !ruby/object:Gem::Requirement
79
78
  requirements:
80
79
  - - ">="
81
80
  - !ruby/object:Gem::Version
82
81
  version: '0'
83
82
  requirements: []
84
- rubygems_version: 3.6.8
83
+ rubygems_version: 3.6.9
85
84
  specification_version: 4
86
85
  summary: Boot large ruby/rails apps faster
87
86
  test_files: []
@@ -1,6 +0,0 @@
1
- #ifndef BOOTSNAP_H
2
- #define BOOTSNAP_H 1
3
-
4
- /* doesn't expose anything */
5
-
6
- #endif /* BOOTSNAP_H */
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bootsnap/bootsnap"
4
-
5
- module Bootsnap
6
- module CompileCache
7
- module JSON
8
- class << self
9
- attr_accessor(:msgpack_factory, :supported_options)
10
- attr_reader(:cache_dir)
11
-
12
- def cache_dir=(cache_dir)
13
- @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}json" : "#{cache_dir}-json"
14
- end
15
-
16
- def input_to_storage(payload, _)
17
- obj = ::JSON.parse(payload)
18
- msgpack_factory.dump(obj)
19
- end
20
-
21
- def storage_to_output(data, kwargs)
22
- if kwargs&.key?(:symbolize_names)
23
- kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
24
- end
25
- msgpack_factory.load(data, kwargs)
26
- end
27
-
28
- def input_to_output(data, kwargs)
29
- ::JSON.parse(data, **(kwargs || {}))
30
- end
31
-
32
- def precompile(path)
33
- Bootsnap::CompileCache::Native.precompile(
34
- cache_dir,
35
- path.to_s,
36
- self,
37
- )
38
- end
39
-
40
- def install!(cache_dir)
41
- self.cache_dir = cache_dir
42
- init!
43
- if ::JSON.respond_to?(:load_file)
44
- ::JSON.singleton_class.prepend(Patch)
45
- end
46
- end
47
-
48
- def init!
49
- require "json"
50
- require "msgpack"
51
-
52
- self.msgpack_factory = MessagePack::Factory.new
53
- self.supported_options = [:symbolize_names]
54
- if supports_freeze?
55
- self.supported_options = [:freeze]
56
- end
57
- supported_options.freeze
58
- end
59
-
60
- private
61
-
62
- def supports_freeze?
63
- ::JSON.parse('["foo"]', freeze: true).first.frozen? &&
64
- MessagePack.load(MessagePack.dump("foo"), freeze: true).frozen?
65
- end
66
- end
67
-
68
- module Patch
69
- def load_file(path, *args)
70
- return super if args.size > 1
71
-
72
- if (kwargs = args.first)
73
- return super unless kwargs.is_a?(Hash)
74
- return super unless (kwargs.keys - ::Bootsnap::CompileCache::JSON.supported_options).empty?
75
- end
76
-
77
- ::Bootsnap::CompileCache::Native.fetch(
78
- Bootsnap::CompileCache::JSON.cache_dir,
79
- File.realpath(path),
80
- ::Bootsnap::CompileCache::JSON,
81
- kwargs,
82
- )
83
- end
84
-
85
- ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
86
- end
87
- end
88
- end
89
- end