bootsnap 1.1.8 → 1.4.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.
@@ -1,4 +1,4 @@
1
- require "mkmf"
1
+ require("mkmf")
2
2
  $CFLAGS << ' -O3 '
3
3
  $CFLAGS << ' -std=c99'
4
4
 
@@ -12,6 +12,7 @@ unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
12
12
 
13
13
  $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
14
14
  $CFLAGS << ' -Wno-keyword-macro' # hiding return
15
+ $CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno
15
16
  end
16
17
 
17
18
  create_makefile("bootsnap/bootsnap")
data/lib/bootsnap.rb CHANGED
@@ -1,7 +1,7 @@
1
- require_relative 'bootsnap/version'
2
- require_relative 'bootsnap/bundler'
3
- require_relative 'bootsnap/load_path_cache'
4
- require_relative 'bootsnap/compile_cache'
1
+ require_relative('bootsnap/version')
2
+ require_relative('bootsnap/bundler')
3
+ require_relative('bootsnap/load_path_cache')
4
+ require_relative('bootsnap/compile_cache')
5
5
 
6
6
  module Bootsnap
7
7
  InvalidConfiguration = Class.new(StandardError)
@@ -16,7 +16,7 @@ module Bootsnap
16
16
  compile_cache_yaml: true
17
17
  )
18
18
  if autoload_paths_cache && !load_path_cache
19
- raise InvalidConfiguration, "feature 'autoload_paths_cache' depends on feature 'load_path_cache'"
19
+ raise(InvalidConfiguration, "feature 'autoload_paths_cache' depends on feature 'load_path_cache'")
20
20
  end
21
21
 
22
22
  setup_disable_trace if disable_trace
@@ -35,6 +35,13 @@ module Bootsnap
35
35
  end
36
36
 
37
37
  def self.setup_disable_trace
38
- RubyVM::InstructionSequence.compile_option = { trace_instruction: false }
38
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
39
+ warn(
40
+ "from #{caller_locations(1, 1)[0]}: The 'disable_trace' method is not allowed with this Ruby version. " \
41
+ "current: #{RUBY_VERSION}, allowed version: < 2.5.0",
42
+ )
43
+ else
44
+ RubyVM::InstructionSequence.compile_option = { trace_instruction: false }
45
+ end
39
46
  end
40
47
  end
@@ -1,12 +1,14 @@
1
1
  module Bootsnap
2
- module_function
2
+ extend(self)
3
3
 
4
4
  def bundler?
5
+ return false unless defined?(::Bundler)
6
+
5
7
  # Bundler environment variable
6
- ['BUNDLE_BIN_PATH', 'BUNDLE_GEMFILE'].each do |current|
8
+ %w(BUNDLE_BIN_PATH BUNDLE_GEMFILE).each do |current|
7
9
  return true if ENV.key?(current)
8
10
  end
9
-
11
+
10
12
  false
11
13
  end
12
14
  end
@@ -2,14 +2,29 @@ module Bootsnap
2
2
  module CompileCache
3
3
  def self.setup(cache_dir:, iseq:, yaml:)
4
4
  if iseq
5
- require_relative 'compile_cache/iseq'
6
- Bootsnap::CompileCache::ISeq.install!(cache_dir)
5
+ if supported?
6
+ require_relative('compile_cache/iseq')
7
+ Bootsnap::CompileCache::ISeq.install!(cache_dir)
8
+ elsif $VERBOSE
9
+ warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby")
10
+ end
7
11
  end
8
12
 
9
13
  if yaml
10
- require_relative 'compile_cache/yaml'
11
- Bootsnap::CompileCache::YAML.install!(cache_dir)
14
+ if supported?
15
+ require_relative('compile_cache/yaml')
16
+ Bootsnap::CompileCache::YAML.install!(cache_dir)
17
+ elsif $VERBOSE
18
+ warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby")
19
+ end
12
20
  end
