bootsnap 1.4.4 → 1.4.9.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +1 -1
  4. data/ext/bootsnap/bootsnap.c +113 -65
  5. data/ext/bootsnap/extconf.rb +1 -0
  6. data/lib/bootsnap.rb +2 -0
  7. data/lib/bootsnap/bundler.rb +1 -0
  8. data/lib/bootsnap/compile_cache.rb +4 -3
  9. data/lib/bootsnap/compile_cache/iseq.rb +6 -4
  10. data/lib/bootsnap/compile_cache/yaml.rb +67 -38
  11. data/lib/bootsnap/explicit_require.rb +1 -0
  12. data/lib/bootsnap/load_path_cache.rb +1 -1
  13. data/lib/bootsnap/load_path_cache/cache.rb +8 -8
  14. data/lib/bootsnap/load_path_cache/change_observer.rb +2 -1
  15. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +1 -0
  16. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -5
  17. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
  18. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +33 -10
  19. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  20. data/lib/bootsnap/load_path_cache/path_scanner.rb +39 -26
  21. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  22. data/lib/bootsnap/load_path_cache/store.rb +18 -14
  23. data/lib/bootsnap/setup.rb +1 -0
  24. data/lib/bootsnap/version.rb +2 -1
  25. metadata +11 -29
  26. data/.github/CODEOWNERS +0 -2
  27. data/.github/probots.yml +0 -2
  28. data/.gitignore +0 -17
  29. data/.rubocop.yml +0 -20
  30. data/.travis.yml +0 -21
  31. data/CODE_OF_CONDUCT.md +0 -74
  32. data/CONTRIBUTING.md +0 -21
  33. data/Gemfile +0 -8
  34. data/README.jp.md +0 -231
  35. data/Rakefile +0 -12
  36. data/bin/ci +0 -10
  37. data/bin/console +0 -14
  38. data/bin/setup +0 -8
  39. data/bin/test-minimal-support +0 -7
  40. data/bin/testunit +0 -8
  41. data/bootsnap.gemspec +0 -45
  42. data/dev.yml +0 -10
  43. data/shipit.rubygems.yml +0 -0
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative('bootsnap/version')
2
4
  require_relative('bootsnap/bundler')
3
5
  require_relative('bootsnap/load_path_cache')
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  extend(self)
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module CompileCache
3
4
  Error = Class.new(StandardError)
@@ -28,14 +29,14 @@ module Bootsnap
28
29
  raise(
29
30
  PermissionError,
30
31
  "bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
31
- "(or, less likely, doesn't have permisison to read '#{path}')",
32
+ "(or, less likely, doesn't have permission to read '#{path}')",
32
33
  )
33
34
  end
34
35
 
35
36
  def self.supported?
36
- # only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
37
+ # only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), Windows (RubyInstaller2) and >= 2.3.0
37
38
  RUBY_ENGINE == 'ruby' &&
38
- RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
39
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
39
40
  Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
40
41
  end
41
42
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require('bootsnap/bootsnap')
2
3
  require('zlib')
3
4
 
@@ -8,13 +9,13 @@ module Bootsnap
8
9
  attr_accessor(:cache_dir)
9
10
  end
10
11
 
11
- def self.input_to_storage(_, path)
12
+ def self.input_to_storage(_, path, _args)
12
13
  RubyVM::InstructionSequence.compile_file(path).to_binary
13
14
  rescue SyntaxError
14
15
  raise(Uncompilable, 'syntax error')
15
16
  end
16
17
 
17
- def self.storage_to_output(binary)
18
+ def self.storage_to_output(binary, _args)
18
19
  RubyVM::InstructionSequence.load_from_binary(binary)
19
20
  rescue RuntimeError => e
20
21
  if e.message == 'broken binary format'
@@ -25,7 +26,7 @@ module Bootsnap
25
26
  end
26
27
  end
27
28
 
28
- def self.input_to_output(_)
29
+ def self.input_to_output(_, _)
29
30
  nil # ruby handles this
30
31
  end
31
32
 
@@ -37,7 +38,8 @@ module Bootsnap
37
38
  Bootsnap::CompileCache::Native.fetch(
38
39
  Bootsnap::CompileCache::ISeq.cache_dir,
39
40
  path.to_s,
40
- Bootsnap::CompileCache::ISeq
41
+ Bootsnap::CompileCache::ISeq,
42
+ nil,
41
43
  )
42
44
  rescue Errno::EACCES
43
45
  Bootsnap::CompileCache.permission_error(path)
@@ -1,59 +1,88 @@
1
+ # frozen_string_literal: true
1
2
  require('bootsnap/bootsnap')
