bootsnap 1.4.6 → 1.5.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.
@@ -34,9 +34,9 @@ module Bootsnap
34
34
  end
35
35
 
36
36
  def self.supported?
37
- # 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
38
38
  RUBY_ENGINE == 'ruby' &&
39
- RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
39
+ RUBY_PLATFORM =~ /darwin|linux|bsd|mswin|mingw|cygwin/ &&
40
40
  Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
41
41
  end
42
42
  end
@@ -9,13 +9,13 @@ module Bootsnap
9
9
  attr_accessor(:cache_dir)
10
10
  end
11
11
 
12
- def self.input_to_storage(_, path)
12
+ def self.input_to_storage(_, path, _args)
13
13
  RubyVM::InstructionSequence.compile_file(path).to_binary
14
14
  rescue SyntaxError
15
15
  raise(Uncompilable, 'syntax error')
16
16
  end
17
17
 
18
- def self.storage_to_output(binary)
18
+ def self.storage_to_output(binary, _args)
19
19
  RubyVM::InstructionSequence.load_from_binary(binary)
20
20
  rescue RuntimeError => e
21
21
  if e.message == 'broken binary format'
@@ -26,7 +26,16 @@ module Bootsnap
26
26
  end
27
27
  end
28
28
 
29
- def self.input_to_output(_)
29
+ def self.fetch(path, cache_dir: ISeq.cache_dir)
30
+ Bootsnap::CompileCache::Native.fetch(
31
+ cache_dir,
32
+ path.to_s,
33
+ Bootsnap::CompileCache::ISeq,
34
+ nil,
35
+ )
36
+ end
37
+
38
+ def self.input_to_output(_, _)
30
39
  nil # ruby handles this
31
40
  end
32
41
 
@@ -35,11 +44,7 @@ module Bootsnap
35
44
  # Having coverage enabled prevents iseq dumping/loading.
36
45
  return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
37
46
 
38
- Bootsnap::CompileCache::Native.fetch(
39
- Bootsnap::CompileCache::ISeq.cache_dir,
40
- path.to_s,
41
- Bootsnap::CompileCache::ISeq
42
- )
47
+ Bootsnap::CompileCache::ISeq.fetch(path.to_s)
43
48
  rescue Errno::EACCES
44
49
  Bootsnap::CompileCache.permission_error(path)
45
50
  rescue RuntimeError => e
@@ -60,6 +65,7 @@ module Bootsnap
60
65
  crc = Zlib.crc32(option.inspect)
61
66
  Bootsnap::CompileCache::Native.compile_option_crc32 = crc
62
67
  end
68
+ compile_option_updated
63
69
 
64
70
  def self.install!(cache_dir)
65
71
  Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
@@ -5,56 +5,84 @@ module Bootsnap
5
5
  module CompileCache
6
6
  module YAML
7
7
  class << self
8
- attr_accessor(:msgpack_factory)
9
- end
8
+ attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
10
9
 
11
- def self.input_to_storage(contents, _)
12
- raise(Uncompilable) if contents.index("!ruby/object")
13
- obj = ::YAML.load(contents)
14
- msgpack_factory.packer.write(obj).to_s
15
- rescue NoMethodError, RangeError
16
- # if the object included things that we can't serialize, fall back to
17
- # Marshal. It's a bit slower, but can encode anything yaml can.
18
- # NoMethodError is unexpected types; RangeError is Bignums
19
- Marshal.dump(obj)
20
- 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
21
31
 
22
- def self.storage_to_output(data)
23
- # This could have a meaning in messagepack, and we're being a little lazy
24
- # about it. -- but a leading 0x04 would indicate the contents of the YAML
25
- # is a positive integer, which is rare, to say the least.
26
- if data[0] == 0x04.chr && data[1] == 0x08.chr
27
- Marshal.load(data)
28
- else
29
- msgpack_factory.unpacker.feed(data).read
32
+ def input_to_output(data, kwargs)
33
+ ::YAML.load(data, **(kwargs || {}))
30
34
  end
31
- end
32
35
 
33
- def self.input_to_output(data)
34
- ::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
35
65
  end
36
66
 
37
- def self.install!(cache_dir)
38
- require('yaml')
39
- require('msgpack')
67
+ module Patch
68
+ extend self
40
69
 
41
- # MessagePack serializes symbols as strings by default.
42
- # We want them to roundtrip cleanly, so we use a custom factory.
43
- # see: https://github.com/msgpack/msgpack-ruby/pull/122
44
- factory = MessagePack::Factory.new
45
- factory.register_type(0x00, Symbol)
46
- 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
47
76
 