13
21
  end
22
+
23
+ def self.supported?
24
+ # only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0
25
+ RUBY_ENGINE == 'ruby' &&
26
+ RUBY_PLATFORM =~ /darwin|linux|bsd/ &&
27
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
28
+ end
14
29
  end
15
30
  end
@@ -1,25 +1,25 @@
1
- require 'bootsnap/bootsnap'
2
- require 'zlib'
1
+ require('bootsnap/bootsnap')
2
+ require('zlib')
3
3
 
4
4
  module Bootsnap
5
5
  module CompileCache
6
6
  module ISeq
7
7
  class << self
8
- attr_accessor :cache_dir
8
+ attr_accessor(:cache_dir)
9
9
  end
10
10
 
11
11
  def self.input_to_storage(_, path)
12
12
  RubyVM::InstructionSequence.compile_file(path).to_binary
13
13
  rescue SyntaxError
14
- raise Uncompilable, 'syntax error'
14
+ raise(Uncompilable, 'syntax error')
15
15
  end
16
16
 
17
17
  def self.storage_to_output(binary)
18
18
  RubyVM::InstructionSequence.load_from_binary(binary)
19
19
  rescue RuntimeError => e
20
20
  if e.message == 'broken binary format'
21
- STDERR.puts "[Bootsnap::CompileCache] warning: rejecting broken binary"
22
- return nil
21
+ STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
22
+ nil
23
23
  else
24
24
  raise
25
25
  end
@@ -41,7 +41,7 @@ module Bootsnap
41
41
  )
42
42
  rescue RuntimeError => e
43
43
  if e.message =~ /unmatched platform/
44
- puts "unmatched platform for file #{path}"
44
+ puts("unmatched platform for file #{path}")
45
45
  end
46
46
  raise
47
47
  end
@@ -62,7 +62,7 @@ module Bootsnap
62
62
  Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
63
63
  Bootsnap::CompileCache::ISeq.compile_option_updated
64
64
  class << RubyVM::InstructionSequence
65
- prepend InstructionSequenceMixin
65
+ prepend(InstructionSequenceMixin)
66
66
  end
67
67
  end
68
68
  end
@@ -1,21 +1,21 @@
1
- require 'bootsnap/bootsnap'
1
+ require('bootsnap/bootsnap')
2
2
 
3
3
  module Bootsnap
4
4
  module CompileCache
5
5
  module YAML
6
6
  class << self
7
- attr_accessor :msgpack_factory
7
+ attr_accessor(:msgpack_factory)
8
8
  end
9
9
 
10
10
  def self.input_to_storage(contents, _)
11
- raise Uncompilable if contents.index("!ruby/object")
11
+ raise(Uncompilable) if contents.index("!ruby/object")
12
12
  obj = ::YAML.load(contents)
13
13
  msgpack_factory.packer.write(obj).to_s
14
14
  rescue NoMethodError, RangeError
15
15
  # if the object included things that we can't serialize, fall back to
16
16
  # Marshal. It's a bit slower, but can encode anything yaml can.
17
17
  # NoMethodError is unexpected types; RangeError is Bignums
18
- return Marshal.dump(obj)
18
+ Marshal.dump(obj)
19
19
  end
20
20
 
21
21
  def self.storage_to_output(data)
@@ -34,8 +34,8 @@ module Bootsnap
34
34
  end
35
35
 
36
36
  def self.install!(cache_dir)
37
- require 'yaml'
38
- require 'msgpack'
37
+ require('yaml')
38
+ require('msgpack')
39
39
 
40
40
  # MessagePack serializes symbols as strings by default.
41
41
  # We want them to roundtrip cleanly, so we use a custom factory.
@@ -48,7 +48,7 @@ module Bootsnap
48
48
  klass.send(:define_method, :load_file) do |path|
49
49
  Bootsnap::CompileCache::Native.fetch(
50
50
  cache_dir,
51
- path.to_s,
51
+ path,
52
52
  Bootsnap::CompileCache::YAML
53
53
  )
