ruby_wasm 2.5.1-aarch64-linux-musl
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.
- checksums.yaml +7 -0
- data/.clang-format +8 -0
- data/CONTRIBUTING.md +128 -0
- data/Gemfile +17 -0
- data/LICENSE +21 -0
- data/NOTICE +1293 -0
- data/README.md +154 -0
- data/Rakefile +164 -0
- data/Steepfile +24 -0
- data/benchmarks/vm_deep_call.rb +55 -0
- data/docs/api.md +2 -0
- data/docs/cheat_sheet.md +195 -0
- data/docs/faq.md +25 -0
- data/exe/rbwasm +7 -0
- data/ext/.gitignore +2 -0
- data/ext/README.md +11 -0
- data/ext/extinit.c.erb +32 -0
- data/lib/ruby_wasm/3.1/ruby_wasm.so +0 -0
- data/lib/ruby_wasm/3.2/ruby_wasm.so +0 -0
- data/lib/ruby_wasm/3.3/ruby_wasm.so +0 -0
- data/lib/ruby_wasm/build/build_params.rb +3 -0
- data/lib/ruby_wasm/build/downloader.rb +18 -0
- data/lib/ruby_wasm/build/executor.rb +191 -0
- data/lib/ruby_wasm/build/product/baseruby.rb +37 -0
- data/lib/ruby_wasm/build/product/crossruby.rb +360 -0
- data/lib/ruby_wasm/build/product/libyaml.rb +70 -0
- data/lib/ruby_wasm/build/product/openssl.rb +93 -0
- data/lib/ruby_wasm/build/product/product.rb +39 -0
- data/lib/ruby_wasm/build/product/ruby_source.rb +103 -0
- data/lib/ruby_wasm/build/product/wasi_vfs.rb +45 -0
- data/lib/ruby_wasm/build/product/zlib.rb +70 -0
- data/lib/ruby_wasm/build/product.rb +8 -0
- data/lib/ruby_wasm/build/target.rb +24 -0
- data/lib/ruby_wasm/build/toolchain/wit_bindgen.rb +31 -0
- data/lib/ruby_wasm/build/toolchain.rb +193 -0
- data/lib/ruby_wasm/build.rb +92 -0
- data/lib/ruby_wasm/cli.rb +347 -0
- data/lib/ruby_wasm/packager/component_adapter/wasi_snapshot_preview1.command.wasm +0 -0
- data/lib/ruby_wasm/packager/component_adapter/wasi_snapshot_preview1.reactor.wasm +0 -0
- data/lib/ruby_wasm/packager/component_adapter.rb +14 -0
- data/lib/ruby_wasm/packager/core.rb +333 -0
- data/lib/ruby_wasm/packager/file_system.rb +160 -0
- data/lib/ruby_wasm/packager.rb +96 -0
- data/lib/ruby_wasm/rake_task.rb +60 -0
- data/lib/ruby_wasm/util.rb +15 -0
- data/lib/ruby_wasm/version.rb +3 -0
- data/lib/ruby_wasm.rb +34 -0
- data/package-lock.json +9777 -0
- data/package.json +12 -0
- data/rakelib/check.rake +37 -0
- data/rakelib/ci.rake +152 -0
- data/rakelib/doc.rake +29 -0
- data/rakelib/format.rake +35 -0
- data/rakelib/gem.rake +22 -0
- data/rakelib/packaging.rake +165 -0
- data/rakelib/version.rake +40 -0
- data/sig/open_uri.rbs +4 -0
- data/sig/ruby_wasm/build.rbs +327 -0
- data/sig/ruby_wasm/cli.rbs +51 -0
- data/sig/ruby_wasm/ext.rbs +26 -0
- data/sig/ruby_wasm/packager.rbs +122 -0
- data/sig/ruby_wasm/util.rbs +5 -0
- data/tools/clang-format-diff.sh +18 -0
- data/tools/exe/rbminify +12 -0
- data/tools/lib/syntax_tree/minify_ruby.rb +63 -0
- metadata +114 -0
@@ -0,0 +1,347 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "rbconfig"
|
3
|
+
|
4
|
+
module RubyWasm
|
5
|
+
class CLI
|
6
|
+
def initialize(stdout:, stderr:)
|
7
|
+
@stdout = stdout
|
8
|
+
@stderr = stderr
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(args)
|
12
|
+
available_commands = %w[build pack]
|
13
|
+
parser =
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.banner = <<~USAGE
|
16
|
+
Usage: rbwasm [options...] [command]
|
17
|
+
|
18
|
+
Available commands: #{available_commands.join(", ")}
|
19
|
+
USAGE
|
20
|
+
opts.version = RubyWasm::VERSION
|
21
|
+
opts.on("-h", "--help", "Prints this help") do
|
22
|
+
@stderr.puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
opts.on("--log-level LEVEL", "Log level") do |level|
|
26
|
+
RubyWasm.log_level = level.to_sym
|
27
|
+
end
|
28
|
+
end
|
29
|
+
parser.order!(args)
|
30
|
+
|
31
|
+
command = args.shift
|
32
|
+
case command
|
33
|
+
when "build"
|
34
|
+
build(args)
|
35
|
+
when "pack"
|
36
|
+
pack(args)
|
37
|
+
else
|
38
|
+
@stderr.puts "Unknown command: #{command}"
|
39
|
+
@stderr.puts parser
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def build(args)
|
45
|
+
# @type var options: cli_options
|
46
|
+
options = {
|
47
|
+
save_temps: false,
|
48
|
+
optimize: false,
|
49
|
+
remake: false,
|
50
|
+
reconfigure: false,
|
51
|
+
clean: false,
|
52
|
+
ruby_version: "3.3",
|
53
|
+
target_triplet: "wasm32-unknown-wasip1",
|
54
|
+
profile: "full",
|
55
|
+
stdlib: true,
|
56
|
+
disable_gems: false,
|
57
|
+
gemfile: nil,
|
58
|
+
patches: [],
|
59
|
+
}
|
60
|
+
OptionParser
|
61
|
+
.new do |opts|
|
62
|
+
opts.banner = "Usage: rbwasm build [options]"
|
63
|
+
opts.on("-h", "--help", "Prints this help") do
|
64
|
+
@stdout.puts opts
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("--save-temps", "Save temporary files") do
|
69
|
+
options[:save_temps] = true
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on("--ruby-version VERSION", "Ruby version") do |version|
|
73
|
+
options[:ruby_version] = version
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on("--target TRIPLET", "Target triplet") do |triplet|
|
77
|
+
options[:target_triplet] = triplet
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on(
|
81
|
+
"--build-profile PROFILE",
|
82
|
+
"Build profile. full or minimal"
|
83
|
+
) { |profile| options[:profile] = profile }
|
84
|
+
|
85
|
+
opts.on("--optimize", "Optimize the output") do
|
86
|
+
options[:optimize] = true
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on("--remake", "Re-execute make for Ruby") do
|
90
|
+
options[:remake] = true
|
91
|
+
end
|
92
|
+
|
93
|
+
opts.on("--reconfigure", "Re-execute configure for Ruby") do
|
94
|
+
options[:reconfigure] = true
|
95
|
+
end
|
96
|
+
|
97
|
+
opts.on("--clean", "Clean build artifacts") { options[:clean] = true }
|
98
|
+
|
99
|
+
opts.on("-o", "--output FILE", "Output file") do |file|
|
100
|
+
options[:output] = file
|
101
|
+
end
|
102
|
+
|
103
|
+
opts.on("--[no-]stdlib", "Include stdlib") do |stdlib|
|
104
|
+
options[:stdlib] = stdlib
|
105
|
+
end
|
106
|
+
|
107
|
+
opts.on("--disable-gems", "Disable gems") do
|
108
|
+
options[:disable_gems] = true
|
109
|
+
end
|
110
|
+
|
111
|
+
opts.on("-p", "--patch PATCH", "Apply a patch") do |patch|
|
112
|
+
options[:patches] << patch
|
113
|
+
end
|
114
|
+
|
115
|
+
opts.on("--format FORMAT", "Output format") do |format|
|
116
|
+
options[:format] = format
|
117
|
+
end
|
118
|
+
|
119
|
+
opts.on("--print-ruby-cache-key", "Print Ruby cache key") do
|
120
|
+
options[:print_ruby_cache_key] = true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
.parse!(args)
|
124
|
+
|
125
|
+
__skip__ = if defined?(Bundler)
|
126
|
+
Bundler.settings.temporary(force_ruby_platform: true) do
|
127
|
+
do_build_with_force_ruby_platform(options)
|
128
|
+
end
|
129
|
+
else
|
130
|
+
do_build_with_force_ruby_platform(options)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def do_build_with_force_ruby_platform(options)
|
135
|
+
verbose = RubyWasm.logger.level == :debug
|
136
|
+
executor = RubyWasm::BuildExecutor.new(verbose: verbose)
|
137
|
+
|
138
|
+
packager = self.derive_packager(options)
|
139
|
+
|
140
|
+
if options[:print_ruby_cache_key]
|
141
|
+
self.do_print_ruby_cache_key(packager)
|
142
|
+
exit
|
143
|
+
end
|
144
|
+
|
145
|
+
unless options[:output]
|
146
|
+
@stderr.puts "Output file is not specified"
|
147
|
+
exit 1
|
148
|
+
end
|
149
|
+
|
150
|
+
require "tmpdir"
|
151
|
+
|
152
|
+
if options[:save_temps]
|
153
|
+
tmpdir = Dir.mktmpdir
|
154
|
+
self.do_build(executor, tmpdir, packager, options)
|
155
|
+
@stderr.puts "Temporary files are saved to #{tmpdir}"
|
156
|
+
exit
|
157
|
+
else
|
158
|
+
Dir.mktmpdir do |tmpdir|
|
159
|
+
self.do_build(executor, tmpdir, packager, options)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def pack(args)
|
165
|
+
self.require_extension
|
166
|
+
RubyWasmExt::WasiVfs.run_cli([$0, "pack", *args])
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def build_config(options)
|
172
|
+
# @type var config: Packager::build_config
|
173
|
+
config = { target: options[:target_triplet], src: compute_build_source(options) }
|
174
|
+
case options[:profile]
|
175
|
+
when "full"
|
176
|
+
config[:default_exts] = config[:src][:all_default_exts]
|
177
|
+
env_additional_exts = ENV["RUBY_WASM_ADDITIONAL_EXTS"] || ""
|
178
|
+
unless env_additional_exts.empty?
|
179
|
+
config[:default_exts] += "," + env_additional_exts
|
180
|
+
end
|
181
|
+
when "minimal"
|
182
|
+
config[:default_exts] = ""
|
183
|
+
else
|
184
|
+
RubyWasm.logger.error "Unknown profile: #{options[:profile]} (available: full, minimal)"
|
185
|
+
exit 1
|
186
|
+
end
|
187
|
+
config[:suffix] = "-#{options[:profile]}"
|
188
|
+
config
|
189
|
+
end
|
190
|
+
|
191
|
+
def compute_build_source(options)
|
192
|
+
src_name = options[:ruby_version]
|
193
|
+
aliases = self.class.build_source_aliases(root)
|
194
|
+
source = aliases[src_name]
|
195
|
+
if source.nil?
|
196
|
+
if File.directory?(src_name)
|
197
|
+
# Treat as a local source if the given name is a source directory.
|
198
|
+
RubyWasm.logger.debug "Using local source: #{src_name}"
|
199
|
+
if options[:patches].any?
|
200
|
+
RubyWasm.logger.warn "Patches specified through --patch are ignored for local sources"
|
201
|
+
end
|
202
|
+
# @type var local_source: RubyWasm::Packager::build_source_local
|
203
|
+
local_source = { type: "local", path: src_name }
|
204
|
+
# @type var local_source: RubyWasm::Packager::build_source
|
205
|
+
local_source = local_source.merge(name: "local", patches: [])
|
206
|
+
return local_source
|
207
|
+
end
|
208
|
+
# Otherwise, it's an unknown source.
|
209
|
+
raise(
|
210
|
+
"Unknown Ruby source: #{src_name} (available: #{aliases.keys.join(", ")} or a local directory)"
|
211
|
+
)
|
212
|
+
end
|
213
|
+
# Apply user-specified patches in addition to bundled patches.
|
214
|
+
source[:patches].concat(options[:patches])
|
215
|
+
source
|
216
|
+
end
|
217
|
+
|
218
|
+
# Retrieves the alias definitions for the Ruby sources.
|
219
|
+
def self.build_source_aliases(root)
|
220
|
+
# @type var sources: Hash[string, RubyWasm::Packager::build_source]
|
221
|
+
sources = {
|
222
|
+
"head" => {
|
223
|
+
type: "github",
|
224
|
+
repo: "ruby/ruby",
|
225
|
+
rev: "master",
|
226
|
+
all_default_exts: RubyWasm::Packager::ALL_DEFAULT_EXTS,
|
227
|
+
},
|
228
|
+
"3.3" => {
|
229
|
+
type: "tarball",
|
230
|
+
url: "https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.0.tar.gz",
|
231
|
+
all_default_exts: "bigdecimal,cgi/escape,continuation,coverage,date,dbm,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,fiber,gdbm,json,json/generator,json/parser,nkf,objspace,pathname,psych,racc/cparse,rbconfig/sizeof,ripper,stringio,strscan,monitor,zlib,openssl",
|
232
|
+
},
|
233
|
+
"3.2" => {
|
234
|
+
type: "tarball",
|
235
|
+
url: "https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.3.tar.gz",
|
236
|
+
all_default_exts: "bigdecimal,cgi/escape,continuation,coverage,date,dbm,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,fiber,gdbm,json,json/generator,json/parser,nkf,objspace,pathname,psych,racc/cparse,rbconfig/sizeof,ripper,stringio,strscan,monitor,zlib,openssl",
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
# Apply bundled and user-specified `<root>/patches` directories.
|
241
|
+
sources.each do |name, source|
|
242
|
+
source[:name] = name
|
243
|
+
patches_dirs = [bundled_patches_path, File.join(root, "patches")]
|
244
|
+
source[:patches] = patches_dirs.flat_map do |patches_dir|
|
245
|
+
Dir[File.join(patches_dir, name, "*.patch")]
|
246
|
+
.map { |p| File.expand_path(p) }
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
build_manifest = File.join(root, "build_manifest.json")
|
251
|
+
if File.exist?(build_manifest)
|
252
|
+
begin
|
253
|
+
manifest = JSON.parse(File.read(build_manifest))
|
254
|
+
manifest["ruby_revisions"].each do |name, rev|
|
255
|
+
source = sources[name]
|
256
|
+
next unless source[:type] == "github"
|
257
|
+
# @type var source: RubyWasm::Packager::build_source_github
|
258
|
+
source[:rev] = rev
|
259
|
+
end
|
260
|
+
rescue StandardError => e
|
261
|
+
RubyWasm.logger.warn "Failed to load build_manifest.json: #{e}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
sources
|
265
|
+
end
|
266
|
+
|
267
|
+
# Retrieves the root directory of the Ruby project.
|
268
|
+
def root
|
269
|
+
__skip__ =
|
270
|
+
@root ||=
|
271
|
+
begin
|
272
|
+
if explicit = ENV["RUBY_WASM_ROOT"]
|
273
|
+
File.expand_path(explicit)
|
274
|
+
elsif defined?(Bundler)
|
275
|
+
Bundler.root
|
276
|
+
else
|
277
|
+
Dir.pwd
|
278
|
+
end
|
279
|
+
rescue Bundler::GemfileNotFound
|
280
|
+
Dir.pwd
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Path to the directory containing the bundled patches, which is shipped
|
285
|
+
# as part of ruby_wasm gem to backport fixes or try experimental features
|
286
|
+
# before landing them to the ruby/ruby repository.
|
287
|
+
def self.bundled_patches_path
|
288
|
+
dir = __dir__
|
289
|
+
raise "Unexpected directory structure, no __dir__!??" unless dir
|
290
|
+
lib_source_root = File.join(dir, "..", "..")
|
291
|
+
File.join(lib_source_root, "patches")
|
292
|
+
end
|
293
|
+
|
294
|
+
def derive_packager(options)
|
295
|
+
__skip__ = definition = nil
|
296
|
+
__skip__ = if defined?(Bundler) && !options[:disable_gems]
|
297
|
+
begin
|
298
|
+
# Silence Bundler UI if --print-ruby-cache-key is specified not to bother the JSON output.
|
299
|
+
level = options[:print_ruby_cache_key] ? :silent : Bundler.ui.level
|
300
|
+
old_level = Bundler.ui.level
|
301
|
+
Bundler.ui.level = level
|
302
|
+
definition = Bundler.definition
|
303
|
+
ensure
|
304
|
+
Bundler.ui.level = old_level
|
305
|
+
end
|
306
|
+
end
|
307
|
+
RubyWasm.logger.info "Using Gemfile: #{definition.gemfiles}" if definition
|
308
|
+
RubyWasm::Packager.new(root, build_config(options), definition)
|
309
|
+
end
|
310
|
+
|
311
|
+
def do_print_ruby_cache_key(packager)
|
312
|
+
ruby_core_build = packager.ruby_core_build
|
313
|
+
require "digest"
|
314
|
+
digest = Digest::SHA256.new
|
315
|
+
ruby_core_build.cache_key(digest)
|
316
|
+
hexdigest = digest.hexdigest
|
317
|
+
require "json"
|
318
|
+
@stdout.puts JSON.generate(
|
319
|
+
hexdigest: hexdigest,
|
320
|
+
artifact: ruby_core_build.artifact
|
321
|
+
)
|
322
|
+
end
|
323
|
+
|
324
|
+
def do_build(executor, tmpdir, packager, options)
|
325
|
+
self.require_extension
|
326
|
+
wasm_bytes = packager.package(executor, tmpdir, options)
|
327
|
+
RubyWasm.logger.info "Size: #{SizeFormatter.format(wasm_bytes.size)}"
|
328
|
+
case options[:output]
|
329
|
+
when "-"
|
330
|
+
@stdout.write wasm_bytes
|
331
|
+
else
|
332
|
+
File.binwrite(options[:output], wasm_bytes)
|
333
|
+
RubyWasm.logger.debug "Wrote #{options[:output]}"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def require_extension
|
338
|
+
# Tries to require the extension for the given Ruby version first
|
339
|
+
begin
|
340
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
341
|
+
require_relative "#{Regexp.last_match(1)}/ruby_wasm.so"
|
342
|
+
rescue LoadError
|
343
|
+
require_relative "ruby_wasm.so"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RubyWasm::Packager::ComponentAdapter
|
2
|
+
module_function
|
3
|
+
|
4
|
+
# The path to the component adapter for the given WASI execution model.
|
5
|
+
#
|
6
|
+
# @param exec_model [String] "command" or "reactor"
|
7
|
+
def wasi_snapshot_preview1(exec_model)
|
8
|
+
File.join(
|
9
|
+
File.dirname(__FILE__),
|
10
|
+
"component_adapter",
|
11
|
+
"wasi_snapshot_preview1.#{exec_model}.wasm"
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,333 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
class RubyWasm::Packager::Core
|
4
|
+
def initialize(packager)
|
5
|
+
@packager = packager
|
6
|
+
end
|
7
|
+
|
8
|
+
def build(executor, options)
|
9
|
+
strategy = build_strategy
|
10
|
+
strategy.build(executor, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def_delegators :build_strategy, :cache_key, :artifact, :build_and_link_exts
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_strategy
|
20
|
+
@build_strategy ||=
|
21
|
+
begin
|
22
|
+
has_exts = @packager.specs.any? { |spec| spec.extensions.any? }
|
23
|
+
if @packager.support_dynamic_linking?
|
24
|
+
DynamicLinking.new(@packager)
|
25
|
+
else
|
26
|
+
StaticLinking.new(@packager)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class BuildStrategy
|
32
|
+
def initialize(packager)
|
33
|
+
@packager = packager
|
34
|
+
end
|
35
|
+
|
36
|
+
def build(executor, options)
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_and_link_exts(executor)
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
# Array of paths to extconf.rb files.
|
45
|
+
def specs_with_extensions
|
46
|
+
@packager.specs.filter_map do |spec|
|
47
|
+
exts =
|
48
|
+
spec.extensions.select do |ext|
|
49
|
+
# Filter out extensions of default gems (e.g. json, openssl)
|
50
|
+
# for the exactly same gem version.
|
51
|
+
File.exist?(File.join(spec.full_gem_path, ext))
|
52
|
+
end
|
53
|
+
next nil if exts.empty?
|
54
|
+
[spec, exts]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def cache_key(digest)
|
59
|
+
raise NotImplementedError
|
60
|
+
end
|
61
|
+
|
62
|
+
def artifact
|
63
|
+
raise NotImplementedError
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class DynamicLinking < BuildStrategy
|
68
|
+
def build(executor, options)
|
69
|
+
build = derive_build
|
70
|
+
force_rebuild =
|
71
|
+
options[:remake] || options[:clean] || options[:reconfigure]
|
72
|
+
if File.exist?(build.crossruby.artifact) && !force_rebuild
|
73
|
+
# Always build extensions because they are usually not expensive to build
|
74
|
+
return build.crossruby.artifact
|
75
|
+
end
|
76
|
+
build.crossruby.clean(executor) if options[:clean]
|
77
|
+
|
78
|
+
do_build =
|
79
|
+
proc do
|
80
|
+
build.crossruby.build(
|
81
|
+
executor,
|
82
|
+
remake: options[:remake],
|
83
|
+
reconfigure: options[:reconfigure]
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
__skip__ =
|
88
|
+
if defined?(Bundler)
|
89
|
+
Bundler.with_unbundled_env(&do_build)
|
90
|
+
else
|
91
|
+
do_build.call
|
92
|
+
end
|
93
|
+
build.crossruby.artifact
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_and_link_exts(executor)
|
97
|
+
build = derive_build
|
98
|
+
self.build_exts(executor, build)
|
99
|
+
self.link_exts(executor, build)
|
100
|
+
end
|
101
|
+
|
102
|
+
def link_exts(executor, build)
|
103
|
+
ruby_root = build.crossruby.dest_dir
|
104
|
+
|
105
|
+
libraries = [File.join(ruby_root, "usr", "local", "bin", "ruby")]
|
106
|
+
|
107
|
+
# TODO: Should be computed from dyinfo of ruby binary
|
108
|
+
wasi_libc_shared_libs = [
|
109
|
+
"libc.so",
|
110
|
+
"libwasi-emulated-getpid.so",
|
111
|
+
"libwasi-emulated-mman.so",
|
112
|
+
"libwasi-emulated-process-clocks.so",
|
113
|
+
"libwasi-emulated-signal.so",
|
114
|
+
]
|
115
|
+
|
116
|
+
wasi_libc_shared_libs.each do |lib|
|
117
|
+
# @type var toolchain: RubyWasm::WASISDK
|
118
|
+
toolchain = build.toolchain
|
119
|
+
wasi_sdk_path = toolchain.wasi_sdk_path
|
120
|
+
libraries << File.join(wasi_sdk_path, "share/wasi-sysroot/lib/wasm32-wasi", lib)
|
121
|
+
end
|
122
|
+
wasi_adapter = RubyWasm::Packager::ComponentAdapter.wasi_snapshot_preview1("command")
|
123
|
+
adapters = [wasi_adapter]
|
124
|
+
dl_openable_libs = Dir.glob(File.join(ruby_root, "usr", "local", "lib", "ruby", "**", "*.so"))
|
125
|
+
linker = RubyWasmExt::ComponentLink.new
|
126
|
+
linker.use_built_in_libdl(true)
|
127
|
+
linker.stub_missing_functions(false)
|
128
|
+
linker.validate(true)
|
129
|
+
|
130
|
+
libraries.each do |lib|
|
131
|
+
# Non-DL openable libraries should be referenced as base name
|
132
|
+
lib_name = File.basename(lib)
|
133
|
+
module_bytes = File.binread(lib)
|
134
|
+
RubyWasm.logger.info "Linking #{lib_name} (#{module_bytes.size} bytes)"
|
135
|
+
linker.library(lib_name, module_bytes, false)
|
136
|
+
end
|
137
|
+
|
138
|
+
dl_openable_libs.each do |lib|
|
139
|
+
# DL openable lib_name should be a relative path from ruby_root
|
140
|
+
lib_name = "/" + Pathname.new(lib).relative_path_from(Pathname.new(ruby_root)).to_s
|
141
|
+
module_bytes = File.binread(lib)
|
142
|
+
RubyWasm.logger.info "Linking #{lib_name} (#{module_bytes.size} bytes)"
|
143
|
+
linker.library(lib_name, module_bytes, true)
|
144
|
+
end
|
145
|
+
|
146
|
+
adapters.each do |adapter|
|
147
|
+
adapter_name = File.basename(adapter)
|
148
|
+
# e.g. wasi_snapshot_preview1.command.wasm -> wasi_snapshot_preview1
|
149
|
+
adapter_name = adapter_name.split(".")[0]
|
150
|
+
module_bytes = File.binread(adapter)
|
151
|
+
linker.adapter(adapter_name, module_bytes)
|
152
|
+
end
|
153
|
+
return linker.encode()
|
154
|
+
end
|
155
|
+
|
156
|
+
def build_exts(executor, build)
|
157
|
+
exts = specs_with_extensions.flat_map do |spec, exts|
|
158
|
+
exts.map do |ext|
|
159
|
+
ext_feature = File.dirname(ext) # e.g. "ext/cgi/escape"
|
160
|
+
ext_srcdir = File.join(spec.full_gem_path, ext_feature)
|
161
|
+
ext_relative_path = File.join(spec.full_name, ext_feature)
|
162
|
+
RubyWasm::CrossRubyExtProduct.new(
|
163
|
+
ext_srcdir,
|
164
|
+
build.toolchain,
|
165
|
+
ext_relative_path: ext_relative_path
|
166
|
+
)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
exts.each do |prod|
|
171
|
+
executor.begin_section prod.class, prod.name, "Building"
|
172
|
+
prod.build(executor, build.crossruby)
|
173
|
+
executor.end_section prod.class, prod.name
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def cache_key(digest)
|
178
|
+
derive_build.cache_key(digest)
|
179
|
+
end
|
180
|
+
|
181
|
+
def artifact
|
182
|
+
derive_build.crossruby.artifact
|
183
|
+
end
|
184
|
+
|
185
|
+
def target
|
186
|
+
RubyWasm::Target.new(@packager.full_build_options[:target], pic: true)
|
187
|
+
end
|
188
|
+
|
189
|
+
def derive_build
|
190
|
+
return @build if @build
|
191
|
+
__skip__ =
|
192
|
+
build ||= RubyWasm::Build.new(
|
193
|
+
name, **@packager.full_build_options,
|
194
|
+
target: target,
|
195
|
+
# NOTE: We don't need linking libwasi_vfs because we use wasi-virt instead.
|
196
|
+
wasi_vfs: nil
|
197
|
+
)
|
198
|
+
build.crossruby.cflags = %w[-fPIC -fvisibility=default]
|
199
|
+
if @packager.full_build_options[:target] != "wasm32-unknown-emscripten"
|
200
|
+
build.crossruby.debugflags = %w[-g]
|
201
|
+
build.crossruby.wasmoptflags = %w[-O3 -g --pass-arg=asyncify-relocatable]
|
202
|
+
build.crossruby.ldflags = %w[
|
203
|
+
-Xlinker
|
204
|
+
--stack-first
|
205
|
+
-Xlinker
|
206
|
+
-z
|
207
|
+
-Xlinker
|
208
|
+
stack-size=16777216
|
209
|
+
]
|
210
|
+
build.crossruby.xldflags = %w[
|
211
|
+
-Xlinker -shared
|
212
|
+
-Xlinker --export-dynamic
|
213
|
+
-Xlinker --export-all
|
214
|
+
-Xlinker --experimental-pic
|
215
|
+
-Xlinker -export-if-defined=__main_argc_argv
|
216
|
+
]
|
217
|
+
end
|
218
|
+
@build = build
|
219
|
+
build
|
220
|
+
end
|
221
|
+
|
222
|
+
def name
|
223
|
+
require "digest"
|
224
|
+
options = @packager.full_build_options
|
225
|
+
src_channel = options[:src][:name]
|
226
|
+
target_triplet = options[:target]
|
227
|
+
"ruby-#{src_channel}-#{target_triplet}-pic#{options[:suffix]}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
class StaticLinking < BuildStrategy
|
232
|
+
def build(executor, options)
|
233
|
+
build = derive_build
|
234
|
+
force_rebuild =
|
235
|
+
options[:remake] || options[:clean] || options[:reconfigure]
|
236
|
+
if File.exist?(build.crossruby.artifact) && !force_rebuild
|
237
|
+
return build.crossruby.artifact
|
238
|
+
end
|
239
|
+
build.crossruby.clean(executor) if options[:clean]
|
240
|
+
|
241
|
+
do_build =
|
242
|
+
proc do
|
243
|
+
build.crossruby.build(
|
244
|
+
executor,
|
245
|
+
remake: options[:remake],
|
246
|
+
reconfigure: options[:reconfigure]
|
247
|
+
)
|
248
|
+
end
|
249
|
+
|
250
|
+
__skip__ =
|
251
|
+
if defined?(Bundler)
|
252
|
+
Bundler.with_unbundled_env(&do_build)
|
253
|
+
else
|
254
|
+
do_build.call
|
255
|
+
end
|
256
|
+
build.crossruby.artifact
|
257
|
+
end
|
258
|
+
|
259
|
+
def cache_key(digest)
|
260
|
+
derive_build.cache_key(digest)
|
261
|
+
end
|
262
|
+
|
263
|
+
def artifact
|
264
|
+
derive_build.crossruby.artifact
|
265
|
+
end
|
266
|
+
|
267
|
+
def target
|
268
|
+
RubyWasm::Target.new(@packager.full_build_options[:target])
|
269
|
+
end
|
270
|
+
|
271
|
+
def derive_build
|
272
|
+
return @build if @build
|
273
|
+
__skip__ =
|
274
|
+
build ||= RubyWasm::Build.new(name, **@packager.full_build_options, target: target)
|
275
|
+
build.crossruby.user_exts = user_exts(build)
|
276
|
+
# Emscripten uses --global-base=1024 by default, but it conflicts with
|
277
|
+
# --stack-first and -z stack-size since global-base 1024 is smaller than
|
278
|
+
# the large stack size.
|
279
|
+
# Also -g produces some warnings on Emscripten and it confuses the configure
|
280
|
+
# script of Ruby.
|
281
|
+
if @packager.full_build_options[:target] != "wasm32-unknown-emscripten"
|
282
|
+
build.crossruby.debugflags = %w[-g]
|
283
|
+
# We assume that imported functions provided through WASI will not change
|
284
|
+
# asyncify state, so we ignore them.
|
285
|
+
build.crossruby.wasmoptflags = %w[-O3 -g --pass-arg=asyncify-ignore-imports]
|
286
|
+
build.crossruby.ldflags = %w[
|
287
|
+
-Xlinker
|
288
|
+
--stack-first
|
289
|
+
-Xlinker
|
290
|
+
-z
|
291
|
+
-Xlinker
|
292
|
+
stack-size=16777216
|
293
|
+
]
|
294
|
+
end
|
295
|
+
@build = build
|
296
|
+
build
|
297
|
+
end
|
298
|
+
|
299
|
+
def build_and_link_exts(executor)
|
300
|
+
build = derive_build
|
301
|
+
ruby_root = build.crossruby.dest_dir
|
302
|
+
File.binread(File.join(ruby_root, "usr", "local", "bin", "ruby"))
|
303
|
+
end
|
304
|
+
|
305
|
+
def user_exts(build)
|
306
|
+
@user_exts ||=
|
307
|
+
specs_with_extensions.flat_map do |spec, exts|
|
308
|
+
exts.map do |ext|
|
309
|
+
ext_feature = File.dirname(ext) # e.g. "ext/cgi/escape"
|
310
|
+
ext_srcdir = File.join(spec.full_gem_path, ext_feature)
|
311
|
+
ext_relative_path = File.join(spec.full_name, ext_feature)
|
312
|
+
RubyWasm::CrossRubyExtProduct.new(
|
313
|
+
ext_srcdir,
|
314
|
+
build.toolchain,
|
315
|
+
ext_relative_path: ext_relative_path
|
316
|
+
)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def name
|
322
|
+
require "digest"
|
323
|
+
options = @packager.full_build_options
|
324
|
+
src_channel = options[:src][:name]
|
325
|
+
target_triplet = options[:target]
|
326
|
+
base = "ruby-#{src_channel}-#{target_triplet}#{options[:suffix]}"
|
327
|
+
exts = specs_with_extensions.sort
|
328
|
+
hash = ::Digest::MD5.new
|
329
|
+
specs_with_extensions.each { |spec, _| hash << spec.full_name }
|
330
|
+
exts.empty? ? base : "#{base}-#{hash.hexdigest}"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|