ruby_wasm 2.5.0.pre.1 → 2.5.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +13 -9
  3. data/Cargo.lock +705 -451
  4. data/Gemfile +1 -1
  5. data/README.md +12 -19
  6. data/Rakefile +9 -9
  7. data/benchmarks/vm_deep_call.rb +2 -2
  8. data/docs/cheat_sheet.md +8 -8
  9. data/ext/ruby_wasm/Cargo.toml +6 -5
  10. data/ext/ruby_wasm/src/lib.rs +208 -7
  11. data/lib/ruby_wasm/build/executor.rb +4 -0
  12. data/lib/ruby_wasm/build/product/crossruby.rb +57 -23
  13. data/lib/ruby_wasm/build/product/libyaml.rb +5 -3
  14. data/lib/ruby_wasm/build/product/openssl.rb +7 -2
  15. data/lib/ruby_wasm/build/product/product.rb +3 -3
  16. data/lib/ruby_wasm/build/product/ruby_source.rb +3 -3
  17. data/lib/ruby_wasm/build/product/wasi_vfs.rb +1 -39
  18. data/lib/ruby_wasm/build/product/zlib.rb +5 -3
  19. data/lib/ruby_wasm/build/target.rb +24 -0
  20. data/lib/ruby_wasm/build/toolchain.rb +1 -1
  21. data/lib/ruby_wasm/build.rb +7 -3
  22. data/lib/ruby_wasm/cli.rb +171 -13
  23. data/lib/ruby_wasm/feature_set.rb +30 -0
  24. data/lib/ruby_wasm/packager/component_adapter/wasi_snapshot_preview1.command.wasm +0 -0
  25. data/lib/ruby_wasm/packager/component_adapter/wasi_snapshot_preview1.reactor.wasm +0 -0
  26. data/lib/ruby_wasm/packager/component_adapter.rb +14 -0
  27. data/lib/ruby_wasm/packager/core.rb +192 -4
  28. data/lib/ruby_wasm/packager/file_system.rb +20 -17
  29. data/lib/ruby_wasm/packager.rb +21 -83
  30. data/lib/ruby_wasm/rake_task.rb +2 -0
  31. data/lib/ruby_wasm/version.rb +1 -1
  32. data/lib/ruby_wasm.rb +2 -0
  33. data/package-lock.json +410 -133
  34. data/package.json +3 -3
  35. data/{tasks → rakelib}/ci.rake +3 -3
  36. data/{tasks → rakelib}/doc.rake +6 -1
  37. data/{tasks → rakelib}/format.rake +3 -2
  38. data/{tasks → rakelib}/gem.rake +4 -1
  39. data/{tasks → rakelib}/packaging.rake +34 -17
  40. data/{tasks → rakelib}/version.rake +2 -0
  41. data/sig/ruby_wasm/build.rbs +36 -31
  42. data/sig/ruby_wasm/cli.rbs +30 -3
  43. data/sig/ruby_wasm/ext.rbs +28 -3
  44. data/sig/ruby_wasm/feature_set.rbs +12 -0
  45. data/sig/ruby_wasm/packager.rbs +44 -7
  46. metadata +16 -15
  47. data/builders/wasm32-unknown-emscripten/Dockerfile +0 -43
  48. data/builders/wasm32-unknown-emscripten/entrypoint.sh +0 -7
  49. data/builders/wasm32-unknown-wasi/Dockerfile +0 -47
  50. data/builders/wasm32-unknown-wasi/entrypoint.sh +0 -7
  51. data/ruby_wasm.gemspec +0 -32
  52. /data/{tasks → rakelib}/check.rake +0 -0
data/lib/ruby_wasm/cli.rb CHANGED
@@ -9,7 +9,7 @@ module RubyWasm
9
9
  end
10
10
 
11
11
  def run(args)
12
- available_commands = %w[build]
12
+ available_commands = %w[build pack]
13
13
  parser =
14
14
  OptionParser.new do |opts|
15
15
  opts.banner = <<~USAGE
@@ -32,14 +32,17 @@ module RubyWasm
32
32
  case command
33
33
  when "build"
34
34
  build(args)
35
+ when "pack"
36
+ pack(args)
35
37
  else