54
54
  end
@@ -5,7 +5,7 @@ module Bootsnap
5
5
  DLEXT = RbConfig::CONFIG['DLEXT']
6
6
 
7
7
  def self.from_self(feature)
8
- require_relative "../#{feature}"
8
+ require_relative("../#{feature}")
9
9
  end
10
10
 
11
11
  def self.from_rubylibdir(feature)
@@ -21,32 +21,51 @@ module Bootsnap
21
21
  CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
22
22
 
23
23
  class << self
24
- attr_reader :load_path_cache, :autoload_paths_cache
24
+ attr_reader(:load_path_cache, :autoload_paths_cache,
25
+ :loaded_features_index, :realpath_cache)
25
26
 
26
27
  def setup(cache_path:, development_mode:, active_support: true)
28
+ unless supported?
29
+ warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
30
+ return
31
+ end
32
+
27
33
  store = Store.new(cache_path)
28
34
 
35
+ @loaded_features_index = LoadedFeaturesIndex.new
36
+ @realpath_cache = RealpathCache.new
37
+
29
38
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
30
- require_relative 'load_path_cache/core_ext/kernel_require'
39
+ require_relative('load_path_cache/core_ext/kernel_require')
40
+ require_relative('load_path_cache/core_ext/loaded_features')
31
41
 
32
42
  if active_support
33
43
  # this should happen after setting up the initial cache because it
34
44
  # loads a lot of code. It's better to do after +require+ is optimized.
35
- require 'active_support/dependencies'
45
+ require('active_support/dependencies')
36
46
  @autoload_paths_cache = Cache.new(
37
47
  store,
38
48
  ::ActiveSupport::Dependencies.autoload_paths,
39
49
  development_mode: development_mode
40
50
  )
41
- require_relative 'load_path_cache/core_ext/active_support'
51
+ require_relative('load_path_cache/core_ext/active_support')
42
52
  end
43
53
  end
54
+
55
+ def supported?
56
+ RUBY_ENGINE == 'ruby' &&
57
+ RUBY_PLATFORM =~ /darwin|linux|bsd/
58
+ end
44
59
  end
45
60
  end
46
61
  end
47
62
 
48
- require_relative 'load_path_cache/path_scanner'
49
- require_relative 'load_path_cache/path'
50
- require_relative 'load_path_cache/cache'
51
- require_relative 'load_path_cache/store'
52
- require_relative 'load_path_cache/change_observer'
63
+ if Bootsnap::LoadPathCache.supported?
64
+ require_relative('load_path_cache/path_scanner')
65
+ require_relative('load_path_cache/path')
66
+ require_relative('load_path_cache/cache')
67
+ require_relative('load_path_cache/store')
68
+ require_relative('load_path_cache/change_observer')
69
+ require_relative('load_path_cache/loaded_features_index')
70
+ require_relative('load_path_cache/realpath_cache')
71
+ end
@@ -1,4 +1,4 @@
1
- require_relative '../explicit_require'
1
+ require_relative('../explicit_require')
2
2
 
3
3
  module Bootsnap
4
4
  module LoadPathCache
@@ -9,15 +9,15 @@ module Bootsnap
9
9
  @development_mode = development_mode
10
10
  @store = store
11
11
  @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
12
- @path_obj = path_obj
12
+ @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
13
13
  @has_relative_paths = nil
14
14
  reinitialize
15
15
  end
16
16
 
17
- # Does this directory exist as a child of one of the path items?
18
- # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], has_dir?("c/d")
19
- # is true.
20
- def has_dir?(dir)
17
+ # What is the path item that contains the dir as child?
18
+ # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], load_dir("c/d")
19
+ # is "/a/b".
20
+ def load_dir(dir)
21
21
  reinitialize if stale?
22
22
  @mutex.synchronize { @dirs[dir] }
