bootsnap 1.9.1 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00c1b5b2c37c6a351bb7656ef17ba7f7e6143fd773ceebd6ee09d9a55e25b75a
4
- data.tar.gz: d6d6d1d68d97e4f2c50c61f0b928714cf94f29ecab6604bb0092517779212738
3
+ metadata.gz: 96a4b41c02dc01b416bd262f43812b9541479330e4b073d428a6f0186fe07f9a
4
+ data.tar.gz: b221e0b0ce1da0552a7c08e1dd1ce84daab07129b834fd2bdec82f01a8645c50
5
5
  SHA512:
6
- metadata.gz: e1ade83cee1e4a917317cecd34107493424a5174b8bf92cc74db47743d4d84931de24533979480a23aa4ddb496cae8ce1f3cf30a2fbed3996b885a68138d16f0
7
- data.tar.gz: 23e4f6c77307b30526b880bfd12983b7e66c9539556acd3bc05a2fa3fd6884a142c83dfaa590846651afad243c0bf69c96d994ca8492e5539d706ba3e6cf713f
6
+ metadata.gz: 27c071b0a4efceb864931a673f7889fc95110f1ed8de4b50c17c99ecd4d62230321d14a23f346129f1f2198dc6a3222370e5bf8f105f52299799ba78aa361741
7
+ data.tar.gz: d5546dd05eaf860178526dc3bdcd33546f80cdcc5ebdb748ebf0b1fe8b813efed15387ea5835f4ab846775261d6df05e4d15df28ff81abb04ab795232435a194
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.10.0
4
+
5
+ * Delay requiring `FileUtils`. (#285)
6
+ `FileUtils` can be installed as a gem, so it's best to wait for bundler to have setup the load path before requiring it.
7
+
8
+ * Improve support of Psych 4. (#392)
9
+ Since `1.8.0`, `YAML.load_file` was no longer cached when Psych 4 was used. This is because `load_file` loads
10
+ in safe mode by default, so the Bootsnap cache could defeat that safety.
11
+ Now when precompiling YAML files, Bootsnap first try to parse them in safe mode, and if it can't fallback to unsafe mode,
12
+ and the cache contains a flag that records wether it was generated in safe mode or not.
13
+ `YAML.unsafe_load_file` will use safe caches just fine, but `YAML.load_file` will fallback to uncached YAML parsing
14
+ if the cache was generated using unsafe parsing.
15
+
16
+ * Minimize the Kernel.require extra stack frames. (#393)
17
+ This should reduce the noise generated by bootsnap on `LoadError`.
18
+
19
+ # 1.9.4
20
+
21
+ * Ignore absolute paths in the loaded feature index. (#385)
22
+ This fixes a compatibility issue with Zeitwerk when Zeitwerk is loaded before bootsnap. It also should
23
+ reduce the memory usage and improve load performance of Zeitwerk managed files.
24
+
25
+ * Automatically invalidate the load path cache whenever the Ruby version change. (#387)
26
+ This is to avoid issues in case the same installation path is re-used for subsequent ruby patch releases.
27
+
28
+ # 1.9.3
29
+
30
+ * Only disable the compile cache for source files impacted by [Ruby 3.0.3 [Bug 18250]](https://bugs.ruby-lang.org/issues/18250).
31
+ This should keep the performance loss to a minimum.
32
+
33
+ # 1.9.2
34
+
35
+ * Disable compile cache if [Ruby 3.0.3's ISeq cache bug](https://bugs.ruby-lang.org/issues/18250) is detected.
36
+ AKA `iseq.rb:13 to_binary: wrong argument type false (expected Symbol)`
37
+ * Fix `Kernel.load` behavior: before `load 'a'` would load `a.rb` (and other tried extensions) and wouldn't load `a` unless `development_mode: true`, now only `a` would be loaded and files with extensions wouldn't be.
38
+
3
39
  # 1.9.1
4
40
 
5
41
  * Removed a forgotten debug statement in JSON precompilation.
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Shopify, Inc.
3
+ Copyright (c) 2017-present Shopify, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -41,7 +41,7 @@ getting progressively slower, this is almost certainly the cause.**
41
41
  It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
42
42
  important to load Bootsnap as early as possible to get maximum performance improvement.
43
43
 
44
- You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb).
44
+ You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/setup.rb).
45
45
 
46
46
  If you are not using Rails, or if you are but want more control over things, add this to your
47
47
  application setup immediately after `require 'bundler/setup'` (i.e. as early as possible: the sooner
@@ -161,7 +161,7 @@ The only directories considered "stable" are things under the Ruby install prefi
161
161
  "volatile".
162
162
 
163
163
  In addition to the [`Bootsnap::LoadPathCache::Cache`
164
- source](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/cache.rb),
164
+ source](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/load_path_cache/cache.rb),
165
165
  this diagram may help clarify how entry resolution works:
166
166
 
167
167
  ![How path searching works](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png)
data/exe/bootsnap CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'bootsnap/cli'
4
+ require "bootsnap/cli"
5
5
  exit Bootsnap::CLI.new(ARGV).run
@@ -75,7 +75,7 @@ struct bs_cache_key {
75
75
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
76
76
 
77
77
  /* Effectively a schema version. Bumping invalidates all previous caches */
78
- static const uint32_t current_version = 3;
78
+ static const uint32_t current_version = 4;
79
79
 
80
80
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
81
81
  * new OS ABI, etc. */
@@ -91,8 +91,7 @@ static mode_t current_umask;
91
91
  static VALUE rb_mBootsnap;
92
92
  static VALUE rb_mBootsnap_CompileCache;
93
93
  static VALUE rb_mBootsnap_CompileCache_Native;
94
- static VALUE rb_eBootsnap_CompileCache_Uncompilable;
95
- static ID uncompilable;
94
+ static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
96
95
  static ID instrumentation_method;
97
96
  static VALUE sym_miss;
98
97
  static VALUE sym_stale;
@@ -120,10 +119,8 @@ static uint32_t get_ruby_platform(void);
120
119
  * exception.
121
120
  */
122
121
  static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
123
- static VALUE prot_storage_to_output(VALUE arg);
124
122
  static VALUE prot_input_to_output(VALUE arg);
125
123
  static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
126
- static VALUE prot_input_to_storage(VALUE arg);
127
124
  static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
128
125
  struct s2o_data;
129
126
  struct i2o_data;
@@ -151,12 +148,12 @@ Init_bootsnap(void)
151
148
  rb_mBootsnap = rb_define_module("Bootsnap");
152
149
  rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
153
150
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
154
- rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
151
+ rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
152
+ rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
155
153
 
156
154
  current_ruby_revision = get_ruby_revision();
157
155
  current_ruby_platform = get_ruby_platform();
158
156
 
159
- uncompilable = rb_intern("__bootsnap_uncompilable__");
160
157
  instrumentation_method = rb_intern("_instrument");
161
158
 
162
159
  sym_miss = ID2SYM(rb_intern("miss"));
@@ -426,6 +423,7 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
426
423
  #define ERROR_WITH_ERRNO -1
427
424
  #define CACHE_MISS -2
428
425
  #define CACHE_STALE -3
426
+ #define CACHE_UNCOMPILABLE -4
429
427
 
430
428
  /*
431
429
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -507,14 +505,14 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
507
505
  if (data_size > 100000000000) {
508
506
  *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
509
507
  errno = EINVAL; /* because wtf? */
510
- ret = -1;
508
+ ret = ERROR_WITH_ERRNO;
511
509
  goto done;
512
510
  }
513
511
  data = ALLOC_N(char, data_size);
514
512
  nread = read(fd, data, data_size);
515
513
  if (nread < 0) {
516
514
  *errno_provenance = "bs_fetch:fetch_cached_data:read";
517
- ret = -1;
515
+ ret = ERROR_WITH_ERRNO;
518
516
  goto done;
519
517
  }
520
518
  if (nread != data_size) {
@@ -525,6 +523,10 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
525
523
  storage_data = rb_str_new(data, data_size);
526
524
 
527
525
  *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
526
+ if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
527
+ ret = CACHE_UNCOMPILABLE;
528
+ goto done;
529
+ }
528
530
  ret = 0;
529
531
  done:
530
532
  if (data != NULL) xfree(data);
@@ -737,7 +739,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
737
739
  &output_data, &exception_tag, &errno_provenance
738
740
  );
739
741
  if (exception_tag != 0) goto raise;
740
- else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
742
+ else if (res == CACHE_UNCOMPILABLE) {
743
+ /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
744
+ This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
745
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
746
+ input_data = rb_str_new(contents, current_key.size);
747
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
748
+ if (exception_tag != 0) goto raise;
749
+ goto succeed;
750
+ } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
741
751
  else if (res == ERROR_WITH_ERRNO) goto fail_errno;
742
752
  else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
743
753
  }
@@ -754,7 +764,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
754
764
  if (exception_tag != 0) goto raise;
755
765
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
756
766
  * to cache anything; just return input_to_output(input_data) */
757
- if (storage_data == uncompilable) {
767
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
758
768
  bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
759
769
  if (exception_tag != 0) goto raise;
760
770
  goto succeed;
@@ -772,9 +782,13 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
772
782
  exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
773
783
  if (exception_tag != 0) goto raise;
774
784
 
775
- /* If output_data is nil, delete the cache entry and generate the output
776
- * using input_to_output */
777
- if (NIL_P(output_data)) {
785
+ if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
786
+ /* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
787
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
788
+ if (exception_tag != 0) goto raise;
789
+ } else if (NIL_P(output_data)) {
790
+ /* If output_data is nil, delete the cache entry and generate the output
791
+ * using input_to_output */
778
792
  if (unlink(cache_path) < 0) {
779
793
  errno_provenance = "bs_fetch:unlink";
780
794
  goto fail_errno;
@@ -856,7 +870,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
856
870
 
857
871
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
858
872
  * to cache anything; just return false */
859
- if (storage_data == uncompilable) {
873
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
860
874
  goto fail;
861
875
  }
862
876
  /* If storage_data isn't a string, we can't cache it */
@@ -919,7 +933,7 @@ struct i2s_data {
919
933
  };
920
934
 
921
935
  static VALUE
922
- prot_storage_to_output(VALUE arg)
936
+ try_storage_to_output(VALUE arg)
923
937
  {
924
938
  struct s2o_data * data = (struct s2o_data *)arg;
925
939
  return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
@@ -934,7 +948,7 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
934
948
  .args = args,
935
949
  .storage_data = storage_data,
936
950
  };
937
- *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
951
+ *output_data = rb_protect(try_storage_to_output, (VALUE)&s2o_data, &state);
938
952
  return state;
939
953
  }
940
954
 
@@ -963,22 +977,6 @@ try_input_to_storage(VALUE arg)
963
977
  return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
964
978
  }
965
979
 
966
- static VALUE
967
- rescue_input_to_storage(VALUE arg, VALUE e)
968
- {
969
- return uncompilable;
970
- }
971
-
972
- static VALUE
973
- prot_input_to_storage(VALUE arg)
974
- {
975
- struct i2s_data * data = (struct i2s_data *)arg;
976
- return rb_rescue2(
977
- try_input_to_storage, (VALUE)data,
978
- rescue_input_to_storage, Qnil,
979
- rb_eBootsnap_CompileCache_Uncompilable, 0);
980
- }
981
-
982
980
  static int
983
981
  bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
984
982
  {
@@ -988,6 +986,6 @@ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval,
988
986
  .input_data = input_data,
989
987
  .pathval = pathval,
990
988
  };
991
- *storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state);
989
+ *storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
992
990
  return state;
993
991
  }
@@ -1,21 +1,23 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require("mkmf")
3
4
 
4
- if RUBY_ENGINE == 'ruby'
5
- $CFLAGS << ' -O3 '
6
- $CFLAGS << ' -std=c99'
5
+ if RUBY_ENGINE == "ruby"
6
+ $CFLAGS << " -O3 "
7
+ $CFLAGS << " -std=c99"
7
8
 
8
9
  # ruby.h has some -Wpedantic fails in some cases
9
10
  # (e.g. https://github.com/Shopify/bootsnap/issues/15)
10
- unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
11
- $CFLAGS << ' -Wall'
12
- $CFLAGS << ' -Werror'
13
- $CFLAGS << ' -Wextra'
14
- $CFLAGS << ' -Wpedantic'
11
+ unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
12
+ $CFLAGS << " -Wall"
13
+ $CFLAGS << " -Werror"
14
+ $CFLAGS << " -Wextra"
15
+ $CFLAGS << " -Wpedantic"
15
16
 
16
- $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
17
- $CFLAGS << ' -Wno-keyword-macro' # hiding return
18
- $CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno
17
+ $CFLAGS << " -Wno-unused-parameter" # VALUE self has to be there but we don't care what it is.
18
+ $CFLAGS << " -Wno-keyword-macro" # hiding return
19
+ $CFLAGS << " -Wno-gcc-compat" # ruby.h 2.6.0 on macos 10.14, dunno
20
+ $CFLAGS << " -Wno-compound-token-split-by-macro"
19
21
  end
20
22
 
21
23
  create_makefile("bootsnap/bootsnap")
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bootsnap
3
4
  extend(self)
4
5
 
@@ -63,6 +63,7 @@ module Bootsnap
63
63
  loop do
64
64
  job, *args = Marshal.load(@pipe_out)
65
65
  return if job == :exit
66
+
66
67
  @jobs.fetch(job).call(*args)
67
68
  end
68
69
  rescue IOError
data/lib/bootsnap/cli.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bootsnap'
4
- require 'bootsnap/cli/worker_pool'
5
- require 'optparse'
6
- require 'fileutils'
7
- require 'etc'
3
+ require "bootsnap"
4
+ require "bootsnap/cli/worker_pool"
5
+ require "optparse"
6
+ require "fileutils"
7
+ require "etc"
8
8
 
9
9
  module Bootsnap
10
10
  class CLI
@@ -25,7 +25,7 @@ module Bootsnap
25
25
 
26
26
  def initialize(argv)
27
27
  @argv = argv
28
- self.cache_dir = ENV.fetch('BOOTSNAP_CACHE_DIR', 'tmp/cache')
28
+ self.cache_dir = ENV.fetch("BOOTSNAP_CACHE_DIR", "tmp/cache")
29
29
  self.compile_gemfile = false
30
30
  self.exclude = nil
31
31
  self.verbose = false
@@ -36,16 +36,16 @@ module Bootsnap
36
36
  end
37
37
 
38
38
  def precompile_command(*sources)
39
- require 'bootsnap/compile_cache/iseq'
40
- require 'bootsnap/compile_cache/yaml'
41
- require 'bootsnap/compile_cache/json'
39
+ require "bootsnap/compile_cache/iseq"
40
+ require "bootsnap/compile_cache/yaml"
41
+ require "bootsnap/compile_cache/json"
42
42
 
43
43
  fix_default_encoding do
44
- Bootsnap::CompileCache::ISeq.cache_dir = self.cache_dir
44
+ Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
45
45
  Bootsnap::CompileCache::YAML.init!
46
- Bootsnap::CompileCache::YAML.cache_dir = self.cache_dir
46
+ Bootsnap::CompileCache::YAML.cache_dir = cache_dir
47
47
  Bootsnap::CompileCache::JSON.init!
48
- Bootsnap::CompileCache::JSON.cache_dir = self.cache_dir
48
+ Bootsnap::CompileCache::JSON.cache_dir = cache_dir
49
49
 
50
50
  @work_pool = WorkerPool.create(size: jobs, jobs: {
51
51
  ruby: method(:precompile_ruby),
@@ -61,7 +61,7 @@ module Bootsnap
61
61
 
62
62
  if compile_gemfile
63
63
  # Some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
64
- gem_exclude = Regexp.union([exclude, '/spec/', '/test/'].compact)
64
+ gem_exclude = Regexp.union([exclude, "/spec/", "/test/"].compact)
65
65
  precompile_ruby_files($LOAD_PATH.map { |d| File.expand_path(d) }, exclude: gem_exclude)
66
66
 
67
67
  # Gems that include JSON or YAML files usually don't put them in `lib/`.
@@ -72,7 +72,7 @@ module Bootsnap
72
72
  precompile_json_files(gem_paths, exclude: gem_exclude)
73
73
  end
74
74
 
75
- if exitstatus = @work_pool.shutdown
75
+ if (exitstatus = @work_pool.shutdown)
76
76
  exit(exitstatus)
77
77
  end
78
78
  end
@@ -89,7 +89,7 @@ module Bootsnap
89
89
  if dir_sort
90
90
  def list_files(path, pattern)
91
91
  if File.directory?(path)
92
- Dir[File.join(path, pattern), sort: false]
92
+ Dir[File.join(path, pattern), sort: false]
93
93
  elsif File.exist?(path)
94
94
  [path]
95
95
  else
@@ -99,7 +99,7 @@ module Bootsnap
99
99
  else
100
100
  def list_files(path, pattern)
101
101
  if File.directory?(path)
102
- Dir[File.join(path, pattern)]
102
+ Dir[File.join(path, pattern)]
103
103
  elsif File.exist?(path)
104
104
  [path]
105
105
  else
@@ -126,9 +126,9 @@ module Bootsnap
126
126
 
127
127
  load_paths.each do |path|
128
128
  if !exclude || !exclude.match?(path)
129
- list_files(path, '**/*.{yml,yaml}').each do |yaml_file|
129
+ list_files(path, "**/*.{yml,yaml}").each do |yaml_file|
130
130
  # We ignore hidden files to not match the various .ci.yml files
131
- if !File.basename(yaml_file).start_with?('.') && (!exclude || !exclude.match?(yaml_file))
131
+ if !File.basename(yaml_file).start_with?(".") && (!exclude || !exclude.match?(yaml_file))
132
132
  @work_pool.push(:yaml, yaml_file)
133
133
  end
134
134
  end
@@ -138,7 +138,7 @@ module Bootsnap
138
138
 
139
139
  def precompile_yaml(*yaml_files)
140
140
  Array(yaml_files).each do |yaml_file|
141
- if CompileCache::YAML.precompile(yaml_file, cache_dir: cache_dir)
141
+ if CompileCache::YAML.precompile(yaml_file)
142
142
  STDERR.puts(yaml_file) if verbose
143
143
  end
144
144
  end
@@ -149,9 +149,9 @@ module Bootsnap
149
149
 
150
150
  load_paths.each do |path|
151
151
  if !exclude || !exclude.match?(path)
152
- list_files(path, '**/*.json').each do |json_file|
152
+ list_files(path, "**/*.json").each do |json_file|
153
153
  # We ignore hidden files to not match the various .config.json files
154
- if !File.basename(json_file).start_with?('.') && (!exclude || !exclude.match?(json_file))
154
+ if !File.basename(json_file).start_with?(".") && (!exclude || !exclude.match?(json_file))
155
155
  @work_pool.push(:json, json_file)
156
156
  end
157
157
  end
@@ -161,7 +161,7 @@ module Bootsnap
161
161
 
162
162
  def precompile_json(*json_files)
163
163
  Array(json_files).each do |json_file|
164
- if CompileCache::JSON.precompile(json_file, cache_dir: cache_dir)
164
+ if CompileCache::JSON.precompile(json_file)
165
165
  STDERR.puts(json_file) if verbose
166
166
  end
167
167
  end
@@ -172,7 +172,7 @@ module Bootsnap
172
172
 
173
173
  load_paths.each do |path|
174
174
  if !exclude || !exclude.match?(path)
175
- list_files(path, '**/*.rb').each do |ruby_file|
175
+ list_files(path, "**/*.rb").each do |ruby_file|
176
176
  if !exclude || !exclude.match?(ruby_file)
177
177
  @work_pool.push(:ruby, ruby_file)
178
178
  end
@@ -183,7 +183,7 @@ module Bootsnap
183
183
 
184
184
  def precompile_ruby(*ruby_files)
185
185
  Array(ruby_files).each do |ruby_file|
186
- if CompileCache::ISeq.precompile(ruby_file, cache_dir: cache_dir)
186
+ if CompileCache::ISeq.precompile(ruby_file)
187
187
  STDERR.puts(ruby_file) if verbose
188
188
  end
189
189
  end
@@ -210,7 +210,7 @@ module Bootsnap
210
210
  end
211
211
 
212
212
  def cache_dir=(dir)
213
- @cache_dir = File.expand_path(File.join(dir, 'bootsnap/compile-cache'))
213
+ @cache_dir = File.expand_path(File.join(dir, "bootsnap/compile-cache"))
214
214
  end
215
215
 
216
216
  def exclude_pattern(pattern)
@@ -225,24 +225,24 @@ module Bootsnap
225
225
  opts.separator "GLOBAL OPTIONS"
226
226
  opts.separator ""
227
227
 
228
- help = <<~EOS
228
+ help = <<~HELP
229
229
  Path to the bootsnap cache directory. Defaults to tmp/cache
230
- EOS
231
- opts.on('--cache-dir DIR', help.strip) do |dir|
230
+ HELP
231
+ opts.on("--cache-dir DIR", help.strip) do |dir|
232
232
  self.cache_dir = dir
233
233
  end
234
234
 
235
- help = <<~EOS
235
+ help = <<~HELP
236
236
  Print precompiled paths.
237
- EOS
238
- opts.on('--verbose', '-v', help.strip) do
237
+ HELP
238
+ opts.on("--verbose", "-v", help.strip) do
239
239
  self.verbose = true
240
240
  end
241
241
 
242
- help = <<~EOS
242
+ help = <<~HELP
243
243
  Number of workers to use. Default to number of processors, set to 0 to disable multi-processing.
244
- EOS
245
- opts.on('--jobs JOBS', '-j', help.strip) do |jobs|
244
+ HELP
245
+ opts.on("--jobs JOBS", "-j", help.strip) do |jobs|
246
246
  self.jobs = Integer(jobs)
247
247
  end
248
248
 
@@ -251,30 +251,30 @@ module Bootsnap
251
251
  opts.separator ""
252
252
  opts.separator " precompile [DIRECTORIES...]: Precompile all .rb files in the passed directories"
253
253
 
254
- help = <<~EOS
254
+ help = <<~HELP
255
255
  Precompile the gems in Gemfile
256
- EOS
257
- opts.on('--gemfile', help) { self.compile_gemfile = true }
256
+ HELP
257
+ opts.on("--gemfile", help) { self.compile_gemfile = true }
258
258
 
259
- help = <<~EOS
259
+ help = <<~HELP
260
260
  Path pattern to not precompile. e.g. --exclude 'aws-sdk|google-api'
261
- EOS
262
- opts.on('--exclude PATTERN', help) { |pattern| exclude_pattern(pattern) }
261
+ HELP
262
+ opts.on("--exclude PATTERN", help) { |pattern| exclude_pattern(pattern) }
263
263
 
264
- help = <<~EOS
264
+ help = <<~HELP
265
265
  Disable ISeq (.rb) precompilation.
266
- EOS
267
- opts.on('--no-iseq', help) { self.iseq = false }
266
+ HELP
267
+ opts.on("--no-iseq", help) { self.iseq = false }
268
268
 
269
- help = <<~EOS
269
+ help = <<~HELP
270
270
  Disable YAML precompilation.
271
- EOS
272
- opts.on('--no-yaml', help) { self.yaml = false }
271
+ HELP
272
+ opts.on("--no-yaml", help) { self.yaml = false }
273
273
 
274
- help = <<~EOS
274
+ help = <<~HELP
275
275
  Disable JSON precompilation.
276
- EOS
277
- opts.on('--no-json', help) { self.json = false }
276
+ HELP
277
+ opts.on("--no-json", help) { self.json = false }
278
278
  end
279
279
  end
280
280
  end
@@ -1,24 +1,54 @@
1
1
  # frozen_string_literal: true
2
- require('bootsnap/bootsnap')
3
- require('zlib')
2
+
3
+ require("bootsnap/bootsnap")
4
+ require("zlib")
4
5
 
5
6
  module Bootsnap
6
7
  module CompileCache
7
8
  module ISeq
8
9
  class << self
9
- attr_accessor(:cache_dir)
10
+ attr_reader(:cache_dir)
11
+
12
+ def cache_dir=(cache_dir)
13
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq"
14
+ end
15
+ end
16
+
17
+ has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
18
+ if defined? RubyVM::InstructionSequence
19
+ RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary
20
+ end
21
+ false
22
+ rescue TypeError
23
+ true
10
24
  end
11
25
 
12
- def self.input_to_storage(_, path)
13
- RubyVM::InstructionSequence.compile_file(path).to_binary
14
- rescue SyntaxError
15
- raise(Uncompilable, 'syntax error')
26
+ if has_ruby_bug_18250
27
+ def self.input_to_storage(_, path)
28
+ iseq = begin
29
+ RubyVM::InstructionSequence.compile_file(path)
30
+ rescue SyntaxError
31
+ return UNCOMPILABLE # syntax error
32
+ end
33
+
34
+ begin
35
+ iseq.to_binary
36
+ rescue TypeError
37
+ return UNCOMPILABLE # ruby bug #18250
38
+ end
39
+ end
40
+ else
41
+ def self.input_to_storage(_, path)
42
+ RubyVM::InstructionSequence.compile_file(path).to_binary
43
+ rescue SyntaxError
44
+ return UNCOMPILABLE # syntax error
45
+ end
16
46
  end
17
47
 
18
48
  def self.storage_to_output(binary, _args)
19
49
  RubyVM::InstructionSequence.load_from_binary(binary)
20
- rescue RuntimeError => e
21
- if e.message == 'broken binary format'
50
+ rescue RuntimeError => error
51
+ if error.message == "broken binary format"
22
52
  STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
23
53
  nil
24
54
  else
@@ -35,7 +65,7 @@ module Bootsnap
35
65
  )
36
66
  end
37
67
 
38
- def self.precompile(path, cache_dir: ISeq.cache_dir)
68
+ def self.precompile(path)
39
69
  Bootsnap::CompileCache::Native.precompile(
40
70
  cache_dir,
41
71
  path.to_s,
@@ -55,8 +85,8 @@ module Bootsnap
55
85
  Bootsnap::CompileCache::ISeq.fetch(path.to_s)
56
86
  rescue Errno::EACCES
57
87
  Bootsnap::CompileCache.permission_error(path)
58
- rescue RuntimeError => e
59
- if e.message =~ /unmatched platform/
88
+ rescue RuntimeError => error
89
+ if error.message =~ /unmatched platform/
60
90
  puts("unmatched platform for file #{path}")
61
91
  end
62
92
  raise