bootsnap 1.6.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +30 -13
- data/ext/bootsnap/bootsnap.c +48 -12
- data/lib/bootsnap.rb +75 -13
- data/lib/bootsnap/cli.rb +2 -2
- data/lib/bootsnap/compile_cache/yaml.rb +1 -1
- data/lib/bootsnap/load_path_cache.rb +2 -15
- data/lib/bootsnap/setup.rb +1 -36
- data/lib/bootsnap/version.rb +1 -1
- metadata +2 -3
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -107
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac5be912b9602b8b20f12255208a6a6d0373d64167092e3267b2494376f079f0
|
4
|
+
data.tar.gz: 5f18b74f0c0670b86d40d4f41df87869ce5f145cbc3d71b64eddcee59b63fe12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7abd18f5a495b63083d5b4ae9b105acc7f80c2813fa2c40594d2d34998037e6f433b91bf9ab24e021ca3d727368e5580972fc48b9382a86d30a45e03c8274f6
|
7
|
+
data.tar.gz: 87aaef6229c936321f8940a28f453446a75fd2b6935e13872c1aa7455990c579c1f3f1ebea3ec6416e9f1b3af18c0468d2a1cae2bc4fafb44f4a2c972e4f4688
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 1.7.0
|
4
|
+
|
5
|
+
* Fix detection of YAML files in gems.
|
6
|
+
* Adds an instrumentation API to monitor cache misses.
|
7
|
+
* Allow to control the behavior of `require 'bootsnap/setup'` using environment variables.
|
8
|
+
* Deprecate the `disable_trace` option.
|
9
|
+
* Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
|
10
|
+
|
3
11
|
# 1.6.0
|
4
12
|
|
5
13
|
* Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Bootsnap [![Actions Status](https://github.com/Shopify/bootsnap/workflows/ci/badge.svg)](https://github.com/Shopify/bootsnap/actions)
|
2
2
|
|
3
|
-
Bootsnap is a library that plugs into Ruby, with optional support for `
|
3
|
+
Bootsnap is a library that plugs into Ruby, with optional support for `YAML`,
|
4
4
|
to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
|
5
5
|
|
6
6
|
#### Performance
|
@@ -11,7 +11,7 @@ to optimize and cache expensive computations. See [How Does This Work](#how-does
|
|
11
11
|
- The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
|
12
12
|
dropping from around 25s to 6.5s.
|
13
13
|
* In Shopify core (a large app), about 25% of this gain can be attributed to `compile_cache_*`
|
14
|
-
features; 75% to path caching
|
14
|
+
features; 75% to path caching. This is fairly representative.
|
15
15
|
|
16
16
|
## Usage
|
17
17
|
|
@@ -29,7 +29,7 @@ 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` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
|
32
|
+
Note that bootsnap writes to `tmp/cache` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
|
33
33
|
and that directory *must* be writable. Rails will fail to
|
34
34
|
boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
|
35
35
|
unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
|
@@ -54,15 +54,11 @@ Bootsnap.setup(
|
|
54
54
|
cache_dir: 'tmp/cache', # Path to your cache
|
55
55
|
development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
|
56
56
|
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
57
|
-
autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
|
58
|
-
disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
|
59
57
|
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
60
58
|
compile_cache_yaml: true # Compile YAML into a cache
|
61
59
|
)
|
62
60
|
```
|
63
61
|
|
64
|
-
**Note that `disable_trace` will break debuggers and tracing.**
|
65
|
-
|
66
62
|
**Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
|
67
63
|
'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
|
68
64
|
will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
|
@@ -72,12 +68,39 @@ speeds up the loading of individual source files, Spring keeps a copy of a pre-b
|
|
72
68
|
on hand to completely skip parts of the boot process the next time it's needed. The two tools work
|
73
69
|
well together, and are both included in a newly-generated Rails applications by default.
|
74
70
|
|
71
|
+
### Environment variables
|
72
|
+
|
73
|
+
`require 'bootsnap/setup'` behavior can be changed using environment variables:
|
74
|
+
|
75
|
+
- `BOOTSNAP_CACHE_DIR` allows to define the cache location.
|
76
|
+
- `DISABLE_BOOTSNAP` allows to entirely disable bootsnap.
|
77
|
+
- `DISABLE_BOOTSNAP_LOAD_PATH_CACHE` allows to disable load path caching.
|
78
|
+
- `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
|
79
|
+
- `BOOTSNAP_LOG` configure bootsnap to log all caches misses to STDERR.
|
80
|
+
|
75
81
|
### Environments
|
76
82
|
|
77
83
|
All Bootsnap features are enabled in development, test, production, and all other environments according to the configuration in the setup. At Shopify, we use this gem safely in all environments without issue.
|
78
84
|
|
79
85
|
If you would like to disable any feature for a certain environment, we suggest changing the configuration to take into account the appropriate ENV var or configuration according to your needs.
|
80
86
|
|
87
|
+
### Instrumentation
|
88
|
+
|
89
|
+
Bootsnap cache misses can be monitored though a callback:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" }
|
93
|
+
```
|
94
|
+
|
95
|
+
`event` is either `:miss` or `:stale`. You can also call `Bootsnap.log!` as a shortcut to
|
96
|
+
log all events to STDERR.
|
97
|
+
|
98
|
+
To turn instrumentation back off you can set it to nil:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Bootsnap.instrumentation = nil
|
102
|
+
```
|
103
|
+
|
81
104
|
## How does this work?
|
82
105
|
|
83
106
|
Bootsnap optimizes methods to cache results of expensive computations, and can be grouped
|
@@ -85,8 +108,6 @@ into two broad categories:
|
|
85
108
|
|
86
109
|
* [Path Pre-Scanning](#path-pre-scanning)
|
87
110
|
* `Kernel#require` and `Kernel#load` are modified to eliminate `$LOAD_PATH` scans.
|
88
|
-
* `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` are
|
89
|
-
overridden to eliminate scans of `ActiveSupport::Dependencies.autoload_paths`.
|
90
111
|
* [Compilation caching](#compilation-caching)
|
91
112
|
* `RubyVM::InstructionSequence.load_iseq` is implemented to cache the result of ruby bytecode
|
92
113
|
compilation.
|
@@ -125,10 +146,6 @@ open y/foo.rb
|
|
125
146
|
...
|
126
147
|
```
|
127
148
|
|
128
|
-
Exactly the same strategy is employed for methods that traverse
|
129
|
-
`ActiveSupport::Dependencies.autoload_paths` if the `autoload_paths_cache` option is given to
|
130
|
-
`Bootsnap.setup`.
|
131
|
-
|
132
149
|
The following diagram flowcharts the overrides that make the `*_path_cache` features work.
|
133
150
|
|
134
151
|
![Flowchart explaining
|
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
#include "bootsnap.h"
|
15
15
|
#include "ruby.h"
|
16
16
|
#include <stdint.h>
|
17
|
+
#include <stdbool.h>
|
17
18
|
#include <sys/types.h>
|
18
19
|
#include <errno.h>
|
19
20
|
#include <fcntl.h>
|
@@ -34,6 +35,10 @@
|
|
34
35
|
|
35
36
|
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
36
37
|
|
38
|
+
#ifndef RB_UNLIKELY
|
39
|
+
#define RB_UNLIKELY(x) (x)
|
40
|
+
#endif
|
41
|
+
|
37
42
|
/*
|
38
43
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
39
44
|
* The mtime and size members track whether the file contents have changed, and
|
@@ -88,8 +93,13 @@ static VALUE rb_mBootsnap_CompileCache;
|
|
88
93
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
89
94
|
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
90
95
|
static ID uncompilable;
|
96
|
+
static ID instrumentation_method;
|
97
|
+
static VALUE sym_miss;
|
98
|
+
static VALUE sym_stale;
|
99
|
+
static bool instrumentation_enabled = false;
|
91
100
|
|
92
101
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
102
|
+
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
93
103
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
94
104
|
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
95
105
|
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
@@ -148,7 +158,15 @@ Init_bootsnap(void)
|
|
148
158
|
current_ruby_platform = get_ruby_platform();
|
149
159
|
|
150
160
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
161
|
+
instrumentation_method = rb_intern("_instrument");
|
162
|
+
|
163
|
+
sym_miss = ID2SYM(rb_intern("miss"));
|
164
|
+
rb_global_variable(&sym_miss);
|
151
165
|
|
166
|
+
sym_stale = ID2SYM(rb_intern("stale"));
|
167
|
+
rb_global_variable(&sym_stale);
|
168
|
+
|
169
|
+
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
152
170
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
153
171
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
154
172
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
@@ -158,6 +176,13 @@ Init_bootsnap(void)
|
|
158
176
|
umask(current_umask);
|
159
177
|
}
|
160
178
|
|
179
|
+
static VALUE
|
180
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
181
|
+
{
|
182
|
+
instrumentation_enabled = RTEST(enabled);
|
183
|
+
return enabled;
|
184
|
+
}
|
185
|
+
|
161
186
|
/*
|
162
187
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
163
188
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -386,7 +411,8 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
386
411
|
}
|
387
412
|
|
388
413
|
#define ERROR_WITH_ERRNO -1
|
389
|
-
#define
|
414
|
+
#define CACHE_MISS -2
|
415
|
+
#define CACHE_STALE -3
|
390
416
|
|
391
417
|
/*
|
392
418
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -394,15 +420,16 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
394
420
|
*
|
395
421
|
* Possible return values:
|
396
422
|
* - 0 (OK, key was loaded)
|
397
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
398
423
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
424
|
+
* - CACHE_MISS (-2)
|
425
|
+
* - CACHE_STALE (-3)
|
399
426
|
*/
|
400
427
|
static int
|
401
428
|
bs_read_key(int fd, struct bs_cache_key * key)
|
402
429
|
{
|
403
430
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
404
431
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
405
|
-
if (nread < KEY_SIZE) return
|
432
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
406
433
|
return 0;
|
407
434
|
}
|
408
435
|
|
@@ -412,7 +439,8 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
412
439
|
*
|
413
440
|
* Possible return values:
|
414
441
|
* - 0 (OK, key was loaded)
|
415
|
-
* -
|
442
|
+
* - CACHE_MISS (-2)
|
443
|
+
* - CACHE_STALE (-3)
|
416
444
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
417
445
|
*/
|
418
446
|
static int
|
@@ -423,7 +451,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
423
451
|
fd = open(path, O_RDONLY);
|
424
452
|
if (fd < 0) {
|
425
453
|
*errno_provenance = "bs_fetch:open_cache_file:open";
|
426
|
-
if (errno == ENOENT) return
|
454
|
+
if (errno == ENOENT) return CACHE_MISS;
|
427
455
|
return ERROR_WITH_ERRNO;
|
428
456
|
}
|
429
457
|
#ifdef _WIN32
|
@@ -478,7 +506,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
|
|
478
506
|
goto done;
|
479
507
|
}
|
480
508
|
if (nread != data_size) {
|
481
|
-
ret =
|
509
|
+
ret = CACHE_STALE;
|
482
510
|
goto done;
|
483
511
|
}
|
484
512
|
|
@@ -672,14 +700,22 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
672
700
|
|
673
701
|
/* Open the cache key if it exists, and read its cache key in */
|
674
702
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
675
|
-
if (cache_fd ==
|
703
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
676
704
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
705
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
706
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
707
|
+
}
|
677
708
|
} else if (cache_fd < 0) {
|
678
709
|
goto fail_errno;
|
679
710
|
} else {
|
680
711
|
/* True if the cache existed and no invalidating changes have occurred since
|
681
712
|
* it was generated. */
|
682
713
|
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
714
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
715
|
+
if (!valid_cache) {
|
716
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
|
717
|
+
}
|
718
|
+
}
|
683
719
|
}
|
684
720
|
|
685
721
|
if (valid_cache) {
|
@@ -688,10 +724,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
|
|
688
724
|
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
689
725
|
&output_data, &exception_tag, &errno_provenance
|
690
726
|
);
|
691
|
-
if (exception_tag != 0)
|
692
|
-
else if (res ==
|
693
|
-
else if (res == ERROR_WITH_ERRNO)
|
694
|
-
else if (!NIL_P(output_data))
|
727
|
+
if (exception_tag != 0) goto raise;
|
728
|
+
else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
729
|
+
else if (res == ERROR_WITH_ERRNO) goto fail_errno;
|
730
|
+
else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
|
695
731
|
}
|
696
732
|
close(cache_fd);
|
697
733
|
cache_fd = -1;
|
@@ -778,7 +814,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
778
814
|
|
779
815
|
/* Open the cache key if it exists, and read its cache key in */
|
780
816
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
781
|
-
if (cache_fd ==
|
817
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
782
818
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
783
819
|
} else if (cache_fd < 0) {
|
784
820
|
goto fail;
|
data/lib/bootsnap.rb
CHANGED
@@ -8,25 +8,55 @@ require_relative('bootsnap/compile_cache')
|
|
8
8
|
module Bootsnap
|
9
9
|
InvalidConfiguration = Class.new(StandardError)
|
10
10
|
|
11
|
+
class << self
|
12
|
+
attr_reader :logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.log!
|
16
|
+
self.logger = $stderr.method(:puts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.logger=(logger)
|
20
|
+
@logger = logger
|
21
|
+
if logger.respond_to?(:debug)
|
22
|
+
self.instrumentation = ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") }
|
23
|
+
else
|
24
|
+
self.instrumentation = ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.instrumentation=(callback)
|
29
|
+
@instrumentation = callback
|
30
|
+
self.instrumentation_enabled = !!callback
|
31
|
+
end
|
32
|
+
|
33
|
+
def self._instrument(event, path)
|
34
|
+
@instrumentation.call(event, path)
|
35
|
+
end
|
36
|
+
|
11
37
|
def self.setup(
|
12
38
|
cache_dir:,
|
13
39
|
development_mode: true,
|
14
40
|
load_path_cache: true,
|
15
|
-
autoload_paths_cache:
|
16
|
-
disable_trace:
|
41
|
+
autoload_paths_cache: nil,
|
42
|
+
disable_trace: nil,
|
17
43
|
compile_cache_iseq: true,
|
18
44
|
compile_cache_yaml: true
|
19
45
|
)
|
20
|
-
|
21
|
-
|
46
|
+
unless autoload_paths_cache.nil?
|
47
|
+
warn "[DEPRECATED] Bootsnap's `autoload_paths_cache:` option is deprecated and will be removed. " \
|
48
|
+
"If you use Zeitwerk this option is useless, and if you are still using the classic autoloader " \
|
49
|
+
"upgrading is recommended."
|
22
50
|
end
|
23
51
|
|
24
|
-
|
52
|
+
unless disable_trace.nil?
|
53
|
+
warn "[DEPRECATED] Bootsnap's `disable_trace:` option is deprecated and will be removed. " \
|
54
|
+
"If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended."
|
55
|
+
end
|
25
56
|
|
26
57
|
Bootsnap::LoadPathCache.setup(
|
27
58
|
cache_path: cache_dir + '/bootsnap/load-path-cache',
|
28
59
|
development_mode: development_mode,
|
29
|
-
active_support: autoload_paths_cache
|
30
60
|
) if load_path_cache
|
31
61
|
|
32
62
|
Bootsnap::CompileCache.setup(
|
@@ -36,14 +66,46 @@ module Bootsnap
|
|
36
66
|
)
|
37
67
|
end
|
38
68
|
|
39
|
-
def self.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
69
|
+
def self.default_setup
|
70
|
+
env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
|
71
|
+
development_mode = ['', nil, 'development'].include?(env)
|
72
|
+
|
73
|
+
unless ENV['DISABLE_BOOTSNAP']
|
74
|
+
cache_dir = ENV['BOOTSNAP_CACHE_DIR']
|
75
|
+
unless cache_dir
|
76
|
+
config_dir_frame = caller.detect do |line|
|
77
|
+
line.include?('/config/')
|
78
|
+
end
|
79
|
+
|
80
|
+
unless config_dir_frame
|
81
|
+
$stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
|
82
|
+
$stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
|
83
|
+
$stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
|
84
|
+
|
85
|
+
raise("couldn't infer bootsnap cache directory")
|
86
|
+
end
|
87
|
+
|
88
|
+
path = config_dir_frame.split(/:\d+:/).first
|
89
|
+
path = File.dirname(path) until File.basename(path) == 'config'
|
90
|
+
app_root = File.dirname(path)
|
91
|
+
|
92
|
+
cache_dir = File.join(app_root, 'tmp', 'cache')
|
93
|
+
end
|
94
|
+
|
95
|
+
ruby_version = Gem::Version.new(RUBY_VERSION)
|
96
|
+
iseq_cache_enabled = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.5.4')
|
97
|
+
|
98
|
+
setup(
|
99
|
+
cache_dir: cache_dir,
|
100
|
+
development_mode: development_mode,
|
101
|
+
load_path_cache: !ENV['DISABLE_BOOTSNAP_LOAD_PATH_CACHE'],
|
102
|
+
compile_cache_iseq: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'] && iseq_cache_enabled,
|
103
|
+
compile_cache_yaml: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'],
|
44
104
|
)
|
45
|
-
|
46
|
-
|
105
|
+
|
106
|
+
if ENV['BOOTSNAP_LOG']
|
107
|
+
log!
|
108
|
+
end
|
47
109
|
end
|
48
110
|
end
|
49
111
|
end
|
data/lib/bootsnap/cli.rb
CHANGED
@@ -60,7 +60,7 @@ module Bootsnap
|
|
60
60
|
|
61
61
|
# Gems that include YAML files usually don't put them in `lib/`.
|
62
62
|
# So we look at the gem root.
|
63
|
-
gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?
|
63
|
+
gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems\/[^/]+}
|
64
64
|
gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] }.compact.uniq
|
65
65
|
precompile_yaml_files(gem_paths, exclude: gem_exclude)
|
66
66
|
end
|
@@ -121,7 +121,7 @@ module Bootsnap
|
|
121
121
|
if !exclude || !exclude.match?(path)
|
122
122
|
list_files(path, '**/*.{yml,yaml}').each do |yaml_file|
|
123
123
|
# We ignore hidden files to not match the various .ci.yml files
|
124
|
-
if !yaml_file.
|
124
|
+
if !File.basename(yaml_file).start_with?('.') && (!exclude || !exclude.match?(yaml_file))
|
125
125
|
@work_pool.push(:yaml, yaml_file)
|
126
126
|
end
|
127
127
|
end
|
@@ -28,10 +28,9 @@ module Bootsnap
|
|
28
28
|
CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
|
29
29
|
|
30
30
|
class << self
|
31
|
-
attr_reader(:load_path_cache, :
|
32
|
-
:loaded_features_index, :realpath_cache)
|
31
|
+
attr_reader(:load_path_cache, :loaded_features_index, :realpath_cache)
|
33
32
|
|
34
|
-
def setup(cache_path:, development_mode
|
33
|
+
def setup(cache_path:, development_mode:)
|
35
34
|
unless supported?
|
36
35
|
warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
|
37
36
|
return
|
@@ -45,18 +44,6 @@ module Bootsnap
|
|
45
44
|
@load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
|
46
45
|
require_relative('load_path_cache/core_ext/kernel_require')
|
47
46
|
require_relative('load_path_cache/core_ext/loaded_features')
|
48
|
-
|
49
|
-
if active_support
|
50
|
-
# this should happen after setting up the initial cache because it
|
51
|
-
# loads a lot of code. It's better to do after +require+ is optimized.
|
52
|
-
require('active_support/dependencies')
|
53
|
-
@autoload_paths_cache = Cache.new(
|
54
|
-
store,
|
55
|
-
::ActiveSupport::Dependencies.autoload_paths,
|
56
|
-
development_mode: development_mode
|
57
|
-
)
|
58
|
-
require_relative('load_path_cache/core_ext/active_support')
|
59
|
-
end
|
60
47
|
end
|
61
48
|
|
62
49
|
def supported?
|
data/lib/bootsnap/setup.rb
CHANGED
@@ -1,39 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require_relative('../bootsnap')
|
3
3
|
|
4
|
-
|
5
|
-
development_mode = ['', nil, 'development'].include?(env)
|
6
|
-
|
7
|
-
cache_dir = ENV['BOOTSNAP_CACHE_DIR']
|
8
|
-
unless cache_dir
|
9
|
-
config_dir_frame = caller.detect do |line|
|
10
|
-
line.include?('/config/')
|
11
|
-
end
|
12
|
-
|
13
|
-
unless config_dir_frame
|
14
|
-
$stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
|
15
|
-
$stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
|
16
|
-
$stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
|
17
|
-
|
18
|
-
raise("couldn't infer bootsnap cache directory")
|
19
|
-
end
|
20
|
-
|
21
|
-
path = config_dir_frame.split(/:\d+:/).first
|
22
|
-
path = File.dirname(path) until File.basename(path) == 'config'
|
23
|
-
app_root = File.dirname(path)
|
24
|
-
|
25
|
-
cache_dir = File.join(app_root, 'tmp', 'cache')
|
26
|
-
end
|
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
|
-
|
31
|
-
Bootsnap.setup(
|
32
|
-
cache_dir: cache_dir,
|
33
|
-
development_mode: development_mode,
|
34
|
-
load_path_cache: true,
|
35
|
-
autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
|
36
|
-
disable_trace: false,
|
37
|
-
compile_cache_iseq: iseq_cache_enabled,
|
38
|
-
compile_cache_yaml: true,
|
39
|
-
)
|
4
|
+
Bootsnap.default_setup
|
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.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01
|
11
|
+
date: 2021-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -121,7 +121,6 @@ files:
|
|
121
121
|
- lib/bootsnap/load_path_cache.rb
|
122
122
|
- lib/bootsnap/load_path_cache/cache.rb
|
123
123
|
- lib/bootsnap/load_path_cache/change_observer.rb
|
124
|
-
- lib/bootsnap/load_path_cache/core_ext/active_support.rb
|
125
124
|
- lib/bootsnap/load_path_cache/core_ext/kernel_require.rb
|
126
125
|
- lib/bootsnap/load_path_cache/core_ext/loaded_features.rb
|
127
126
|
- lib/bootsnap/load_path_cache/loaded_features_index.rb
|
@@ -1,107 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module Bootsnap
|
3
|
-
module LoadPathCache
|
4
|
-
module CoreExt
|
5
|
-
module ActiveSupport
|
6
|
-
def self.without_bootsnap_cache
|
7
|
-
prev = Thread.current[:without_bootsnap_cache] || false
|
8
|
-
Thread.current[:without_bootsnap_cache] = true
|
9
|
-
yield
|
10
|
-
ensure
|
11
|
-
Thread.current[:without_bootsnap_cache] = prev
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.allow_bootsnap_retry(allowed)
|
15
|
-
prev = Thread.current[:without_bootsnap_retry] || false
|
16
|
-
Thread.current[:without_bootsnap_retry] = !allowed
|
17
|
-
yield
|
18
|
-
ensure
|
19
|
-
Thread.current[:without_bootsnap_retry] = prev
|
20
|
-
end
|
21
|
-
|
22
|
-
module ClassMethods
|
23
|
-
def autoload_paths=(o)
|
24
|
-
super
|
25
|
-
Bootsnap::LoadPathCache.autoload_paths_cache.reinitialize(o)
|
26
|
-
end
|
27
|
-
|
28
|
-
def search_for_file(path)
|
29
|
-
return super if Thread.current[:without_bootsnap_cache]
|
30
|
-
begin
|
31
|
-
Bootsnap::LoadPathCache.autoload_paths_cache.find(path)
|
32
|
-
rescue Bootsnap::LoadPathCache::ReturnFalse
|
33
|
-
nil # doesn't really apply here
|
34
|
-
rescue Bootsnap::LoadPathCache::FallbackScan
|
35
|
-
nil # doesn't really apply here
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def autoloadable_module?(path_suffix)
|
40
|
-
Bootsnap::LoadPathCache.autoload_paths_cache.load_dir(path_suffix)
|
41
|
-
end
|
42
|
-
|
43
|
-
def remove_constant(const)
|
44
|
-
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
45
|
-
end
|
46
|
-
|
47
|
-
def require_or_load(*)
|
48
|
-
CoreExt::ActiveSupport.allow_bootsnap_retry(true) do
|
49
|
-
super
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# If we can't find a constant using the patched implementation of
|
54
|
-
# search_for_file, try again with the default implementation.
|
55
|
-
#
|
56
|
-
# These methods call search_for_file, and we want to modify its
|
57
|
-
# behaviour. The gymnastics here are a bit awkward, but it prevents
|
58
|
-
# 200+ lines of monkeypatches.
|
59
|
-
def load_missing_constant(from_mod, const_name)
|
60
|
-
CoreExt::ActiveSupport.allow_bootsnap_retry(false) do
|
61
|
-
super
|
62
|
-
end
|
63
|
-
rescue NameError => e
|
64
|
-
raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
|
65
|
-
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
66
|
-
|
67
|
-
# This function can end up called recursively, we only want to
|
68
|
-
# retry at the top-level.
|
69
|
-
raise(e) if Thread.current[:without_bootsnap_retry]
|
70
|
-
# If we already had cache disabled, there's no use retrying
|
71
|
-
raise(e) if Thread.current[:without_bootsnap_cache]
|
72
|
-
# NoMethodError is a NameError, but we only want to handle actual
|
73
|
-
# NameError instances.
|
74
|
-
raise(e) unless e.class == NameError
|
75
|
-
# We can only confidently handle cases when *this* constant fails
|
76
|
-
# to load, not other constants referred to by it.
|
77
|
-
raise(e) unless e.name == const_name
|
78
|
-
# If the constant was actually loaded, something else went wrong?
|
79
|
-
raise(e) if from_mod.const_defined?(const_name)
|
80
|
-
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
81
|
-
end
|
82
|
-
|
83
|
-
# Signature has changed a few times over the years; easiest to not
|
84
|
-
# reiterate it with version polymorphism here...
|
85
|
-
def depend_on(*)
|
86
|
-
super
|
87
|
-
rescue LoadError => e
|
88
|
-
raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
|
89
|
-
e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
|
90
|
-
|
91
|
-
# If we already had cache disabled, there's no use retrying
|
92
|
-
raise(e) if Thread.current[:without_bootsnap_cache]
|
93
|
-
CoreExt::ActiveSupport.without_bootsnap_cache { super }
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
module ActiveSupport
|
102
|
-
module Dependencies
|
103
|
-
class << self
|
104
|
-
prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|