38
+ @stderr.puts "Unknown command: #{command}"
36
39
  @stderr.puts parser
37
40
  exit
38
41
  end
39
42
  end
40
43
 
41
44
  def build(args)
42
- # @type var options: Hash[Symbol, untyped]
45
+ # @type var options: cli_options
43
46
  options = {
44
47
  save_temps: false,
45
48
  optimize: false,
@@ -47,14 +50,16 @@ module RubyWasm
47
50
  reconfigure: false,
48
51
  clean: false,
49
52
  ruby_version: "3.3",
50
- target_triplet: "wasm32-unknown-wasi",
53
+ target_triplet: "wasm32-unknown-wasip1",
51
54
  profile: "full",
52
55
  stdlib: true,
53
- disable_gems: false
56
+ disable_gems: false,
57
+ gemfile: nil,
58
+ patches: [],
54
59
  }
55
60
  OptionParser
56
61
  .new do |opts|
57
- opts.banner = "Usage: rbwasm componentize [options]"
62
+ opts.banner = "Usage: rbwasm build [options]"
58
63
  opts.on("-h", "--help", "Prints this help") do
59
64
  @stdout.puts opts
60
65
  exit
@@ -103,6 +108,10 @@ module RubyWasm
103
108
  options[:disable_gems] = true
104
109
  end
105
110
 
111
+ opts.on("-p", "--patch PATCH", "Apply a patch") do |patch|
112
+ options[:patches] << patch
113
+ end
114
+
106
115
  opts.on("--format FORMAT", "Output format") do |format|
107
116
  options[:format] = format
108
117
  end
@@ -113,8 +122,19 @@ module RubyWasm
113
122
  end
114
123
  .parse!(args)
115
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)
116
135
  verbose = RubyWasm.logger.level == :debug
117
136
  executor = RubyWasm::BuildExecutor.new(verbose: verbose)
137
+
118
138
  packager = self.derive_packager(options)
119
139
 
120
140
  if options[:print_ruby_cache_key]
@@ -141,13 +161,24 @@ module RubyWasm
141
161
  end
142
162
  end
143
163
 
164
+ def pack(args)
165
+ self.require_extension
166
+ RubyWasmExt::WasiVfs.run_cli([$0, "pack", *args])
167
+ end
168
+
144
169
  private
145
170
 
146
171
  def build_config(options)
147
- config = { target: options[:target_triplet], src: options[:ruby_version] }
172
+ build_source, all_default_exts = compute_build_source(options)
173
+ # @type var config: Packager::build_config
174
+ config = { target: options[:target_triplet], src: build_source }
148
175
  case options[:profile]
149
176
  when "full"
150
- config[:default_exts] = RubyWasm::Packager::ALL_DEFAULT_EXTS
177
+ config[:default_exts] = all_default_exts || ""
178
+ env_additional_exts = ENV["RUBY_WASM_ADDITIONAL_EXTS"] || ""
179
+ unless env_additional_exts.empty?
180
+ config[:default_exts] += "," + env_additional_exts
181
+ end
151
182
  when "minimal"
152
183
  config[:default_exts] = ""
153
184
  else
@@ -158,12 +189,129 @@ module RubyWasm
158
189
  config
159
190
  end
160
191
 
