bootsnap 1.5.1 → 1.7.2

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.
@@ -9,7 +9,7 @@ module Bootsnap
9
9
  attr_accessor(:cache_dir)
10
10
  end
11
11
 
12
- def self.input_to_storage(_, path, _args)
12
+ def self.input_to_storage(_, path)
13
13
  RubyVM::InstructionSequence.compile_file(path).to_binary
14
14
  rescue SyntaxError
15
15
  raise(Uncompilable, 'syntax error')
@@ -35,6 +35,14 @@ module Bootsnap
35
35
  )
36
36
  end
37
37
 
38
+ def self.precompile(path, cache_dir: ISeq.cache_dir)
39
+ Bootsnap::CompileCache::Native.precompile(
40
+ cache_dir,
41
+ path.to_s,
42
+ Bootsnap::CompileCache::ISeq,
43
+ )
44
+ end
45
+
38
46
  def self.input_to_output(_data, _kwargs)
39
47
  nil # ruby handles this
40
48
  end
@@ -7,32 +7,34 @@ module Bootsnap
7
7
  class << self
8
8
  attr_accessor(:msgpack_factory, :cache_dir, :supported_options)
9
9
 
10
- def input_to_storage(contents, _, kwargs)
10
+ def input_to_storage(contents, _)
11
11
  raise(Uncompilable) if contents.index("!ruby/object")
12
- obj = ::YAML.load(contents, **(kwargs || {}))
12
+ obj = ::YAML.load(contents)
13
13
  msgpack_factory.dump(obj)
14
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)
15
+ # The object included things that we can't serialize
16
+ raise(Uncompilable)
19
17
  end
20
18
 
21
19
  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 || {}))
20
+ if kwargs && kwargs.key?(:symbolize_names)
21
+ kwargs[:symbolize_keys] = kwargs.delete(:symbolize_names)
29
22
  end
23
+ msgpack_factory.load(data, kwargs)
30
24
  end
31
25
 
32
26
  def input_to_output(data, kwargs)
33
27
  ::YAML.load(data, **(kwargs || {}))
34
28
  end
35
29
 
30
+ def precompile(path, cache_dir: YAML.cache_dir)
31
+ Bootsnap::CompileCache::Native.precompile(
32
+ cache_dir,
33
+ path.to_s,
34
+ Bootsnap::CompileCache::YAML,
35
+ )
36
+ end
37
+
36
38
  def install!(cache_dir)
37
39
  self.cache_dir = cache_dir
38
40
  init!
@@ -42,12 +44,34 @@ module Bootsnap
42
44
  def init!
43
45
  require('yaml')
44
46
  require('msgpack')
47
+ require('date')
45
48
 
46
49
  # MessagePack serializes symbols as strings by default.
47
50
  # We want them to roundtrip cleanly, so we use a custom factory.
48
51
  # see: https://github.com/msgpack/msgpack-ruby/pull/122
49
52
  factory = MessagePack::Factory.new
50
53
  factory.register_type(0x00, Symbol)
54
+
55
+ if defined? MessagePack::Timestamp
56
+ factory.register_type(
57
+ MessagePack::Timestamp::TYPE, # or just -1
58
+ Time,
59
+ packer: MessagePack::Time::Packer,
60
+ unpacker: MessagePack::Time::Unpacker
61
+ )
62
+
63
+ marshal_fallback = {
64
+ packer: ->(value) { Marshal.dump(value) },
65
+ unpacker: ->(payload) { Marshal.load(payload) },
66
+ }
67
+ {
68
+ Date => 0x01,
69
+ Regexp => 0x02,
70
+ }.each do |type, code|
71
+ factory.register_type(code, type, marshal_fallback)
72
+ end
73
+ end
74
+
51
75
  self.msgpack_factory = factory
52
76
 
53
77
  self.supported_options = []
@@ -65,8 +89,6 @@ module Bootsnap
65
89
  end
66
90
 
67
91
  module Patch
68
- extend self
69
-
70
92
  def load_file(path, *args)
71
93
  return super if args.size > 1
72
94
  if kwargs = args.first
@@ -77,7 +99,7 @@ module Bootsnap
77
99
  begin
78
100
  ::Bootsnap::CompileCache::Native.fetch(
79
101
  Bootsnap::CompileCache::YAML.cache_dir,
80
- path,
102
+ File.realpath(path),
81
103
  ::Bootsnap::CompileCache::YAML,
82
104
  kwargs,
83
105
  )
