bootsnap 1.4.5 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/README.md +17 -3
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +183 -65
  6. data/ext/bootsnap/extconf.rb +1 -0
  7. data/lib/bootsnap/bundler.rb +1 -0
  8. data/lib/bootsnap/cli/worker_pool.rb +131 -0
  9. data/lib/bootsnap/cli.rb +246 -0
  10. data/lib/bootsnap/compile_cache/iseq.rb +22 -7
  11. data/lib/bootsnap/compile_cache/yaml.rb +89 -39
  12. data/lib/bootsnap/compile_cache.rb +3 -2
  13. data/lib/bootsnap/explicit_require.rb +1 -0
  14. data/lib/bootsnap/load_path_cache/cache.rb +8 -8
  15. data/lib/bootsnap/load_path_cache/change_observer.rb +2 -1
  16. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +1 -0
  17. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -5
  18. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
  19. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +33 -10
  20. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  21. data/lib/bootsnap/load_path_cache/path_scanner.rb +39 -26
  22. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  23. data/lib/bootsnap/load_path_cache/store.rb +6 -5
  24. data/lib/bootsnap/load_path_cache.rb +1 -1
  25. data/lib/bootsnap/setup.rb +1 -0
  26. data/lib/bootsnap/version.rb +2 -1
  27. data/lib/bootsnap.rb +4 -2
  28. metadata +15 -28
  29. data/.github/CODEOWNERS +0 -2
  30. data/.github/probots.yml +0 -2
  31. data/.gitignore +0 -17
  32. data/.rubocop.yml +0 -20
  33. data/.travis.yml +0 -21
  34. data/CODE_OF_CONDUCT.md +0 -74
  35. data/CONTRIBUTING.md +0 -21
  36. data/Gemfile +0 -8
  37. data/README.jp.md +0 -231
  38. data/Rakefile +0 -12
  39. data/bin/ci +0 -10
  40. data/bin/console +0 -14
  41. data/bin/setup +0 -8
  42. data/bin/test-minimal-support +0 -7
  43. data/bin/testunit +0 -8
  44. data/bootsnap.gemspec +0 -45
  45. data/dev.yml +0 -10
  46. data/shipit.rubygems.yml +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be54a60d54f32f824d5c2fdccf189cdcb5409d1848c55f6e654fe44ace1d3f21
4
- data.tar.gz: c6239c30984a936b76e218c2b1292a7e72d817d3e4847a6c58f0121c1e3068dc
3
+ metadata.gz: '029d63ba428f470d2cd2ef194d857e20689e7c8e377e2eaaaab83570f366034a'
4
+ data.tar.gz: 36a55f9d12dddef6ea3c4739f586c9236d7f4bc677a8b6747dfd7465d46eeca2
5
5
  SHA512:
