bootsnap 1.23.0 → 1.24.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e0ad269f816c24dc901b6544acb59c5ebd65b22432f39ee69bc3387b9c2b04d
4
- data.tar.gz: 700f525d90d4e77421ce83e38f8731981a48cc82a8b9a937c29f661972f78d7f
3
+ metadata.gz: 31d64e50bd32368c8dffe927b63fa6e62243b697a2606ca3e2330c4694ec816b
4
+ data.tar.gz: 5dfc374040409d1d20e893abc6a8c6052de02cf560c15e7678a5db2084bd722c
5
5
  SHA512:
6
- metadata.gz: f5859d7fd4b81f1969c55e12b33a957b34b1dc2825d03bc3f7ab60a57d34153cbcdda7a60626ea758fd66877b1b74a58d17b276aa2055f41086488d6ced5317b
7
- data.tar.gz: c09f2cd48da0ba3bb5e9d3c995ffb86b8b16aec987c311162fc79ca648a66f355e4051d6ae4c5ee486d5ad2d2bc3af32090e15776e1f45129c1ea8c8e6c811ed
6
+ metadata.gz: ff9e6b5b2c47d0337fa8d46c487fd6881bc567f5ae01a17f85aa3821df9b7f8416283764f8309de6207d94eb00e6cd6ee845a1307dd601659e4d26cd6abbc7ea
7
+ data.tar.gz: e34adc7c5162da87d94358a99a5990d0dfbc6497fb141e7407614e181328e1091bd708fac0aa96e6bce8731283a367db6a5aa6e8a2afe3b848b2d0f6b73e3ba4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.24.3
4
+
5
+ * Fix the `1.24.2` workaround to parse Ruby files with UTF-8 even when the `LANG` environment variable
6
+ is unset or set to `C`.
7
+
8
+ # 1.24.2
9
+
10
+ * Workaround two Ruby bugs in `RubyVM::InstructionSequence.compile_file`, that were causing
11
+ files to be loaded with the old Ruby parser instead of Prism, causing issues with some pattern matching syntax.
12
+ Ref: https://bugs.ruby-lang.org/issues/22023
13
+
14
+ # 1.24.1
15
+
16
+ * Fix encoding of Ruby source files loaded when `BOOTSNAP_READONLY` is set.
17
+ Files would incorectly be loaded in `ASCII-8BIT` causing literal strings outside
18
+ the pure ASCII range to have `ASCII-8BIT` encoding instead of `UTF-8`.
19
+ This bug was introduced in `1.24.0`.
20
+
21
+ # 1.24.0
22
+
23
+ * Added a hook API to customize Ruby compilation.
24
+
3
25
  # 1.23.0
4
26
 
5
27
  * Require Ruby 2.7.
data/README.md CHANGED
@@ -75,6 +75,7 @@ well together.
75
75
  `require 'bootsnap/setup'` behavior can be changed using environment variables:
76
76
 
77
77
  - `BOOTSNAP_CACHE_DIR` allows to define the cache location.
78
+ - `BOOTSNAP_CONFIG` allows to change the default config location (`config/bootsnap.rb`).
78
79
  - `DISABLE_BOOTSNAP` allows to entirely disable bootsnap.
79
80
  - `DISABLE_BOOTSNAP_LOAD_PATH_CACHE` allows to disable load path caching.
80
81
  - `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
@@ -320,6 +321,34 @@ open /c/nope.bundle -> -1
320
321
  # (nothing!)
321
322
  ```
322
323
 
324
+ ## Custom Compilers
325
+
326
+ Bootsnap allows substituing the default Ruby compiler by another one.
327
+ This can be configured from the bootsnap config file (defaults to `config/bootsnap.rb`).
328
+
329
+ The main use case is to programmatically enable frozen string literals for your project without impacting dependencies:
330
+
331
+ ```ruby
332
+ Bootsnap.enable_frozen_string_literal(app_only: true)
333
+ ```
334
+
335
+ But it can also be used for more fine grained logic, or to implement all sort of Ruby code preprocessing:
336
+
337
+ ```ruby
338
+ # config/bootsnap.rb
339
+ gems_root = File.join(Bundler.bundle_path.cleanpath, "")
340
+ app_root = File.join(Dir.pwd, "")
341
+ Bootsnap::CompileCache::ISeq.compiler_selector = ->(path) do
342
+ # Enable `frozen_string_literal: true` for app code, but not gems.
343
+
344
+ if path.start_with?(app_root) && !path.start_with?(gems_root)
345
+ Bootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL
346
+ else
347
+ Bootsnap::CompileCache::ISeq::DEFAULT
348
+ end
349
+ end
350
+ ```
351
+
323
352
  ## Precompilation