@@ -85,6 +107,8 @@ module Bootsnap
85
107
  ::Bootsnap::CompileCache.permission_error(path)
86
108
  end
87
109
  end
110
+
111
+ ruby2_keywords :load_file if respond_to?(:ruby2_keywords, true)
88
112
  end
89
113
  end
90
114
  end
@@ -28,10 +28,9 @@ module Bootsnap
28
28
  CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
29
29
 
30
30
  class << self
31
- attr_reader(:load_path_cache, :autoload_paths_cache,
32
- :loaded_features_index, :realpath_cache)
31
+ attr_reader(:load_path_cache, :loaded_features_index, :realpath_cache)
33
32
 
34
- def setup(cache_path:, development_mode:, active_support: true)
33
+ def setup(cache_path:, development_mode:)
35
34
  unless supported?
36
35
  warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE
37
36
  return
@@ -45,18 +44,6 @@ module Bootsnap
45
44
  @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
46
45
  require_relative('load_path_cache/core_ext/kernel_require')
47
46
  require_relative('load_path_cache/core_ext/loaded_features')
48
-
49
- if active_support
50
- # this should happen after setting up the initial cache because it
51
- # loads a lot of code. It's better to do after +require+ is optimized.
52
- require('active_support/dependencies')
53
- @autoload_paths_cache = Cache.new(
54
- store,
55
- ::ActiveSupport::Dependencies.autoload_paths,
56
- development_mode: development_mode
57
- )
58
- require_relative('load_path_cache/core_ext/active_support')
59
- end
60
47
  end
61
48
 
62
49
  def supported?
@@ -10,8 +10,8 @@ module Bootsnap
10
10
  def initialize(store, path_obj, development_mode: false)
11
11
  @development_mode = development_mode
12
12
  @store = store
13
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
14
- @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f }
13
+ @mutex = Mutex.new
14
+ @path_obj = path_obj.map! { |f| PathScanner.os_path(File.exist?(f) ? File.realpath(f) : f.dup) }
15
15
  @has_relative_paths = nil
16
16
  reinitialize
17
17
  end
@@ -178,25 +178,42 @@ 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
- def try_index(f)
198
- if (p = @index[f])
199
- "#{p}/#{f}"
197
+ s = rand.to_s.force_encoding(Encoding::US_ASCII).freeze
198
+ if s.respond_to?(:-@)
199
+ if (-s).equal?(s) && (-s.dup).equal?(s)
200
+ def try_index(f)
201
+ if (p = @index[f])
202
+ -(File.join(p, f).freeze)
203
+ end
204
+ end
205
+ else
206
+ def try_index(f)
207
+ if (p = @index[f])
208
+ -File.join(p, f).untaint
209
+ end
210
+ end
211
+ end
212
+ else
213
+ def try_index(f)
214
+ if (p = @index[f])
215
+ File.join(p, f)
216
+ end
200
217
  end
201
218
  end
202
219
 
@@ -26,7 +26,7 @@ module Bootsnap
26
26
  class LoadedFeaturesIndex
27
27
  def initialize
28
28
  @lfi = {}
29
- @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
29
+ @mutex = Mutex.new
30
30
 
31
31
  # In theory the user could mutate $LOADED_FEATURES and invalidate our
32
32
  # cache. If this ever comes up in practice — or if you, the
@@ -33,10 +33,10 @@ module Bootsnap
33
33
  requirables = []
34
34
  walk(path, nil) do |relative_path, absolute_path, is_directory|
35
35
  if is_directory
36
- dirs << relative_path
36
+ dirs << os_path(relative_path)
37
37
  !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
38
38
  elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
39
- requirables << relative_path
39
+ requirables << os_path(relative_path)
40
40
  end
41
41
  end
42
42
  [requirables, dirs]
@@ -45,7 +45,7 @@ module Bootsnap
45
45
  def walk(absolute_dir_path, relative_dir_path, &block)
46
46
  Dir.foreach(absolute_dir_path) do |name|
47
47
  next if name.start_with?('.')
48
- relative_path = relative_dir_path ? "#{relative_dir_path}/#{name}" : name.freeze
48
+ relative_path = relative_dir_path ? File.join(relative_dir_path, name) : name
49
49
 
50
50
  absolute_path = "#{absolute_dir_path}/#{name}"
