bootsnap 1.1.0-java

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