48
- klass = class << ::YAML; self; end
49
- klass.send(:define_method, :load_file) do |path|
50
77
  begin
51
- Bootsnap::CompileCache::Native.fetch(
52
- cache_dir,
78
+ ::Bootsnap::CompileCache::Native.fetch(
79
+ Bootsnap::CompileCache::YAML.cache_dir,
53
80
  path,
54
- Bootsnap::CompileCache::YAML
81
+ ::Bootsnap::CompileCache::YAML,
82
+ kwargs,
55
83
  )
56
84
  rescue Errno::EACCES
57
- Bootsnap::CompileCache.permission_error(path)
85
+ ::Bootsnap::CompileCache.permission_error(path)
58
86
  end
59
87
  end
60
88
  end
@@ -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
@@ -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
 
@@ -26,7 +26,7 @@ module Bootsnap
26
26
  super
27
27
  end
28
28
 
29
- # uniq! keeps the first occurance of each path, otherwise preserving
29
+ # uniq! keeps the first occurrence of each path, otherwise preserving
30
30
  # order, preserving the effective load path
31
31
  def uniq!(*args)
32
32
  ret = super
@@ -38,7 +38,11 @@ module Kernel
38
38
  rescue Bootsnap::LoadPathCache::ReturnFalse
39
39
  false
40
40
  rescue Bootsnap::LoadPathCache::FallbackScan
41
- require_with_bootsnap_lfi(path)
41
+ fallback = true
42
+ ensure
43
+ if fallback
44
+ require_with_bootsnap_lfi(path)
45
+ end
42
46
  end
43
47
 
44
48
  alias_method(:require_relative_without_bootsnap, :require_relative)
@@ -56,7 +60,7 @@ module Kernel
56
60
  end
57
61
 
58
62
  # load also allows relative paths from pwd even when not in $:
59
- if File.exist?(relative = File.expand_path(path))
63
+ if File.exist?(relative = File.expand_path(path).freeze)
60
64
  return load_without_bootsnap(relative, wrap)
61
65
  end
62
66
 
@@ -67,7 +71,11 @@ module Kernel
67
71
  rescue Bootsnap::LoadPathCache::ReturnFalse
68
72
  false
69
73
  rescue Bootsnap::LoadPathCache::FallbackScan
70
- load_without_bootsnap(path, wrap)
74
+ fallback = true
75
+ ensure
76
+ if fallback
77
+ load_without_bootsnap(path, wrap)
78
+ end
71
79
  end
72
80
  end
73
81
 
@@ -88,6 +96,10 @@ class Module
88
96
  rescue Bootsnap::LoadPathCache::ReturnFalse
89
97
  false
90
98
  rescue Bootsnap::LoadPathCache::FallbackScan
91
- autoload_without_bootsnap(const, path)
99
+ fallback = true
100
+ ensure
101
+ if fallback
102
+ autoload_without_bootsnap(const, path)
103
+ end
92
104
  end
93
105
  end
@@ -99,7 +99,7 @@ module Bootsnap
99
99
  altname = if extension_elidable?(short)
100
100
  # Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
101
101
  strip_extension_if_elidable(short)
102
- elsif long && (ext = File.extname(long))
102
+ elsif long && (ext = File.extname(long.freeze))
103
103
  # We already know the extension of the actual file this
104
104
  # resolves to, so put that back on.
105
105
  short + ext
@@ -129,7 +129,7 @@ module Bootsnap
129
129
  # to name files in a way that assumes otherwise.
130
130
  # (E.g. It's unlikely that someone will know that their code
131
131
  # will _never_ run on MacOS, and therefore think they can get away
132
- # with callling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
132
+ # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
133
133
  #
134
134
  # See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
135
135
  def extension_elidable?(f)
@@ -21,7 +21,7 @@ module Bootsnap
21
21
  attr_reader(:path)
22
22
 
23
23
  def initialize(path)
24
- @path = path.to_s
24
+ @path = path.to_s.freeze
25
25
  end
26
26
 
27
27
  # True if the path exists, but represents a non-directory object
@@ -60,7 +60,7 @@ module Bootsnap
60
60
  end
61
61
 
62
62
  def expanded_path
63
- File.expand_path(path)
63
+ File.expand_path(path).freeze
64
64
  end
65
65
 
66
66
  private
@@ -5,7 +5,6 @@ require_relative('../explicit_require')
5
5
  module Bootsnap
6
6
  module LoadPathCache
7
7
  module PathScanner
8
- ALL_FILES = "/{,**/*/**/}*"
9
8
  REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
10
9
  NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
11
10
  ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
@@ -16,34 +15,48 @@ module Bootsnap
16
15
  ''
17
16
  end
18
17
 
19
- def self.call(path)
20
- path = path.to_s
21
-
22
- relative_slice = (path.size + 1)..-1
23
- # If the bundle path is a descendent of this path, we do additional
24
- # checks to prevent recursing into the bundle path as we recurse
25
- # through this path. We don't want to scan the bundle path because
26
- # anything useful in it will be present on other load path items.
27
- #
28
- # This can happen if, for example, the user adds '.' to the load path,
29
- # and the bundle path is '.bundle'.
30
- contains_bundle_path = BUNDLE_PATH.start_with?(path)
31
-
32
- dirs = []
33
- requirables = []
34
-
35
- Dir.glob(path + ALL_FILES).each do |absolute_path|
36
- next if contains_bundle_path && absolute_path.start_with?(BUNDLE_PATH)
37
- relative_path = absolute_path.slice(relative_slice)
38
-
39
- if File.directory?(absolute_path)
40
- dirs << relative_path
41
- elsif REQUIRABLE_EXTENSIONS.include?(File.extname(relative_path))
42
- requirables << relative_path
18
+ class << self
19
+ def call(path)
20
+ path = File.expand_path(path.to_s).freeze
21
+ return [[], []] unless File.directory?(path)
22
+
23
+ # If the bundle path is a descendent of this path, we do additional
24
+ # checks to prevent recursing into the bundle path as we recurse
25
+ # through this path. We don't want to scan the bundle path because
26
+ # anything useful in it will be present on other load path items.
27
+ #
28
+ # This can happen if, for example, the user adds '.' to the load path,
29
+ # and the bundle path is '.bundle'.
30
+ contains_bundle_path = BUNDLE_PATH.start_with?(path)
31
+
32
+ dirs = []
33
+ requirables = []
34
+ walk(path, nil) do |relative_path, absolute_path, is_directory|
35
+ if is_directory
36
+ dirs << relative_path
37
+ !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
38
+ elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
39
+ requirables << relative_path
40
+ end
43
41
  end
42
+ [requirables, dirs]
44
43
  end
45
44
 
46
- [requirables, dirs]
45
+ def walk(absolute_dir_path, relative_dir_path, &block)
46
+ Dir.foreach(absolute_dir_path) do |name|
47
+ next if name.start_with?('.')
48
+ relative_path = relative_dir_path ? "#{relative_dir_path}/#{name}" : name.freeze
49
+
50
+ absolute_path = "#{absolute_dir_path}/#{name}"
51
+ if File.directory?(absolute_path)
52
+ if yield relative_path, absolute_path, true
53
+ walk(absolute_path, relative_path, &block)
54
+ end
55
+ else
56
+ yield relative_path, absolute_path, false
57
+ end
58
+ end
59
+ end
47
60
  end
48
61
  end
49
62
  end
@@ -15,15 +15,15 @@ module Bootsnap
15
15
 
16
16
  def realpath(caller_location, path)
17
17
  base = File.dirname(caller_location)
18
- file = find_file(File.expand_path(path, base))
19
- dir = File.dirname(file)
20
- File.join(dir, File.basename(file))
18
+ abspath = File.expand_path(path, base).freeze
19
+ find_file(abspath)
21
20
  end
22
21
 
23
22
  def find_file(name)
24
- ['', *CACHED_EXTENSIONS].each do |ext|
23
+ return File.realpath(name).freeze if File.exist?(name)
24
+ CACHED_EXTENSIONS.each do |ext|
25
25
  filename = "#{name}#{ext}"
26
- return File.realpath(filename) if File.exist?(filename)
26
+ return File.realpath(filename).freeze if File.exist?(filename)
27
27
  end
28
28
  name
29
29
  end
@@ -64,7 +64,7 @@ module Bootsnap
64
64
  def load_data
65
65
  @data = begin
66
66
  MessagePack.load(File.binread(@store_path))
67
- # handle malformed data due to upgrade incompatability
67
+ # handle malformed data due to upgrade incompatibility
68
68
  rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
69
69
  {}
70
70
  rescue ArgumentError => e
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Bootsnap
3
- VERSION = "1.4.6"
3
+ VERSION = "1.5.0"
4
4
  end