51
51
  if File.directory?(absolute_path)
@@ -57,6 +57,17 @@ module Bootsnap
57
57
  end
58
58
  end
59
59
  end
60
+
61
+ if RUBY_VERSION >= '3.1'
62
+ def os_path(path)
63
+ path.freeze
64
+ end
65
+ else
66
+ def os_path(path)
67
+ path.force_encoding(Encoding::US_ASCII) if path.ascii_only?
68
+ path.freeze
69
+ end
70
+ end
60
71
  end
61
72
  end
62
73
  end
@@ -12,8 +12,7 @@ module Bootsnap
12
12
 
13
13
  def initialize(store_path)
14
14
  @store_path = store_path
15
- # TODO: Remove conditional once Ruby 2.2 support is dropped.
16
- @txn_mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new
15
+ @txn_mutex = Mutex.new
17
16
  @dirty = false
18
17
  load_data
19
18
  end
@@ -63,12 +62,18 @@ module Bootsnap
63
62
 
64
63
  def load_data
65
64
  @data = begin
66
- MessagePack.load(File.binread(@store_path))
67
- # handle malformed data due to upgrade incompatibility
68
- rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
69
- {}
70
- rescue ArgumentError => e
71
- e.message =~ /negative array size/ ? {} : raise
65
+ File.open(@store_path, encoding: Encoding::BINARY) do |io|
66
+ MessagePack.load(io)
67
+ end
68
+ # handle malformed data due to upgrade incompatibility
69
+ rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
70
+ {}
71
+ rescue ArgumentError => error
72
+ if error.message =~ /negative array size/
73
+ {}
74
+ else
75
+ raise
76
+ end
72
77
  end
73
78
  end
74
79
 
@@ -80,7 +85,9 @@ module Bootsnap
80
85
  exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY
81
86
  # `encoding:` looks redundant wrt `binwrite`, but necessary on windows
82
87
  # because binary is part of mode.
83
- File.binwrite(tmp, MessagePack.dump(@data), mode: exclusive_write, encoding: Encoding::BINARY)
88
+ File.open(tmp, mode: exclusive_write, encoding: Encoding::BINARY) do |io|
89
+ MessagePack.dump(@data, io, freeze: true)
90
+ end
84
91
  FileUtils.mv(tmp, @store_path)
85
92
  rescue Errno::EEXIST
86
93
  retry
@@ -1,39 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative('../bootsnap')
3
3
 
4
- env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
5
- development_mode = ['', nil, 'development'].include?(env)
6
-
7
- cache_dir = ENV['BOOTSNAP_CACHE_DIR']
8
- unless cache_dir
9
- config_dir_frame = caller.detect do |line|
10
- line.include?('/config/')
11
- end
12
-
13
- unless config_dir_frame
14
- $stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
15
- $stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
16
- $stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
17
-
18
- raise("couldn't infer bootsnap cache directory")
19
- end
20
-
21
- path = config_dir_frame.split(/:\d+:/).first
22
- path = File.dirname(path) until File.basename(path) == 'config'
23
- app_root = File.dirname(path)
24
-
25
- cache_dir = File.join(app_root, 'tmp', 'cache')
26
- end
27
-
28
- ruby_version = Gem::Version.new(RUBY_VERSION)
29
- iseq_cache_enabled = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
30
-
31
- Bootsnap.setup(
32
- cache_dir: cache_dir,
33
- development_mode: development_mode,
34
- load_path_cache: true,
35
- autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
36
- disable_trace: false,
37
- compile_cache_iseq: iseq_cache_enabled,
38
- compile_cache_yaml: true,
39
- )
4
+ Bootsnap.default_setup
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Bootsnap
3
- VERSION = "1.5.1"
3
+ VERSION = "1.7.2"
4
4
  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.5.1
4
+ version: 1.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-10 00:00:00.000000000 Z
11
+ date: 2021-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -113,6 +113,7 @@ files:
113
113
  - lib/bootsnap.rb
114
114
  - lib/bootsnap/bundler.rb
115
115
  - lib/bootsnap/cli.rb
116
+ - lib/bootsnap/cli/worker_pool.rb
116
117
  - lib/bootsnap/compile_cache.rb
117
118
  - lib/bootsnap/compile_cache/iseq.rb
118
119
  - lib/bootsnap/compile_cache/yaml.rb
