bootsnap 1.10.3 → 1.11.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: 3dfd9cfb2cde26fc31c82206b73f3940b4b46903a3fa73d0f3f1a4c4a90c6984
4
- data.tar.gz: 6dd8acfb2676a2585eb320ed50ba0ddb9f9c847fb415bcda824c7ebff0cce752
3
+ metadata.gz: af127c58fb31e53309de406862116407d9f57d43514937d400a0f87c13080447
4
+ data.tar.gz: 497f3a471eed46b68e624f8cda87bc6d5e97d5a17bdf4cbeb2221019fb21d531
5
5
  SHA512:
6
- metadata.gz: 219a84be718e4ac5681621739b6f9400d77a16dee631bdc286adad87c0ddf99cc581da7281150add34075c08c59832df2a468524656d2f1ac36ac1613be051bb
7
- data.tar.gz: 0e407a034fd081c9e506637e0997d0c2cc0eea312be798f6fd0d573173699f906f1b43a1f3d37ff64ee6efb2f9257228c6c4e7663b7337bfc49d99e5089fccb9
6
+ metadata.gz: 32c3bba78bf66bb71b47d78e06592e4a670735d581c84043b4c125704fb91d4cff900da0e2884c910f3c66ddf9e31f4860a33718da978cc9f2719e32a9f1f617
7
+ data.tar.gz: 11a5bdee97b35fc99d346ae2cd44e95e8fc3cfd949bddc06a7561b7329fd42a0e63ef5e747a90fa599125100cc368d9ef6c9ad207a1a69fe51f6c32e71992611
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.11.0
4
+
5
+ * Drop dependency on `fileutils`.
6
+
7
+ * Better respect `Kernel#require` duck typing. While it almost never comes up in practice, `Kernel#require`
8
+ follow a fairly intricate duck-typing protocol on its argument implemented as `rb_get_path(VALUE)` in MRI.
9
+ So when applicable we bind `rb_get_path` and use it for improved compatibility. See #396 and #406.
10
+
11
+ * Get rid of the `Kernel.require_relative` decorator by resolving `$LOAD_PATH` members to their real path.
12
+ This way we handle symlinks in `$LOAD_PATH` much more efficiently. See #402 for the detailed explanation.
13
+
14
+ * Drop support for Ruby 2.3 (to allow getting rid of the `Kernel.require_relative` decorator).
15
+
3
16
  # 1.10.3
4
17
 
5
18
  * Fix Regexp and Date type support in YAML compile cache. (#400)
@@ -2,7 +2,7 @@
2
2
  * Suggested reading order:
3
3
  * 1. Skim Init_bootsnap
4
4
  * 2. Skim bs_fetch
5
- * 3. The rest of everyrything
5
+ * 3. The rest of everything
6
6
  *
7
7
  * Init_bootsnap sets up the ruby objects and binds bs_fetch to
8
8
  * Bootsnap::CompileCache::Native.fetch.
@@ -135,6 +135,12 @@ bs_rb_coverage_running(VALUE self)
135
135
  return RTEST(cov) ? Qtrue : Qfalse;
136
136
  }
137
137
 
