bootsnap 1.22.0 → 1.24.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 +9 -0
- data/README.md +29 -0
- data/ext/bootsnap/bootsnap.c +60 -54
- data/lib/bootsnap/cli.rb +1 -0
- data/lib/bootsnap/compile_cache/iseq.rb +54 -41
- data/lib/bootsnap/compile_cache/yaml.rb +8 -3
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +1 -1
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +1 -1
- data/lib/bootsnap/load_path_cache/path_scanner.rb +65 -51
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +30 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9680e880d828e4c61704db75e793e97b4bcaa9142e37335b697d28a14c1d7f9c
|
|
4
|
+
data.tar.gz: 876136914c8d89c771304fffc2d8587b3b15b0eb6abbf684ad5d4adb39b98550
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 84bd60feda93f48dfeb2c8149c72a72a619050eb163decf83bf14d78173a3aa52adff84dc6a22f7821ee7d1c937a0449f96686f6199c77e53ff990f123c30359
|
|
7
|
+
data.tar.gz: 41d345dee52323ba80df2c8165bad4a4ea6c04780d8eb7e459a5e766973b797f68267ac63fbad38a3add47e59041aa8d7786ddb43b5ed0cfb7ac98743cd70ca5
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -75,6 +75,7 @@ well together.
|
|
|
75
75
|
`require 'bootsnap/setup'` behavior can be changed using environment variables:
|
|
76
76
|
|
|
77
77
|
- `BOOTSNAP_CACHE_DIR` allows to define the cache location.
|
|
78
|
+
- `BOOTSNAP_CONFIG` allows to change the default config location (`config/bootsnap.rb`).
|
|
78
79
|
- `DISABLE_BOOTSNAP` allows to entirely disable bootsnap.
|
|
79
80
|
- `DISABLE_BOOTSNAP_LOAD_PATH_CACHE` allows to disable load path caching.
|
|
80
81
|
- `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
|
|
@@ -320,6 +321,34 @@ open /c/nope.bundle -> -1
|
|
|
320
321
|
# (nothing!)
|
|
321
322
|
```
|
|
322
323
|
|
|
324
|
+
## Custom Compilers
|
|
325
|
+
|
|
326
|
+
Bootsnap allows substituing the default Ruby compiler by another one.
|
|
327
|
+
This can be configured from the bootsnap config file (defaults to `config/bootsnap.rb`).
|
|
328
|
+
|
|
329
|
+
The main use case is to programmatically enable frozen string literals for your project without impacting dependencies:
|
|
330
|
+
|
|
331
|
+
```ruby
|
|
332
|
+
Bootsnap.enable_frozen_string_literal(app_only: true)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
But it can also be used for more fine grained logic, or to implement all sort of Ruby code preprocessing:
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
# config/bootsnap.rb
|
|
339
|
+
gems_root = File.join(Bundler.bundle_path.cleanpath, "")
|
|
340
|
+
app_root = File.join(Dir.pwd, "")
|
|
341
|
+
Bootsnap::CompileCache::ISeq.compiler_selector = ->(path) do
|
|
342
|
+
# Enable `frozen_string_literal: true` for app code, but not gems.
|
|
343
|
+
|
|
344
|
+
if path.start_with?(app_root) && !path.start_with?(gems_root)
|
|
345
|
+
Bootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL
|
|
346
|
+
else
|
|
347
|
+
Bootsnap::CompileCache::ISeq::DEFAULT
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
```
|
|
351
|
+
|
|
323
352
|
## Precompilation
|
|
324
353
|
|
|
325
354
|
In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
|
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -109,13 +109,15 @@ static bool readonly = false;
|
|
|
109
109
|
static bool revalidation = false;
|
|
110
110
|
static bool perm_issue = false;
|
|
111
111
|
|
|
112
|
+
static ID id_storage_to_output, id_input_to_output, id_input_to_storage;
|
|
113
|
+
|
|
112
114
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
|
113
115
|
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
|
114
116
|
static VALUE bs_readonly_set(VALUE self, VALUE enabled);
|
|
115
117
|
static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
|
|
116
118
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
|
117
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
|
118
|
-
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
|
119
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args);
|
|
120
|
+
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler);
|
|
119
121
|
|
|
120
122
|
/* Helpers */
|
|
121
123
|
enum cache_status {
|
|
@@ -123,7 +125,7 @@ enum cache_status {
|
|
|
123
125
|
hit,
|
|
124
126
|
stale,
|
|
125
127
|
};
|
|
126
|
-
static void bs_cache_path(
|
|
128
|
+
static void bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
|
127
129
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
|
128
130
|
static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
|
129
131
|
static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
|
|
@@ -143,7 +145,7 @@ static uint32_t get_ruby_platform(void);
|
|
|
143
145
|
*/
|
|
144
146
|
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
|
145
147
|
static VALUE prot_input_to_output(VALUE arg);
|
|
146
|
-
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
|
148
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * output_data, int * exception_tag);
|
|
147
149
|
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
|
148
150
|
struct s2o_data;
|
|
149
151
|
struct i2o_data;
|
|
@@ -194,8 +196,12 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
|
|
|
194
196
|
struct stat st;
|
|
195
197
|
int dfd = -1;
|
|
196
198
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
while (1) {
|
|
200
|
+
errno = 0;
|
|
201
|
+
|
|
202
|
+
entry = readdir(dirp);
|
|
203
|
+
if (entry == NULL) break;
|
|
204
|
+
|
|
199
205
|
if (entry->d_name[0] == '.') continue;
|
|
200
206
|
|
|
201
207
|
if (RB_UNLIKELY(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) {
|
|
@@ -212,11 +218,8 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
|
|
|
212
218
|
}
|
|
213
219
|
|
|
214
220
|
if (fstatat(dfd, entry->d_name, &st, 0)) {
|
|
215
|
-
if (errno == ENOENT)
|
|
216
|
-
|
|
217
|
-
errno = 0;
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
221
|
+
if (errno == ENOENT) continue; // Broken symlink
|
|
222
|
+
|
|
220
223
|
int err = errno;
|
|
221
224
|
closedir(dirp);
|
|
222
225
|
bs_syserr_fail_dir_entry("fstatat", err, abspath, entry->d_name);
|
|
@@ -249,10 +252,15 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
|
|
|
249
252
|
}
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
if (
|
|
255
|
+
if (errno) {
|
|
256
|
+
int err = errno;
|
|
257
|
+
closedir(dirp);
|
|
258
|
+
bs_syserr_fail_path("readdir", err, abspath);
|
|
259
|
+
} else if (closedir(dirp)) {
|
|
253
260
|
bs_syserr_fail_path("closedir", errno, abspath);
|
|
254
261
|
return Qundef;
|
|
255
262
|
}
|
|
263
|
+
|
|
256
264
|
return result;
|
|
257
265
|
}
|
|
258
266
|
#endif
|
|
@@ -269,6 +277,10 @@ Init_bootsnap(void)
|
|
|
269
277
|
{
|
|
270
278
|
rb_mBootsnap = rb_define_module("Bootsnap");
|
|
271
279
|
|
|
280
|
+
id_storage_to_output = rb_intern("storage_to_output");
|
|
281
|
+
id_input_to_output = rb_intern("input_to_output");
|
|
282
|
+
id_input_to_storage = rb_intern("input_to_storage");
|
|
283
|
+
|
|
272
284
|
rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
|
|
273
285
|
|
|
274
286
|
#ifdef HAVE_FSTATAT
|
|
@@ -296,8 +308,8 @@ Init_bootsnap(void)
|
|
|
296
308
|
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
|
297
309
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
|
|
298
310
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
|
|
299
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
|
300
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile,
|
|
311
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 5);
|
|
312
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 4);
|
|
301
313
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
|
302
314
|
|
|
303
315
|
current_umask = umask(0777);
|
|
@@ -414,13 +426,28 @@ get_ruby_platform(void)
|
|
|
414
426
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
|
415
427
|
*/
|
|
416
428
|
static void
|
|
417
|
-
bs_cache_path(
|
|
429
|
+
bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
|
418
430
|
{
|
|
419
|
-
|
|
431
|
+
FilePathValue(path_v);
|
|
432
|
+
|
|
433
|
+
Check_Type(cachedir_v, T_STRING);
|
|
434
|
+
Check_Type(path_v, T_STRING);
|
|
435
|
+
if (!NIL_P(namespace_v)) {
|
|
436
|
+
Check_Type(namespace_v, T_STRING);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
|
440
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const char * cachedir = RSTRING_PTR(cachedir_v);
|
|
444
|
+
const char * namespace = NIL_P(namespace_v) ? "" : RSTRING_PTR(namespace_v);
|
|
445
|
+
|
|
446
|
+
uint64_t hash = fnv1a_64(path_v);
|
|
420
447
|
uint8_t first_byte = (hash >> (64 - 8));
|
|
421
448
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
|
422
449
|
|
|
423
|
-
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
|
450
|
+
sprintf(*cache_path, "%s%s/%02"PRIx8"/%014"PRIx64, cachedir, namespace, first_byte, remainder);
|
|
424
451
|
}
|
|
425
452
|
|
|
426
453
|
/*
|
|
@@ -492,25 +519,14 @@ static void bs_cache_key_digest(struct bs_cache_key *key,
|
|
|
492
519
|
* conversions on the ruby VALUE arguments before passing them along.
|
|
493
520
|
*/
|
|
494
521
|
static VALUE
|
|
495
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
|
522
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args)
|
|
496
523
|
{
|
|
497
|
-
FilePathValue(path_v);
|
|
498
|
-
|
|
499
|
-
Check_Type(cachedir_v, T_STRING);
|
|
500
|
-
Check_Type(path_v, T_STRING);
|
|
501
|
-
|
|
502
|
-
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
|
503
|
-
rb_raise(rb_eArgError, "cachedir too long");
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
char * cachedir = RSTRING_PTR(cachedir_v);
|
|
507
|
-
char * path = RSTRING_PTR(path_v);
|
|
508
524
|
char cache_path[MAX_CACHEPATH_SIZE];
|
|
509
525
|
|
|
510
526
|
/* generate cache path to cache_path */
|
|
511
|
-
bs_cache_path(
|
|
527
|
+
bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
|
|
512
528
|
|
|
513
|
-
return bs_fetch(
|
|
529
|
+
return bs_fetch(RSTRING_PTR(path_v), path_v, cache_path, handler, args);
|
|
514
530
|
}
|
|
515
531
|
|
|
516
532
|
/*
|
|
@@ -519,25 +535,13 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
|
|
|
519
535
|
* and doesn't return the content.
|
|
520
536
|
*/
|
|
521
537
|
static VALUE
|
|
522
|
-
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
538
|
+
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler)
|
|
523
539
|
{
|
|
524
|
-
FilePathValue(path_v);
|
|
525
|
-
|
|
526
|
-
Check_Type(cachedir_v, T_STRING);
|
|
527
|
-
Check_Type(path_v, T_STRING);
|
|
528
|
-
|
|
529
|
-
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
|
530
|
-
rb_raise(rb_eArgError, "cachedir too long");
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
char * cachedir = RSTRING_PTR(cachedir_v);
|
|
534
|
-
char * path = RSTRING_PTR(path_v);
|
|
535
540
|
char cache_path[MAX_CACHEPATH_SIZE];
|
|
536
|
-
|
|
537
541
|
/* generate cache path to cache_path */
|
|
538
|
-
bs_cache_path(
|
|
542
|
+
bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
|
|
539
543
|
|
|
540
|
-
return bs_precompile(
|
|
544
|
+
return bs_precompile(RSTRING_PTR(path_v), path_v, cache_path, handler);
|
|
541
545
|
}
|
|
542
546
|
|
|
543
547
|
static int bs_open_noatime(const char *path, int flags) {
|
|
@@ -957,7 +961,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
957
961
|
exception_message = path_v;
|
|
958
962
|
goto fail_errno;
|
|
959
963
|
}
|
|
960
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
964
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
961
965
|
if (exception_tag != 0) goto raise;
|
|
962
966
|
goto succeed;
|
|
963
967
|
} else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
|
@@ -983,7 +987,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
983
987
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
|
984
988
|
* to cache anything; just return input_to_output(input_data) */
|
|
985
989
|
if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
|
986
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
990
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
987
991
|
if (exception_tag != 0) goto raise;
|
|
988
992
|
goto succeed;
|
|
989
993
|
}
|
|
@@ -1003,7 +1007,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
1003
1007
|
|
|
1004
1008
|
if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
|
|
1005
1009
|
/* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
|
|
1006
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
1010
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
1007
1011
|
if (exception_tag != 0) goto raise;
|
|
1008
1012
|
} else if (NIL_P(output_data)) {
|
|
1009
1013
|
/* If output_data is nil, delete the cache entry and generate the output
|
|
@@ -1017,7 +1021,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
|
1017
1021
|
goto fail_errno;
|
|
1018
1022
|
}
|
|
1019
1023
|
}
|
|
1020
|
-
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
|
1024
|
+
bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
|
|
1021
1025
|
if (exception_tag != 0) goto raise;
|
|
1022
1026
|
}
|
|
1023
1027
|
|
|
@@ -1175,6 +1179,7 @@ struct i2o_data {
|
|
|
1175
1179
|
VALUE handler;
|
|
1176
1180
|
VALUE args;
|
|
1177
1181
|
VALUE input_data;
|
|
1182
|
+
VALUE pathval;
|
|
1178
1183
|
};
|
|
1179
1184
|
|
|
1180
1185
|
struct i2s_data {
|
|
@@ -1187,7 +1192,7 @@ static VALUE
|
|
|
1187
1192
|
try_storage_to_output(VALUE arg)
|
|
1188
1193
|
{
|
|
1189
1194
|
struct s2o_data * data = (struct s2o_data *)arg;
|
|
1190
|
-
return rb_funcall(data->handler,
|
|
1195
|
+
return rb_funcall(data->handler, id_storage_to_output, 2, data->storage_data, data->args);
|
|
1191
1196
|
}
|
|
1192
1197
|
|
|
1193
1198
|
static int
|
|
@@ -1204,12 +1209,13 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
|
|
|
1204
1209
|
}
|
|
1205
1210
|
|
|
1206
1211
|
static void
|
|
1207
|
-
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
|
1212
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE path_v, VALUE * output_data, int * exception_tag)
|
|
1208
1213
|
{
|
|
1209
1214
|
struct i2o_data i2o_data = {
|
|
1210
1215
|
.handler = handler,
|
|
1211
1216
|
.args = args,
|
|
1212
1217
|
.input_data = input_data,
|
|
1218
|
+
.pathval = path_v,
|
|
1213
1219
|
};
|
|
1214
1220
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
|
1215
1221
|
}
|
|
@@ -1218,14 +1224,14 @@ static VALUE
|
|
|
1218
1224
|
prot_input_to_output(VALUE arg)
|
|
1219
1225
|
{
|
|
1220
1226
|
struct i2o_data * data = (struct i2o_data *)arg;
|
|
1221
|
-
return rb_funcall(data->handler,
|
|
1227
|
+
return rb_funcall(data->handler, id_input_to_output, 3, data->input_data, data->pathval, data->args);
|
|
1222
1228
|
}
|
|
1223
1229
|
|
|
1224
1230
|
static VALUE
|
|
1225
1231
|
try_input_to_storage(VALUE arg)
|
|
1226
1232
|
{
|
|
1227
1233
|
struct i2s_data * data = (struct i2s_data *)arg;
|
|
1228
|
-
return rb_funcall(data->handler,
|
|
1234
|
+
return rb_funcall(data->handler, id_input_to_storage, 2, data->input_data, data->pathval);
|
|
1229
1235
|
}
|
|
1230
1236
|
|
|
1231
1237
|
static int
|
data/lib/bootsnap/cli.rb
CHANGED
|
@@ -7,7 +7,8 @@ module Bootsnap
|
|
|
7
7
|
module CompileCache
|
|
8
8
|
module ISeq
|
|
9
9
|
class << self
|
|
10
|
-
attr_reader
|
|
10
|
+
attr_reader :cache_dir
|
|
11
|
+
attr_accessor :compiler_selector, :default_compiler
|
|
11
12
|
|
|
12
13
|
def cache_dir=(cache_dir)
|
|
13
14
|
@cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq"
|
|
@@ -18,71 +19,83 @@ module Bootsnap
|
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
class Compiler
|
|
23
|
+
attr_reader :namespace, :compile_options
|
|
24
|
+
|
|
25
|
+
def initialize(namespace = nil, compile_options = nil)
|
|
26
|
+
@namespace = namespace
|
|
27
|
+
@compile_options = compile_options
|
|
24
28
|
end
|
|
25
|
-
false
|
|
26
|
-
rescue TypeError
|
|
27
|
-
true
|
|
28
|
-
end
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
RubyVM::InstructionSequence.compile_file(path)
|
|
34
|
-
rescue SyntaxError
|
|
35
|
-
return UNCOMPILABLE # syntax error
|
|
30
|
+
has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
|
|
31
|
+
if defined? RubyVM::InstructionSequence
|
|
32
|
+
RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary
|
|
36
33
|
end
|
|
34
|
+
false
|
|
35
|
+
rescue TypeError
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if has_ruby_bug_18250
|
|
40
|
+
def input_to_storage(_, path)
|
|
41
|
+
iseq = RubyVM::InstructionSequence.compile_file(path, @compile_options)
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
begin
|
|
44
|
+
iseq.to_binary
|
|
45
|
+
rescue TypeError
|
|
46
|
+
UNCOMPILABLE # ruby bug #18250
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
def input_to_storage(_, path)
|
|
51
|
+
RubyVM::InstructionSequence.compile_file(path, @compile_options).to_binary
|
|
42
52
|
end
|
|
43
53
|
end
|
|
44
|
-
|
|
45
|
-
def
|
|
46
|
-
RubyVM::InstructionSequence.
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
|
|
55
|
+
def storage_to_output(binary, _args)
|
|
56
|
+
iseq = RubyVM::InstructionSequence.load_from_binary(binary)
|
|
57
|
+
binary.clear
|
|
58
|
+
iseq
|
|
59
|
+
rescue RuntimeError => error
|
|
60
|
+
if error.message == "broken binary format"
|
|
61
|
+
$stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
|
|
62
|
+
nil
|
|
63
|
+
else
|
|
64
|
+
raise
|
|
65
|
+
end
|
|
49
66
|
end
|
|
50
|
-
end
|
|
51
67
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
binary.clear
|
|
55
|
-
iseq
|
|
56
|
-
rescue RuntimeError => error
|
|
57
|
-
if error.message == "broken binary format"
|
|
58
|
-
$stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
|
|
59
|
-
nil
|
|
60
|
-
else
|
|
61
|
-
raise
|
|
68
|
+
def input_to_output(source, path, _kwargs)
|
|
69
|
+
RubyVM::InstructionSequence.compile(source, path, path, nil, @compile_options)
|
|
62
70
|
end
|
|
63
71
|
end
|
|
64
72
|
|
|
73
|
+
DEFAULT = Compiler.new
|
|
74
|
+
FROZEN_STRING_LITERAL = Compiler.new("-fstr", {frozen_string_literal: true}.freeze)
|
|
75
|
+
MUTABLE_STRING_LITERAL = Compiler.new("-no-fstr", {frozen_string_literal: false}.freeze)
|
|
76
|
+
@default_compiler = DEFAULT
|
|
77
|
+
|
|
65
78
|
def self.fetch(path, cache_dir: ISeq.cache_dir)
|
|
79
|
+
compiler = compiler_selector&.call(path) || default_compiler
|
|
66
80
|
Bootsnap::CompileCache::Native.fetch(
|
|
67
81
|
cache_dir,
|
|
82
|
+
compiler.namespace,
|
|
68
83
|
path.to_s,
|
|
69
|
-
|
|
84
|
+
compiler,
|
|
70
85
|
nil,
|
|
71
86
|
)
|
|
72
87
|
end
|
|
73
88
|
|
|
74
89
|
def self.precompile(path)
|
|
90
|
+
compiler = compiler_selector&.call(path) || default_compiler
|
|
75
91
|
Bootsnap::CompileCache::Native.precompile(
|
|
76
92
|
cache_dir,
|
|
93
|
+
compiler.namespace,
|
|
77
94
|
path.to_s,
|
|
78
|
-
|
|
95
|
+
compiler,
|
|
79
96
|
)
|
|
80
97
|
end
|
|
81
98
|
|
|
82
|
-
def self.input_to_output(_data, _kwargs)
|
|
83
|
-
nil # ruby handles this
|
|
84
|
-
end
|
|
85
|
-
|
|
86
99
|
module InstructionSequenceMixin
|
|
87
100
|
def load_iseq(path)
|
|
88
101
|
# Having coverage enabled prevents iseq dumping/loading.
|
|
@@ -97,7 +110,7 @@ module Bootsnap
|
|
|
97
110
|
end
|
|
98
111
|
|
|
99
112
|
def compile_option=(hash)
|
|
100
|
-
super
|
|
113
|
+
super
|
|
101
114
|
Bootsnap::CompileCache::ISeq.compile_option_updated
|
|
102
115
|
end
|
|
103
116
|
end
|
|
@@ -28,6 +28,7 @@ module Bootsnap
|
|
|
28
28
|
|
|
29
29
|
CompileCache::Native.precompile(
|
|
30
30
|
cache_dir,
|
|
31
|
+
nil,
|
|
31
32
|
path.to_s,
|
|
32
33
|
@implementation,
|
|
33
34
|
)
|
|
@@ -175,7 +176,7 @@ module Bootsnap
|
|
|
175
176
|
result
|
|
176
177
|
end
|
|
177
178
|
|
|
178
|
-
def input_to_output(data, kwargs)
|
|
179
|
+
def input_to_output(data, _path, kwargs)
|
|
179
180
|
::YAML.unsafe_load(data, **(kwargs || {}))
|
|
180
181
|
end
|
|
181
182
|
end
|
|
@@ -215,7 +216,7 @@ module Bootsnap
|
|
|
215
216
|
end
|
|
216
217
|
end
|
|
217
218
|
|
|
218
|
-
def input_to_output(data, kwargs)
|
|
219
|
+
def input_to_output(data, _path, kwargs)
|
|
219
220
|
::YAML.load(data, **(kwargs || {}))
|
|
220
221
|
end
|
|
221
222
|
end
|
|
@@ -233,6 +234,7 @@ module Bootsnap
|
|
|
233
234
|
|
|
234
235
|
CompileCache::Native.fetch(
|
|
235
236
|
CompileCache::YAML.cache_dir,
|
|
237
|
+
nil,
|
|
236
238
|
File.realpath(path),
|
|
237
239
|
CompileCache::YAML::Psych4::SafeLoad,
|
|
238
240
|
kwargs,
|
|
@@ -253,6 +255,7 @@ module Bootsnap
|
|
|
253
255
|
|
|
254
256
|
CompileCache::Native.fetch(
|
|
255
257
|
CompileCache::YAML.cache_dir,
|
|
258
|
+
nil,
|
|
256
259
|
File.realpath(path),
|
|
257
260
|
CompileCache::YAML::Psych4::UnsafeLoad,
|
|
258
261
|
kwargs,
|
|
@@ -288,7 +291,7 @@ module Bootsnap
|
|
|
288
291
|
unpacker.unpack
|
|
289
292
|
end
|
|
290
293
|
|
|
291
|
-
def input_to_output(data, kwargs)
|
|
294
|
+
def input_to_output(data, _path, kwargs)
|
|
292
295
|
::YAML.load(data, **(kwargs || {}))
|
|
293
296
|
end
|
|
294
297
|
|
|
@@ -305,6 +308,7 @@ module Bootsnap
|
|
|
305
308
|
|
|
306
309
|
CompileCache::Native.fetch(
|
|
307
310
|
CompileCache::YAML.cache_dir,
|
|
311
|
+
nil,
|
|
308
312
|
File.realpath(path),
|
|
309
313
|
CompileCache::YAML::Psych3,
|
|
310
314
|
kwargs,
|
|
@@ -325,6 +329,7 @@ module Bootsnap
|
|
|
325
329
|
|
|
326
330
|
CompileCache::Native.fetch(
|
|
327
331
|
CompileCache::YAML.cache_dir,
|
|
332
|
+
nil,
|
|
328
333
|
File.realpath(path),
|
|
329
334
|
CompileCache::YAML::Psych3,
|
|
330
335
|
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)
|
|
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)
|
|
@@ -59,7 +59,7 @@ module Bootsnap
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def purge_multi(features)
|
|
62
|
-
rejected_hashes = features.
|
|
62
|
+
rejected_hashes = features.to_h { |f| [f.hash, true] }
|
|
63
63
|
@mutex.synchronize do
|
|
64
64
|
@lfi.reject! { |_, hash| rejected_hashes.key?(hash) }
|
|
65
65
|
end
|
|
@@ -18,21 +18,12 @@ module Bootsnap
|
|
|
18
18
|
class << self
|
|
19
19
|
attr_accessor :ignored_directories
|
|
20
20
|
|
|
21
|
-
def ruby_call(
|
|
22
|
-
|
|
23
|
-
return [] unless File.directory?(
|
|
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(
|
|
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
|
-
|
|
87
|
-
|
|
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 (
|
|
91
|
-
|
|
92
|
-
dirs
|
|
93
|
-
dirs.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
data/lib/bootsnap/version.rb
CHANGED
data/lib/bootsnap.rb
CHANGED
|
@@ -43,6 +43,13 @@ module Bootsnap
|
|
|
43
43
|
@instrumentation.call(event, path)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
def load_config
|
|
47
|
+
config_path = File.expand_path(ENV["BOOTSNAP_CONFIG"] || "config/bootsnap.rb")
|
|
48
|
+
if File.exist?(config_path)
|
|
49
|
+
require(config_path)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
46
53
|
def setup(
|
|
47
54
|
cache_dir:,
|
|
48
55
|
development_mode: true,
|
|
@@ -76,6 +83,28 @@ module Bootsnap
|
|
|
76
83
|
readonly: readonly,
|
|
77
84
|
revalidation: revalidation,
|
|
78
85
|
)
|
|
86
|
+
|
|
87
|
+
load_config
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def enable_frozen_string_literal(app_only: false)
|
|
91
|
+
if app_only
|
|
92
|
+
gems_root = File.join(Bundler.bundle_path.cleanpath, "")
|
|
93
|
+
app_root = File.join(Dir.pwd, "")
|
|
94
|
+
Bootsnap::CompileCache::ISeq.default_compiler = Bootsnap::CompileCache::ISeq::DEFAULT
|
|
95
|
+
Bootsnap::CompileCache::ISeq.compiler_selector = lambda { |path|
|
|
96
|
+
# Enable `frozen_string_literal: true` for app code, but not gems.
|
|
97
|
+
|
|
98
|
+
if path.start_with?(app_root) && !path.start_with?(gems_root)
|
|
99
|
+
Bootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL
|
|
100
|
+
else
|
|
101
|
+
Bootsnap::CompileCache::ISeq::DEFAULT
|
|
102
|
+
end
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
Bootsnap::CompileCache::ISeq.compiler_selector = nil
|
|
106
|
+
Bootsnap::CompileCache::ISeq.default_compiler = Bootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL
|
|
107
|
+
end
|
|
79
108
|
end
|
|
80
109
|
|
|
81
110
|
def unload_cache!
|
|
@@ -150,7 +179,7 @@ module Bootsnap
|
|
|
150
179
|
end
|
|
151
180
|
|
|
152
181
|
# Allow the C extension to redefine `rb_get_path` without warning.
|
|
153
|
-
alias_method :rb_get_path, :rb_get_path
|
|
182
|
+
alias_method :rb_get_path, :rb_get_path
|
|
154
183
|
|
|
155
184
|
private
|
|
156
185
|
|
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.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Burke Libbey
|
|
@@ -73,14 +73,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
73
73
|
requirements:
|
|
74
74
|
- - ">="
|
|
75
75
|
- !ruby/object:Gem::Version
|
|
76
|
-
version: 2.
|
|
76
|
+
version: 2.7.0
|
|
77
77
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0'
|
|
82
82
|
requirements: []
|
|
83
|
-
rubygems_version: 4.0.
|
|
83
|
+
rubygems_version: 4.0.6
|
|
84
84
|
specification_version: 4
|
|
85
85
|
summary: Boot large ruby/rails apps faster
|
|
86
86
|
test_files: []
|