2
3
 
3
4
  module Bootsnap
4
5
  module CompileCache
5
6
  module YAML
6
7
  class << self
7
- attr_accessor(:msgpack_factory)
8
- end
8
+ attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
9
9
 
10
- def self.input_to_storage(contents, _)
11
- raise(Uncompilable) if contents.index("!ruby/object")
12
- obj = ::YAML.load(contents)
13
- msgpack_factory.packer.write(obj).to_s
14
- rescue NoMethodError, RangeError
15
- # if the object included things that we can't serialize, fall back to
16
- # Marshal. It's a bit slower, but can encode anything yaml can.
17
- # NoMethodError is unexpected types; RangeError is Bignums
18
- Marshal.dump(obj)
19
- end
10
+ def input_to_storage(contents, _, kwargs)
11
+ raise(Uncompilable) if contents.index("!ruby/object")
12
+ obj = ::YAML.load(contents, **(kwargs || {}))
13
+ msgpack_factory.dump(obj)
14
+ rescue NoMethodError, RangeError
15
+ # if the object included things that we can't serialize, fall back to
16
+ # Marshal. It's a bit slower, but can encode anything yaml can.
17
+ # NoMethodError is unexpected types; RangeError is Bignums
18
+ Marshal.dump(obj)
19
+ end
20
+
21
+ def storage_to_output(data, kwargs)
22
+ # This could have a meaning in messagepack, and we're being a little lazy
23
+ # about it. -- but a leading 0x04 would indicate the contents of the YAML
24
+ # is a positive integer, which is rare, to say the least.
25
+ if data[0] == 0x04.chr && data[1] == 0x08.chr
26
+ Marshal.load(data)
27
+ else
28
+ msgpack_factory.load(data, **(kwargs || {}))
29
+ end
30
+ end
20
31
 
21
- def self.storage_to_output(data)
22
- # This could have a meaning in messagepack, and we're being a little lazy
23
- # about it. -- but a leading 0x04 would indicate the contents of the YAML
24
- # is a positive integer, which is rare, to say the least.
25
- if data[0] == 0x04.chr && data[1] == 0x08.chr
26
- Marshal.load(data)
27
- else
28
- msgpack_factory.unpacker.feed(data).read
32
+ def input_to_output(data, kwargs)
33
+ ::YAML.load(data, **(kwargs || {}))
29
34
  end
30
- end
31
35
 
32
- def self.input_to_output(data)
33
- ::YAML.load(data)
36
+ def install!(cache_dir)
37
+ self.cache_dir = cache_dir
38
+ init!
39
+ ::YAML.singleton_class.prepend(Patch)
40
+ end
41
+
42
+ def init!
43
+ require('yaml')
44
+ require('msgpack')
45
+
46
+ # MessagePack serializes symbols as strings by default.
47
+ # We want them to roundtrip cleanly, so we use a custom factory.
48
+ # see: https://github.com/msgpack/msgpack-ruby/pull/122
49
+ factory = MessagePack::Factory.new
50
+ factory.register_type(0x00, Symbol)
51
+ self.msgpack_factory = factory
52
+
53
+ self.supported_options = []
54
+ params = ::YAML.method(:load).parameters
55
+ if params.include?([:key, :symbolize_names])
56
+ self.supported_options << :symbolize_names
57
+ end
58
+ if params.include?([:key, :freeze])
59
+ if factory.load(factory.dump('yaml'), freeze: true).frozen?
60
+ self.supported_options << :freeze
61
+ end
62
+ end
63
+ self.supported_options.freeze
64
+ end
34
65
  end
35
66
 
36
- def self.install!(cache_dir)
37
- require('yaml')
38
- require('msgpack')
67
+ module Patch
68
+ extend self
39
69
 
40
- # MessagePack serializes symbols as strings by default.
41
- # We want them to roundtrip cleanly, so we use a custom factory.
42
- # see: https://github.com/msgpack/msgpack-ruby/pull/122
43
- factory = MessagePack::Factory.new
44
- factory.register_type(0x00, Symbol)
45
- Bootsnap::CompileCache::YAML.msgpack_factory = factory
70
+ def load_file(path, *args)
71
+ return super if args.size > 1
72
+ if kwargs = args.first
73
+ return super unless kwargs.is_a?(Hash)
74
+ return super unless (kwargs.keys - ::Bootsnap::CompileCache::YAML.supported_options).empty?
75
+ end
46
76
 
47
- klass = class << ::YAML; self; end
48
- klass.send(:define_method, :load_file) do |path|
49
77
  begin