161
- def derive_packager(options)
192
+ def compute_build_source(options)
193
+ src_name = options[:ruby_version]
194
+ aliases = self.class.build_source_aliases(root)
195
+ source = aliases[src_name]
196
+ if source.nil?
197
+ if File.directory?(src_name)
198
+ # Treat as a local source if the given name is a source directory.
199
+ RubyWasm.logger.debug "Using local source: #{src_name}"
200
+ if options[:patches].any?
201
+ RubyWasm.logger.warn "Patches specified through --patch are ignored for local sources"
202
+ end
203
+ # @type var local_source: RubyWasm::Packager::build_source_local
204
+ local_source = { type: "local", path: src_name }
205
+ # @type var local_source: RubyWasm::Packager::build_source
206
+ local_source = local_source.merge(name: "local", patches: [])
207
+ return [local_source, nil]
208
+ end
209
+ # Otherwise, it's an unknown source.
210
+ raise(
211
+ "Unknown Ruby source: #{src_name} (available: #{aliases.keys.join(", ")} or a local directory)"
212
+ )
213
+ end
214
+ # Apply user-specified patches in addition to bundled patches.
215
+ source[:patches].concat(options[:patches])
216
+ # @type var all_default_exts: String
217
+ __skip__ = all_default_exts = source[:all_default_exts]
218
+ [source, all_default_exts]
219
+ end
220
+
221
+ # Retrieves the alias definitions for the Ruby sources.
222
+ def self.build_source_aliases(root)
223
+ # @type var sources: Hash[string, RubyWasm::Packager::build_source]
224
+ sources = {
225
+ "head" => {
226
+ type: "github",
227
+ repo: "ruby/ruby",
228
+ rev: "master",
229
+ all_default_exts: RubyWasm::Packager::ALL_DEFAULT_EXTS,
230
+ },
231
+ "3.3" => {
232
+ type: "tarball",
233
+ url: "https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.0.tar.gz",
234
+ 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",
235
+ },
236
+ "3.2" => {
237
+ type: "tarball",
238
+ url: "https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.3.tar.gz",
239
+ 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",
240
+ }
241
+ }
242
+
243
+ # Apply bundled and user-specified `<root>/patches` directories.
244
+ sources.each do |name, source|
245
+ source[:name] = name
246
+ patches_dirs = [bundled_patches_path, File.join(root, "patches")]
247
+ source[:patches] = patches_dirs.flat_map do |patches_dir|
248
+ Dir[File.join(patches_dir, name, "*.patch")]
249
+ .map { |p| File.expand_path(p) }
250
+ end
251
+ end
252
+
253
+ build_manifest = File.join(root, "build_manifest.json")
254
+ if File.exist?(build_manifest)
255
+ begin
256
+ manifest = JSON.parse(File.read(build_manifest))
257
+ manifest["ruby_revisions"].each do |name, rev|
258
+ source = sources[name]
259
+ next unless source[:type] == "github"
260
+ # @type var source: RubyWasm::Packager::build_source_github
261
+ source[:rev] = rev
262
+ end
263
+ rescue StandardError => e
264
+ RubyWasm.logger.warn "Failed to load build_manifest.json: #{e}"
265
+ end
266
+ end
267
+ sources
268
+ end
269
+
270
+ # Retrieves the root directory of the Ruby project.
271
+ def root
162
272
  __skip__ =
163
- if defined?(Bundler) && !options[:disable_gems]
273
+ @root ||=
274
+ begin
275
+ if explicit = ENV["RUBY_WASM_ROOT"]
276
+ File.expand_path(explicit)
277
+ elsif defined?(Bundler)
278
+ Bundler.root
279
+ else
280
+ Dir.pwd
281
+ end
282
+ rescue Bundler::GemfileNotFound
283
+ Dir.pwd
284
+ end
285
+ end
286
+
287
+ # Path to the directory containing the bundled patches, which is shipped
288
+ # as part of ruby_wasm gem to backport fixes or try experimental features
289
+ # before landing them to the ruby/ruby repository.
290
+ def self.bundled_patches_path
291
+ dir = __dir__
292
+ raise "Unexpected directory structure, no __dir__!??" unless dir
293
+ lib_source_root = File.join(dir, "..", "..")
294
+ File.join(lib_source_root, "patches")
295
+ end
296
+
297
+ def derive_packager(options)
298
+ __skip__ = definition = nil
299
+ __skip__ = if defined?(Bundler) && !options[:disable_gems]
300
+ begin
301
+ # Silence Bundler UI if --print-ruby-cache-key is specified not to bother the JSON output.
302
+ level = options[:print_ruby_cache_key] ? :silent : Bundler.ui.level
303
+ old_level = Bundler.ui.level
304
+ Bundler.ui.level = level
164
305
  definition = Bundler.definition
306
+ ensure
307
+ Bundler.ui.level = old_level
165
308
  end
166
- RubyWasm::Packager.new(build_config(options), definition)
309
+ end
310
+ RubyWasm.logger.info "Using Gemfile: #{definition.gemfiles}" if definition
311
+ RubyWasm::Packager.new(
312
+ root, build_config(options), definition,
313
+ features: RubyWasm::FeatureSet.derive_from_env
314
+ )
167
315
  end