23
23
  end
@@ -56,7 +56,7 @@ module Bootsnap
56
56
  # returns false as if it were already loaded; however, there is no
57
57
  # file to find on disk. We've pre-built a list of these, and we
58
58
  # return false if any of them is loaded.
59
- raise LoadPathCache::ReturnFalse if BUILTIN_FEATURES.key?(feature)
59
+ raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature)
60
60
 
61
61
  # The feature wasn't found on our preliminary search through the index.
62
62
  # We resolve this differently depending on what the extension was.
@@ -73,14 +73,21 @@ module Bootsnap
73
73
  x = search_index(feature[0..-4] + DLEXT)
74
74
  return x if x
75
75
  if DLEXT2
76
- search_index(feature[0..-4] + DLEXT2)
76
+ x = search_index(feature[0..-4] + DLEXT2)
77
+ return x if x
77
78
  end
78
79
  else
79
80
  # other, unknown extension. For example, `.rake`. Since we haven't
80
81
  # cached these, we legitimately need to run the load path search.
81
- raise LoadPathCache::FallbackScan
82
+ raise(LoadPathCache::FallbackScan, '', [])
82
83
  end
83
84
  end
85
+
86
+ # In development mode, we don't want to confidently return failures for
87
+ # cases where the file doesn't appear to be on the load path. We should
88
+ # be able to detect newly-created files without rebooting the
89
+ # application.
90
+ raise(LoadPathCache::FallbackScan, '', []) if @development_mode
84
91
  end
85
92
 
86
93
  if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
@@ -103,20 +110,12 @@ module Bootsnap
103
110
  @mutex.synchronize { push_paths_locked(*paths) }
104
111
  end
105
112
 
106
- def each_requirable
107
- @mutex.synchronize do
108
- @index.each do |rel, entry|
109
- yield "#{entry}/#{rel}"
110
- end
111
- end
112
- end
113
-
114
113
  def reinitialize(path_obj = @path_obj)
115
114
  @mutex.synchronize do
116
115
  @path_obj = path_obj
117
116
  ChangeObserver.register(self, @path_obj)
118
117
  @index = {}
119
- @dirs = Hash.new(false)
118
+ @dirs = {}
120
119
  @generated_at = now
121
120
  push_paths_locked(*@path_obj)
122
121
  end
@@ -140,10 +139,11 @@ module Bootsnap
140
139
  p = Path.new(path)
141
140
  @has_relative_paths = true if p.relative?
142
141
  next if p.non_directory?
142
+ expanded_path = p.expanded_path
143
143
  entries, dirs = p.entries_and_dirs(@store)
144
144
  # push -> low precedence -> set only if unset
145
- dirs.each { |dir| @dirs[dir] ||= true }
146
- entries.each { |rel| @index[rel] ||= p.expanded_path }
145
+ dirs.each { |dir| @dirs[dir] ||= path }
146
+ entries.each { |rel| @index[rel] ||= expanded_path }
147
147
  end
148
148
  end
149
149
  end
@@ -153,10 +153,11 @@ module Bootsnap
153
153
  paths.map(&:to_s).reverse_each do |path|
154
154
  p = Path.new(path)
155
155
  next if p.non_directory?
156
+ expanded_path = p.expanded_path
156
157
  entries, dirs = p.entries_and_dirs(@store)
157
158
  # unshift -> high precedence -> unconditional set
158
- dirs.each { |dir| @dirs[dir] = true }
159
- entries.each { |rel| @index[rel] = p.expanded_path }
159
+ dirs.each { |dir| @dirs[dir] = path }
160
+ entries.each { |rel| @index[rel] = expanded_path }
160
161
  end
161
162
  end
162
163
  end
@@ -180,7 +181,7 @@ module Bootsnap
180
181
  end
181
182
 
182
183
  def try_index(f)
183
- if p = @index[f]
184
+ if (p = @index[f])
184
185
  p + '/' + f