@@ -120,7 +121,6 @@ files:
120
121
  - lib/bootsnap/load_path_cache.rb
121
122
  - lib/bootsnap/load_path_cache/cache.rb
122
123
  - lib/bootsnap/load_path_cache/change_observer.rb
123
- - lib/bootsnap/load_path_cache/core_ext/active_support.rb
124
124
  - lib/bootsnap/load_path_cache/core_ext/kernel_require.rb
125
125
  - lib/bootsnap/load_path_cache/core_ext/loaded_features.rb
126
126
  - lib/bootsnap/load_path_cache/loaded_features_index.rb
@@ -1,107 +0,0 @@
1
- # frozen_string_literal: true
2
- module Bootsnap
3
- module LoadPathCache
4
- module CoreExt
5
- module ActiveSupport
6
- def self.without_bootsnap_cache
7
- prev = Thread.current[:without_bootsnap_cache] || false
8
- Thread.current[:without_bootsnap_cache] = true
9
- yield
10
- ensure
11
- Thread.current[:without_bootsnap_cache] = prev
12
- end
13
-
14
- def self.allow_bootsnap_retry(allowed)
15
- prev = Thread.current[:without_bootsnap_retry] || false
16
- Thread.current[:without_bootsnap_retry] = !allowed
17
- yield
18
- ensure
19
- Thread.current[:without_bootsnap_retry] = prev
20
- end
21
-
22
- module ClassMethods
23
- def autoload_paths=(o)
24
- super
25
- Bootsnap::LoadPathCache.autoload_paths_cache.reinitialize(o)
26
- end
27
-
28
- def search_for_file(path)
29
- return super if Thread.current[:without_bootsnap_cache]
30
- begin
31
- Bootsnap::LoadPathCache.autoload_paths_cache.find(path)
32
- rescue Bootsnap::LoadPathCache::ReturnFalse
33
- nil # doesn't really apply here
34
- rescue Bootsnap::LoadPathCache::FallbackScan
35
- nil # doesn't really apply here
36
- end
37
- end
38
-
39
- def autoloadable_module?(path_suffix)
40
- Bootsnap::LoadPathCache.autoload_paths_cache.load_dir(path_suffix)
41
- end
42
-
43
- def remove_constant(const)
44
- CoreExt::ActiveSupport.without_bootsnap_cache { super }
45
- end
46
-
47
- def require_or_load(*)
48
- CoreExt::ActiveSupport.allow_bootsnap_retry(true) do
49
- super
50
- end
51
- end
52
-
53
- # If we can't find a constant using the patched implementation of
54
- # search_for_file, try again with the default implementation.
55
- #
56
- # These methods call search_for_file, and we want to modify its
57
- # behaviour. The gymnastics here are a bit awkward, but it prevents
58
- # 200+ lines of monkeypatches.
59
- def load_missing_constant(from_mod, const_name)
60
- CoreExt::ActiveSupport.allow_bootsnap_retry(false) do
61
- super
62
- end
63
- rescue NameError => e
64
- raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
65
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
66
-
67
- # This function can end up called recursively, we only want to
68
- # retry at the top-level.
69
- raise(e) if Thread.current[:without_bootsnap_retry]
70
- # If we already had cache disabled, there's no use retrying
71
- raise(e) if Thread.current[:without_bootsnap_cache]
72
- # NoMethodError is a NameError, but we only want to handle actual
73
- # NameError instances.
74
- raise(e) unless e.class == NameError
75
- # We can only confidently handle cases when *this* constant fails
76
- # to load, not other constants referred to by it.
77
- raise(e) unless e.name == const_name
78
- # If the constant was actually loaded, something else went wrong?
79
- raise(e) if from_mod.const_defined?(const_name)
80
- CoreExt::ActiveSupport.without_bootsnap_cache { super }
81
- end
82
-
83
- # Signature has changed a few times over the years; easiest to not
84
- # reiterate it with version polymorphism here...
85
- def depend_on(*)
86
- super
87
- rescue LoadError => e
88
- raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR)
89
- e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
90
-
91
- # If we already had cache disabled, there's no use retrying
92
- raise(e) if Thread.current[:without_bootsnap_cache]
93
- CoreExt::ActiveSupport.without_bootsnap_cache { super }
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end
100
-
101
- module ActiveSupport
102
- module Dependencies
103
- class << self
104
- prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods)
105
- end
106
- end
107
- end