324
353
 
325
354
  In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
@@ -109,13 +109,15 @@ static bool readonly = false;
109
109
  static bool revalidation = false;
110
110
  static bool perm_issue = false;
111
111
 
112
+ static ID id_storage_to_output, id_input_to_output, id_input_to_storage;
113
+
112
114
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
113
115
  static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
114
116
  static VALUE bs_readonly_set(VALUE self, VALUE enabled);
115
117
  static VALUE bs_revalidation_set(VALUE self, VALUE enabled);
116
118
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
117
- static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
118
- static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
119
+ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args);
120
+ static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler);
119
121
 
120
122
  /* Helpers */
121
123
  enum cache_status {
@@ -123,7 +125,7 @@ enum cache_status {
123
125
  hit,
124
126
  stale,
125
127
  };
126
- static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
128
+ static void bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE]);
127
129
  static int bs_read_key(int fd, struct bs_cache_key * key);
128
130
  static enum cache_status cache_key_equal_fast_path(struct bs_cache_key * k1, struct bs_cache_key * k2);
129
131
  static int cache_key_equal_slow_path(struct bs_cache_key * current_key, struct bs_cache_key * cached_key, const VALUE input_data);
@@ -143,7 +145,7 @@ static uint32_t get_ruby_platform(void);
143
145
  */
144
146
  static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
145
147
  static VALUE prot_input_to_output(VALUE arg);
146
- static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
148
+ static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * output_data, int * exception_tag);
147
149
  static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
148
150
  struct s2o_data;
149
151
  struct i2o_data;
@@ -275,6 +277,10 @@ Init_bootsnap(void)
275
277
  {
276
278
  rb_mBootsnap = rb_define_module("Bootsnap");
277
279
 
280
+ id_storage_to_output = rb_intern("storage_to_output");
281
+ id_input_to_output = rb_intern("input_to_output");
282
+ id_input_to_storage = rb_intern("input_to_storage");
283
+
278
284
  rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
279
285
 
280
286
  #ifdef HAVE_FSTATAT
@@ -302,8 +308,8 @@ Init_bootsnap(void)
302
308
  rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
303
309
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "readonly=", bs_readonly_set, 1);
304
310
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "revalidation=", bs_revalidation_set, 1);
305
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
306
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
311
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 5);
312
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 4);
307
313
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
308
314
 
309
315
  current_umask = umask(0777);
@@ -420,13 +426,28 @@ get_ruby_platform(void)
420
426
  * The path will look something like: <cachedir>/12/34567890abcdef
421
427
  */
422
428
  static void
423
- bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
429
+ bs_cache_path(VALUE cachedir_v, VALUE namespace_v, VALUE path_v, char (* cache_path)[MAX_CACHEPATH_SIZE])
424
430
  {
425
- uint64_t hash = fnv1a_64(path);
431
+ FilePathValue(path_v);
432
+
433
+ Check_Type(cachedir_v, T_STRING);
434
+ Check_Type(path_v, T_STRING);
435
+ if (!NIL_P(namespace_v)) {
436
+ Check_Type(namespace_v, T_STRING);
437
+ }
438
+
439
+ if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
440
+ rb_raise(rb_eArgError, "cachedir too long");
441
+ }
442
+
443
+ const char * cachedir = RSTRING_PTR(cachedir_v);
444
+ const char * namespace = NIL_P(namespace_v) ? "" : RSTRING_PTR(namespace_v);
445
+
446
+ uint64_t hash = fnv1a_64(path_v);
426
447
  uint8_t first_byte = (hash >> (64 - 8));
427
448
  uint64_t remainder = hash & 0x00ffffffffffffff;
428
449
 
429
- sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
450
+ sprintf(*cache_path, "%s%s/%02"PRIx8"/%014"PRIx64, cachedir, namespace, first_byte, remainder);
430
451
  }
431
452
 
432
453
  /*
@@ -498,25 +519,14 @@ static void bs_cache_key_digest(struct bs_cache_key *key,
498
519
  * conversions on the ruby VALUE arguments before passing them along.
499
520
  */
500
521
  static VALUE