138
+ static VALUE
139
+ bs_rb_get_path(VALUE self, VALUE fname)
140
+ {
141
+ return rb_get_path(fname);
142
+ }
143
+
138
144
  /*
139
145
  * Ruby C extensions are initialized by calling Init_<extname>.
140
146
  *
@@ -146,6 +152,9 @@ void
146
152
  Init_bootsnap(void)
147
153
  {
148
154
  rb_mBootsnap = rb_define_module("Bootsnap");
155
+
156
+ rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
157
+
149
158
  rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
150
159
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
151
160
  rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
@@ -50,9 +50,7 @@ module Bootsnap
50
50
 
51
51
  def self.supported?
52
52
  # only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
53
- RUBY_ENGINE == "ruby" &&
54
- RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
55
- Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
53
+ RUBY_ENGINE == "ruby" && RUBY_PLATFORM.match?(/darwin|linux|bsd|mswin|mingw|cygwin/)
56
54
  end
57
55
  end
58
56
  end
@@ -142,6 +142,8 @@ module Bootsnap
142
142
  @has_relative_paths = true if p.relative?
143
143
  next if p.non_directory?
144
144
 
145
+ p = p.to_realpath
146
+
145
147
  expanded_path = p.expanded_path
146
148
  entries, dirs = p.entries_and_dirs(@store)
147
149
  # push -> low precedence -> set only if unset
@@ -157,6 +159,8 @@ module Bootsnap
157
159
  p = Path.new(path)
158
160
  next if p.non_directory?
159
161
 
162
+ p = p.to_realpath
163
+
160
164
  expanded_path = p.expanded_path
161
165
  entries, dirs = p.entries_and_dirs(@store)
162
166
  # unshift -> high precedence -> unconditional set
@@ -6,7 +6,7 @@ module Kernel
6
6
  alias_method(:require_without_bootsnap, :require)
7
7
 
8
8
  def require(path)
9
- string_path = path.to_s
9
+ string_path = Bootsnap.rb_get_path(path)
10
10
  return false if Bootsnap::LoadPathCache.loaded_features_index.key?(string_path)
11
11
 
12
12
  resolved = Bootsnap::LoadPathCache.load_path_cache.find(string_path)
@@ -33,18 +33,9 @@ module Kernel
33
33
  end
34
34
  end
35
35
 
36
- alias_method(:require_relative_without_bootsnap, :require_relative)
37
- def require_relative(path)
38
- location = caller_locations(1..1).first
39
- realpath = Bootsnap::LoadPathCache.realpath_cache.call(
40
- location.absolute_path || location.path, path
41
- )
42
- require(realpath)
43
- end
44
-
45
36
  alias_method(:load_without_bootsnap, :load)
46
37
  def load(path, wrap = false)
47
- if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path, try_extensions: false))
38
+ if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path), try_extensions: false))
48
39
  load_without_bootsnap(resolved, wrap)
49
40
  else
50
41
  load_without_bootsnap(path, wrap)
@@ -62,7 +53,7 @@ class Module
62
53
  # The challenge is that we don't control the point at which the entry gets
63
54
  # added to $LOADED_FEATURES and won't be able to hook that modification
64
55
  # since it's done in C-land.
65
- resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)
56
+ resolved = Bootsnap::LoadPathCache.load_path_cache.find(Bootsnap.rb_get_path(path))
66
57
  if Bootsnap::LoadPathCache::FALLBACK_SCAN.equal?(resolved)
67
58
  autoload_without_bootsnap(const, path)
68
59
  elsif resolved == false
@@ -21,8 +21,26 @@ module Bootsnap
21
21
 
22
22
  attr_reader(:path)
23
23
 
24
- def initialize(path)
24
+ def initialize(path, real: false)
25
25
  @path = path.to_s.freeze
26
+ @real = real
27
+ end
28
+
29
+ def to_realpath
30
+ return self if @real
31
+
32
+ realpath = begin
33
+ File.realpath(path)
34
+ rescue Errno::ENOENT
35
+ return self
36
+ end
37
+
38
+ if realpath != path
39
+ Path.new(realpath, real: true)
40
+ else
41
+ @real = true
42
+ self
43
+ end
26
44
  end
27
45
 
28
46
  # True if the path exists, but represents a non-directory object
@@ -62,7 +80,11 @@ module Bootsnap
62
80
  end
63
81
 
64
82
  def expanded_path
65
- File.expand_path(path).freeze
83
+ if @real
84
+ path
85
+ else
86
+ @expanded_path ||= File.expand_path(path).freeze
87
+ end
66
88
  end
67
89
 
68
90
  private
@@ -69,7 +69,7 @@ module Bootsnap
69
69
  def load_data
70
70
  @data = begin
71
71
  data = File.open(@store_path, encoding: Encoding::BINARY) do |io|
72
- MessagePack.load(io)
72
+ MessagePack.load(io, freeze: true)
73
73
  end
74
74
  if data.is_a?(Hash) && data[VERSION_KEY] == CURRENT_VERSION
75
75
  data
@@ -89,19 +89,17 @@ module Bootsnap
89
89
  end
90
90
 
91
91
  def dump_data
92
- require "fileutils" unless defined? FileUtils
93
-
94
92
  # Change contents atomically so other processes can't get invalid
95
93
  # caches if they read at an inopportune time.
96
94
  tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100_000).to_i}.tmp"
97
- FileUtils.mkpath(File.dirname(tmp))
95
+ mkdir_p(File.dirname(tmp))
98
96
  exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
99
97
  # `encoding:` looks redundant wrt `binwrite`, but necessary on windows
100
98
  # because binary is part of mode.
101
99
  File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
102
- MessagePack.dump(@data, io, freeze: true)
100
+ MessagePack.dump(@data, io)
103
101
  end
104
- FileUtils.mv(tmp, @store_path)
102
+ File.rename(tmp, @store_path)
105
103
  rescue Errno::EEXIST
106
104
  retry
107
105
  rescue SystemCallError
@@ -110,6 +108,21 @@ module Bootsnap
110
108
  def default_data
111
109
  {VERSION_KEY => CURRENT_VERSION}
112
110
  end
111
+
112
+ def mkdir_p(path)
113
+ stack = []
114
+ until File.directory?(path)
115
+ stack.push path
116
+ path = File.dirname(path)
117
+ end
118
+ stack.reverse_each do |dir|
119
+ begin
120
+ Dir.mkdir(dir)
121
+ rescue SystemCallError
122
+ raise unless File.directory?(dir)
123
+ end
124
+ end
125
+ end
113
126
  end
114
127
  end
115
128
  end
@@ -22,7 +22,7 @@ module Bootsnap
22
22
  CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
23
23
 
24
24
  class << self
25
- attr_reader(:load_path_cache, :loaded_features_index, :realpath_cache)
25
+ attr_reader(:load_path_cache, :loaded_features_index)
26
26
 
27
27
  def setup(cache_path:, development_mode:)
28
28
  unless supported?
@@ -33,7 +33,6 @@ module Bootsnap
33
33
  store = Store.new(cache_path)
34
34
 
35
35
  @loaded_features_index = LoadedFeaturesIndex.new
36
- @realpath_cache = RealpathCache.new
37
36
 
38
37
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
39
38
  require_relative("load_path_cache/core_ext/kernel_require")
@@ -55,5 +54,4 @@ if Bootsnap::LoadPathCache.supported?
55
54
  require_relative("load_path_cache/store")
56
55
  require_relative("load_path_cache/change_observer")
57
56
  require_relative("load_path_cache/loaded_features_index")
58
- require_relative("load_path_cache/realpath_cache")
59
57
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bootsnap
4
- VERSION = "1.10.3"
4
+ VERSION = "1.11.0"
5
5
  end
data/lib/bootsnap.rb CHANGED
@@ -133,5 +133,16 @@ module Bootsnap
133
133
  path.start_with?("/")
134
134
  end
135
135
  end
136
+
137
+ # This is a semi-accurate ruby implementation of the native `rb_get_path(VALUE)` function.
138
+ # The native version is very intricate and may behave differently on windows etc.
139
+ # But we only use it for non-MRI platform.
140
+ def rb_get_path(fname)
141
+ path_path = fname.respond_to?(:to_path) ? fname.to_path : fname
142
+ String.try_convert(path_path) || raise(TypeError, "no implicit conversion of #{path_path.class} into String")
143
+ end
144
+
145
+ # Allow the C extension to redefine `rb_get_path` without warning.
146
+ alias_method :rb_get_path, :rb_get_path # rubocop:disable Lint/DuplicateMethods
136
147
  end
137
148
  end
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.10.3
4
+ version: 1.11.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: 2022-02-02 00:00:00.000000000 Z
11
+ date: 2022-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -57,7 +57,6 @@ files:
57
57
  - lib/bootsnap/load_path_cache/loaded_features_index.rb
58
58
  - lib/bootsnap/load_path_cache/path.rb
59
59
  - lib/bootsnap/load_path_cache/path_scanner.rb
60
- - lib/bootsnap/load_path_cache/realpath_cache.rb
61
60
  - lib/bootsnap/load_path_cache/store.rb
62
61
  - lib/bootsnap/setup.rb
63
62
  - lib/bootsnap/version.rb
@@ -77,7 +76,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
76
  requirements:
78
77
  - - ">="
79
78
  - !ruby/object:Gem::Version
80
- version: 2.3.0
79
+ version: 2.4.0
81
80
  required_rubygems_version: !ruby/object:Gem::Requirement
82
81
  requirements:
83
82
  - - ">="
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Bootsnap
4
- module LoadPathCache
5
- class RealpathCache
6
- def initialize
7
- @cache = Hash.new { |h, k| h[k] = realpath(*k) }
8
- end
9
-
10
- def call(*key)
11
- @cache[key]
12
- end
13
-
14
- private
15
-
16
- def realpath(caller_location, path)
17
- base = File.dirname(caller_location)
18
- abspath = File.expand_path(path, base).freeze
19
- find_file(abspath)
20
- end
21
-
22
- def find_file(name)
23
- return File.realpath(name).freeze if File.exist?(name)
24
-
25
- CACHED_EXTENSIONS.each do |ext|
26
- filename = "#{name}#{ext}"
27
- return File.realpath(filename).freeze if File.exist?(filename)
28
- end
29
- name
30
- end
31
- end
32
- end
33
- end