bootsnap 1.4.2 → 1.4.7
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/.rubocop.yml +2 -2
- data/.travis.yml +7 -3
- data/CHANGELOG.md +32 -0
- data/Gemfile +1 -0
- data/README.jp.md +1 -1
- data/README.md +1 -1
- data/Rakefile +6 -1
- data/bin/ci +1 -2
- data/bin/console +1 -0
- data/bootsnap.gemspec +3 -2
- data/ext/bootsnap/bootsnap.c +85 -42
- data/ext/bootsnap/extconf.rb +1 -0
- data/lib/bootsnap.rb +2 -0
- data/lib/bootsnap/bundler.rb +1 -0
- data/lib/bootsnap/compile_cache.rb +2 -1
- data/lib/bootsnap/compile_cache/iseq.rb +1 -0
- data/lib/bootsnap/compile_cache/yaml.rb +1 -0
- data/lib/bootsnap/explicit_require.rb +1 -0
- data/lib/bootsnap/load_path_cache/cache.rb +8 -8
- data/lib/bootsnap/load_path_cache/change_observer.rb +2 -1
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +1 -0
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +3 -2
- data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +33 -10
- data/lib/bootsnap/load_path_cache/path.rb +3 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +39 -26
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +18 -14
- data/lib/bootsnap/setup.rb +5 -1
- data/lib/bootsnap/version.rb +2 -1
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3678f8871286d394bbfc31303d0123aae9374370aeda42701336206293447f9a
|
4
|
+
data.tar.gz: 88a8005d7a4fac49b6a1dde52e2d2ec891fd5fa35766cdfb46f52c83cf441b25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1025b1d2a7b60083e6e7bf460cf4aae1ad1c6b2376e024fae199ea0728837b897f417e89b30fcfb3a7fb3260993846566df0537523ee8f703f7bc7d5c1590ace
|
7
|
+
data.tar.gz: 64686090f0dcf1c8835b59690e700b0abd394edf399733cd2ca53d952bdd5f1cc3109d381bac8079f3e64e9c9e12913e132e1fdf07ea3262891914cef4f9c299
|
data/.rubocop.yml
CHANGED
@@ -5,10 +5,10 @@ AllCops:
|
|
5
5
|
Exclude:
|
6
6
|
- 'vendor/**/*'
|
7
7
|
- 'tmp/**/*'
|
8
|
-
TargetRubyVersion: '2.
|
8
|
+
TargetRubyVersion: '2.3'
|
9
9
|
|
10
10
|
# This doesn't take into account retrying from an exception
|
11
|
-
Lint/
|
11
|
+
Lint/SuppressedException:
|
12
12
|
Enabled: false
|
13
13
|
|
14
14
|
# allow String.new to create mutable strings
|
data/.travis.yml
CHANGED
@@ -6,8 +6,10 @@ os:
|
|
6
6
|
- osx
|
7
7
|
|
8
8
|
rvm:
|
9
|
-
-
|
10
|
-
-
|
9
|
+
- '2.4'
|
10
|
+
- '2.5'
|
11
|
+
- '2.6'
|
12
|
+
- '2.7'
|
11
13
|
- ruby-head
|
12
14
|
|
13
15
|
matrix:
|
@@ -17,5 +19,7 @@ matrix:
|
|
17
19
|
- rvm: jruby
|
18
20
|
os: linux
|
19
21
|
env: MINIMAL_SUPPORT=1
|
20
|
-
|
22
|
+
- rvm: truffleruby
|
23
|
+
os: linux
|
24
|
+
env: MINIMAL_SUPPORT=1
|
21
25
|
script: bin/ci
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,35 @@
|
|
1
|
+
# 1.4.7
|
2
|
+
|
3
|
+
* Various performance enhancements
|
4
|
+
* Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
|
5
|
+
|
6
|
+
# 1.4.6
|
7
|
+
|
8
|
+
* Fix bug that was erroneously considering that files containing `.` in the names were being
|
9
|
+
required if a different file with the same name was already being required
|
10
|
+
|
11
|
+
Example:
|
12
|
+
|
13
|
+
require 'foo'
|
14
|
+
require 'foo.en'
|
15
|
+
|
16
|
+
Before bootsnap was considering `foo.en` to be the same file as `foo`
|
17
|
+
|
18
|
+
* Use glibc as part of the ruby_platform cache key
|
19
|
+
|
20
|
+
# 1.4.5
|
21
|
+
|
22
|
+
* MRI 2.7 support
|
23
|
+
* Fixed concurrency bugs
|
24
|
+
|
25
|
+
# 1.4.4
|
26
|
+
|
27
|
+
* Disable ISeq cache in `bootsnap/setup` by default in Ruby 2.5
|
28
|
+
|
29
|
+
# 1.4.3
|
30
|
+
|
31
|
+
* Fix some cache permissions and umask issues after switch to mkstemp
|
32
|
+
|
1
33
|
# 1.4.2
|
2
34
|
|
3
35
|
* Fix bug when removing features loaded by relative path from `$LOADED_FEATURES`
|
data/Gemfile
CHANGED
data/README.jp.md
CHANGED
@@ -152,7 +152,7 @@ close n
|
|
152
152
|
Bootsnap は、64バイトのヘッダーとそれに続くキャッシュの内容を含んだキャッシュファイルを書き込みます。ヘッダーは、次のいくつかのフィールドで構成されるキャッシュキーです。
|
153
153
|
|
154
154
|
- `version`、Bootsnapにハードコードされる基本的なスキーマのバージョン
|
155
|
-
- `
|
155
|
+
- `ruby_platform`、`RUBY_PLATFORM`(x86_64-linux-gnuなど)変数とglibcバージョン(Linuxの場合)またはOSバージョン(BSD、macOSの場合は` uname -v`)のハッシュ
|
156
156
|
- `compile_option`、`RubyVM::InstructionSequence.compile_option` の返り値
|
157
157
|
- `ruby_revision`、コンパイルされたRubyのバージョン
|
158
158
|
- `size`、ソースファイルのサイズ
|
data/README.md
CHANGED
@@ -214,7 +214,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
|
|
214
214
|
is a cache key including several fields:
|
215
215
|
|
216
216
|
* `version`, hardcoded in bootsnap. Essentially a schema version;
|
217
|
-
* `
|
217
|
+
* `ruby_platform`, A hash of `RUBY_PLATFORM` (e.g. x86_64-linux-gnu) variable and glibc version (on Linux) or OS version (`uname -v` on BSD, macOS)
|
218
218
|
* `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
|
219
219
|
* `ruby_revision`, the version of Ruby this was compiled with;
|
220
220
|
* `size`, the size of the source file;
|
data/Rakefile
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require('rake/extensiontask')
|
2
3
|
require('bundler/gem_tasks')
|
3
4
|
|
@@ -9,4 +10,8 @@ Rake::ExtensionTask.new do |ext|
|
|
9
10
|
ext.gem_spec = gemspec
|
10
11
|
end
|
11
12
|
|
12
|
-
task
|
13
|
+
task :test do
|
14
|
+
sh 'bin/testunit'
|
15
|
+
end
|
16
|
+
|
17
|
+
task(default: %i(compile test))
|
data/bin/ci
CHANGED
data/bin/console
CHANGED
data/bootsnap.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require('bootsnap/version')
|
@@ -26,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
26
27
|
end
|
27
28
|
spec.require_paths = %w(lib)
|
28
29
|
|
29
|
-
spec.required_ruby_version = '>= 2.
|
30
|
+
spec.required_ruby_version = '>= 2.3.0'
|
30
31
|
|
31
32
|
if RUBY_PLATFORM =~ /java/
|
32
33
|
spec.platform = 'java'
|
@@ -36,7 +37,7 @@ Gem::Specification.new do |spec|
|
|
36
37
|
end
|
37
38
|
|
38
39
|
spec.add_development_dependency("bundler")
|
39
|
-
spec.add_development_dependency('rake'
|
40
|
+
spec.add_development_dependency('rake')
|
40
41
|
spec.add_development_dependency('rake-compiler', '~> 0')
|
41
42
|
spec.add_development_dependency("minitest", "~> 5.0")
|
42
43
|
spec.add_development_dependency("mocha", "~> 1.2")
|
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -21,6 +21,9 @@
|
|
21
21
|
#ifndef _WIN32
|
22
22
|
#include <sys/utsname.h>
|
23
23
|
#endif
|
24
|
+
#ifdef __GLIBC__
|
25
|
+
#include <gnu/libc-version.h>
|
26
|
+
#endif
|
24
27
|
|
25
28
|
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
26
29
|
* 981 for the cache dir */
|
@@ -29,6 +32,8 @@
|
|
29
32
|
|
30
33
|
#define KEY_SIZE 64
|
31
34
|
|
35
|
+
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
36
|
+
|
32
37
|
/*
|
33
38
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
34
39
|
* The mtime and size members track whether the file contents have changed, and
|
@@ -74,6 +79,8 @@ static uint32_t current_ruby_platform;
|
|
74
79
|
static uint32_t current_ruby_revision;
|
75
80
|
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
76
81
|
static uint32_t current_compile_option_crc32 = 0;
|
82
|
+
/* Current umask */
|
83
|
+
static mode_t current_umask;
|
77
84
|
|
78
85
|
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
79
86
|
static VALUE rb_mBootsnap;
|
@@ -88,12 +95,13 @@ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handl
|
|
88
95
|
|
89
96
|
/* Helpers */
|
90
97
|
static uint64_t fnv1a_64(const char *str);
|
91
|
-
static void bs_cache_path(const char * cachedir, const char * path, char
|
98
|
+
static void bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
92
99
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
93
100
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
94
101
|
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
95
|
-
static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
|
96
|
-
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
|
102
|
+
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);
|
104
|
+
static uint32_t get_ruby_revision(void);
|
97
105
|
static uint32_t get_ruby_platform(void);
|
98
106
|
|
99
107
|
/*
|
@@ -134,7 +142,7 @@ Init_bootsnap(void)
|
|
134
142
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
135
143
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
136
144
|
|
137
|
-
current_ruby_revision =
|
145
|
+
current_ruby_revision = get_ruby_revision();
|
138
146
|
current_ruby_platform = get_ruby_platform();
|
139
147
|
|
140
148
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
@@ -142,6 +150,9 @@ Init_bootsnap(void)
|
|
142
150
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
143
151
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3);
|
144
152
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
153
|
+
|
154
|
+
current_umask = umask(0777);
|
155
|
+
umask(current_umask);
|
145
156
|
}
|
146
157
|
|
147
158
|
/*
|
@@ -191,6 +202,26 @@ fnv1a_64(const char *str)
|
|
191
202
|
return fnv1a_64_iter(h, str);
|
192
203
|
}
|
193
204
|
|
205
|
+
/*
|
206
|
+
* Ruby's revision may be Integer or String. CRuby 2.7 or later uses
|
207
|
+
* Git commit ID as revision. It's String.
|
208
|
+
*/
|
209
|
+
static uint32_t
|
210
|
+
get_ruby_revision(void)
|
211
|
+
{
|
212
|
+
VALUE ruby_revision;
|
213
|
+
|
214
|
+
ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
|
215
|
+
if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
|
216
|
+
return FIX2INT(ruby_revision);
|
217
|
+
} else {
|
218
|
+
uint64_t hash;
|
219
|
+
|
220
|
+
hash = fnv1a_64(StringValueCStr(ruby_revision));
|
221
|
+
return (uint32_t)(hash >> 32);
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
194
225
|
/*
|
195
226
|
* When ruby's version doesn't change, but it's recompiled on a different OS
|
196
227
|
* (or OS version), we need to invalidate the cache.
|
@@ -210,6 +241,9 @@ get_ruby_platform(void)
|
|
210
241
|
|
211
242
|
#ifdef _WIN32
|
212
243
|
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
244
|
+
#elif defined(__GLIBC__)
|
245
|
+
hash = fnv1a_64_iter(hash, gnu_get_libc_version());
|
246
|
+
return (uint32_t)(hash >> 32);
|
213
247
|
#else
|
214
248
|
struct utsname utsname;
|
215
249
|
|
@@ -230,7 +264,7 @@ get_ruby_platform(void)
|
|
230
264
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
231
265
|
*/
|
232
266
|
static void
|
233
|
-
bs_cache_path(const char * cachedir, const char * path, char
|
267
|
+
bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
234
268
|
{
|
235
269
|
uint64_t hash = fnv1a_64(path);
|
236
270
|
|
@@ -282,10 +316,8 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
282
316
|
char * path = RSTRING_PTR(path_v);
|
283
317
|
char cache_path[MAX_CACHEPATH_SIZE];
|
284
318
|
|
285
|
-
|
286
|
-
|
287
|
-
bs_cache_path(cachedir, path, &tmp);
|
288
|
-
}
|
319
|
+
/* generate cache path to cache_path */
|
320
|
+
bs_cache_path(cachedir, path, &cache_path);
|
289
321
|
|
290
322
|
return bs_fetch(path, path_v, cache_path, handler);
|
291
323
|
}
|
@@ -295,14 +327,14 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
295
327
|
* was loaded.
|
296
328
|
*/
|
297
329
|
static int
|
298
|
-
open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
|
330
|
+
open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
299
331
|
{
|
300
332
|
struct stat statbuf;
|
301
333
|
int fd;
|
302
334
|
|
303
335
|
fd = open(path, O_RDONLY);
|
304
336
|
if (fd < 0) {
|
305
|
-
*errno_provenance =
|
337
|
+
*errno_provenance = "bs_fetch:open_current_file:open";
|
306
338
|
return fd;
|
307
339
|
}
|
308
340
|
#ifdef _WIN32
|
@@ -310,7 +342,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
|
|
310
342
|
#endif
|
311
343
|
|
312
344
|
if (fstat(fd, &statbuf) < 0) {
|
313
|
-
*errno_provenance =
|
345
|
+
*errno_provenance = "bs_fetch:open_current_file:fstat";
|
314
346
|
close(fd);
|
315
347
|
return -1;
|
316
348
|
}
|
@@ -356,13 +388,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
356
388
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
357
389
|
*/
|
358
390
|
static int
|
359
|
-
open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
|
391
|
+
open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
|
360
392
|
{
|
361
393
|
int fd, res;
|
362
394
|
|
363
395
|
fd = open(path, O_RDONLY);
|
364
396
|
if (fd < 0) {
|
365
|
-
*errno_provenance =
|
397
|
+
*errno_provenance = "bs_fetch:open_cache_file:open";
|
366
398
|
if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
|
367
399
|
return ERROR_WITH_ERRNO;
|
368
400
|
}
|
@@ -372,7 +404,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
372
404
|
|
373
405
|
res = bs_read_key(fd, key);
|
374
406
|
if (res < 0) {
|
375
|
-
*errno_provenance =
|
407
|
+
*errno_provenance = "bs_fetch:open_cache_file:read";
|
376
408
|
close(fd);
|
377
409
|
return res;
|
378
410
|
}
|
@@ -396,7 +428,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
|
|
396
428
|
* or exception, will be the final data returnable to the user.
|
397
429
|
*/
|
398
430
|
static int
|
399
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
|
431
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
400
432
|
{
|
401
433
|
char * data = NULL;
|
402
434
|
ssize_t nread;
|
@@ -405,7 +437,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
405
437
|
VALUE storage_data;
|
406
438
|
|
407
439
|
if (data_size > 100000000000) {
|
408
|
-
*errno_provenance =
|
440
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:datasize";
|
409
441
|
errno = EINVAL; /* because wtf? */
|
410
442
|
ret = -1;
|
411
443
|
goto done;
|
@@ -413,7 +445,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
413
445
|
data = ALLOC_N(char, data_size);
|
414
446
|
nread = read(fd, data, data_size);
|
415
447
|
if (nread < 0) {
|
416
|
-
*errno_provenance =
|
448
|
+
*errno_provenance = "bs_fetch:fetch_cached_data:read";
|
417
449
|
ret = -1;
|
418
450
|
goto done;
|
419
451
|
}
|
@@ -465,30 +497,36 @@ mkpath(char * file_path, mode_t mode)
|
|
465
497
|
* path.
|
466
498
|
*/
|
467
499
|
static int
|
468
|
-
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
|
500
|
+
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
|
469
501
|
{
|
470
502
|
char template[MAX_CACHEPATH_SIZE + 20];
|
471
503
|
char * tmp_path;
|
472
|
-
int fd, ret;
|
504
|
+
int fd, ret, attempt;
|
473
505
|
ssize_t nwrite;
|
474
506
|
|
475
|
-
|
476
|
-
|
507
|
+
for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
|
508
|
+
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
509
|
+
strcat(tmp_path, ".tmp.XXXXXX");
|
477
510
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
}
|
485
|
-
close(fd);
|
486
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
487
|
-
if (fd < 0) {
|
488
|
-
*errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
|
511
|
+
// mkstemp modifies the template to be the actual created path
|
512
|
+
fd = mkstemp(tmp_path);
|
513
|
+
if (fd > 0) break;
|
514
|
+
|
515
|
+
if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
|
516
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
|
489
517
|
return -1;
|
490
518
|
}
|
491
519
|
}
|
520
|
+
if (fd < 0) {
|
521
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
|
522
|
+
return -1;
|
523
|
+
}
|
524
|
+
|
525
|
+
if (chmod(tmp_path, 0644) < 0) {
|
526
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
527
|
+
return -1;
|
528
|
+
}
|
529
|
+
|
492
530
|
#ifdef _WIN32
|
493
531
|
setmode(fd, O_BINARY);
|
494
532
|
#endif
|
@@ -496,11 +534,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
496
534
|
key->data_size = RSTRING_LEN(data);
|
497
535
|
nwrite = write(fd, key, KEY_SIZE);
|
498
536
|
if (nwrite < 0) {
|
499
|
-
*errno_provenance =
|
537
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:write";
|
500
538
|
return -1;
|
501
539
|
}
|
502
540
|
if (nwrite != KEY_SIZE) {
|
503
|
-
*errno_provenance =
|
541
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
|
504
542
|
errno = EIO; /* Lies but whatever */
|
505
543
|
return -1;
|
506
544
|
}
|
@@ -508,7 +546,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
508
546
|
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
509
547
|
if (nwrite < 0) return -1;
|
510
548
|
if (nwrite != RSTRING_LEN(data)) {
|
511
|
-
*errno_provenance =
|
549
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
|
512
550
|
errno = EIO; /* Lies but whatever */
|
513
551
|
return -1;
|
514
552
|
}
|
@@ -516,7 +554,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
516
554
|
close(fd);
|
517
555
|
ret = rename(tmp_path, path);
|
518
556
|
if (ret < 0) {
|
519
|
-
*errno_provenance =
|
557
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
|
558
|
+
return -1;
|
559
|
+
}
|
560
|
+
ret = chmod(path, 0664 & ~current_umask);
|
561
|
+
if (ret < 0) {
|
562
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
520
563
|
}
|
521
564
|
return ret;
|
522
565
|
}
|
@@ -525,13 +568,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
|
|
525
568
|
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
526
569
|
* long, into a buffer */
|
527
570
|
static ssize_t
|
528
|
-
bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
|
571
|
+
bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
|
529
572
|
{
|
530
573
|
ssize_t nread;
|
531
574
|
*contents = ALLOC_N(char, size);
|
532
575
|
nread = read(fd, *contents, size);
|
533
576
|
if (nread < 0) {
|
534
|
-
*errno_provenance =
|
577
|
+
*errno_provenance = "bs_fetch:bs_read_contents:read";
|
535
578
|
}
|
536
579
|
return nread;
|
537
580
|
}
|
@@ -587,7 +630,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
587
630
|
char * contents = NULL;
|
588
631
|
int cache_fd = -1, current_fd = -1;
|
589
632
|
int res, valid_cache = 0, exception_tag = 0;
|
590
|
-
char * errno_provenance = NULL;
|
633
|
+
const char * errno_provenance = NULL;
|
591
634
|
|
592
635
|
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
593
636
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
@@ -655,7 +698,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
655
698
|
* using input_to_output */
|
656
699
|
if (NIL_P(output_data)) {
|
657
700
|
if (unlink(cache_path) < 0) {
|
658
|
-
errno_provenance =
|
701
|
+
errno_provenance = "bs_fetch:unlink";
|
659
702
|
goto fail_errno;
|
660
703
|
}
|
661
704
|
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
@@ -766,7 +809,7 @@ try_input_to_storage(VALUE arg)
|
|
766
809
|
}
|
767
810
|
|
768
811
|
static VALUE
|
769
|
-
rescue_input_to_storage(VALUE arg)
|
812
|
+
rescue_input_to_storage(VALUE arg, VALUE e)
|
770
813
|
{
|
771
814
|
return uncompilable;
|
772
815
|
}
|
data/ext/bootsnap/extconf.rb
CHANGED
data/lib/bootsnap.rb
CHANGED
data/lib/bootsnap/bundler.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module CompileCache
|
3
4
|
Error = Class.new(StandardError)
|
@@ -28,7 +29,7 @@ module Bootsnap
|
|
28
29
|
raise(
|
29
30
|
PermissionError,
|
30
31
|
"bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
|
31
|
-
"(or, less likely, doesn't have
|
32
|
+
"(or, less likely, doesn't have permission to read '#{path}')",
|
32
33
|
)
|
33
34
|
end
|
34
35
|
|
@@ -46,7 +46,7 @@ module Bootsnap
|
|
46
46
|
# loadpath.
|
47
47
|
def find(feature)
|
48
48
|
reinitialize if (@has_relative_paths && dir_changed?) || stale?
|
49
|
-
feature = feature.to_s
|
49
|
+
feature = feature.to_s.freeze
|
50
50
|
return feature if absolute_path?(feature)
|
51
51
|
return expand_path(feature) if feature.start_with?('./')
|
52
52
|
@mutex.synchronize do
|
@@ -67,7 +67,7 @@ module Bootsnap
|
|
67
67
|
# native dynamic extension, e.g. .bundle or .so), we know it was a
|
68
68
|
# failure and there's nothing more we can do to find the file.
|
69
69
|
# no extension, .rb, (.bundle or .so)
|
70
|
-
when '', *CACHED_EXTENSIONS
|
70
|
+
when '', *CACHED_EXTENSIONS
|
71
71
|
nil
|
72
72
|
# Ruby allows specifying native extensions as '.so' even when DLEXT
|
73
73
|
# is '.bundle'. This is where we handle that case.
|
@@ -144,7 +144,7 @@ module Bootsnap
|
|
144
144
|
expanded_path = p.expanded_path
|
145
145
|
entries, dirs = p.entries_and_dirs(@store)
|
146
146
|
# push -> low precedence -> set only if unset
|
147
|
-
dirs.each { |dir| @dirs[dir]
|
147
|
+
dirs.each { |dir| @dirs[dir] ||= path }
|
148
148
|
entries.each { |rel| @index[rel] ||= expanded_path }
|
149
149
|
end
|
150
150
|
end
|
@@ -178,25 +178,25 @@ module Bootsnap
|
|
178
178
|
|
179
179
|
if DLEXT2
|
180
180
|
def search_index(f)
|
181
|
-
try_index(f
|
181
|
+
try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
|
182
182
|
end
|
183
183
|
|
184
184
|
def maybe_append_extension(f)
|
185
|
-
try_ext(f
|
185
|
+
try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
|
186
186
|
end
|
187
187
|
else
|
188
188
|
def search_index(f)
|
189
|
-
try_index(f
|
189
|
+
try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
|
190
190
|
end
|
191
191
|
|
192
192
|
def maybe_append_extension(f)
|
193
|
-
try_ext(f
|
193
|
+
try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
197
|
def try_index(f)
|
198
198
|
if (p = @index[f])
|
199
|
-
p
|
199
|
+
"#{p}/#{f}"
|
200
200
|
end
|
201
201
|
end
|
202
202
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module LoadPathCache
|
3
4
|
module ChangeObserver
|
@@ -25,7 +26,7 @@ module Bootsnap
|
|
25
26
|
super
|
26
27
|
end
|
27
28
|
|
28
|
-
# uniq! keeps the first
|
29
|
+
# uniq! keeps the first occurrence of each path, otherwise preserving
|
29
30
|
# order, preserving the effective load path
|
30
31
|
def uniq!(*args)
|
31
32
|
ret = super
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Bootsnap
|
2
3
|
module LoadPathCache
|
3
4
|
module CoreExt
|
4
5
|
def self.make_load_error(path)
|
5
|
-
err = LoadError.new("cannot load such file -- #{path}")
|
6
|
+
err = LoadError.new(+"cannot load such file -- #{path}")
|
6
7
|
err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
7
8
|
err.define_singleton_method(:path) { path }
|
8
9
|
err
|
@@ -55,7 +56,7 @@ module Kernel
|
|
55
56
|
end
|
56
57
|
|
57
58
|
# load also allows relative paths from pwd even when not in $:
|
58
|
-
if File.exist?(relative = File.expand_path(path))
|
59
|
+
if File.exist?(relative = File.expand_path(path).freeze)
|
59
60
|
return load_without_bootsnap(relative, wrap)
|
60
61
|
end
|
61
62
|
|
@@ -40,7 +40,7 @@ module Bootsnap
|
|
40
40
|
# /a/b/lib/my/foo.rb
|
41
41
|
# ^^^^^^^^^
|
42
42
|
short = feat[(lpe.length + 1)..-1]
|
43
|
-
stripped =
|
43
|
+
stripped = strip_extension_if_elidable(short)
|
44
44
|
@lfi[short] = hash
|
45
45
|
@lfi[stripped] = hash
|
46
46
|
end
|
@@ -94,13 +94,14 @@ module Bootsnap
|
|
94
94
|
|
95
95
|
hash = long.hash
|
96
96
|
|
97
|
-
#
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
#
|
97
|
+
# Do we have a filename with an elidable extension, e.g.,
|
98
|
+
# 'bundler.rb', or 'libgit2.so'?
|
99
|
+
altname = if extension_elidable?(short)
|
100
|
+
# Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
|
101
|
+
strip_extension_if_elidable(short)
|
102
|
+
elsif long && (ext = File.extname(long.freeze))
|
103
|
+
# We already know the extension of the actual file this
|
104
|
+
# resolves to, so put that back on.
|
104
105
|
short + ext
|
105
106
|
end
|
106
107
|
|
@@ -117,8 +118,30 @@ module Bootsnap
|
|
117
118
|
STRIP_EXTENSION = /\.[^.]*?$/
|
118
119
|
private_constant(:STRIP_EXTENSION)
|
119
120
|
|
120
|
-
|
121
|
-
|
121
|
+
# Might Ruby automatically search for this extension if
|
122
|
+
# someone tries to 'require' the file without it? E.g. Ruby
|
123
|
+
# will implicitly try 'x.rb' if you ask for 'x'.
|
124
|
+
#
|
125
|
+
# This is complex and platform-dependent, and the Ruby docs are a little
|
126
|
+
# handwavy about what will be tried when and in what order.
|
127
|
+
# So optimistically pretend that all known elidable extensions
|
128
|
+
# will be tried on all platforms, and that people are unlikely
|
129
|
+
# to name files in a way that assumes otherwise.
|
130
|
+
# (E.g. It's unlikely that someone will know that their code
|
131
|
+
# will _never_ run on MacOS, and therefore think they can get away
|
132
|
+
# with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
|
133
|
+
#
|
134
|
+
# See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
|
135
|
+
def extension_elidable?(f)
|
136
|
+
f.to_s.end_with?('.rb', '.so', '.o', '.dll', '.dylib')
|
137
|
+
end
|
138
|
+
|
139
|
+
def strip_extension_if_elidable(f)
|
140
|
+
if extension_elidable?(f)
|
141
|
+
f.sub(STRIP_EXTENSION, '')
|
142
|
+
else
|
143
|
+
f
|
144
|
+
end
|
122
145
|
end
|
123
146
|
end
|
124
147
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative('path_scanner')
|
2
3
|
|
3
4
|
module Bootsnap
|
@@ -20,7 +21,7 @@ module Bootsnap
|
|
20
21
|
attr_reader(:path)
|
21
22
|
|
22
23
|
def initialize(path)
|
23
|
-
@path = path.to_s
|
24
|
+
@path = path.to_s.freeze
|
24
25
|
end
|
25
26
|
|
26
27
|
# True if the path exists, but represents a non-directory object
|
@@ -59,7 +60,7 @@ module Bootsnap
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def expanded_path
|
62
|
-
File.expand_path(path)
|
63
|
+
File.expand_path(path).freeze
|
63
64
|
end
|
64
65
|
|
65
66
|
private
|
@@ -5,7 +5,6 @@ require_relative('../explicit_require')
|
|
5
5
|
module Bootsnap
|
6
6
|
module LoadPathCache
|
7
7
|
module PathScanner
|
8
|
-
ALL_FILES = "/{,**/*/**/}*"
|
9
8
|
REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
|
10
9
|
NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
|
11
10
|
ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
|
@@ -16,34 +15,48 @@ module Bootsnap
|
|
16
15
|
''
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
requirables << relative_path
|
18
|
+
class << self
|
19
|
+
def call(path)
|
20
|
+
path = File.expand_path(path.to_s).freeze
|
21
|
+
return [[], []] unless File.directory?(path)
|
22
|
+
|
23
|
+
# If the bundle path is a descendent of this path, we do additional
|
24
|
+
# checks to prevent recursing into the bundle path as we recurse
|
25
|
+
# through this path. We don't want to scan the bundle path because
|
26
|
+
# anything useful in it will be present on other load path items.
|
27
|
+
#
|
28
|
+
# This can happen if, for example, the user adds '.' to the load path,
|
29
|
+
# and the bundle path is '.bundle'.
|
30
|
+
contains_bundle_path = BUNDLE_PATH.start_with?(path)
|
31
|
+
|
32
|
+
dirs = []
|
33
|
+
requirables = []
|
34
|
+
walk(path, nil) do |relative_path, absolute_path, is_directory|
|
35
|
+
if is_directory
|
36
|
+
dirs << relative_path
|
37
|
+
!contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
|
38
|
+
elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
|
39
|
+
requirables << relative_path
|
40
|
+
end
|
43
41
|
end
|
42
|
+
[requirables, dirs]
|
44
43
|
end
|
45
44
|
|
46
|
-
|
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
|
+
relative_path = relative_dir_path ? "#{relative_dir_path}/#{name}" : name.freeze
|
49
|
+
|
50
|
+
absolute_path = "#{absolute_dir_path}/#{name}"
|
51
|
+
if File.directory?(absolute_path)
|
52
|
+
if yield relative_path, absolute_path, true
|
53
|
+
walk(absolute_path, relative_path, &block)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
yield relative_path, absolute_path, false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
47
60
|
end
|
48
61
|
end
|
49
62
|
end
|
@@ -15,15 +15,15 @@ module Bootsnap
|
|
15
15
|
|
16
16
|
def realpath(caller_location, path)
|
17
17
|
base = File.dirname(caller_location)
|
18
|
-
|
19
|
-
|
20
|
-
File.join(dir, File.basename(file))
|
18
|
+
abspath = File.expand_path(path, base).freeze
|
19
|
+
find_file(abspath)
|
21
20
|
end
|
22
21
|
|
23
22
|
def find_file(name)
|
24
|
-
|
23
|
+
return File.realpath(name).freeze if File.exist?(name)
|
24
|
+
CACHED_EXTENSIONS.each do |ext|
|
25
25
|
filename = "#{name}#{ext}"
|
26
|
-
return File.realpath(filename) if File.exist?(filename)
|
26
|
+
return File.realpath(filename).freeze if File.exist?(filename)
|
27
27
|
end
|
28
28
|
name
|
29
29
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative('../explicit_require')
|
2
3
|
|
3
4
|
Bootsnap::ExplicitRequire.with_gems('msgpack') { require('msgpack') }
|
@@ -11,7 +12,8 @@ module Bootsnap
|
|
11
12
|
|
12
13
|
def initialize(store_path)
|
13
14
|
@store_path = store_path
|
14
|
-
|
15
|
+
# TODO: Remove conditional once Ruby 2.2 support is dropped.
|
16
|
+
@txn_mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new
|
15
17
|
@dirty = false
|
16
18
|
load_data
|
17
19
|
end
|
@@ -21,7 +23,7 @@ module Bootsnap
|
|
21
23
|
end
|
22
24
|
|
23
25
|
def fetch(key)
|
24
|
-
raise(SetOutsideTransactionNotAllowed) unless @
|
26
|
+
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
25
27
|
v = get(key)
|
26
28
|
unless v
|
27
29
|
@dirty = true
|
@@ -32,7 +34,7 @@ module Bootsnap
|
|
32
34
|
end
|
33
35
|
|
34
36
|
def set(key, value)
|
35
|
-
raise(SetOutsideTransactionNotAllowed) unless @
|
37
|
+
raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
|
36
38
|
if value != @data[key]
|
37
39
|
@dirty = true
|
38
40
|
@data[key] = value
|
@@ -40,12 +42,14 @@ module Bootsnap
|
|
40
42
|
end
|
41
43
|
|
42
44
|
def transaction
|
43
|
-
raise(NestedTransactionError) if @
|
44
|
-
@
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
raise(NestedTransactionError) if @txn_mutex.owned?
|
46
|
+
@txn_mutex.synchronize do
|
47
|
+
begin
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
commit_transaction
|
51
|
+
end
|
52
|
+
end
|
49
53
|
end
|
50
54
|
|
51
55
|
private
|
@@ -60,11 +64,11 @@ module Bootsnap
|
|
60
64
|
def load_data
|
61
65
|
@data = begin
|
62
66
|
MessagePack.load(File.binread(@store_path))
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
# handle malformed data due to upgrade incompatibility
|
68
|
+
rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
|
69
|
+
{}
|
70
|
+
rescue ArgumentError => e
|
71
|
+
e.message =~ /negative array size/ ? {} : raise
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
data/lib/bootsnap/setup.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative('../bootsnap')
|
2
3
|
|
3
4
|
env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
|
@@ -24,12 +25,15 @@ unless cache_dir
|
|
24
25
|
cache_dir = File.join(app_root, 'tmp', 'cache')
|
25
26
|
end
|
26
27
|
|
28
|
+
ruby_version = Gem::Version.new(RUBY_VERSION)
|
29
|
+
iseq_cache_enabled = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
|
30
|
+
|
27
31
|
Bootsnap.setup(
|
28
32
|
cache_dir: cache_dir,
|
29
33
|
development_mode: development_mode,
|
30
34
|
load_path_cache: true,
|
31
35
|
autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
|
32
36
|
disable_trace: false,
|
33
|
-
compile_cache_iseq:
|
37
|
+
compile_cache_iseq: iseq_cache_enabled,
|
34
38
|
compile_cache_yaml: true,
|
35
39
|
)
|
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.
|
4
|
+
version: 1.4.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake-compiler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -160,15 +160,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
160
160
|
requirements:
|
161
161
|
- - ">="
|
162
162
|
- !ruby/object:Gem::Version
|
163
|
-
version: 2.
|
163
|
+
version: 2.3.0
|
164
164
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
165
|
requirements:
|
166
166
|
- - ">="
|
167
167
|
- !ruby/object:Gem::Version
|
168
168
|
version: '0'
|
169
169
|
requirements: []
|
170
|
-
|
171
|
-
rubygems_version: 2.7.6
|
170
|
+
rubygems_version: 3.0.2
|
172
171
|
signing_key:
|
173
172
|
specification_version: 4
|
174
173
|
summary: Boot large ruby/rails apps faster
|