168
316
 
169
317
  def do_print_ruby_cache_key(packager)
@@ -180,16 +328,26 @@ module RubyWasm
180
328
  end
181
329
 
182
330
  def do_build(executor, tmpdir, packager, options)
183
- require_relative "ruby_wasm.so"
331
+ self.require_extension
184
332
  wasm_bytes = packager.package(executor, tmpdir, options)
185
333
  RubyWasm.logger.info "Size: #{SizeFormatter.format(wasm_bytes.size)}"
186
334
  case options[:output]
187
335
  when "-"
188
- @stdout.write wasm_bytes.pack("C*")
336
+ @stdout.write wasm_bytes
189
337
  else
190
- File.binwrite(options[:output], wasm_bytes.pack("C*"))
338
+ File.binwrite(options[:output], wasm_bytes)
191
339
  RubyWasm.logger.debug "Wrote #{options[:output]}"
192
340
  end
193
341
  end
342
+
343
+ def require_extension
344
+ # Tries to require the extension for the given Ruby version first
345
+ begin
346
+ RUBY_VERSION =~ /(\d+\.\d+)/
347
+ require_relative "#{Regexp.last_match(1)}/ruby_wasm.so"
348
+ rescue LoadError
349
+ require_relative "ruby_wasm.so"
350
+ end
351
+ end
194
352
  end
195
353
  end
@@ -0,0 +1,30 @@
1
+ ##
2
+ # A set of feature flags that can be used to enable or disable experimental features.
3
+ class RubyWasm::FeatureSet
4
+ def initialize(features)
5
+ @features = features
6
+ end
7
+
8
+ # Maps the feature to the environment variable.
9
+ FEATURES = {
10
+ dynamic_linking: "RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING",
11
+ component_model: "RUBY_WASM_EXPERIMENTAL_COMPONENT_MODEL",
12
+ }.freeze
13
+ private_constant :FEATURES
14
+
15
+ # Derives the feature set from the environment variables. A feature
16
+ # is enabled if the corresponding environment variable is set to "1",
17
+ # otherwise it is disabled.
18
+ def self.derive_from_env
19
+ values = FEATURES.transform_values { |key| ENV[key] == "1" }
20
+ new(values)
21
+ end
22
+
23
+ def support_dynamic_linking?
24
+ @features[:dynamic_linking]
25
+ end
26
+
27
+ def support_component_model?
28
+ @features[:component_model] || @features[:dynamic_linking]
29
+ end
30
+ 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
@@ -12,7 +12,7 @@ class RubyWasm::Packager::Core
12
12
 
13
13
  extend Forwardable
14
14
 
15
- def_delegators :build_strategy, :cache_key, :artifact
15
+ def_delegators :build_strategy, :cache_key, :artifact, :build_and_link_exts
16
16
 
17
17
  private
18
18
 
@@ -20,7 +20,7 @@ class RubyWasm::Packager::Core
20
20
  @build_strategy ||=
21
21
  begin
22
22
  has_exts = @packager.specs.any? { |spec| spec.extensions.any? }
23
- if @packager.support_dynamic_linking?
23
+ if @packager.features.support_dynamic_linking?
24
24
  DynamicLinking.new(@packager)
25
25
  else
26
26
  StaticLinking.new(@packager)
@@ -37,6 +37,10 @@ class RubyWasm::Packager::Core
37
37
  raise NotImplementedError
38
38
  end
39
39
 
40
+ def build_and_link_exts(executor)
41
+ raise NotImplementedError
42
+ end
43
+
40
44
  # Array of paths to extconf.rb files.
41
45
  def specs_with_extensions
42
46
  @packager.specs.filter_map do |spec|
@@ -61,6 +65,167 @@ class RubyWasm::Packager::Core
61
65
  end
62
66
 
63
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
64
229
  end
65
230
 
66
231
  class StaticLinking < BuildStrategy
@@ -99,10 +264,14 @@ class RubyWasm::Packager::Core
99
264
  derive_build.crossruby.artifact
100
265
  end
101
266
 
267
+ def target
268
+ RubyWasm::Target.new(@packager.full_build_options[:target])
269
+ end
270
+
102
271
  def derive_build
103
272
  return @build if @build
