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 +4 -4
- data/CHANGELOG.md +42 -7
- data/LICENSE.txt +2 -1
- data/README.md +18 -5
- data/ext/bootsnap/bootsnap.c +122 -4
- data/ext/bootsnap/extconf.rb +2 -1
- data/lib/bootsnap/cli/worker_pool.rb +1 -1
- data/lib/bootsnap/cli.rb +3 -31
- data/lib/bootsnap/compile_cache/iseq.rb +4 -2
- data/lib/bootsnap/compile_cache/yaml.rb +3 -1
- data/lib/bootsnap/compile_cache.rb +5 -10
- data/lib/bootsnap/load_path_cache/cache.rb +11 -14
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +9 -4
- data/lib/bootsnap/load_path_cache/path.rb +34 -27
- data/lib/bootsnap/load_path_cache/path_scanner.rb +78 -32
- data/lib/bootsnap/load_path_cache/store.rb +1 -1
- data/lib/bootsnap/rake.rb +14 -0
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +14 -9
- metadata +8 -9
- data/ext/bootsnap/bootsnap.h +0 -6
- data/lib/bootsnap/compile_cache/json.rb +0 -89
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e0ad269f816c24dc901b6544acb59c5ebd65b22432f39ee69bc3387b9c2b04d
|
|
4
|
+
data.tar.gz: 700f525d90d4e77421ce83e38f8731981a48cc82a8b9a937c29f661972f78d7f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/
|
|
253
|
-
* [Fix potential crash](https://github.com/
|
|
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/
|
|
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/
|
|
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-
|
|
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 [](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/
|
|
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/
|
|
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/
|
|
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
|

|
|
@@ -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
|
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
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);
|
data/ext/bootsnap/extconf.rb
CHANGED
|
@@ -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/
|
|
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/
|
|
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, :
|
|
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) {
|
|
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
|
|
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
|
|
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!
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
58
|
-
|
|
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,
|
|
64
|
-
return
|
|
62
|
+
_, entries, = store.get(expanded_path)
|
|
63
|
+
return entries if entries # cache hit
|
|
65
64
|
|
|
66
|
-
entries
|
|
67
|
-
store.set(expanded_path, [0, entries
|
|
68
|
-
return
|
|
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
|
|
70
|
+
cached_mtime, entries = store.get(expanded_path)
|
|
72
71
|
|
|
73
|
-
current_mtime = latest_mtime(expanded_path,
|
|
74
|
-
return [
|
|
75
|
-
return
|
|
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
|
|
78
|
-
store.set(expanded_path, [current_mtime, entries
|
|
79
|
-
|
|
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(
|
|
100
|
-
max =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
return [
|
|
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?(
|
|
97
|
+
contains_bundle_path = BUNDLE_PATH.start_with?(root_path)
|
|
35
98
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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 =
|
|
112
|
+
absolute_path = File.join(absolute_dir_path, name)
|
|
56
113
|
if File.directory?(absolute_path)
|
|
57
|
-
next if
|
|
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
|
data/lib/bootsnap/version.rb
CHANGED
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}/
|
|
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}/
|
|
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
|
|
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.
|
|
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/
|
|
61
|
+
homepage: https://github.com/rails/bootsnap
|
|
63
62
|
licenses:
|
|
64
63
|
- MIT
|
|
65
64
|
metadata:
|
|
66
|
-
bug_tracker_uri: https://github.com/
|
|
67
|
-
changelog_uri: https://github.com/
|
|
68
|
-
source_code_uri: https://github.com/
|
|
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.
|
|
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.
|
|
83
|
+
rubygems_version: 3.6.9
|
|
85
84
|
specification_version: 4
|
|
86
85
|
summary: Boot large ruby/rails apps faster
|
|
87
86
|
test_files: []
|
data/ext/bootsnap/bootsnap.h
DELETED
|
@@ -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
|