501
- bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
522
+ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler, VALUE args)
502
523
  {
503
- FilePathValue(path_v);
504
-
505
- Check_Type(cachedir_v, T_STRING);
506
- Check_Type(path_v, T_STRING);
507
-
508
- if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
509
- rb_raise(rb_eArgError, "cachedir too long");
510
- }
511
-
512
- char * cachedir = RSTRING_PTR(cachedir_v);
513
- char * path = RSTRING_PTR(path_v);
514
524
  char cache_path[MAX_CACHEPATH_SIZE];
515
525
 
516
526
  /* generate cache path to cache_path */
517
- bs_cache_path(cachedir, path_v, &cache_path);
527
+ bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
518
528
 
519
- return bs_fetch(path, path_v, cache_path, handler, args);
529
+ return bs_fetch(RSTRING_PTR(path_v), path_v, cache_path, handler, args);
520
530
  }
521
531
 
522
532
  /*
@@ -525,25 +535,13 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE arg
525
535
  * and doesn't return the content.
526
536
  */
527
537
  static VALUE
528
- bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
538
+ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE namespace_v, VALUE path_v, VALUE handler)
529
539
  {
530
- FilePathValue(path_v);
531
-
532
- Check_Type(cachedir_v, T_STRING);
533
- Check_Type(path_v, T_STRING);
534
-
535
- if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
536
- rb_raise(rb_eArgError, "cachedir too long");
537
- }
538
-
539
- char * cachedir = RSTRING_PTR(cachedir_v);
540
- char * path = RSTRING_PTR(path_v);
541
540
  char cache_path[MAX_CACHEPATH_SIZE];
542
-
543
541
  /* generate cache path to cache_path */
544
- bs_cache_path(cachedir, path_v, &cache_path);
542
+ bs_cache_path(cachedir_v, namespace_v, path_v, &cache_path);
545
543
 
546
- return bs_precompile(path, path_v, cache_path, handler);
544
+ return bs_precompile(RSTRING_PTR(path_v), path_v, cache_path, handler);
547
545
  }
548
546
 
549
547
  static int bs_open_noatime(const char *path, int flags) {
@@ -963,7 +961,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
963
961
  exception_message = path_v;
964
962
  goto fail_errno;
965
963
  }
966
- bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
964
+ bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
967
965
  if (exception_tag != 0) goto raise;
968
966
  goto succeed;
969
967
  } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
@@ -989,7 +987,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
989
987
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
990
988
  * to cache anything; just return input_to_output(input_data) */
991
989
  if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
992
- bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
990
+ bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
993
991
  if (exception_tag != 0) goto raise;
994
992
  goto succeed;
995
993
  }
@@ -1009,7 +1007,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
1009
1007
 
1010
1008
  if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
