ruby_wasm 2.5.1-aarch64-linux-musl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-format +8 -0
  3. data/CONTRIBUTING.md +128 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE +21 -0
  6. data/NOTICE +1293 -0
  7. data/README.md +154 -0
  8. data/Rakefile +164 -0
  9. data/Steepfile +24 -0
  10. data/benchmarks/vm_deep_call.rb +55 -0
  11. data/docs/api.md +2 -0
  12. data/docs/cheat_sheet.md +195 -0
  13. data/docs/faq.md +25 -0
  14. data/exe/rbwasm +7 -0
  15. data/ext/.gitignore +2 -0
  16. data/ext/README.md +11 -0
  17. data/ext/extinit.c.erb +32 -0
  18. data/lib/ruby_wasm/3.1/ruby_wasm.so +0 -0
  19. data/lib/ruby_wasm/3.2/ruby_wasm.so +0 -0
  20. data/lib/ruby_wasm/3.3/ruby_wasm.so +0 -0
  21. data/lib/ruby_wasm/build/build_params.rb +3 -0
  22. data/lib/ruby_wasm/build/downloader.rb +18 -0
  23. data/lib/ruby_wasm/build/executor.rb +191 -0
  24. data/lib/ruby_wasm/build/product/baseruby.rb +37 -0
  25. data/lib/ruby_wasm/build/product/crossruby.rb +360 -0
  26. data/lib/ruby_wasm/build/product/libyaml.rb +70 -0
  27. data/lib/ruby_wasm/build/product/openssl.rb +93 -0
  28. data/lib/ruby_wasm/build/product/product.rb +39 -0
  29. data/lib/ruby_wasm/build/product/ruby_source.rb +103 -0
  30. data/lib/ruby_wasm/build/product/wasi_vfs.rb +45 -0
  31. data/lib/ruby_wasm/build/product/zlib.rb +70 -0
  32. data/lib/ruby_wasm/build/product.rb +8 -0
  33. data/lib/ruby_wasm/build/target.rb +24 -0
  34. data/lib/ruby_wasm/build/toolchain/wit_bindgen.rb +31 -0
  35. data/lib/ruby_wasm/build/toolchain.rb +193 -0
  36. data/lib/ruby_wasm/build.rb +92 -0
  37. data/lib/ruby_wasm/cli.rb +347 -0
  38. data/lib/ruby_wasm/packager/component_adapter/wasi_snapshot_preview1.command.wasm +0 -0
  39. data/lib/ruby_wasm/packager/component_adapter/wasi_snapshot_preview1.reactor.wasm +0 -0
  40. data/lib/ruby_wasm/packager/component_adapter.rb +14 -0
  41. data/lib/ruby_wasm/packager/core.rb +333 -0
  42. data/lib/ruby_wasm/packager/file_system.rb +160 -0
  43. data/lib/ruby_wasm/packager.rb +96 -0
  44. data/lib/ruby_wasm/rake_task.rb +60 -0
  45. data/lib/ruby_wasm/util.rb +15 -0
  46. data/lib/ruby_wasm/version.rb +3 -0
  47. data/lib/ruby_wasm.rb +34 -0
  48. data/package-lock.json +9777 -0
  49. data/package.json +12 -0
  50. data/rakelib/check.rake +37 -0
  51. data/rakelib/ci.rake +152 -0
  52. data/rakelib/doc.rake +29 -0
  53. data/rakelib/format.rake +35 -0
  54. data/rakelib/gem.rake +22 -0
  55. data/rakelib/packaging.rake +165 -0
  56. data/rakelib/version.rake +40 -0
  57. data/sig/open_uri.rbs +4 -0
  58. data/sig/ruby_wasm/build.rbs +327 -0
  59. data/sig/ruby_wasm/cli.rbs +51 -0
  60. data/sig/ruby_wasm/ext.rbs +26 -0
  61. data/sig/ruby_wasm/packager.rbs +122 -0
  62. data/sig/ruby_wasm/util.rbs +5 -0
  63. data/tools/clang-format-diff.sh +18 -0
  64. data/tools/exe/rbminify +12 -0
  65. data/tools/lib/syntax_tree/minify_ruby.rb +63 -0
  66. 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