bootsnap 1.1.8 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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