1011
1009
  /* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
1012
- bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
1010
+ bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
1013
1011
  if (exception_tag != 0) goto raise;
1014
1012
  } else if (NIL_P(output_data)) {
1015
1013
  /* If output_data is nil, delete the cache entry and generate the output
@@ -1023,7 +1021,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
1023
1021
  goto fail_errno;
1024
1022
  }
1025
1023
  }
1026
- bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
1024
+ bs_input_to_output(handler, args, input_data, path_v, &output_data, &exception_tag);
1027
1025
  if (exception_tag != 0) goto raise;
1028
1026
  }
1029
1027
 
@@ -1181,6 +1179,7 @@ struct i2o_data {
1181
1179
  VALUE handler;
1182
1180
  VALUE args;
1183
1181
  VALUE input_data;
1182
+ VALUE pathval;
1184
1183
  };
1185
1184
 
1186
1185
  struct i2s_data {
@@ -1193,7 +1192,7 @@ static VALUE
1193
1192
  try_storage_to_output(VALUE arg)
1194
1193
  {
1195
1194
  struct s2o_data * data = (struct s2o_data *)arg;
1196
- return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
1195
+ return rb_funcall(data->handler, id_storage_to_output, 2, data->storage_data, data->args);
1197
1196
  }
1198
1197
 
1199
1198
  static int
@@ -1210,12 +1209,13 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
1210
1209
  }
1211
1210
 
1212
1211
  static void
1213
- bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
1212
+ bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE path_v, VALUE * output_data, int * exception_tag)
1214
1213
  {
1215
1214
  struct i2o_data i2o_data = {
1216
1215
  .handler = handler,
1217
1216
  .args = args,
1218
1217
  .input_data = input_data,
1218
+ .pathval = path_v,
1219
1219
  };
1220
1220
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
1221
1221
  }
@@ -1224,14 +1224,14 @@ static VALUE
1224
1224
  prot_input_to_output(VALUE arg)
1225
1225
  {
1226
1226
  struct i2o_data * data = (struct i2o_data *)arg;
1227
- return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
1227
+ return rb_funcall(data->handler, id_input_to_output, 3, data->input_data, data->pathval, data->args);
1228
1228
  }
1229
1229
 
1230
1230
  static VALUE
1231
1231
  try_input_to_storage(VALUE arg)
1232
1232
  {
1233
1233
  struct i2s_data * data = (struct i2s_data *)arg;
1234
- return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
1234
+ return rb_funcall(data->handler, id_input_to_storage, 2, data->input_data, data->pathval);
1235
1235
  }
1236
1236
 
1237
1237
  static int
data/lib/bootsnap/cli.rb CHANGED
@@ -43,6 +43,7 @@ module Bootsnap
43
43
  yaml: yaml,
44
44
  revalidation: true,
45
45
  )
46
+ Bootsnap.load_config
46
47
 
47
48
  @work_pool = WorkerPool.create(size: jobs, jobs: {
48
49
  ruby: method(:precompile_ruby),
@@ -7,7 +7,8 @@ module Bootsnap
7
7
  module CompileCache
8
8
  module ISeq
9
9
  class << self
10
- attr_reader(:cache_dir)
10
+ attr_reader :cache_dir
11
+ attr_accessor :compiler_selector, :default_compiler
11
12
 
12
13
  def cache_dir=(cache_dir)
13
14
  @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq"
@@ -18,71 +19,117 @@ module Bootsnap
18
19
  end
19
20
  end
20
21
 
21
- has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
22
- if defined? RubyVM::InstructionSequence
23
- RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary
22
+ class Compiler
23
+ attr_reader :namespace, :compile_options
24
+
25
+ def initialize(namespace = nil, compile_options = nil)
26
+ @namespace = namespace
27
+ @compile_options = compile_options
24
28
  end
25
- false
26
- rescue TypeError
27
- true
28
- end
29
29
 
30
- if has_ruby_bug_18250
31
- def self.input_to_storage(_, path)
32
- iseq = begin
33
- RubyVM::InstructionSequence.compile_file(path)
34
- rescue SyntaxError
35
- return UNCOMPILABLE # syntax error
30
+ has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
31
+ if defined? RubyVM::InstructionSequence
32
+ RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary
36
33
  end
34
+ false
35
+ rescue TypeError
36
+ true
37
+ end
37
38
 
39
+ has_ruby_bug_22023 = if defined?(RubyVM::InstructionSequence) && RubyVM::InstructionSequence.respond_to?(:compile_file_prism)
38
40
  begin
39
- iseq.to_binary
40
- rescue TypeError
41
- UNCOMPILABLE # ruby bug #18250
41
+ RubyVM::InstructionSequence.compile_file(File.expand_path("../ruby_bug_22023_canary.rb", __FILE__))
42
+ false
43
+ rescue SyntaxError
44
+ true
42
45
  end
43
46
  end
44
- else
45
- def self.input_to_storage(_, path)
46
- RubyVM::InstructionSequence.compile_file(path).to_binary
47
- rescue SyntaxError
48
- UNCOMPILABLE # syntax error
47
+
48
+ if has_ruby_bug_22023 && RUBY_DESCRIPTION.include?("+PRISM")
49
+ module PatchRubyBug22023
50
+ def compile_file(path, options = nil)
51
+ compile_file_prism(path, options)
52
+ end
53
+
54
+ has_ruby_bug_22023_bis = !RubyVM::InstructionSequence.compile_file_prism(
55
+ File.expand_path("../ruby_bug_22023_canary.rb", __FILE__),
56
+ {frozen_string_literal: true},
57
+ ).eval.frozen?
58
+
59
+ if has_ruby_bug_22023_bis
60
+ def compile_file_prism(path, options = nil)
61
+ compile_prism(::File.read(path, encoding: Encoding::UTF_8), path, path, nil, options)
62
+ end
63
+ end
64
+ end
65
+ RubyVM::InstructionSequence.singleton_class.prepend(PatchRubyBug22023)
49
66
  end
50
- end
51
67
 
52
- def self.storage_to_output(binary, _args)
53
- iseq = RubyVM::InstructionSequence.load_from_binary(binary)
54
- binary.clear
55
- iseq
56
- rescue RuntimeError => error
57
- if error.message == "broken binary format"
58
- $stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
59
- nil
68
+ if has_ruby_bug_18250
69
+ def input_to_storage(_, path)
70
+ iseq = RubyVM::InstructionSequence.compile_file(path, @compile_options)
71
+ iseq.to_binary
72
+ rescue TypeError, SyntaxError # Ruby [Bug #18250] & [Bug #22023]
73
+ UNCOMPILABLE
74
+ end
60
75
  else
61
- raise
76
+ def input_to_storage(_, path)
77
+ RubyVM::InstructionSequence.compile_file(path, @compile_options).to_binary
78
+ rescue SyntaxError # Ruby [Bug #22023]
79
+ UNCOMPILABLE
80
+ end
81
+ end
82
+
83
+ def storage_to_output(binary, _args)
84
+ iseq = RubyVM::InstructionSequence.load_from_binary(binary)
85
+ binary.clear
86
+ iseq
87
+ rescue RuntimeError => error
88
+ if error.message == "broken binary format"
89
+ $stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
90
+ nil
91
+ else
92
+ raise
93
+ end
94
+ end
95
+
96
+ def input_to_output(source, path, _kwargs)
97
+ RubyVM::InstructionSequence.compile(
98
+ source.force_encoding(Encoding.default_external),
99
+ path,
100
+ path,
101
+ nil,
102
+ @compile_options,
103
+ )
62
104
  end
63
105
  end
64
106
 
107
+ DEFAULT = Compiler.new
108
+ FROZEN_STRING_LITERAL = Compiler.new("-fstr", {frozen_string_literal: true}.freeze)
109
+ MUTABLE_STRING_LITERAL = Compiler.new("-no-fstr", {frozen_string_literal: false}.freeze)
110
+ @default_compiler = DEFAULT
111
+
65
112
  def self.fetch(path, cache_dir: ISeq.cache_dir)
113
+ compiler = compiler_selector&.call(path) || default_compiler
66
114
  Bootsnap::CompileCache::Native.fetch(
67
115
  cache_dir,
116
+ compiler.namespace,
68
117
  path.to_s,
69
- Bootsnap::CompileCache::ISeq,
118
+ compiler,
70
119
  nil,
71
120
  )
72
121
  end
73
122
 
74
123
  def self.precompile(path)
124
+ compiler = compiler_selector&.call(path) || default_compiler
75
125
  Bootsnap::CompileCache::Native.precompile(
76
126
  cache_dir,
127
+ compiler.namespace,
77
128
  path.to_s,
78
- Bootsnap::CompileCache::ISeq,
129
+ compiler,
79
130
  )
80
131
  end
81
132
 
82
- def self.input_to_output(_data, _kwargs)
83
- nil # ruby handles this
84
- end
85
-
86
133
  module InstructionSequenceMixin
87
134
  def load_iseq(path)
88
135
  # Having coverage enabled prevents iseq dumping/loading.
@@ -0,0 +1,10 @@
1
+ # rubocop:disable Style/FrozenStringLiteralComment
2
+ def foo(bars)
3
+ case bars
4
+ in [one, "a" | "b" => two]
5
+ puts "#{one} - #{two}"
6
+ end
7
+ end
8
+
9
+ _ = "test"
10
+ # rubocop:enable Style/FrozenStringLiteralComment
@@ -28,6 +28,7 @@ module Bootsnap
28
28
 
29
29
  CompileCache::Native.precompile(
30
30
  cache_dir,
31
+ nil,
31
32
  path.to_s,
32
33
  @implementation,
33
34
  )
@@ -175,7 +176,7 @@ module Bootsnap
175
176
  result
176
177
  end
177
178
 
178
- def input_to_output(data, kwargs)
179
+ def input_to_output(data, _path, kwargs)
179
180
  ::YAML.unsafe_load(data, **(kwargs || {}))
180
181
  end
181
182
  end
@@ -215,7 +216,7 @@ module Bootsnap
215
216
  end
216
217
  end
217
218
 
218
- def input_to_output(data, kwargs)
219
+ def input_to_output(data, _path, kwargs)
219
220
  ::YAML.load(data, **(kwargs || {}))
220
221
  end
221
222
  end
@@ -233,6 +234,7 @@ module Bootsnap
233
234
 
234
235
  CompileCache::Native.fetch(
235
236
  CompileCache::YAML.cache_dir,
237
+ nil,
236
238
  File.realpath(path),
237
239
  CompileCache::YAML::Psych4::SafeLoad,
238
240
  kwargs,
@@ -253,6 +255,7 @@ module Bootsnap
253
255
 
254
256
  CompileCache::Native.fetch(
255
257
  CompileCache::YAML.cache_dir,
258
+ nil,
256
259
  File.realpath(path),
257
260
  CompileCache::YAML::Psych4::UnsafeLoad,
258
261
  kwargs,
@@ -288,7 +291,7 @@ module Bootsnap
288
291
  unpacker.unpack
289
292
  end
290
293
 
291
- def input_to_output(data, kwargs)
294
+ def input_to_output(data, _path, kwargs)
292
295
  ::YAML.load(data, **(kwargs || {}))
293
296
  end
294
297
 
@@ -305,6 +308,7 @@ module Bootsnap
305
308
 
306
309
  CompileCache::Native.fetch(
307
310
  CompileCache::YAML.cache_dir,
311
+ nil,
308
312
  File.realpath(path),
309
313
  CompileCache::YAML::Psych3,
310
314
  kwargs,
@@ -325,6 +329,7 @@ module Bootsnap
325
329
 
326
330
  CompileCache::Native.fetch(
327
331
  CompileCache::YAML.cache_dir,
332
+ nil,
328
333
  File.realpath(path),
329
334
  CompileCache::YAML::Psych3,
330
335
  kwargs,
@@ -59,7 +59,7 @@ module Bootsnap
59
59
  end
60
60
 
61
61
  def purge_multi(features)
62
- rejected_hashes = features.each_with_object({}) { |f, h| h[f.hash] = true }
62
+ rejected_hashes = features.to_h { |f| [f.hash, true] }
63
63
  @mutex.synchronize do
64
64
  @lfi.reject! { |_, hash| rejected_hashes.key?(hash) }
65
65
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- VERSION = "1.23.0"
4
+ VERSION = "1.24.3"
5
5
  end
data/lib/bootsnap.rb CHANGED
@@ -43,6 +43,13 @@ module Bootsnap
43
43
  @instrumentation.call(event, path)
44
44
  end
45
45
 
46
+ def load_config
47
+ config_path = File.expand_path(ENV["BOOTSNAP_CONFIG"] || "config/bootsnap.rb")
48
+ if File.exist?(config_path)
49
+ require(config_path)
50
+ end
51
+ end
52
+
46
53
  def setup(
47
54
  cache_dir:,
48
55
  development_mode: true,
@@ -76,6 +83,28 @@ module Bootsnap
76
83
  readonly: readonly,
77
84
  revalidation: revalidation,
78
85
  )
86
+
87
+ load_config
88
+ end
89
+
90
+ def enable_frozen_string_literal(app_only: false)
91
+ if app_only
92
+ gems_root = File.join(Bundler.bundle_path.cleanpath, "")
93
+ app_root = File.join(Dir.pwd, "")
94
+ Bootsnap::CompileCache::ISeq.default_compiler = Bootsnap::CompileCache::ISeq::DEFAULT
95
+ Bootsnap::CompileCache::ISeq.compiler_selector = lambda { |path|
96
+ # Enable `frozen_string_literal: true` for app code, but not gems.
97
+
98
+ if path.start_with?(app_root) && !path.start_with?(gems_root)
99
+ Bootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL
100
+ else
101
+ Bootsnap::CompileCache::ISeq::DEFAULT
102
+ end
103
+ }
104
+ else
105
+ Bootsnap::CompileCache::ISeq.compiler_selector = nil
106
+ Bootsnap::CompileCache::ISeq.default_compiler = Bootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL
107
+ end
79
108
  end
80
109
 
81
110
  def unload_cache!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.23.0
4
+ version: 1.24.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -44,6 +44,7 @@ files:
44
44
  - lib/bootsnap/cli/worker_pool.rb
45
45
  - lib/bootsnap/compile_cache.rb
46
46
  - lib/bootsnap/compile_cache/iseq.rb
47
+ - lib/bootsnap/compile_cache/ruby_bug_22023_canary.rb
47
48
  - lib/bootsnap/compile_cache/yaml.rb
48
49
  - lib/bootsnap/explicit_require.rb
49
50
  - lib/bootsnap/load_path_cache.rb
@@ -80,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
81
  - !ruby/object:Gem::Version
81
82
  version: '0'
82
83
  requirements: []
83
- rubygems_version: 3.6.9
84
+ rubygems_version: 4.0.6
84
85
  specification_version: 4
85
86
  summary: Boot large ruby/rails apps faster
86
87
  test_files: []