50
- Bootsnap::CompileCache::Native.fetch(
51
- cache_dir,
78
+ ::Bootsnap::CompileCache::Native.fetch(
79
+ Bootsnap::CompileCache::YAML.cache_dir,
52
80
  path,
53
- Bootsnap::CompileCache::YAML
81
+ ::Bootsnap::CompileCache::YAML,
82
+ kwargs,
54
83
  )
55
84
  rescue Errno::EACCES
56
- Bootsnap::CompileCache.permission_error(path)
85
+ ::Bootsnap::CompileCache.permission_error(path)
57
86
  end
58
87
  end
59
88
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module ExplicitRequire
3
4
  ARCHDIR = RbConfig::CONFIG['archdir']
@@ -61,7 +61,7 @@ module Bootsnap
61
61
 
62
62
  def supported?
63
63
  RUBY_ENGINE == 'ruby' &&
64
- RUBY_PLATFORM =~ /darwin|linux|bsd/
64
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/
65
65
  end
66
66
  end
67
67
  end
@@ -46,7 +46,7 @@ module Bootsnap
46
46
  # loadpath.
47
47
  def find(feature)
48
48
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
49
- feature = feature.to_s
49
+ feature = feature.to_s.freeze
50
50
  return feature if absolute_path?(feature)
51
51
  return expand_path(feature) if feature.start_with?('./')
52
52
  @mutex.synchronize do
@@ -67,7 +67,7 @@ module Bootsnap
67
67
  # native dynamic extension, e.g. .bundle or .so), we know it was a
68
68
  # failure and there's nothing more we can do to find the file.
69
69
  # no extension, .rb, (.bundle or .so)
70
- when '', *CACHED_EXTENSIONS # rubocop:disable Performance/CaseWhenSplat
70
+ when '', *CACHED_EXTENSIONS
71
71
  nil
72
72
  # Ruby allows specifying native extensions as '.so' even when DLEXT
73
73
  # is '.bundle'. This is where we handle that case.
@@ -144,7 +144,7 @@ module Bootsnap
144
144
  expanded_path = p.expanded_path
145
145
  entries, dirs = p.entries_and_dirs(@store)
146
146
  # push -> low precedence -> set only if unset
147
- dirs.each { |dir| @dirs[dir] ||= path }
147
+ dirs.each { |dir| @dirs[dir] ||= path }
148
148
  entries.each { |rel| @index[rel] ||= expanded_path }
149
149
  end
150
150
  end
@@ -178,25 +178,25 @@ module Bootsnap
178
178
 
179
179
  if DLEXT2
180
180
  def search_index(f)
181
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
181
+ try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
182
182
  end
183
183
 
184
184
  def maybe_append_extension(f)
185
- try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
185
+ try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
186
186
  end
187
187
  else
188
188
  def search_index(f)
189
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
189
+ try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
190
190
  end
191
191
 
192
192
  def maybe_append_extension(f)
193
- try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
193
+ try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
194
194
  end
195
195
  end
196
196
 
197
197
  def try_index(f)
198
198
  if (p = @index[f])
199
- p + '/' + f
199
+ "#{p}/#{f}"
200
200
  end
201
201
  end
202
202
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module ChangeObserver
@@ -25,7 +26,7 @@ module Bootsnap
25
26
  super
26
27
  end
27
28
 
28
- # uniq! keeps the first occurance of each path, otherwise preserving
29
+ # uniq! keeps the first occurrence of each path, otherwise preserving
29
30
  # order, preserving the effective load path
30
31
  def uniq!(*args)
31
32
  ret = super
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module CoreExt
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module CoreExt
4
5
  def self.make_load_error(path)
5
- err = LoadError.new("cannot load such file -- #{path}")
6
+ err = LoadError.new(+"cannot load such file -- #{path}")
6
7
  err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
7
8
  err.define_singleton_method(:path) { path }
8
9
  err
@@ -37,7 +38,11 @@ module Kernel
37
38
  rescue Bootsnap::LoadPathCache::ReturnFalse
38
39
  false
39
40
  rescue Bootsnap::LoadPathCache::FallbackScan
40
- require_with_bootsnap_lfi(path)
41
+ fallback = true
42
+ ensure
43
+ if fallback
44
+ require_with_bootsnap_lfi(path)
45
+ end
41
46
  end
42
47
 
43
48
  alias_method(:require_relative_without_bootsnap, :require_relative)
@@ -55,7 +60,7 @@ module Kernel
55
60
  end
56
61
 
57
62
  # load also allows relative paths from pwd even when not in $:
58
- if File.exist?(relative = File.expand_path(path))
63
+ if File.exist?(relative = File.expand_path(path).freeze)
59
64
  return load_without_bootsnap(relative, wrap)
60
65
  end
61
66
 
@@ -66,7 +71,11 @@ module Kernel
66
71
  rescue Bootsnap::LoadPathCache::ReturnFalse
67
72
  false
68
73
  rescue Bootsnap::LoadPathCache::FallbackScan
69
- load_without_bootsnap(path, wrap)
74
+ fallback = true
75
+ ensure
76
+ if fallback
77
+ load_without_bootsnap(path, wrap)
78
+ end
70
79
  end
71
80
  end
72
81
 
@@ -87,6 +96,10 @@ class Module
87
96
  rescue Bootsnap::LoadPathCache::ReturnFalse
88
97
  false
89
98
  rescue Bootsnap::LoadPathCache::FallbackScan
90
- autoload_without_bootsnap(const, path)
99
+ fallback = true
100
+ ensure
101
+ if fallback
102
+ autoload_without_bootsnap(const, path)
103
+ end
91
104
  end
92
105
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class << $LOADED_FEATURES
2
3
  alias_method(:delete_without_bootsnap, :delete)
3
4
  def delete(key)
@@ -40,7 +40,7 @@ module Bootsnap
40
40
  # /a/b/lib/my/foo.rb
41
41
  # ^^^^^^^^^
42
42
  short = feat[(lpe.length + 1)..-1]
43
- stripped = strip_extension(short)
43
+ stripped = strip_extension_if_elidable(short)
44
44
  @lfi[short] = hash
45
45
  @lfi[stripped] = hash
46
46
  end
@@ -94,13 +94,14 @@ module Bootsnap
94
94
 
95
95
  hash = long.hash
96
96
 
97
- # do we have 'bundler' or 'bundler.rb'?
98
- altname = if File.extname(short) != ''
99
- # strip the path from 'bundler.rb' -> 'bundler'
100
- strip_extension(short)
101
- elsif long && (ext = File.extname(long))
102
- # get the extension from the expanded path if given
103
- # 'bundler' + '.rb'
97
+ # Do we have a filename with an elidable extension, e.g.,
98
+ # 'bundler.rb', or 'libgit2.so'?
99
+ altname = if extension_elidable?(short)
100
+ # Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
101
+ strip_extension_if_elidable(short)
102
+ elsif long && (ext = File.extname(long.freeze))
103
+ # We already know the extension of the actual file this
104
+ # resolves to, so put that back on.
104
105
  short + ext
105
106
  end
106
107
 
@@ -117,8 +118,30 @@ module Bootsnap
117
118
  STRIP_EXTENSION = /\.[^.]*?$/
118
119
  private_constant(:STRIP_EXTENSION)
119
120
 
120
- def strip_extension(f)
121
- f.sub(STRIP_EXTENSION, '')
121
+ # Might Ruby automatically search for this extension if
122
+ # someone tries to 'require' the file without it? E.g. Ruby
123
+ # will implicitly try 'x.rb' if you ask for 'x'.
124
+ #
125
+ # This is complex and platform-dependent, and the Ruby docs are a little
126
+ # handwavy about what will be tried when and in what order.
127
+ # So optimistically pretend that all known elidable extensions
128
+ # will be tried on all platforms, and that people are unlikely
129
+ # to name files in a way that assumes otherwise.
130
+ # (E.g. It's unlikely that someone will know that their code
131
+ # will _never_ run on MacOS, and therefore think they can get away
132
+ # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
133
+ #
134
+ # See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
135
+ def extension_elidable?(f)
136
+ f.to_s.end_with?('.rb', '.so', '.o', '.dll', '.dylib')
137
+ end
138
+
139
+ def strip_extension_if_elidable(f)
140
+ if extension_elidable?(f)
141
+ f.sub(STRIP_EXTENSION, '')
142
+ else
143
+ f
144
+ end
122
145
  end
123
146
  end
124
147
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative('path_scanner')
2
3
 
3
4
  module Bootsnap
@@ -20,7 +21,7 @@ module Bootsnap
20
21
  attr_reader(:path)
21
22
 
22
23
  def initialize(path)
23
- @path = path.to_s
24
+ @path = path.to_s.freeze
24
25
  end
25
26
 
26
27
  # True if the path exists, but represents a non-directory object
@@ -59,7 +60,7 @@ module Bootsnap
59
60
  end
60
61
 
61
62
  def expanded_path
62
- File.expand_path(path)
63
+ File.expand_path(path).freeze
63
64
  end
64
65
 
65
66
  private