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.
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