185
186
  end
186
187
  end
@@ -1,37 +1,36 @@
1
1
  module Bootsnap
2
2
  module LoadPathCache
3
3
  module ChangeObserver
4
- def self.register(observer, arr)
5
- # Re-overriding these methods on an array that already has them would
6
- # cause StackOverflowErrors
7
- return if arr.respond_to?(:push_without_lpc)
8
-
4
+ module ArrayMixin
9
5
  # For each method that adds items to one end or another of the array
10
6
  # (<<, push, unshift, concat), override that method to also notify the
11
7
  # observer of the change.
12
- sc = arr.singleton_class
13
- sc.send(:alias_method, :shovel_without_lpc, :<<)
14
- arr.define_singleton_method(:<<) do |entry|
15
- observer.push_paths(self, entry.to_s)
16
- shovel_without_lpc(entry)
8
+ def <<(entry)
9
+ @lpc_observer.push_paths(self, entry.to_s)
10
+ super
11
+ end
12
+
13
+ def push(*entries)
14
+ @lpc_observer.push_paths(self, *entries.map(&:to_s))
15
+ super
17
16
  end
18
17
 
19
- sc.send(:alias_method, :push_without_lpc, :push)
20
- arr.define_singleton_method(:push) do |*entries|
21
- observer.push_paths(self, *entries.map(&:to_s))
22
- push_without_lpc(*entries)
18
+ def unshift(*entries)
19
+ @lpc_observer.unshift_paths(self, *entries.map(&:to_s))
20
+ super
23
21
  end
24
22
 
25
- sc.send(:alias_method, :unshift_without_lpc, :unshift)
26
- arr.define_singleton_method(:unshift) do |*entries|
27
- observer.unshift_paths(self, *entries.map(&:to_s))
28
- unshift_without_lpc(*entries)
23
+ def concat(entries)
24
+ @lpc_observer.push_paths(self, *entries.map(&:to_s))
25
+ super
29
26
  end
30
27
 
31
- sc.send(:alias_method, :concat_without_lpc, :concat)
32
- arr.define_singleton_method(:concat) do |entries|
33
- observer.push_paths(self, *entries.map(&:to_s))
34
- concat_without_lpc(entries)
28
+ # uniq! keeps the first occurance of each path, otherwise preserving
29
+ # order, preserving the effective load path
30
+ def uniq!(*args)
31
+ ret = super
32
+ @lpc_observer.reinitialize if block_given? || !args.empty?
33
+ ret
35
34
  end
36
35
 
37
36
  # For each method that modifies the array more aggressively, override
@@ -41,16 +40,22 @@ module Bootsnap
41
40
  # accounting cost would be greater than the hit from these, since we
42
41
  # actively discourage calling them.
43
42
  %i(
44
- collect! compact! delete delete_at delete_if fill flatten! insert map!
45
- reject! reverse! select! shuffle! shift slice! sort! sort_by!
46
- ).each do |meth|
47
- sc.send(:alias_method, :"#{meth}_without_lpc", meth)
48
- arr.define_singleton_method(meth) do |*a|
49
- send(:"#{meth}_without_lpc", *a)
50
- observer.reinitialize
43
+ []= clear collect! compact! delete delete_at delete_if fill flatten!
44
+ insert keep_if map! pop reject! replace reverse! rotate! select!
45
+ shift shuffle! slice! sort! sort_by!
46
+ ).each do |method_name|
47
+ define_method(method_name) do |*args, &block|
48
+ ret = super(*args, &block)
49
+ @lpc_observer.reinitialize
50
+ ret
51
51
  end
52
52
  end
53
53
  end
54
+
55
+ def self.register(observer, arr)
56
+ arr.instance_variable_set(:@lpc_observer, observer)
57
+ arr.extend(ArrayMixin)
58
+ end
54
59
  end
55
60
  end
56
61
  end