6
- metadata.gz: c251990457406cd51726eec8d62e0e1028bbeade6e24f266cb743d6f00f53650841beed9ee8e1795c78febd18ea234691125920c16aa5a95031718a28619bb31
7
- data.tar.gz: 554a885bf2264f088618c41864fb84069d0664b35af0ae4b619c8fbb1cf285c80fad564d36845161f8044c35c6b53a33e66f27eb75c6715f25869af2795c97f6
6
+ metadata.gz: 131ec17c4e4912f387c18778250e689467ebfcc80e91eb884237307af1a57a3c89d4fb20c772fbc0330123a796d631861a9cf145bd32c54183077bbc97d7fa6f
7
+ data.tar.gz: d0c92454c8a5d8b16a25908fd0ae134fc817c5977958bde8424baca2bb6c96e60eb648c9f770bf7c7f58a3dd98871d4e7b0e3a0950c269100fded45ca9fddfa3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,49 @@
1
+ # Unreleased
2
+
3
+ # 1.6.0
4
+
5
+ * Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
6
+ * `bootsnap precompile` CLI use multiple processes to complete faster. (#341)
7
+ * `bootsnap precompile` CLI also precompile YAML files. (#340)
8
+ * Changed the load path cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-load-path-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/load-path-cache` for ease of use. (#334)
9
+ * Changed the compile cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-compile-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/compile-cache` for ease of use. (#334)
10
+
11
+ # 1.5.1
12
+
13
+ * Workaround a Ruby bug in InstructionSequence.compile_file. (#332)
14
+
15
+ # 1.5.0
16
+
17
+ * Add a command line to statically precompile the ISeq cache. (#326)
18
+
19
+ # 1.4.9
20
+
21
+ * [Windows support](https://github.com/Shopify/bootsnap/pull/319)
22
+ * [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
23
+
24
+ # 1.4.8
25
+
26
+ * [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
27
+
28
+ # 1.4.7
29
+
30
+ * Various performance enhancements
31
+ * Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
32
+
33
+ # 1.4.6
34
+
35
+ * Fix bug that was erroneously considering that files containing `.` in the names were being
36
+ required if a different file with the same name was already being required
37
+
38
+ Example:
39
+
40
+ require 'foo'
41
+ require 'foo.en'
42
+
43
+ Before bootsnap was considering `foo.en` to be the same file as `foo`
44
+
45
+ * Use glibc as part of the ruby_platform cache key
46
+
1
47
  # 1.4.5
2
48
 
3
49
  * MRI 2.7 support
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Bootsnap [![Build Status](https://travis-ci.org/Shopify/bootsnap.svg?branch=master)](https://travis-ci.org/Shopify/bootsnap)
1
+ # Bootsnap [![Actions Status](https://github.com/Shopify/bootsnap/workflows/ci/badge.svg)](https://github.com/Shopify/bootsnap/actions)
2
2
 
3
3
  Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSupport` and `YAML`,
4
4
  to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
@@ -29,7 +29,8 @@ If you are using Rails, add this to `config/boot.rb` immediately after `require
29
29
  require 'bootsnap/setup'
30
30
  ```
31
31
 
32
- Note that bootsnap writes to `tmp/cache`, and that directory *must* be writable. Rails will fail to
32
+ Note that bootsnap writes to `tmp/cache` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
33
+ and that directory *must* be writable. Rails will fail to
33
34
  boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
34
35
  unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
35
36
 
@@ -214,7 +215,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
214
215
  is a cache key including several fields:
215
216
 
216
217
  * `version`, hardcoded in bootsnap. Essentially a schema version;
217
- * `os_version`, A hash of the current kernel version (on macOS, BSD) or glibc version (on Linux);
218
+ * `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
219
  * `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
219
220
  * `ruby_revision`, the version of Ruby this was compiled with;
220
221
  * `size`, the size of the source file;
@@ -294,6 +295,19 @@ open /c/nope.bundle -> -1
294
295
  # (nothing!)
295
296
  ```
296
297
 
298
+ ## Precompilation
299
+
300
+ In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
301
+ But in production environments, such as docker images, you might need to precompile the cache.
302
+
303
+ To do so you can use the `bootsnap precompile` command.
304
+
305
+ Example:
306
+
307
+ ```bash
308
+ $ bundle exec bootsnap precompile --gemfile app/ lib/
309
+ ```
310
+
297
311
  ## When not to use Bootsnap
298
312
 
299
313
  *Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
data/exe/bootsnap ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bootsnap/cli'
5
+ exit Bootsnap::CLI.new(ARGV).run
@@ -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
@@ -65,7 +70,7 @@ struct bs_cache_key {
65
70
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
66
71
 
67
72
  /* Effectively a schema version. Bumping invalidates all previous caches */
68
- static const uint32_t current_version = 2;
73
+ static const uint32_t current_version = 3;
69
74
 
70
75
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
71
76
  * new OS ABI, etc. */
@@ -86,16 +91,18 @@ static ID uncompilable;
86
91
 
87
92
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
88
93
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
89
- static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
94
+ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
95
+ static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
90
96
 
91
97
  /* Helpers */
92
98
  static uint64_t fnv1a_64(const char *str);
93
- static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path);
99
+ static void bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
94
100
  static int bs_read_key(int fd, struct bs_cache_key * key);
95
101
  static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
96
- static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
97
- static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
98
- static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
102
+ static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
103
+ static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
104
+ static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
105
+ static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
99
106
  static uint32_t get_ruby_revision(void);
100
107
  static uint32_t get_ruby_platform(void);
101
108
 
@@ -103,12 +110,12 @@ static uint32_t get_ruby_platform(void);
103
110
  * Helper functions to call ruby methods on handler object without crashing on
104
111
  * exception.
105
112
  */
106
- static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
113
+ static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
107
114
  static VALUE prot_storage_to_output(VALUE arg);
108
115
  static VALUE prot_input_to_output(VALUE arg);
109
- static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
116
+ static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
110
117
  static VALUE prot_input_to_storage(VALUE arg);
111
- static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
118
+ static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
112
119
  struct s2o_data;
113
120
  struct i2o_data;
114
121
  struct i2s_data;
@@ -143,7 +150,8 @@ Init_bootsnap(void)
143
150
  uncompilable = rb_intern("__bootsnap_uncompilable__");
144
151
 
145
152
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
146
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3);
153
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
154
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
147
155
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
148
156
 
149
157
  current_umask = umask(0777);
@@ -236,6 +244,9 @@ get_ruby_platform(void)
236
244
 
237
245
  #ifdef _WIN32
238
246
  return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
247
+ #elif defined(__GLIBC__)
248
+ hash = fnv1a_64_iter(hash, gnu_get_libc_version());
249
+ return (uint32_t)(hash >> 32);
239
250
  #else
240
251
  struct utsname utsname;
241
252
 
@@ -256,10 +267,9 @@ get_ruby_platform(void)
256
267
  * The path will look something like: <cachedir>/12/34567890abcdef
257
268
  */
258
269
  static void
259
- bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
270
+ bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE])
260
271
  {
261
272
  uint64_t hash = fnv1a_64(path);
262
-
263
273
  uint8_t first_byte = (hash >> (64 - 8));
264
274
  uint64_t remainder = hash & 0x00ffffffffffffff;
265
275
 
@@ -293,7 +303,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
293
303
  * conversions on the ruby VALUE arguments before passing them along.
294
304
  */
295
305
  static VALUE
296
- bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
306
+ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
297
307
  {
298
308
  FilePathValue(path_v);
299
309
 
@@ -308,27 +318,51 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
308
318
  char * path = RSTRING_PTR(path_v);
309
319
  char cache_path[MAX_CACHEPATH_SIZE];
310
320
 
311
- { /* generate cache path to cache_path */
312
- char * tmp = (char *)&cache_path;
313
- bs_cache_path(cachedir, path, &tmp);
314
- }
321
+ /* generate cache path to cache_path */
322
+ bs_cache_path(cachedir, path, &cache_path);
315
323
 
316
- return bs_fetch(path, path_v, cache_path, handler);
324
+ return bs_fetch(path, path_v, cache_path, handler, args);
317
325
  }
318
326
 
327
+ /*
328
+ * Entrypoint for Bootsnap::CompileCache::Native.precompile.
329
+ * Similar to fetch, but it only generate the cache if missing
330
+ * and doesn't return the content.
331
+ */
332
+ static VALUE
333
+ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
334
+ {
335
+ FilePathValue(path_v);
336
+
337
+ Check_Type(cachedir_v, T_STRING);
338
+ Check_Type(path_v, T_STRING);
339
+
340
+ if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
341
+ rb_raise(rb_eArgError, "cachedir too long");
342
+ }
343
+
344
+ char * cachedir = RSTRING_PTR(cachedir_v);
345
+ char * path = RSTRING_PTR(path_v);
346
+ char cache_path[MAX_CACHEPATH_SIZE];
347
+
348
+ /* generate cache path to cache_path */
349
+ bs_cache_path(cachedir, path, &cache_path);
350
+
351
+ return bs_precompile(path, path_v, cache_path, handler);
352
+ }
319
353
  /*
320
354
  * Open the file we want to load/cache and generate a cache key for it if it
321
355
  * was loaded.
322
356
  */
323
357
  static int
324
- open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
358
+ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
325
359
  {
326
360
  struct stat statbuf;
327
361
  int fd;
328
362
 
329
363
  fd = open(path, O_RDONLY);
330
364
  if (fd < 0) {
331
- *errno_provenance = (char *)"bs_fetch:open_current_file:open";
365
+ *errno_provenance = "bs_fetch:open_current_file:open";
332
366
  return fd;
333
367
  }
334
368
  #ifdef _WIN32
@@ -336,7 +370,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
336
370
  #endif
337
371
 
338
372
  if (fstat(fd, &statbuf) < 0) {
339
- *errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
373
+ *errno_provenance = "bs_fetch:open_current_file:fstat";
340
374
  close(fd);
341
375
  return -1;
342
376
  }
@@ -382,13 +416,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
382
416
  * - ERROR_WITH_ERRNO (-1, errno is set)
383
417
  */
384
418
  static int
385
- open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
419
+ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
386
420
  {
387
421
  int fd, res;
388
422
 
389
423
  fd = open(path, O_RDONLY);
390
424
  if (fd < 0) {
391
- *errno_provenance = (char *)"bs_fetch:open_cache_file:open";
425
+ *errno_provenance = "bs_fetch:open_cache_file:open";
392
426
  if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
393
427
  return ERROR_WITH_ERRNO;
394
428
  }
@@ -398,7 +432,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
398
432
 
399
433
  res = bs_read_key(fd, key);
400
434
  if (res < 0) {
401
- *errno_provenance = (char *)"bs_fetch:open_cache_file:read";
435
+ *errno_provenance = "bs_fetch:open_cache_file:read";
402
436
  close(fd);
403
437
  return res;
404
438
  }
@@ -422,7 +456,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
422
456
  * or exception, will be the final data returnable to the user.
423
457
  */
424
458
  static int
425
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
459
+ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
426
460
  {
427
461
  char * data = NULL;
428
462
  ssize_t nread;
@@ -431,7 +465,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
431
465
  VALUE storage_data;
432
466
 
433
467
  if (data_size > 100000000000) {
434
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
468
+ *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
435
469
  errno = EINVAL; /* because wtf? */
436
470
  ret = -1;
437
471
  goto done;
@@ -439,7 +473,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
439
473
  data = ALLOC_N(char, data_size);
440
474
  nread = read(fd, data, data_size);
441
475
  if (nread < 0) {
442
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
476
+ *errno_provenance = "bs_fetch:fetch_cached_data:read";
443
477
  ret = -1;
444
478
  goto done;
445
479
  }
@@ -448,9 +482,9 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
448
482
  goto done;
449
483
  }
450
484
 
451
- storage_data = rb_str_new_static(data, data_size);
485
+ storage_data = rb_str_new(data, data_size);
452
486
 
453
- *exception_tag = bs_storage_to_output(handler, storage_data, output_data);
487
+ *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
454
488
  ret = 0;
455
489
  done:
456
490
  if (data != NULL) xfree(data);
@@ -491,29 +525,36 @@ mkpath(char * file_path, mode_t mode)
491
525
  * path.
492
526
  */
493
527
  static int
494
- atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
528
+ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
495
529
  {
496
530
  char template[MAX_CACHEPATH_SIZE + 20];
497
531
  char * tmp_path;
498
- int fd, ret;
532
+ int fd, ret, attempt;
499
533
  ssize_t nwrite;
500
534
 
501
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
502
- strcat(tmp_path, ".tmp.XXXXXX");
535
+ for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
536
+ tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
537
+ strcat(tmp_path, ".tmp.XXXXXX");
503
538
 
504
- // mkstemp modifies the template to be the actual created path
505
- fd = mkstemp(tmp_path);
506
- if (fd < 0) {
507
- if (mkpath(tmp_path, 0775) < 0) {
508
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
509
- return -1;
510
- }
511
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
512
- if (fd < 0) {
513
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
539
+ // mkstemp modifies the template to be the actual created path
540
+ fd = mkstemp(tmp_path);
541
+ if (fd > 0) break;
542
+
543
+ if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
544
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
514
545
  return -1;
515
546
  }
516
547
  }
548
+ if (fd < 0) {
549
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
550
+ return -1;
551
+ }
552
+
553
+ if (chmod(tmp_path, 0644) < 0) {
554
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
555
+ return -1;
556
+ }
557
+
517
558
  #ifdef _WIN32
518
559
  setmode(fd, O_BINARY);
519
560
  #endif
@@ -521,11 +562,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
521
562
  key->data_size = RSTRING_LEN(data);
522
563
  nwrite = write(fd, key, KEY_SIZE);
523
564
  if (nwrite < 0) {
524
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
565
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
525
566
  return -1;
526
567
  }
527
568
  if (nwrite != KEY_SIZE) {
528
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
569
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
529
570
  errno = EIO; /* Lies but whatever */
530
571
  return -1;
531
572
  }
@@ -533,7 +574,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
533
574
  nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
534
575
  if (nwrite < 0) return -1;
535
576
  if (nwrite != RSTRING_LEN(data)) {
536
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
577
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
537
578
  errno = EIO; /* Lies but whatever */
538
579
  return -1;
539
580
  }
@@ -541,12 +582,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
541
582
  close(fd);
542
583
  ret = rename(tmp_path, path);
543
584
  if (ret < 0) {
544
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
585
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
545
586
  return -1;
546
587
  }
547
588
  ret = chmod(path, 0664 & ~current_umask);
548
589
  if (ret < 0) {
549
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:chmod";
590
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
550
591
  }
551
592
  return ret;
552
593
  }
@@ -555,13 +596,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
555
596
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
556
597
  * long, into a buffer */
557
598
  static ssize_t
558
- bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
599
+ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
559
600
  {
560
601
  ssize_t nread;
561
602
  *contents = ALLOC_N(char, size);
562
603
  nread = read(fd, *contents, size);
563
604
  if (nread < 0) {
564
- *errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
605
+ *errno_provenance = "bs_fetch:bs_read_contents:read";
565
606
  }
566
607
  return nread;
567
608
  }
@@ -611,13 +652,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
611
652
  * - Return storage_to_output(storage_data)
612
653
  */
613
654
  static VALUE
614
- bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
655
+ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
615
656
  {
616
657
  struct bs_cache_key cached_key, current_key;
617
658
  char * contents = NULL;
618
659
  int cache_fd = -1, current_fd = -1;
619
660
  int res, valid_cache = 0, exception_tag = 0;
620
- char * errno_provenance = NULL;
661
+ const char * errno_provenance = NULL;
621
662
 
622
663
  VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
623
664
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
@@ -644,7 +685,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
644
685
  if (valid_cache) {
645
686
  /* Fetch the cache data and return it if we're able to load it successfully */
646
687
  res = fetch_cached_data(
647
- cache_fd, (ssize_t)cached_key.data_size, handler,
688
+ cache_fd, (ssize_t)cached_key.data_size, handler, args,
648
689
  &output_data, &exception_tag, &errno_provenance
649
690
  );
650
691
  if (exception_tag != 0) goto raise;
@@ -658,15 +699,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
658
699
 
659
700
  /* Read the contents of the source file into a buffer */
660
701
  if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
661
- input_data = rb_str_new_static(contents, current_key.size);
702
+ input_data = rb_str_new(contents, current_key.size);
662
703
 
663
704
  /* Try to compile the input_data using input_to_storage(input_data) */
664
- exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
705
+ exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
665
706
  if (exception_tag != 0) goto raise;
666
707
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
667
708
  * to cache anything; just return input_to_output(input_data) */
668
709
  if (storage_data == uncompilable) {
669
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
710
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
670
711
  if (exception_tag != 0) goto raise;
671
712
  goto succeed;
672
713
  }
@@ -678,17 +719,17 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
678
719
  if (res < 0) goto fail_errno;
679
720
 
680
721
  /* Having written the cache, now convert storage_data to output_data */
681
- exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
722
+ exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
682
723
  if (exception_tag != 0) goto raise;
683
724
 
684
725
  /* If output_data is nil, delete the cache entry and generate the output
685
726
  * using input_to_output */
686
727
  if (NIL_P(output_data)) {
687
728
  if (unlink(cache_path) < 0) {
688
- errno_provenance = (char *)"bs_fetch:unlink";
729
+ errno_provenance = "bs_fetch:unlink";
689
730
  goto fail_errno;
690
731
  }
691
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
732
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
692
733
  if (exception_tag != 0) goto raise;
693
734
  }
694
735
 
@@ -719,6 +760,79 @@ invalid_type_storage_data:
719
760
  #undef CLEANUP
720
761
  }
721
762
 
763
+ static VALUE
764
+ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
765
+ {
766
+ struct bs_cache_key cached_key, current_key;
767
+ char * contents = NULL;
768
+ int cache_fd = -1, current_fd = -1;
769
+ int res, valid_cache = 0, exception_tag = 0;
770
+ const char * errno_provenance = NULL;
771
+
772
+ VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
773
+ VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
774
+
775
+ /* Open the source file and generate a cache key for it */
776
+ current_fd = open_current_file(path, &current_key, &errno_provenance);
777
+ if (current_fd < 0) goto fail;
778
+
779
+ /* Open the cache key if it exists, and read its cache key in */
780
+ cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
781
+ if (cache_fd == CACHE_MISSING_OR_INVALID) {
782
+ /* This is ok: valid_cache remains false, we re-populate it. */
783
+ } else if (cache_fd < 0) {
784
+ goto fail;
785
+ } else {
786
+ /* True if the cache existed and no invalidating changes have occurred since
787
+ * it was generated. */
788
+ valid_cache = cache_key_equal(&current_key, &cached_key);
789
+ }
790
+
791
+ if (valid_cache) {
792
+ goto succeed;
793
+ }
794
+
795
+ close(cache_fd);
796
+ cache_fd = -1;
797
+ /* Cache is stale, invalid, or missing. Regenerate and write it out. */
798
+
799
+ /* Read the contents of the source file into a buffer */
800
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
801
+ input_data = rb_str_new(contents, current_key.size);
802
+
803
+ /* Try to compile the input_data using input_to_storage(input_data) */
804
+ exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
805
+ if (exception_tag != 0) goto fail;
806
+
807
+ /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
808
+ * to cache anything; just return false */
809
+ if (storage_data == uncompilable) {
810
+ goto fail;
811
+ }
812
+ /* If storage_data isn't a string, we can't cache it */
813
+ if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
814
+
815
+ /* Write the cache key and storage_data to the cache directory */
816
+ res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
817
+ if (res < 0) goto fail;
818
+
819
+ goto succeed;
820
+
821
+ #define CLEANUP \
822
+ if (contents != NULL) xfree(contents); \
823
+ if (current_fd >= 0) close(current_fd); \
824
+ if (cache_fd >= 0) close(cache_fd);
825
+
826
+ succeed:
827
+ CLEANUP;
828
+ return Qtrue;
829
+ fail:
830
+ CLEANUP;
831
+ return Qfalse;
832
+ #undef CLEANUP
833
+ }
834
+
835
+
722
836
  /*****************************************************************************/
723
837
  /********************* Handler Wrappers **************************************/
724
838
  /*****************************************************************************
@@ -738,11 +852,13 @@ invalid_type_storage_data:
738
852
 
739
853
  struct s2o_data {
740
854
  VALUE handler;
855
+ VALUE args;
741
856
  VALUE storage_data;
742
857
  };
743
858
 
744
859
  struct i2o_data {
745
860
  VALUE handler;
861
+ VALUE args;
746
862
  VALUE input_data;
747
863
  };
748
864
 
@@ -756,15 +872,16 @@ static VALUE
756
872
  prot_storage_to_output(VALUE arg)
757
873
  {
758
874
  struct s2o_data * data = (struct s2o_data *)arg;
759
- return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
875
+ return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
760
876
  }
761
877
 
762
878
  static int
763
- bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
879
+ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
764
880
  {
765
881
  int state;
766
882
  struct s2o_data s2o_data = {
767
883
  .handler = handler,
884
+ .args = args,
768
885
  .storage_data = storage_data,
769
886
  };
770
887
  *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
@@ -772,10 +889,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
772
889
  }
773
890
 
774
891
  static void
775
- bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
892
+ bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
776
893
  {
777
894
  struct i2o_data i2o_data = {
778
895
  .handler = handler,
896
+ .args = args,
779
897
  .input_data = input_data,
780
898
  };
781
899
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
@@ -785,7 +903,7 @@ static VALUE
785
903
  prot_input_to_output(VALUE arg)
786
904
  {
787
905
  struct i2o_data * data = (struct i2o_data *)arg;
788
- return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data);
906
+ return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
789
907
  }
790
908
 
791
909
  static VALUE
@@ -796,7 +914,7 @@ try_input_to_storage(VALUE arg)
796
914
  }
797
915
 
798
916
  static VALUE
799
- rescue_input_to_storage(VALUE arg)
917
+ rescue_input_to_storage(VALUE arg, VALUE e)
800
918
  {
801
919
  return uncompilable;
802
920
  }
@@ -812,7 +930,7 @@ prot_input_to_storage(VALUE arg)
812
930
  }
813
931
 
814
932
  static int
815
- bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
933
+ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
816
934
  {
817
935
  int state;
818
936
  struct i2s_data i2s_data = {
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require("mkmf")
2
3
  $CFLAGS << ' -O3 '
3
4
  $CFLAGS << ' -std=c99'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  extend(self)
3
4