ed-precompiled_bootsnap 1.18.6-arm64-darwin

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,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+
5
+ if %w[ruby truffleruby].include?(RUBY_ENGINE)
6
+ have_func "fdatasync", "unistd.h"
7
+
8
+ unless RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
9
+ append_cppflags ["-D_GNU_SOURCE"] # Needed of O_NOATIME
10
+ end
11
+
12
+ append_cflags ["-O3", "-std=c99"]
13
+
14
+ # ruby.h has some -Wpedantic fails in some cases
15
+ # (e.g. https://github.com/rails/bootsnap/issues/15)
16
+ unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
17
+ append_cflags([
18
+ "-Wall",
19
+ "-Werror",
20
+ "-Wextra",
21
+ "-Wpedantic",
22
+
23
+ "-Wno-unused-parameter", # VALUE self has to be there but we don't care what it is.
24
+ "-Wno-keyword-macro", # hiding return
25
+ "-Wno-gcc-compat", # ruby.h 2.6.0 on macos 10.14, dunno
26
+ "-Wno-compound-token-split-by-macro",
27
+ ])
28
+ end
29
+
30
+ create_makefile("bootsnap/bootsnap")
31
+ else
32
+ File.write("Makefile", dummy_makefile($srcdir).join)
33
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ ruby_version = /(\d+\.\d+)/.match(::RUBY_VERSION)
2
+
3
+ begin
4
+ require "bootsnap/#{ruby_version}/bootsnap"
5
+ rescue LoadError
6
+ require "bootsnap/bootsnap"
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bootsnap
4
+ extend self
5
+
6
+ def bundler?
7
+ return false unless defined?(::Bundler)
8
+
9
+ # Bundler environment variable
10
+ %w(BUNDLE_BIN_PATH BUNDLE_GEMFILE).each do |current|
11
+ return true if ENV.key?(current)
12
+ end
13
+
14
+ false
15
+ end
16
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+ require "rbconfig"
5
+ require "io/wait" unless IO.method_defined?(:wait_readable)
6
+
7
+ module Bootsnap
8
+ class CLI
9
+ class WorkerPool
10
+ class << self
11
+ def create(size:, jobs:)
12
+ size ||= default_size
13
+ if size > 0 && Process.respond_to?(:fork)
14
+ new(size: size, jobs: jobs)
15
+ else
16
+ Inline.new(jobs: jobs)
17
+ end
18
+ end
19
+
20
+ def default_size
21
+ nprocessors = Etc.nprocessors
22
+ size = [nprocessors, cpu_quota&.to_i || nprocessors].min
23
+ case size
24
+ when 0, 1
25
+ 0
26
+ else
27
+ if fork_defunct?
28
+ $stderr.puts "warning: faulty fork(2) detected, probably in cross platform docker builds. " \
29
+ "Disabling parallel compilation."
30
+ 0
31
+ else
32
+ size
33
+ end
34
+ end
35
+ end
36
+
37
+ def cpu_quota
38
+ if RbConfig::CONFIG["target_os"].include?("linux")
39
+ if File.exist?("/sys/fs/cgroup/cpu.max")
40
+ # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
41
+ cpu_max = File.read("/sys/fs/cgroup/cpu.max")
42
+ return nil if cpu_max.start_with?("max ") # no limit
43
+
44
+ max, period = cpu_max.split.map(&:to_f)
45
+ max / period
46
+ elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
47
+ # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
48
+ max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i
49
+ # If the cpu.cfs_quota_us is -1, cgroup does not adhere to any CPU time restrictions
50
+ # https://docs.kernel.org/scheduler/sched-bwc.html#management
51
+ return nil if max <= 0
52
+
53
+ period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f
54
+ max / period
55
+ end
56
+ end
57
+ end
58
+
59
+ def fork_defunct?
60
+ return true unless ::Process.respond_to?(:fork)
61
+
62
+ # Ref: https://github.com/rails/bootsnap/issues/495
63
+ # The second forked process will hang on some QEMU environments
64
+ r, w = IO.pipe
65
+ pids = 2.times.map do
66
+ ::Process.fork do
67
+ exit!(true)
68
+ end
69
+ end
70
+ w.close
71
+ r.wait_readable(1) # Wait at most 1s
72
+
73
+ defunct = false
74
+
75
+ pids.each do |pid|
76
+ _pid, status = ::Process.wait2(pid, ::Process::WNOHANG)
77
+ if status.nil? # Didn't exit in 1s
78
+ defunct = true
79
+ Process.kill(:KILL, pid)
80
+ ::Process.wait2(pid)
81
+ end
82
+ end
83
+
84
+ defunct
85
+ end
86
+ end
87
+
88
+ class Inline
89
+ def initialize(jobs: {})
90
+ @jobs = jobs
91
+ end
92
+
93
+ def push(job, *args)
94
+ @jobs.fetch(job).call(*args)
95
+ nil
96
+ end
97
+
98
+ def spawn
99
+ # noop
100
+ end
101
+
102
+ def shutdown
103
+ # noop
104
+ end
105
+ end
106
+
107
+ class Worker
108
+ attr_reader :to_io, :pid
109
+
110
+ def initialize(jobs)
111
+ @jobs = jobs
112
+ @pipe_out, @to_io = IO.pipe(binmode: true)
113
+ # Set the writer encoding to binary since IO.pipe only sets it for the reader.
114
+ # https://github.com/rails/rails/issues/16514#issuecomment-52313290
115
+ @to_io.set_encoding(Encoding::BINARY)
116
+
117
+ @pid = nil
118
+ end
119
+
120
+ def write(message, block: true)
121
+ payload = Marshal.dump(message)
122
+ if block
123
+ to_io.write(payload)
124
+ true
125
+ else
126
+ to_io.write_nonblock(payload, exception: false) != :wait_writable
127
+ end
128
+ end
129
+
130
+ def close
131
+ to_io.close
132
+ end
133
+
134
+ def work_loop
135
+ loop do
136
+ job, *args = Marshal.load(@pipe_out)
137
+ return if job == :exit
138
+
139
+ @jobs.fetch(job).call(*args)
140
+ end
141
+ rescue IOError
142
+ nil
143
+ end
144
+
145
+ def spawn
146
+ @pid = Process.fork do
147
+ to_io.close
148
+ work_loop
149
+ exit!(0)
150
+ end
151
+ @pipe_out.close
152
+ true
153
+ end
154
+ end
155
+
156
+ def initialize(size:, jobs: {})
157
+ @size = size
158
+ @jobs = jobs
159
+ @queue = Queue.new
160
+ @pids = []
161
+ end
162
+
163
+ def spawn
164
+ @workers = @size.times.map { Worker.new(@jobs) }
165
+ @workers.each(&:spawn)
166
+ @dispatcher_thread = Thread.new { dispatch_loop }
167
+ @dispatcher_thread.abort_on_exception = true
168
+ true
169
+ end
170
+
171
+ def dispatch_loop
172
+ loop do
173
+ case job = @queue.pop
174
+ when nil
175
+ @workers.each do |worker|
176
+ worker.write([:exit])
177
+ worker.close
178
+ end
179
+ return true
180
+ else
181
+ unless @workers.sample.write(job, block: false)
182
+ free_worker.write(job)
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ def free_worker
189
+ IO.select(nil, @workers)[1].sample
190
+ end
191
+
192
+ def push(*args)
193
+ @queue.push(args)
194
+ nil
195
+ end
196
+
197
+ def shutdown
198
+ @queue.close
199
+ @dispatcher_thread.join
200
+ @workers.each do |worker|
201
+ _pid, status = Process.wait2(worker.pid)
202
+ return status.exitstatus unless status.success?
203
+ end
204
+ nil
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bootsnap"
4
+ require "bootsnap/cli/worker_pool"
5
+ require "optparse"
6
+ require "fileutils"
7
+
8
+ module Bootsnap
9
+ class CLI
10
+ unless Regexp.method_defined?(:match?)
11
+ module RegexpMatchBackport
12
+ refine Regexp do
13
+ def match?(string)
14
+ !!match(string)
15
+ end
16
+ end
17
+ end
18
+ using RegexpMatchBackport
19
+ end
20
+
21
+ attr_reader :cache_dir, :argv
22
+
23
+ attr_accessor :compile_gemfile, :exclude, :verbose, :iseq, :yaml, :json, :jobs
24
+
25
+ def initialize(argv)
26
+ @argv = argv
27
+ self.cache_dir = ENV.fetch("BOOTSNAP_CACHE_DIR", "tmp/cache")
28
+ self.compile_gemfile = false
29
+ self.exclude = nil
30
+ self.verbose = false
31
+ self.jobs = nil
32
+ self.iseq = true
33
+ self.yaml = true
34
+ self.json = true
35
+ end
36
+
37
+ def precompile_command(*sources)
38
+ require "bootsnap/compile_cache"
39
+
40
+ fix_default_encoding do
41
+ Bootsnap::CompileCache.setup(
42
+ cache_dir: cache_dir,
43
+ iseq: iseq,
44
+ yaml: yaml,
45
+ json: json,
46
+ revalidation: true,
47
+ )
48
+
49
+ @work_pool = WorkerPool.create(size: jobs, jobs: {
50
+ ruby: method(:precompile_ruby),
51
+ yaml: method(:precompile_yaml),
52
+ json: method(:precompile_json),
53
+ })
54
+ @work_pool.spawn
55
+
56
+ main_sources = sources.map { |d| File.expand_path(d) }
57
+ precompile_ruby_files(main_sources)
58
+ precompile_yaml_files(main_sources)
59
+ precompile_json_files(main_sources)
60
+
61
+ if compile_gemfile
62
+ # Gems that include JSON or YAML files usually don't put them in `lib/`.
63
+ # So we look at the gem root.
64
+ # Similarly, gems that include Rails engines generally file Ruby files in `app/`.
65
+ # However some gems embed their tests, they're very unlikely to be loaded, so not worth precompiling.
66
+ gem_exclude = Regexp.union([exclude, "/spec/", "/test/", "/features/"].compact)
67
+
68
+ gem_pattern = %r{^#{Regexp.escape(Bundler.bundle_path.to_s)}/?(?:bundler/)?gems/[^/]+}
69
+ gem_paths = $LOAD_PATH.map { |p| p[gem_pattern] || p }.uniq
70
+
71
+ precompile_ruby_files(gem_paths, exclude: gem_exclude)
72
+ precompile_yaml_files(gem_paths, exclude: gem_exclude)
73
+ precompile_json_files(gem_paths, exclude: gem_exclude)
74
+ end
75
+
76
+ if (exitstatus = @work_pool.shutdown)
77
+ exit(exitstatus)
78
+ end
79
+ end
80
+ 0
81
+ end
82
+
83
+ dir_sort = begin
84
+ Dir[__FILE__, sort: false]
85
+ true
86
+ rescue ArgumentError, TypeError
87
+ false
88
+ end
89
+
90
+ if dir_sort
91
+ def list_files(path, pattern)
92
+ if File.directory?(path)
93
+ Dir[File.join(path, pattern), sort: false]
94
+ elsif File.exist?(path)
95
+ [path]
96
+ else
97
+ []
98
+ end
99
+ end
100
+ else
101
+ def list_files(path, pattern)
102
+ if File.directory?(path)
103
+ Dir[File.join(path, pattern)]
104
+ elsif File.exist?(path)
105
+ [path]
106
+ else
107
+ []
108
+ end
109
+ end
110
+ end
111
+
112
+ def run
113
+ parser.parse!(argv)
114
+ command = argv.shift
115
+ method = "#{command}_command"
116
+ if respond_to?(method)
117
+ public_send(method, *argv)
118
+ else
119
+ invalid_usage!("Unknown command: #{command}")
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def precompile_yaml_files(load_paths, exclude: self.exclude)
126
+ return unless yaml
127
+
128
+ load_paths.each do |path|
129
+ if !exclude || !exclude.match?(path)
130
+ list_files(path, "**/*.{yml,yaml}").each do |yaml_file|
131
+ # We ignore hidden files to not match the various .ci.yml files
132
+ if !File.basename(yaml_file).start_with?(".") && (!exclude || !exclude.match?(yaml_file))
133
+ @work_pool.push(:yaml, yaml_file)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def precompile_yaml(*yaml_files)
141
+ Array(yaml_files).each do |yaml_file|
142
+ if CompileCache::YAML.precompile(yaml_file) && verbose
143
+ $stderr.puts(yaml_file)
144
+ end
145
+ end
146
+ end
147
+
148
+ def precompile_json_files(load_paths, exclude: self.exclude)
149
+ return unless json
150
+
151
+ load_paths.each do |path|
152
+ if !exclude || !exclude.match?(path)
153
+ list_files(path, "**/*.json").each do |json_file|
154
+ # We ignore hidden files to not match the various .config.json files
155
+ if !File.basename(json_file).start_with?(".") && (!exclude || !exclude.match?(json_file))
156
+ @work_pool.push(:json, json_file)
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ def precompile_json(*json_files)
164
+ Array(json_files).each do |json_file|
165
+ if CompileCache::JSON.precompile(json_file) && verbose
166
+ $stderr.puts(json_file)
167
+ end
168
+ end
169
+ end
170
+
171
+ def precompile_ruby_files(load_paths, exclude: self.exclude)
172
+ return unless iseq
173
+
174
+ load_paths.each do |path|
175
+ if !exclude || !exclude.match?(path)
176
+ list_files(path, "**/{*.rb,*.rake,Rakefile}").each do |ruby_file|
177
+ if !exclude || !exclude.match?(ruby_file)
178
+ @work_pool.push(:ruby, ruby_file)
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ def precompile_ruby(*ruby_files)
186
+ Array(ruby_files).each do |ruby_file|
187
+ if CompileCache::ISeq.precompile(ruby_file) && verbose
188
+ $stderr.puts(ruby_file)
189
+ end
190
+ end
191
+ end
192
+
193
+ def fix_default_encoding
194
+ if Encoding.default_external == Encoding::US_ASCII
195
+ Encoding.default_external = Encoding::UTF_8
196
+ begin
197
+ yield
198
+ ensure
199
+ Encoding.default_external = Encoding::US_ASCII
200
+ end
201
+ else
202
+ yield
203
+ end
204
+ end
205
+
206
+ def invalid_usage!(message)
207
+ $stderr.puts message
208
+ $stderr.puts
209
+ $stderr.puts parser
210
+ 1
211
+ end
212
+
213
+ def cache_dir=(dir)
214
+ @cache_dir = File.expand_path(File.join(dir, "bootsnap/compile-cache"))
215
+ end
216
+
217
+ def exclude_pattern(pattern)
218
+ (@exclude_patterns ||= []) << Regexp.new(pattern)
219
+ self.exclude = Regexp.union(@exclude_patterns)
220
+ end
221
+
222
+ def parser
223
+ @parser ||= OptionParser.new do |opts|
224
+ opts.version = Bootsnap::VERSION
225
+ opts.program_name = "bootsnap"
226
+
227
+ opts.banner = "Usage: bootsnap COMMAND [ARGS]"
228
+ opts.separator ""
229
+ opts.separator "GLOBAL OPTIONS"
230
+ opts.separator ""
231
+
232
+ help = <<~HELP
233
+ Path to the bootsnap cache directory. Defaults to tmp/cache
234
+ HELP
235
+ opts.on("--cache-dir DIR", help.strip) do |dir|
236
+ self.cache_dir = dir
237
+ end
238
+
239
+ help = <<~HELP
240
+ Print precompiled paths.
241
+ HELP
242
+ opts.on("--verbose", "-v", help.strip) do
243
+ self.verbose = true
244
+ end
245
+
246
+ help = <<~HELP
247
+ Number of workers to use. Default to number of processors, set to 0 to disable multi-processing.
248
+ HELP
249
+ opts.on("--jobs JOBS", "-j", help.strip) do |jobs|
250
+ self.jobs = Integer(jobs)
251
+ end
252
+
253
+ opts.separator ""
254
+ opts.separator "COMMANDS"
255
+ opts.separator ""
256
+ opts.separator " precompile [DIRECTORIES...]: Precompile all .rb files in the passed directories"
257
+
258
+ help = <<~HELP
259
+ Precompile the gems in Gemfile
260
+ HELP
261
+ opts.on("--gemfile", help) { self.compile_gemfile = true }
262
+
263
+ help = <<~HELP
264
+ Path pattern to not precompile. e.g. --exclude 'aws-sdk|google-api'
265
+ HELP
266
+ opts.on("--exclude PATTERN", help) { |pattern| exclude_pattern(pattern) }
267
+
268
+ help = <<~HELP
269
+ Disable ISeq (.rb) precompilation.
270
+ HELP
271
+ opts.on("--no-iseq", help) { self.iseq = false }
272
+
273
+ help = <<~HELP
274
+ Disable YAML precompilation.
275
+ HELP
276
+ opts.on("--no-yaml", help) { self.yaml = false }
277
+
278
+ help = <<~HELP
279
+ Disable JSON precompilation.
280
+ HELP
281
+ opts.on("--no-json", help) { self.json = false }
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bootsnap/bootsnap_ext"
4
+ require "zlib"
5
+
6
+ module Bootsnap
7
+ module CompileCache
8
+ module ISeq
9
+ class << self
10
+ attr_reader(:cache_dir)
11
+
12
+ def cache_dir=(cache_dir)
13
+ @cache_dir = cache_dir.end_with?("/") ? "#{cache_dir}iseq" : "#{cache_dir}-iseq"
14
+ end
15
+
16
+ def supported?
17
+ CompileCache.supported? && defined?(RubyVM)
18
+ end
19
+ end
20
+
21
+ has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250
22
+ if defined? RubyVM::InstructionSequence
23
+ RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary
24
+ end
25
+ false
26
+ rescue TypeError
27
+ true
28
+ end
29
+
30
+ if has_ruby_bug_18250
31
+ def self.input_to_storage(_, path)
32
+ iseq = begin
33
+ RubyVM::InstructionSequence.compile_file(path)
34
+ rescue SyntaxError
35
+ return UNCOMPILABLE # syntax error
36
+ end
37
+
38
+ begin
39
+ iseq.to_binary
40
+ rescue TypeError
41
+ UNCOMPILABLE # ruby bug #18250
42
+ end
43
+ end
44
+ else
45
+ def self.input_to_storage(_, path)
46
+ RubyVM::InstructionSequence.compile_file(path).to_binary
47
+ rescue SyntaxError
48
+ UNCOMPILABLE # syntax error
49
+ end
50
+ end
51
+
52
+ def self.storage_to_output(binary, _args)
53
+ RubyVM::InstructionSequence.load_from_binary(binary)
54
+ rescue RuntimeError => error
55
+ if error.message == "broken binary format"
56
+ $stderr.puts("[Bootsnap::CompileCache] warning: rejecting broken binary")
57
+ nil
58
+ else
59
+ raise
60
+ end
61
+ end
62
+
63
+ def self.fetch(path, cache_dir: ISeq.cache_dir)
64
+ Bootsnap::CompileCache::Native.fetch(
65
+ cache_dir,
66
+ path.to_s,
67
+ Bootsnap::CompileCache::ISeq,
68
+ nil,
69
+ )
70
+ end
71
+
72
+ def self.precompile(path)
73
+ Bootsnap::CompileCache::Native.precompile(
74
+ cache_dir,
75
+ path.to_s,
76
+ Bootsnap::CompileCache::ISeq,
77
+ )
78
+ end
79
+
80
+ def self.input_to_output(_data, _kwargs)
81
+ nil # ruby handles this
82
+ end
83
+
84
+ module InstructionSequenceMixin
85
+ def load_iseq(path)
86
+ # Having coverage enabled prevents iseq dumping/loading.
87
+ return nil if defined?(Coverage) && Coverage.running?
88
+
89
+ Bootsnap::CompileCache::ISeq.fetch(path.to_s)
90
+ rescue RuntimeError => error
91
+ if error.message =~ /unmatched platform/
92
+ puts("unmatched platform for file #{path}")
93
+ end
94
+ raise
95
+ end
96
+
97
+ def compile_option=(hash)
98
+ super(hash)
99
+ Bootsnap::CompileCache::ISeq.compile_option_updated
100
+ end
101
+ end
102
+
103
+ def self.compile_option_updated
104
+ option = RubyVM::InstructionSequence.compile_option
105
+ crc = Zlib.crc32(option.inspect)
106
+ Bootsnap::CompileCache::Native.compile_option_crc32 = crc
107
+ end
108
+ compile_option_updated if supported?
109
+
110
+ def self.install!(cache_dir)
111
+ Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
112
+
113
+ return unless supported?
114
+
115
+ Bootsnap::CompileCache::ISeq.compile_option_updated
116
+
117
+ class << RubyVM::InstructionSequence
118
+ prepend(InstructionSequenceMixin)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end