bootsnap 1.1.0-java

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.
@@ -0,0 +1,6 @@
1
+ #ifndef BOOTSNAP_H
2
+ #define BOOTSNAP_H 1
3
+
4
+ /* doesn't expose anything */
5
+
6
+ #endif /* BOOTSNAP_H */
@@ -0,0 +1,17 @@
1
+ require "mkmf"
2
+ $CFLAGS << ' -O3 '
3
+ $CFLAGS << ' -std=c99'
4
+
5
+ # ruby.h has some -Wpedantic fails in some cases
6
+ # (e.g. https://github.com/Shopify/bootsnap/issues/15)
7
+ unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
8
+ $CFLAGS << ' -Wall'
9
+ $CFLAGS << ' -Werror'
10
+ $CFLAGS << ' -Wextra'
11
+ $CFLAGS << ' -Wpedantic'
12
+
13
+ $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
14
+ $CFLAGS << ' -Wno-keyword-macro' # hiding return
15
+ end
16
+
17
+ create_makefile("bootsnap/bootsnap")
@@ -0,0 +1,39 @@
1
+ require_relative 'bootsnap/version'
2
+ require_relative 'bootsnap/load_path_cache'
3
+ require_relative 'bootsnap/compile_cache'
4
+
5
+ module Bootsnap
6
+ InvalidConfiguration = Class.new(StandardError)
7
+
8
+ def self.setup(
9
+ cache_dir:,
10
+ development_mode: true,
11
+ load_path_cache: true,
12
+ autoload_paths_cache: true,
13
+ disable_trace: false,
14
+ compile_cache_iseq: true,
15
+ compile_cache_yaml: true
16
+ )
17
+ if autoload_paths_cache && !load_path_cache
18
+ raise InvalidConfiguration, "feature 'autoload_paths_cache' depends on feature 'load_path_cache'"
19
+ end
20
+
21
+ setup_disable_trace if disable_trace
22
+
23
+ Bootsnap::LoadPathCache.setup(
24
+ cache_path: cache_dir + '/bootsnap-load-path-cache',
25
+ development_mode: development_mode,
26
+ active_support: autoload_paths_cache
27
+ ) if load_path_cache
28
+
29
+ Bootsnap::CompileCache.setup(
30
+ cache_dir: cache_dir + '/bootsnap-compile-cache',
31
+ iseq: compile_cache_iseq,
32
+ yaml: compile_cache_yaml
33
+ )
34
+ end
35
+
36
+ def self.setup_disable_trace
37
+ RubyVM::InstructionSequence.compile_option = { trace_instruction: false }
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Bootsnap
2
+ module CompileCache
3
+ def self.setup(cache_dir:, iseq:, yaml:)
4
+ if iseq
5
+ require_relative 'compile_cache/iseq'
6
+ Bootsnap::CompileCache::ISeq.install!(cache_dir)
7
+ end
8
+
9
+ if yaml
10
+ require_relative 'compile_cache/yaml'
11
+ Bootsnap::CompileCache::YAML.install!(cache_dir)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,71 @@
1
+ require 'bootsnap/bootsnap'
2
+ require 'zlib'
3
+
4
+ module Bootsnap
5
+ module CompileCache
6
+ module ISeq
7
+ class << self
8
+ attr_accessor :cache_dir
9
+ end
10
+
11
+ def self.input_to_storage(_, path)
12
+ RubyVM::InstructionSequence.compile_file(path).to_binary
13
+ rescue SyntaxError
14
+ raise Uncompilable, 'syntax error'
15
+ end
16
+
17
+ def self.storage_to_output(binary)
18
+ RubyVM::InstructionSequence.load_from_binary(binary)
19
+ rescue RuntimeError => e
20
+ if e.message == 'broken binary format'
21
+ STDERR.puts "[Bootsnap::CompileCache] warning: rejecting broken binary"
22
+ return nil
23
+ else
24
+ raise
25
+ end
26
+ end
27
+
28
+ def self.input_to_output(_)
29
+ nil # ruby handles this
30
+ end
31
+
32
+ module InstructionSequenceMixin
33
+ def load_iseq(path)
34
+ # Having coverage enabled prevents iseq dumping/loading.
35
+ return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running?
36
+
37
+ Bootsnap::CompileCache::Native.fetch(
38
+ Bootsnap::CompileCache::ISeq.cache_dir,
39
+ path.to_s,
40
+ Bootsnap::CompileCache::ISeq
41
+ )
42
+ rescue RuntimeError => e
43
+ if e.message =~ /unmatched platform/
44
+ puts "unmatched platform for file #{path}"
45
+ end
46
+ raise
47
+ end
48
+
49
+ def compile_option=(hash)
50
+ super(hash)
51
+ Bootsnap::CompileCache::ISeq.compile_option_updated
52
+ end
53
+ end
54
+
55
+ def self.compile_option_updated
56
+ option = RubyVM::InstructionSequence.compile_option
57
+ crc = Zlib.crc32(option.inspect)
58
+ Bootsnap::CompileCache::Native.compile_option_crc32 = crc
59
+ end
60
+
61
+ def self.install!(cache_dir)
62
+ Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
63
+ Bootsnap::CompileCache::ISeq.compile_option_updated
64
+ class << RubyVM::InstructionSequence
65
+ prepend InstructionSequenceMixin
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,57 @@
1
+ require 'bootsnap/bootsnap'
2
+
3
+ module Bootsnap
4
+ module CompileCache
5
+ module YAML
6
+ class << self
7
+ attr_accessor :msgpack_factory
8
+ end
9
+
10
+ def self.input_to_storage(contents, _)
11
+ obj = ::YAML.load(contents)
12
+ msgpack_factory.packer.write(obj).to_s
13
+ rescue NoMethodError, RangeError
14
+ # if the object included things that we can't serialize, fall back to
15
+ # Marshal. It's a bit slower, but can encode anything yaml can.
16
+ # NoMethodError is unexpected types; RangeError is Bignums
17
+ return Marshal.dump(obj)
18
+ end
19
+
20
+ def self.storage_to_output(data)
21
+ # This could have a meaning in messagepack, and we're being a little lazy
22
+ # about it. -- but a leading 0x04 would indicate the contents of the YAML
23
+ # is a positive integer, which is rare, to say the least.
24
+ if data[0] == 0x04.chr && data[1] == 0x08.chr
25
+ Marshal.load(data)
26
+ else
27
+ msgpack_factory.unpacker.feed(data).read
28
+ end
29
+ end
30
+
31
+ def self.input_to_output(data)
32
+ ::YAML.load(data)
33
+ end
34
+
35
+ def self.install!(cache_dir)
36
+ require 'yaml'
37
+ require 'msgpack'
38
+
39
+ # MessagePack serializes symbols as strings by default.
40
+ # We want them to roundtrip cleanly, so we use a custom factory.
41
+ # see: https://github.com/msgpack/msgpack-ruby/pull/122
42
+ factory = MessagePack::Factory.new
43
+ factory.register_type(0x00, Symbol)
44
+ Bootsnap::CompileCache::YAML.msgpack_factory = factory
45
+
46
+ klass = class << ::YAML; self; end
47
+ klass.send(:define_method, :load_file) do |path|
48
+ Bootsnap::CompileCache::Native.fetch(
49
+ cache_dir,
50
+ path.to_s,
51
+ Bootsnap::CompileCache::YAML
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,44 @@
1
+ module Bootsnap
2
+ module ExplicitRequire
3
+ ARCHDIR = RbConfig::CONFIG['archdir']
4
+ RUBYLIBDIR = RbConfig::CONFIG['rubylibdir']
5
+ DLEXT = RbConfig::CONFIG['DLEXT']
6
+
7
+ def self.from_self(feature)
8
+ require_relative "../#{feature}"
9
+ end
10
+
11
+ def self.from_rubylibdir(feature)
12
+ require(File.join(RUBYLIBDIR, "#{feature}.rb"))
13
+ end
14
+
15
+ def self.from_archdir(feature)
16
+ require(File.join(ARCHDIR, "#{feature}.#{DLEXT}"))
17
+ end
18
+
19
+ # Given a set of gems, run a block with the LOAD_PATH narrowed to include
20
+ # only core ruby source paths and these gems -- that is, roughly,
21
+ # temporarily remove all gems not listed in this call from the LOAD_PATH.
22
+ #
23
+ # This is useful before bootsnap is fully-initialized to load gems that it
24
+ # depends on, without forcing full LOAD_PATH traversals.
25
+ def self.with_gems(*gems)
26
+ orig = $LOAD_PATH.dup
27
+ $LOAD_PATH.clear
28
+ gems.each do |gem|
29
+ pat = %r{
30
+ /
31
+ (gems|extensions/[^/]+/[^/]+) # "gems" or "extensions/x64_64-darwin16/2.3.0"
32
+ /
33
+ #{Regexp.escape(gem)}-(\h{12}|(\d+\.)) # msgpack-1.2.3 or msgpack-1234567890ab
34
+ }x
35
+ $LOAD_PATH.concat(orig.grep(pat))
36
+ end
37
+ $LOAD_PATH << ARCHDIR
38
+ $LOAD_PATH << RUBYLIBDIR
39
+ yield
40
+ ensure
41
+ $LOAD_PATH.replace(orig)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,52 @@
1
+ module Bootsnap
2
+ module LoadPathCache
3
+ ReturnFalse = Class.new(StandardError)
4
+ FallbackScan = Class.new(StandardError)
5
+
6
+ DOT_RB = '.rb'
7
+ DOT_SO = '.so'
8
+ SLASH = '/'
9
+
10
+ DL_EXTENSIONS = ::RbConfig::CONFIG
11
+ .values_at('DLEXT', 'DLEXT2')
12
+ .reject { |ext| !ext || ext.empty? }
13
+ .map { |ext| ".#{ext}" }
14
+ .freeze
15
+ DLEXT = DL_EXTENSIONS[0]
16
+ # This is nil on linux and darwin, but I think it's '.o' on some other
17
+ # platform. I'm not really sure which, but it seems better to replicate
18
+ # ruby's semantics as faithfully as possible.
19
+ DLEXT2 = DL_EXTENSIONS[1]
20
+
21
+ CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT]
22
+
23
+ class << self
24
+ attr_reader :load_path_cache, :autoload_paths_cache
25
+
26
+ def setup(cache_path:, development_mode:, active_support: true)
27
+ store = Store.new(cache_path)
28
+
29
+ @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode)
30
+ require_relative 'load_path_cache/core_ext/kernel_require'
31
+
32
+ if active_support
33
+ # this should happen after setting up the initial cache because it
34
+ # loads a lot of code. It's better to do after +require+ is optimized.
35
+ require 'active_support/dependencies'
36
+ @autoload_paths_cache = Cache.new(
37
+ store,
38
+ ::ActiveSupport::Dependencies.autoload_paths,
39
+ development_mode: development_mode
40
+ )
41
+ require_relative 'load_path_cache/core_ext/active_support'
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
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'
@@ -0,0 +1,191 @@
1
+ require_relative '../load_path_cache'
2
+ require_relative '../explicit_require'
3
+
4
+ module Bootsnap
5
+ module LoadPathCache
6
+ class Cache
7
+ AGE_THRESHOLD = 30 # seconds
8
+
9
+ def initialize(store, path_obj, development_mode: false)
10
+ @development_mode = development_mode
11
+ @store = store
12
+ @mutex = ::Thread::Mutex.new
13
+ @path_obj = path_obj
14
+ reinitialize
15
+ end
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)
21
+ reinitialize if stale?
22
+ @mutex.synchronize { @dirs[dir] }
23
+ end
24
+
25
+ # { 'enumerator' => nil, 'enumerator.so' => nil, ... }
26
+ BUILTIN_FEATURES = $LOADED_FEATURES.reduce({}) do |acc, feat|
27
+ # Builtin features are of the form 'enumerator.so'.
28
+ # All others include paths.
29
+ next acc unless feat.size < 20 && !feat.include?('/')
30
+
31
+ base = File.basename(feat, '.*') # enumerator.so -> enumerator
32
+ ext = File.extname(feat) # .so
33
+
34
+ acc[feat] = nil # enumerator.so
35
+ acc[base] = nil # enumerator
36
+
37
+ if [DOT_SO, *DL_EXTENSIONS].include?(ext)
38
+ DL_EXTENSIONS.each do |ext|
39
+ acc["#{base}#{ext}"] = nil # enumerator.bundle
40
+ end
41
+ end
42
+
43
+ acc
44
+ end.freeze
45
+
46
+ # Try to resolve this feature to an absolute path without traversing the
47
+ # loadpath.
48
+ def find(feature)
49
+ reinitialize if (@has_relative_paths && dir_changed?) || stale?
50
+ feature = feature.to_s
51
+ return feature if absolute_path?(feature)
52
+ return File.expand_path(feature) if feature.start_with?('./')
53
+ @mutex.synchronize do
54
+ x = search_index(feature)
55
+ return x if x
56
+
57
+ # Ruby has some built-in features that require lies about.
58
+ # For example, 'enumerator' is built in. If you require it, ruby
59
+ # returns false as if it were already loaded; however, there is no
60
+ # file to find on disk. We've pre-built a list of these, and we
61
+ # return false if any of them is loaded.
62
+ raise LoadPathCache::ReturnFalse if BUILTIN_FEATURES.key?(feature)
63
+
64
+ # The feature wasn't found on our preliminary search through the index.
65
+ # We resolve this differently depending on what the extension was.
66
+ case File.extname(feature)
67
+ # If the extension was one of the ones we explicitly cache (.rb and the
68
+ # native dynamic extension, e.g. .bundle or .so), we know it was a
69
+ # failure and there's nothing more we can do to find the file.
70
+ when '', *CACHED_EXTENSIONS # no extension, .rb, (.bundle or .so)
71
+ nil
72
+ # Ruby allows specifying native extensions as '.so' even when DLEXT
73
+ # is '.bundle'. This is where we handle that case.
74
+ when DOT_SO
75
+ x = search_index(feature[0..-4] + DLEXT)
76
+ return x if x
77
+ if DLEXT2
78
+ search_index(feature[0..-4] + DLEXT2)
79
+ end
80
+ else
81
+ # other, unknown extension. For example, `.rake`. Since we haven't
82
+ # cached these, we legitimately need to run the load path search.
83
+ raise LoadPathCache::FallbackScan
84
+ end
85
+ end
86
+ end
87
+
88
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
89
+ def absolute_path?(path)
90
+ path[1] == ':'
91
+ end
92
+ else
93
+ def absolute_path?(path)
94
+ path.start_with?(SLASH)
95
+ end
96
+ end
97
+
98
+ def unshift_paths(sender, *paths)
99
+ return unless sender == @path_obj
100
+ @mutex.synchronize { unshift_paths_locked(*paths) }
101
+ end
102
+
103
+ def push_paths(sender, *paths)
104
+ return unless sender == @path_obj
105
+ @mutex.synchronize { push_paths_locked(*paths) }
106
+ end
107
+
108
+ def each_requirable
109
+ @mutex.synchronize do
110
+ @index.each do |rel, entry|
111
+ yield "#{entry}/#{rel}"
112
+ end
113
+ end
114
+ end
115
+
116
+ def reinitialize(path_obj = @path_obj)
117
+ @mutex.synchronize do
118
+ @path_obj = path_obj
119
+ ChangeObserver.register(self, @path_obj)
120
+ @index = {}
121
+ @dirs = Hash.new(false)
122
+ @generated_at = now
123
+ push_paths_locked(*@path_obj)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def dir_changed?
130
+ @prev_dir ||= Dir.pwd
131
+ if @prev_dir == Dir.pwd
132
+ false
133
+ else
134
+ @prev_dir = Dir.pwd
135
+ true
136
+ end
137
+ end
138
+
139
+ def push_paths_locked(*paths)
140
+ @store.transaction do
141
+ paths.map(&:to_s).each do |path|
142
+ p = Path.new(path)
143
+ @has_relative_paths = true if p.relative?
144
+ next if p.non_directory?
145
+ entries, dirs = p.entries_and_dirs(@store)
146
+ # push -> low precedence -> set only if unset
147
+ dirs.each { |dir| @dirs[dir] ||= true }
148
+ entries.each { |rel| @index[rel] ||= p.expanded_path }
149
+ end
150
+ end
151
+ end
152
+
153
+ def unshift_paths_locked(*paths)
154
+ @store.transaction do
155
+ paths.map(&:to_s).reverse.each do |path|
156
+ p = Path.new(path)
157
+ next if p.non_directory?
158
+ entries, dirs = p.entries_and_dirs(@store)
159
+ # unshift -> high precedence -> unconditional set
160
+ dirs.each { |dir| @dirs[dir] = true }
161
+ entries.each { |rel| @index[rel] = p.expanded_path }
162
+ end
163
+ end
164
+ end
165
+
166
+ def stale?
167
+ @development_mode && @generated_at + AGE_THRESHOLD < now
168
+ end
169
+
170
+ def now
171
+ Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i
172
+ end
173
+
174
+ if DLEXT2
175
+ def search_index(f)
176
+ try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
177
+ end
178
+ else
179
+ def search_index(f)
180
+ try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
181
+ end
182
+ end
183
+
184
+ def try_index(f)
185
+ if p = @index[f]
186
+ p + '/' + f
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end