bootsnap 1.4.8 → 1.6.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 +23 -0
- data/README.md +16 -2
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +131 -26
- data/lib/bootsnap.rb +2 -2
- data/lib/bootsnap/cli.rb +246 -0
- data/lib/bootsnap/cli/worker_pool.rb +131 -0
- data/lib/bootsnap/compile_cache.rb +2 -2
- data/lib/bootsnap/compile_cache/iseq.rb +21 -7
- data/lib/bootsnap/compile_cache/yaml.rb +88 -39
- data/lib/bootsnap/load_path_cache.rb +1 -1
- data/lib/bootsnap/version.rb +1 -1
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '029d63ba428f470d2cd2ef194d857e20689e7c8e377e2eaaaab83570f366034a'
|
4
|
+
data.tar.gz: 36a55f9d12dddef6ea3c4739f586c9236d7f4bc677a8b6747dfd7465d46eeca2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 131ec17c4e4912f387c18778250e689467ebfcc80e91eb884237307af1a57a3c89d4fb20c772fbc0330123a796d631861a9cf145bd32c54183077bbc97d7fa6f
|
7
|
+
data.tar.gz: d0c92454c8a5d8b16a25908fd0ae134fc817c5977958bde8424baca2bb6c96e60eb648c9f770bf7c7f58a3dd98871d4e7b0e3a0950c269100fded45ca9fddfa3
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
# 1.6.0
|
4
|
+
|
5
|
+
* Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
|
6
|
+
* `bootsnap precompile` CLI use multiple processes to complete faster. (#341)
|
7
|
+
* `bootsnap precompile` CLI also precompile YAML files. (#340)
|
8
|
+
* Changed the load path cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-load-path-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/load-path-cache` for ease of use. (#334)
|
9
|
+
* Changed the compile cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-compile-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/compile-cache` for ease of use. (#334)
|
10
|
+
|
11
|
+
# 1.5.1
|
12
|
+
|
13
|
+
* Workaround a Ruby bug in InstructionSequence.compile_file. (#332)
|
14
|
+
|
15
|
+
# 1.5.0
|
16
|
+
|
17
|
+
* Add a command line to statically precompile the ISeq cache. (#326)
|
18
|
+
|
19
|
+
# 1.4.9
|
20
|
+
|
21
|
+
* [Windows support](https://github.com/Shopify/bootsnap/pull/319)
|
22
|
+
* [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
|
23
|
+
|
1
24
|
# 1.4.8
|
2
25
|
|
3
26
|
* [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Bootsnap [](https://github.com/Shopify/bootsnap/actions)
|
2
2
|
|
3
3
|
Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSupport` and `YAML`,
|
4
4
|
to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
|
@@ -29,7 +29,8 @@ If you are using Rails, add this to `config/boot.rb` immediately after `require
|
|
29
29
|
require 'bootsnap/setup'
|
30
30
|
```
|
31
31
|
|
32
|
-
Note that bootsnap writes to `tmp/cache
|
32
|
+
Note that bootsnap writes to `tmp/cache` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
|
33
|
+
and that directory *must* be writable. Rails will fail to
|
33
34
|
boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
|
34
35
|
unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
|
35
36
|
|
@@ -294,6 +295,19 @@ open /c/nope.bundle -> -1
|
|
294
295
|
# (nothing!)
|
295
296
|
```
|
296
297
|
|
298
|
+
## Precompilation
|
299
|
+
|
300
|
+
In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
|
301
|
+
But in production environments, such as docker images, you might need to precompile the cache.
|
302
|
+
|
303
|
+
To do so you can use the `bootsnap precompile` command.
|
304
|
+
|
305
|
+
Example:
|
306
|
+
|
307
|
+
```bash
|
308
|
+
$ bundle exec bootsnap precompile --gemfile app/ lib/
|
309
|
+
```
|
310
|
+
|
297
311
|
## When not to use Bootsnap
|
298
312
|
|
299
313
|
*Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
|
data/exe/bootsnap
ADDED
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -70,7 +70,7 @@ struct bs_cache_key {
|
|
70
70
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
71
71
|
|
72
72
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
73
|
-
static const uint32_t current_version =
|
73
|
+
static const uint32_t current_version = 3;
|
74
74
|
|
75
75
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
76
76
|
* new OS ABI, etc. */
|
@@ -91,16 +91,18 @@ static ID uncompilable;
|
|
91
91
|
|
92
92
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
93
93
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
94
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
94
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
95
|
+
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
95
96
|
|
96
97
|
/* Helpers */
|
97
98
|
static uint64_t fnv1a_64(const char *str);
|
98
99
|
static void bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
99
100
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
100
101
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
101
|
-
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
102
|
+
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
103
|
+
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
102
104
|
static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
103
|
-
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
|
105
|
+
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
|
104
106
|
static uint32_t get_ruby_revision(void);
|
105
107
|
static uint32_t get_ruby_platform(void);
|
106
108
|
|
@@ -108,12 +110,12 @@ static uint32_t get_ruby_platform(void);
|
|
108
110
|
* Helper functions to call ruby methods on handler object without crashing on
|
109
111
|
* exception.
|
110
112
|
*/
|
111
|
-
static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
|
113
|
+
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
112
114
|
static VALUE prot_storage_to_output(VALUE arg);
|
113
115
|
static VALUE prot_input_to_output(VALUE arg);
|
114
|
-
static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
|
116
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
115
117
|
static VALUE prot_input_to_storage(VALUE arg);
|
116
|
-
static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
118
|
+
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
117
119
|
struct s2o_data;
|
118
120
|
struct i2o_data;
|
119
121
|
struct i2s_data;
|
@@ -148,7 +150,8 @@ Init_bootsnap(void)
|
|
148
150
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
149
151
|
|
150
152
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
151
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
153
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
154
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
152
155
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
153
156
|
|
154
157
|
current_umask = umask(0777);
|
@@ -267,7 +270,6 @@ static void
|
|
267
270
|
bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
268
271
|
{
|
269
272
|
uint64_t hash = fnv1a_64(path);
|
270
|
-
|
271
273
|
uint8_t first_byte = (hash >> (64 - 8));
|
272
274
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
273
275
|
|
@@ -301,7 +303,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
301
303
|
* conversions on the ruby VALUE arguments before passing them along.
|
302
304
|
*/
|
303
305
|
static VALUE
|
304
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
306
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
305
307
|
{
|
306
308
|
FilePathValue(path_v);
|
307
309
|
|
@@ -319,9 +321,35 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
319
321
|
/* generate cache path to cache_path */
|
320
322
|
bs_cache_path(cachedir, path, &cache_path);
|
321
323
|
|
322
|
-
return bs_fetch(path, path_v, cache_path, handler);
|
324
|
+
return bs_fetch(path, path_v, cache_path, handler, args);
|
323
325
|
}
|
324
326
|
|
327
|
+
/*
|
328
|
+
* Entrypoint for Bootsnap::CompileCache::Native.precompile.
|
329
|
+
* Similar to fetch, but it only generate the cache if missing
|
330
|
+
* and doesn't return the content.
|
331
|
+
*/
|
332
|
+
static VALUE
|
333
|
+
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
334
|
+
{
|
335
|
+
FilePathValue(path_v);
|
336
|
+
|
337
|
+
Check_Type(cachedir_v, T_STRING);
|
338
|
+
Check_Type(path_v, T_STRING);
|
339
|
+
|
340
|
+
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
341
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
342
|
+
}
|
343
|
+
|
344
|
+
char * cachedir = RSTRING_PTR(cachedir_v);
|
345
|
+
char * path = RSTRING_PTR(path_v);
|
346
|
+
char cache_path[MAX_CACHEPATH_SIZE];
|
347
|
+
|
348
|
+
/* generate cache path to cache_path */
|
349
|
+
bs_cache_path(cachedir, path, &cache_path);
|
350
|
+
|
351
|
+
return bs_precompile(path, path_v, cache_path, handler);
|
352
|
+
}
|
325
353
|
/*
|
326
354
|
* Open the file we want to load/cache and generate a cache key for it if it
|
327
355
|
* was loaded.
|
@@ -428,7 +456,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
428
456
|
* or exception, will be the final data returnable to the user.
|
429
457
|
*/
|
430
458
|
static int
|
431
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
459
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
432
460
|
{
|
433
461
|
char * data = NULL;
|
434
462
|
ssize_t nread;
|
@@ -454,9 +482,9 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
454
482
|
goto done;
|
455
483
|
}
|
456
484
|
|
457
|
-
storage_data =
|
485
|
+
storage_data = rb_str_new(data, data_size);
|
458
486
|
|
459
|
-
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
487
|
+
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
460
488
|
ret = 0;
|
461
489
|
done:
|
462
490
|
if (data != NULL) xfree(data);
|
@@ -624,7 +652,7 @@ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_prov
|
|
624
652
|
* - Return storage_to_output(storage_data)
|
625
653
|
*/
|
626
654
|
static VALUE
|
627
|
-
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
655
|
+
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
|
628
656
|
{
|
629
657
|
struct bs_cache_key cached_key, current_key;
|
630
658
|
char * contents = NULL;
|
@@ -657,7 +685,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
657
685
|
if (valid_cache) {
|
658
686
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
659
687
|
res = fetch_cached_data(
|
660
|
-
cache_fd, (ssize_t)cached_key.data_size, handler,
|
688
|
+
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
661
689
|
&output_data, &exception_tag, &errno_provenance
|
662
690
|
);
|
663
691
|
if (exception_tag != 0) goto raise;
|
@@ -671,15 +699,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
671
699
|
|
672
700
|
/* Read the contents of the source file into a buffer */
|
673
701
|
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
674
|
-
input_data =
|
702
|
+
input_data = rb_str_new(contents, current_key.size);
|
675
703
|
|
676
704
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
677
|
-
exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
|
705
|
+
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
678
706
|
if (exception_tag != 0) goto raise;
|
679
707
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
680
708
|
* to cache anything; just return input_to_output(input_data) */
|
681
709
|
if (storage_data == uncompilable) {
|
682
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
710
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
683
711
|
if (exception_tag != 0) goto raise;
|
684
712
|
goto succeed;
|
685
713
|
}
|
@@ -691,7 +719,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
691
719
|
if (res < 0) goto fail_errno;
|
692
720
|
|
693
721
|
/* Having written the cache, now convert storage_data to output_data */
|
694
|
-
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
722
|
+
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
695
723
|
if (exception_tag != 0) goto raise;
|
696
724
|
|
697
725
|
/* If output_data is nil, delete the cache entry and generate the output
|
@@ -701,7 +729,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
701
729
|
errno_provenance = "bs_fetch:unlink";
|
702
730
|
goto fail_errno;
|
703
731
|
}
|
704
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
732
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
705
733
|
if (exception_tag != 0) goto raise;
|
706
734
|
}
|
707
735
|
|
@@ -732,6 +760,79 @@ invalid_type_storage_data:
|
|
732
760
|
#undef CLEANUP
|
733
761
|
}
|
734
762
|
|
763
|
+
static VALUE
|
764
|
+
bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
765
|
+
{
|
766
|
+
struct bs_cache_key cached_key, current_key;
|
767
|
+
char * contents = NULL;
|
768
|
+
int cache_fd = -1, current_fd = -1;
|
769
|
+
int res, valid_cache = 0, exception_tag = 0;
|
770
|
+
const char * errno_provenance = NULL;
|
771
|
+
|
772
|
+
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
773
|
+
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
774
|
+
|
775
|
+
/* Open the source file and generate a cache key for it */
|
776
|
+
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
777
|
+
if (current_fd < 0) goto fail;
|
778
|
+
|
779
|
+
/* Open the cache key if it exists, and read its cache key in */
|
780
|
+
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
781
|
+
if (cache_fd == CACHE_MISSING_OR_INVALID) {
|
782
|
+
/* This is ok: valid_cache remains false, we re-populate it. */
|
783
|
+
} else if (cache_fd < 0) {
|
784
|
+
goto fail;
|
785
|
+
} else {
|
786
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
787
|
+
* it was generated. */
|
788
|
+
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
789
|
+
}
|
790
|
+
|
791
|
+
if (valid_cache) {
|
792
|
+
goto succeed;
|
793
|
+
}
|
794
|
+
|
795
|
+
close(cache_fd);
|
796
|
+
cache_fd = -1;
|
797
|
+
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
798
|
+
|
799
|
+
/* Read the contents of the source file into a buffer */
|
800
|
+
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
|
801
|
+
input_data = rb_str_new(contents, current_key.size);
|
802
|
+
|
803
|
+
/* Try to compile the input_data using input_to_storage(input_data) */
|
804
|
+
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
805
|
+
if (exception_tag != 0) goto fail;
|
806
|
+
|
807
|
+
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
808
|
+
* to cache anything; just return false */
|
809
|
+
if (storage_data == uncompilable) {
|
810
|
+
goto fail;
|
811
|
+
}
|
812
|
+
/* If storage_data isn't a string, we can't cache it */
|
813
|
+
if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
|
814
|
+
|
815
|
+
/* Write the cache key and storage_data to the cache directory */
|
816
|
+
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
817
|
+
if (res < 0) goto fail;
|
818
|
+
|
819
|
+
goto succeed;
|
820
|
+
|
821
|
+
#define CLEANUP \
|
822
|
+
if (contents != NULL) xfree(contents); \
|
823
|
+
if (current_fd >= 0) close(current_fd); \
|
824
|
+
if (cache_fd >= 0) close(cache_fd);
|
825
|
+
|
826
|
+
succeed:
|
827
|
+
CLEANUP;
|
828
|
+
return Qtrue;
|
829
|
+
fail:
|
830
|
+
CLEANUP;
|
831
|
+
return Qfalse;
|
832
|
+
#undef CLEANUP
|
833
|
+
}
|
834
|
+
|
835
|
+
|
735
836
|
/*****************************************************************************/
|
736
837
|
/********************* Handler Wrappers **************************************/
|
737
838
|
/*****************************************************************************
|
@@ -751,11 +852,13 @@ invalid_type_storage_data:
|
|
751
852
|
|
752
853
|
struct s2o_data {
|
753
854
|
VALUE handler;
|
855
|
+
VALUE args;
|
754
856
|
VALUE storage_data;
|
755
857
|
};
|
756
858
|
|
757
859
|
struct i2o_data {
|
758
860
|
VALUE handler;
|
861
|
+
VALUE args;
|
759
862
|
VALUE input_data;
|
760
863
|
};
|
761
864
|
|
@@ -769,15 +872,16 @@ static VALUE
|
|
769
872
|
prot_storage_to_output(VALUE arg)
|
770
873
|
{
|
771
874
|
struct s2o_data * data = (struct s2o_data *)arg;
|
772
|
-
return rb_funcall(data->handler, rb_intern("storage_to_output"),
|
875
|
+
return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
|
773
876
|
}
|
774
877
|
|
775
878
|
static int
|
776
|
-
bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
879
|
+
bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
|
777
880
|
{
|
778
881
|
int state;
|
779
882
|
struct s2o_data s2o_data = {
|
780
883
|
.handler = handler,
|
884
|
+
.args = args,
|
781
885
|
.storage_data = storage_data,
|
782
886
|
};
|
783
887
|
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
@@ -785,10 +889,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
785
889
|
}
|
786
890
|
|
787
891
|
static void
|
788
|
-
bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
|
892
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
789
893
|
{
|
790
894
|
struct i2o_data i2o_data = {
|
791
895
|
.handler = handler,
|
896
|
+
.args = args,
|
792
897
|
.input_data = input_data,
|
793
898
|
};
|
794
899
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
@@ -798,7 +903,7 @@ static VALUE
|
|
798
903
|
prot_input_to_output(VALUE arg)
|
799
904
|
{
|
800
905
|
struct i2o_data * data = (struct i2o_data *)arg;
|
801
|
-
return rb_funcall(data->handler, rb_intern("input_to_output"),
|
906
|
+
return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
|
802
907
|
}
|
803
908
|
|
804
909
|
static VALUE
|
@@ -825,7 +930,7 @@ prot_input_to_storage(VALUE arg)
|
|
825
930
|
}
|
826
931
|
|
827
932
|
static int
|
828
|
-
bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
933
|
+
bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
829
934
|
{
|
830
935
|
int state;
|
831
936
|
struct i2s_data i2s_data = {
|
data/lib/bootsnap.rb
CHANGED
@@ -24,13 +24,13 @@ module Bootsnap
|
|
24
24
|
setup_disable_trace if disable_trace
|
25
25
|
|
26
26
|
Bootsnap::LoadPathCache.setup(
|
27
|
-
cache_path: cache_dir + '/bootsnap
|
27
|
+
cache_path: cache_dir + '/bootsnap/load-path-cache',
|
28
28
|
development_mode: development_mode,
|
29
29
|
active_support: autoload_paths_cache
|
30
30
|
) if load_path_cache
|
31
31
|
|
32
32
|
Bootsnap::CompileCache.setup(
|
33
|
-
cache_dir: cache_dir + '/bootsnap
|
33
|
+
cache_dir: cache_dir + '/bootsnap/compile-cache',
|
34
34
|
iseq: compile_cache_iseq,
|
35
35
|
yaml: compile_cache_yaml
|
36
36
|
)
|
data/lib/bootsnap/cli.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bootsnap'
|
4
|
+
require 'bootsnap/cli/worker_pool'
|
5
|
+
require 'optparse'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'etc'
|
8
|
+
|
9
|
+
module Bootsnap
|
10
|
+
class CLI
|
11
|
+
unless Regexp.method_defined?(:match?)
|
12
|
+
module RegexpMatchBackport
|
13
|
+
refine Regexp do
|
14
|
+
def match?(string)
|
15
|
+
!!match(string)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
using RegexpMatchBackport
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :cache_dir, :argv
|
23
|
+
|
24
|
+
attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :jobs
|
25
|
+
|
26
|
+
def initialize(argv)
|
27
|
+
@argv = argv
|
28
|
+
self.cache_dir = ENV.fetch('BOOTSNAP_CACHE_DIR', 'tmp/cache')
|
29
|
+
self.compile_gemfile = false
|
30
|
+
self.exclude = nil
|
31
|
+
self.verbose = false
|
32
|
+
self.jobs = Etc.nprocessors
|
33
|
+
self.iseq = true
|
34
|
+
self.yaml = true
|
35
|
+
end
|
36
|
+
|
37
|
+
def precompile_command(*sources)
|
38
|
+
require 'bootsnap/compile_cache/iseq'
|
39
|
+
require 'bootsnap/compile_cache/yaml'
|
40
|
+
|
41
|
+
fix_default_encoding do
|
42
|
+
Bootsnap::CompileCache::ISeq.cache_dir = self.cache_dir
|
43
|
+
Bootsnap::CompileCache::YAML.init!
|
44
|
+
Bootsnap::CompileCache::YAML.cache_dir = self.cache_dir
|
45
|
+
|
46
|
+
@work_pool = WorkerPool.create(size: jobs, jobs: {
|
47
|
+
ruby: method(:precompile_ruby),
|
48
|
+
yaml: method(:precompile_yaml),
|
49
|
+
})
|
50
|
+
@work_pool.spawn
|
51
|
+
|
52
|
+
main_sources = sources.map { |d| File.expand_path(d) }
|
53
|
+
precompile_ruby_files(main_sources)
|
54
|
+
precompile_yaml_files(main_sources)
|
55
|
+
|
56
|
+
if compile_gemfile
|
57
|
+
# Some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
|
58
|
+
gem_exclude = Regexp.union([exclude, '/spec/', '/test/'].compact)
|
59
|
+
precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude)
|
60
|
+
|
61
|
+
# Gems that include YAML files usually don't put them in `lib/`.
|
62
|
+
# So we look at the gem root.
|
63
|
+
gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gem\/[^/]+}
|
64
|
+
gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq
|
65
|
+
precompile_yaml_files(gem_paths, exclude: gem_exclude)
|
66
|
+
end
|
67
|
+
|
68
|
+
if exitstatus = @work_pool.shutdown
|
69
|
+
exit(exitstatus)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
0
|
73
|
+
end
|
74
|
+
|
75
|
+
dir_sort = begin
|
76
|
+
Dir[__FILE__, sort: false]
|
77
|
+
true
|
78
|
+
rescue ArgumentError, TypeError
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
if dir_sort
|
83
|
+
def list_files(path, pattern)
|
84
|
+
if File.directory?(path)
|
85
|
+
Dir[File.join(path, pattern), sort: false]
|
86
|
+
elsif File.exist?(path)
|
87
|
+
[path]
|
88
|
+
else
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
else
|
93
|
+
def list_files(path, pattern)
|
94
|
+
if File.directory?(path)
|
95
|
+
Dir[File.join(path, pattern)]
|
96
|
+
elsif File.exist?(path)
|
97
|
+
[path]
|
98
|
+
else
|
99
|
+
[]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def run
|
105
|
+
parser.parse!(argv)
|
106
|
+
command = argv.shift
|
107
|
+
method = "#{command}_command"
|
108
|
+
if respond_to?(method)
|
109
|
+
public_send(method, *argv)
|
110
|
+
else
|
111
|
+
invalid_usage!("Unknown command: #{command}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def precompile_yaml_files(load_paths, exclude: self.exclude)
|
118
|
+
return unless yaml
|
119
|
+
|
120
|
+
load_paths.each do |path|
|
121
|
+
if !exclude || !exclude.match?(path)
|
122
|
+
list_files(path, '**/*.{yml,yaml}').each do |yaml_file|
|
123
|
+
# We ignore hidden files to not match the various .ci.yml files
|
124
|
+
if !yaml_file.include?('/.') && (!exclude || !exclude.match?(yaml_file))
|
125
|
+
@work_pool.push(:yaml, yaml_file)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def precompile_yaml(*yaml_files)
|
133
|
+
Array(yaml_files).each do |yaml_file|
|
134
|
+
if CompileCache::YAML.precompile(yaml_file, cache_dir: cache_dir)
|
135
|
+
STDERR.puts(yaml_file) if verbose
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def precompile_ruby_files(load_paths, exclude: self.exclude)
|
141
|
+
return unless iseq
|
142
|
+
|
143
|
+
load_paths.each do |path|
|
144
|
+
if !exclude || !exclude.match?(path)
|
145
|
+
list_files(path, '**/*.rb').each do |ruby_file|
|
146
|
+
if !exclude || !exclude.match?(ruby_file)
|
147
|
+
@work_pool.push(:ruby, ruby_file)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def precompile_ruby(*ruby_files)
|
155
|
+
Array(ruby_files).each do |ruby_file|
|
156
|
+
if CompileCache::ISeq.precompile(ruby_file, cache_dir: cache_dir)
|
157
|
+
STDERR.puts(ruby_file) if verbose
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def fix_default_encoding
|
163
|
+
if Encoding.default_external == Encoding::US_ASCII
|
164
|
+
Encoding.default_external = Encoding::UTF_8
|
165
|
+
begin
|
166
|
+
yield
|
167
|
+
ensure
|
168
|
+
Encoding.default_external = Encoding::US_ASCII
|
169
|
+
end
|
170
|
+
else
|
171
|
+
yield
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def invalid_usage!(message)
|
176
|
+
STDERR.puts message
|
177
|
+
STDERR.puts
|
178
|
+
STDERR.puts parser
|
179
|
+
1
|
180
|
+
end
|
181
|
+
|
182
|
+
def cache_dir=(dir)
|
183
|
+
@cache_dir = File.expand_path(File.join(dir, 'bootsnap/compile-cache'))
|
184
|
+
end
|
185
|
+
|
186
|
+
def exclude_pattern(pattern)
|
187
|
+
(@exclude_patterns ||= []) << Regexp.new(pattern)
|
188
|
+
self.exclude = Regexp.union(@exclude_patterns)
|
189
|
+
end
|
190
|
+
|
191
|
+
def parser
|
192
|
+
@parser ||= OptionParser.new do |opts|
|
193
|
+
opts.banner = "Usage: bootsnap COMMAND [ARGS]"
|
194
|
+
opts.separator ""
|
195
|
+
opts.separator "GLOBAL OPTIONS"
|
196
|
+
opts.separator ""
|
197
|
+
|
198
|
+
help = <<~EOS
|
199
|
+
Path to the bootsnap cache directory. Defaults to tmp/cache
|
200
|
+
EOS
|
201
|
+
opts.on('--cache-dir DIR', help.strip) do |dir|
|
202
|
+
self.cache_dir = dir
|
203
|
+
end
|
204
|
+
|
205
|
+
help = <<~EOS
|
206
|
+
Print precompiled paths.
|
207
|
+
EOS
|
208
|
+
opts.on('--verbose', '-v', help.strip) do
|
209
|
+
self.verbose = true
|
210
|
+
end
|
211
|
+
|
212
|
+
help = <<~EOS
|
213
|
+
Number of workers to use. Default to number of processors, set to 0 to disable multi-processing.
|
214
|
+
EOS
|
215
|
+
opts.on('--jobs JOBS', '-j', help.strip) do |jobs|
|
216
|
+
self.jobs = Integer(jobs)
|
217
|
+
end
|
218
|
+
|
219
|
+
opts.separator ""
|
220
|
+
opts.separator "COMMANDS"
|
221
|
+
opts.separator ""
|
222
|
+
opts.separator " precompile [DIRECTORIES...]: Precompile all .rb files in the passed directories"
|
223
|
+
|
224
|
+
help = <<~EOS
|
225
|
+
Precompile the gems in Gemfile
|
226
|
+
EOS
|
227
|
+
opts.on('--gemfile', help) { self.compile_gemfile = true }
|
228
|
+
|
229
|
+
help = <<~EOS
|
230
|
+
Path pattern to not precompile. e.g. --exclude 'aws-sdk|google-api'
|
231
|
+
EOS
|
232
|
+
opts.on('--exclude PATTERN', help) { |pattern| exclude_pattern(pattern) }
|
233
|
+
|
234
|
+
help = <<~EOS
|
235
|
+
Disable ISeq (.rb) precompilation.
|
236
|
+
EOS
|
237
|
+
opts.on('--no-iseq', help) { self.iseq = false }
|
238
|
+
|
239
|
+
help = <<~EOS
|
240
|
+
Disable YAML precompilation.
|
241
|
+
EOS
|
242
|
+
opts.on('--no-yaml', help) { self.yaml = false }
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bootsnap
|
4
|
+
class CLI
|
5
|
+
class WorkerPool
|
6
|
+
class << self
|
7
|
+
def create(size:, jobs:)
|
8
|
+
if size > 0 && Process.respond_to?(:fork)
|
9
|
+
new(size: size, jobs: jobs)
|
10
|
+
else
|
11
|
+
Inline.new(jobs: jobs)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Inline
|
17
|
+
def initialize(jobs: {})
|
18
|
+
@jobs = jobs
|
19
|
+
end
|
20
|
+
|
21
|
+
def push(job, *args)
|
22
|
+
@jobs.fetch(job).call(*args)
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def spawn
|
27
|
+
# noop
|
28
|
+
end
|
29
|
+
|
30
|
+
def shutdown
|
31
|
+
# noop
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Worker
|
36
|
+
attr_reader :to_io, :pid
|
37
|
+
|
38
|
+
def initialize(jobs)
|
39
|
+
@jobs = jobs
|
40
|
+
@pipe_out, @to_io = IO.pipe
|
41
|
+
@pid = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def write(message, block: true)
|
45
|
+
payload = Marshal.dump(message)
|
46
|
+
if block
|
47
|
+
to_io.write(payload)
|
48
|
+
true
|
49
|
+
else
|
50
|
+
to_io.write_nonblock(payload, exception: false) != :wait_writable
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def close
|
55
|
+
to_io.close
|
56
|
+
end
|
57
|
+
|
58
|
+
def work_loop
|
59
|
+
loop do
|
60
|
+
job, *args = Marshal.load(@pipe_out)
|
61
|
+
return if job == :exit
|
62
|
+
@jobs.fetch(job).call(*args)
|
63
|
+
end
|
64
|
+
rescue IOError
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def spawn
|
69
|
+
@pid = Process.fork do
|
70
|
+
to_io.close
|
71
|
+
work_loop
|
72
|
+
exit!(0)
|
73
|
+
end
|
74
|
+
@pipe_out.close
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(size:, jobs: {})
|
80
|
+
@size = size
|
81
|
+
@jobs = jobs
|
82
|
+
@queue = Queue.new
|
83
|
+
@pids = []
|
84
|
+
end
|
85
|
+
|
86
|
+
def spawn
|
87
|
+
@workers = @size.times.map { Worker.new(@jobs) }
|
88
|
+
@workers.each(&:spawn)
|
89
|
+
@dispatcher_thread = Thread.new { dispatch_loop }
|
90
|
+
@dispatcher_thread.abort_on_exception = true
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
def dispatch_loop
|
95
|
+
loop do
|
96
|
+
case job = @queue.pop
|
97
|
+
when nil
|
98
|
+
@workers.each do |worker|
|
99
|
+
worker.write([:exit])
|
100
|
+
worker.close
|
101
|
+
end
|
102
|
+
return true
|
103
|
+
else
|
104
|
+
unless @workers.sample.write(job, block: false)
|
105
|
+
free_worker.write(job)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def free_worker
|
112
|
+
IO.select(nil, @workers)[1].sample
|
113
|
+
end
|
114
|
+
|
115
|
+
def push(*args)
|
116
|
+
@queue.push(args)
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def shutdown
|
121
|
+
@queue.close
|
122
|
+
@dispatcher_thread.join
|
123
|
+
@workers.each do |worker|
|
124
|
+
_pid, status = Process.wait2(worker.pid)
|
125
|
+
return status.exitstatus unless status.success?
|
126
|
+
end
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -34,9 +34,9 @@ module Bootsnap
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def self.supported?
|
37
|
-
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
|
37
|
+
# only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
|
38
38
|
RUBY_ENGINE == 'ruby' &&
|
39
|
-
RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
|
39
|
+
RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
|
40
40
|
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
|
41
41
|
end
|
42
42
|
end
|
@@ -15,7 +15,7 @@ module Bootsnap
|
|
15
15
|
raise(Uncompilable, 'syntax error')
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.storage_to_output(binary)
|
18
|
+
def self.storage_to_output(binary, _args)
|
19
19
|
RubyVM::InstructionSequence.load_from_binary(binary)
|
20
20
|
rescue RuntimeError => e
|
21
21
|
if e.message == 'broken binary format'
|
@@ -26,7 +26,24 @@ module Bootsnap
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
def self.
|
29
|
+
def self.fetch(path, cache_dir: ISeq.cache_dir)
|
30
|
+
Bootsnap::CompileCache::Native.fetch(
|
31
|
+
cache_dir,
|
32
|
+
path.to_s,
|
33
|
+
Bootsnap::CompileCache::ISeq,
|
34
|
+
nil,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.precompile(path, cache_dir: ISeq.cache_dir)
|
39
|
+
Bootsnap::CompileCache::Native.precompile(
|
40
|
+
cache_dir,
|
41
|
+
path.to_s,
|
42
|
+
Bootsnap::CompileCache::ISeq,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.input_to_output(_data, _kwargs)
|
30
47
|
nil # ruby handles this
|
31
48
|
end
|
32
49
|
|
@@ -35,11 +52,7 @@ module Bootsnap
|
|
35
52
|
# Having coverage enabled prevents iseq dumping/loading.
|
36
53
|
return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
|
37
54
|
|
38
|
-
Bootsnap::CompileCache::
|
39
|
-
Bootsnap::CompileCache::ISeq.cache_dir,
|
40
|
-
path.to_s,
|
41
|
-
Bootsnap::CompileCache::ISeq
|
42
|
-
)
|
55
|
+
Bootsnap::CompileCache::ISeq.fetch(path.to_s)
|
43
56
|
rescue Errno::EACCES
|
44
57
|
Bootsnap::CompileCache.permission_error(path)
|
45
58
|
rescue RuntimeError => e
|
@@ -60,6 +73,7 @@ module Bootsnap
|
|
60
73
|
crc = Zlib.crc32(option.inspect)
|
61
74
|
Bootsnap::CompileCache::Native.compile_option_crc32 = crc
|
62
75
|
end
|
76
|
+
compile_option_updated
|
63
77
|
|
64
78
|
def self.install!(cache_dir)
|
65
79
|
Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
|
@@ -5,58 +5,107 @@ module Bootsnap
|
|
5
5
|
module CompileCache
|
6
6
|
module YAML
|
7
7
|
class << self
|
8
|
-
attr_accessor(:msgpack_factory)
|
9
|
-
end
|
8
|
+
attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
Marshal.dump(obj)
|
20
|
-
end
|
10
|
+
def input_to_storage(contents, _)
|
11
|
+
raise(Uncompilable) if contents.index("!ruby/object")
|
12
|
+
obj = ::YAML.load(contents)
|
13
|
+
msgpack_factory.dump(obj)
|
14
|
+
rescue NoMethodError, RangeError
|
15
|
+
# The object included things that we can't serialize
|
16
|
+
raise(Uncompilable)
|
17
|
+
end
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
Marshal.load(data)
|
28
|
-
else
|
29
|
-
msgpack_factory.unpacker.feed(data).read
|
19
|
+
def storage_to_output(data, kwargs)
|
20
|
+
if kwargs && kwargs.key?(:symbolize_names)
|
21
|
+
kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
|
22
|
+
end
|
23
|
+
msgpack_factory.load(data, kwargs)
|
30
24
|
end
|
31
|
-
end
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
def input_to_output(data, kwargs)
|
27
|
+
::YAML.load(data, **(kwargs || {}))
|
28
|
+
end
|
29
|
+
|
30
|
+
def precompile(path, cache_dir: YAML.cache_dir)
|
31
|
+
Bootsnap::CompileCache::Native.precompile(
|
32
|
+
cache_dir,
|
33
|
+
path.to_s,
|
34
|
+
Bootsnap::CompileCache::YAML,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def install!(cache_dir)
|
39
|
+
self.cache_dir = cache_dir
|
40
|
+
init!
|
41
|
+
::YAML.singleton_class.prepend(Patch)
|
42
|
+
end
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
44
|
+
def init!
|
45
|
+
require('yaml')
|
46
|
+
require('msgpack')
|
47
|
+
require('date')
|
40
48
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
# MessagePack serializes symbols as strings by default.
|
50
|
+
# We want them to roundtrip cleanly, so we use a custom factory.
|
51
|
+
# see: https://github.com/msgpack/msgpack-ruby/pull/122
|
52
|
+
factory = MessagePack::Factory.new
|
53
|
+
factory.register_type(0x00, Symbol)
|
54
|
+
factory.register_type(
|
55
|
+
MessagePack::Timestamp::TYPE, # or just -1
|
56
|
+
Time,
|
57
|
+
packer: MessagePack::Time::Packer,
|
58
|
+
unpacker: MessagePack::Time::Unpacker
|
59
|
+
)
|
60
|
+
|
61
|
+
marshal_fallback = {
|
62
|
+
packer: ->(value) { Marshal.dump(value) },
|
63
|
+
unpacker: ->(payload) { Marshal.load(payload) },
|
64
|
+
}
|
65
|
+
{
|
66
|
+
Date => 0x01,
|
67
|
+
Regexp => 0x02,
|
68
|
+
}.each do |type, code|
|
69
|
+
factory.register_type(code, type, marshal_fallback)
|
70
|
+
end
|
71
|
+
|
72
|
+
self.msgpack_factory = factory
|
73
|
+
|
74
|
+
self.supported_options = []
|
75
|
+
params = ::YAML.method(:load).parameters
|
76
|
+
if params.include?([:key, :symbolize_names])
|
77
|
+
self.supported_options << :symbolize_names
|
78
|
+
end
|
79
|
+
if params.include?([:key, :freeze])
|
80
|
+
if factory.load(factory.dump('yaml'), freeze: true).frozen?
|
81
|
+
self.supported_options << :freeze
|
82
|
+
end
|
83
|
+
end
|
84
|
+
self.supported_options.freeze
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module Patch
|
89
|
+
def load_file(path, *args)
|
90
|
+
return super if args.size > 1
|
91
|
+
if kwargs = args.first
|
92
|
+
return super unless kwargs.is_a?(Hash)
|
93
|
+
return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
|
94
|
+
end
|
47
95
|
|
48
|
-
klass = class << ::YAML; self; end
|
49
|
-
klass.send(:define_method, :load_file) do |path|
|
50
96
|
begin
|
51
|
-
Bootsnap::CompileCache::Native.fetch(
|
52
|
-
cache_dir,
|
97
|
+
::Bootsnap::CompileCache::Native.fetch(
|
98
|
+
Bootsnap::CompileCache::YAML.cache_dir,
|
53
99
|
path,
|
54
|
-
Bootsnap::CompileCache::YAML
|
100
|
+
::Bootsnap::CompileCache::YAML,
|
101
|
+
kwargs,
|
55
102
|
)
|
56
103
|
rescue Errno::EACCES
|
57
|
-
Bootsnap::CompileCache.permission_error(path)
|
104
|
+
::Bootsnap::CompileCache.permission_error(path)
|
58
105
|
end
|
59
106
|
end
|
107
|
+
|
108
|
+
ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
|
60
109
|
end
|
61
110
|
end
|
62
111
|
end
|
data/lib/bootsnap/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bootsnap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -97,7 +97,8 @@ dependencies:
|
|
97
97
|
description: Boot large ruby/rails apps faster
|
98
98
|
email:
|
99
99
|
- burke.libbey@shopify.com
|
100
|
-
executables:
|
100
|
+
executables:
|
101
|
+
- bootsnap
|
101
102
|
extensions:
|
102
103
|
- ext/bootsnap/extconf.rb
|
103
104
|
extra_rdoc_files: []
|
@@ -105,11 +106,14 @@ files:
|
|
105
106
|
- CHANGELOG.md
|
106
107
|
- LICENSE.txt
|
107
108
|
- README.md
|
109
|
+
- exe/bootsnap
|
108
110
|
- ext/bootsnap/bootsnap.c
|
109
111
|
- ext/bootsnap/bootsnap.h
|
110
112
|
- ext/bootsnap/extconf.rb
|
111
113
|
- lib/bootsnap.rb
|
112
114
|
- lib/bootsnap/bundler.rb
|
115
|
+
- lib/bootsnap/cli.rb
|
116
|
+
- lib/bootsnap/cli/worker_pool.rb
|
113
117
|
- lib/bootsnap/compile_cache.rb
|
114
118
|
- lib/bootsnap/compile_cache/iseq.rb
|
115
119
|
- lib/bootsnap/compile_cache/yaml.rb
|
@@ -134,6 +138,7 @@ metadata:
|
|
134
138
|
bug_tracker_uri: https://github.com/Shopify/bootsnap/issues
|
135
139
|
changelog_uri: https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md
|
136
140
|
source_code_uri: https://github.com/Shopify/bootsnap
|
141
|
+
allowed_push_host: https://rubygems.org
|
137
142
|
post_install_message:
|
138
143
|
rdoc_options: []
|
139
144
|
require_paths:
|
@@ -149,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
154
|
- !ruby/object:Gem::Version
|
150
155
|
version: '0'
|
151
156
|
requirements: []
|
152
|
-
rubygems_version: 3.0.
|
157
|
+
rubygems_version: 3.0.3
|
153
158
|
signing_key:
|
154
159
|
specification_version: 4
|
155
160
|
summary: Boot large ruby/rails apps faster
|