104
273
  __skip__ =
105
- build ||= RubyWasm::Build.new(name, **@packager.full_build_options)
274
+ build ||= RubyWasm::Build.new(name, **@packager.full_build_options, target: target)
106
275
  build.crossruby.user_exts = user_exts(build)
107
276
  # Emscripten uses --global-base=1024 by default, but it conflicts with
108
277
  # --stack-first and -z stack-size since global-base 1024 is smaller than
@@ -111,7 +280,9 @@ class RubyWasm::Packager::Core
111
280
  # script of Ruby.
112
281
  if @packager.full_build_options[:target] != "wasm32-unknown-emscripten"
113
282
  build.crossruby.debugflags = %w[-g]
114
- build.crossruby.wasmoptflags = %w[-O3 -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]
115
286
  build.crossruby.ldflags = %w[
116
287
  -Xlinker
117
288
  --stack-first
@@ -125,6 +296,23 @@ class RubyWasm::Packager::Core
125
296
  build
126
297
  end
127
298
 
299
+ def build_and_link_exts(executor)
300
+ build = derive_build
301
+ ruby_root = build.crossruby.dest_dir
302
+ module_bytes = File.binread(File.join(ruby_root, "usr", "local", "bin", "ruby"))
303
+ return module_bytes unless @packager.features.support_component_model?
304
+
305
+ linker = RubyWasmExt::ComponentEncode.new
306
+ linker.validate(true)
307
+ linker.module(module_bytes)
308
+ linker.adapter(
309
+ "wasi_snapshot_preview1",
310
+ File.binread(RubyWasm::Packager::ComponentAdapter.wasi_snapshot_preview1("reactor"))
311
+ )
312
+
313
+ linker.encode()
314
+ end
315
+
128
316
  def user_exts(build)
129
317
  @user_exts ||=
130
318
  specs_with_extensions.flat_map do |spec, exts|
@@ -65,12 +65,14 @@ class RubyWasm::Packager::FileSystem
65
65
  end
66
66
 
67
67
  def remove_non_runtime_files(executor)
68
- %w[
69
- **/*.so
68
+ patterns = %w[
70
69
  usr/local/lib/libruby-static.a
71
70
  usr/local/bin/ruby
72
71
  usr/local/include
73
- ].each do |pattern|
72
+ ]
73
+
74
+ patterns << "**/*.so" unless @packager.features.support_dynamic_linking?
75
+ patterns.each do |pattern|
74
76
  Dir
75
77
  .glob(File.join(@dest_dir, pattern))
76
78
  .each do |entry|
@@ -98,6 +100,7 @@ class RubyWasm::Packager::FileSystem
98
100
  def each_gem_require_path(&block)
99
101
  each_gem_extension_path(&block)
100
102
  @packager.specs.each do |spec|
103
+ # Use raw_require_paths to exclude extensions
101
104
  spec.raw_require_paths.each do |require_path|
102
105
  source = File.expand_path(require_path, spec.full_gem_path)
103
106
  next unless File.exist?(source)
@@ -114,20 +117,20 @@ class RubyWasm::Packager::FileSystem
114
117
  @packager.specs.each do |spec|
115
118
  next unless File.exist?(spec.full_gem_path)
116
119
 
117
- Dir
118
- .children(spec.full_gem_path)
119
- .each do |require_path|
120
- source = File.expand_path(require_path, spec.full_gem_path)
121
- next unless File.exist?(source)
122
- relative =
123
- File.join(
124
- bundle_relative_path,
125
- "gems",
126
- spec.full_name,
127
- require_path
128
- )
129
- yield relative, source
130
- end
120
+ # spec.files is only valid before the gem is packaged.
121
+ if spec.source.path?
122
+ relative_paths = spec.files
123
+ else
124
+ # All files in .gem are required.
125
+ relative_paths = Dir.children(spec.full_gem_path)
126
+ end
127
+ relative_paths.each do |require_path|
128
+ source = File.expand_path(require_path, spec.full_gem_path)
129
+ next unless File.exist?(source)
130
+ relative =
131
+ File.join(bundle_relative_path, "gems", spec.full_name, require_path)
132
+ yield relative, source
133
+ end
131
134
  end
132
135
  end
133
136