ruby_wasm 2.5.0 → 2.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +7 -7
  3. data/Cargo.lock +91 -6
  4. data/Gemfile +1 -1
  5. data/README.md +9 -9
  6. data/Rakefile +9 -7
  7. data/docs/cheat_sheet.md +8 -8
  8. data/ext/ruby_wasm/Cargo.toml +3 -1
  9. data/ext/ruby_wasm/src/lib.rs +198 -8
  10. data/lib/ruby_wasm/build/executor.rb +4 -0
  11. data/lib/ruby_wasm/build/product/crossruby.rb +59 -25
  12. data/lib/ruby_wasm/build/product/libyaml.rb +5 -3
  13. data/lib/ruby_wasm/build/product/openssl.rb +7 -2
  14. data/lib/ruby_wasm/build/product/product.rb +3 -3
  15. data/lib/ruby_wasm/build/product/ruby_source.rb +3 -3
  16. data/lib/ruby_wasm/build/product/wasi_vfs.rb +1 -1
  17. data/lib/ruby_wasm/build/product/zlib.rb +3 -1
  18. data/lib/ruby_wasm/build/target.rb +24 -0
  19. data/lib/ruby_wasm/build/toolchain/wit_bindgen.rb +2 -2
  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 +147 -11
  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 +199 -5
  28. data/lib/ruby_wasm/packager/file_system.rb +5 -3
  29. data/lib/ruby_wasm/packager.rb +22 -82
  30. data/lib/ruby_wasm/rake_task.rb +1 -0
  31. data/lib/ruby_wasm/version.rb +1 -1
  32. data/lib/ruby_wasm.rb +2 -0
  33. data/package-lock.json +5571 -7015
  34. data/package.json +3 -3
  35. data/rakelib/check.rake +23 -10
  36. data/rakelib/ci.rake +3 -3
  37. data/rakelib/packaging.rake +44 -15
  38. data/sig/ruby_wasm/build.rbs +38 -28
  39. data/sig/ruby_wasm/cli.rbs +27 -3
  40. data/sig/ruby_wasm/ext.rbs +25 -2
  41. data/sig/ruby_wasm/feature_set.rbs +12 -0
  42. data/sig/ruby_wasm/packager.rbs +44 -7
  43. metadata +9 -10
  44. data/builders/wasm32-unknown-emscripten/Dockerfile +0 -43
  45. data/builders/wasm32-unknown-emscripten/entrypoint.sh +0 -7
  46. data/builders/wasm32-unknown-wasi/Dockerfile +0 -47
  47. data/builders/wasm32-unknown-wasi/entrypoint.sh +0 -7
@@ -5,11 +5,12 @@ module RubyWasm
5
5
  class CrossRubyExtProduct < BuildProduct
6
6
  attr_reader :name
7
7
 
8
- def initialize(srcdir, toolchain, ext_relative_path: nil)
8
+ def initialize(srcdir, toolchain, features:, ext_relative_path: nil)
9
9
  @srcdir, @toolchain = srcdir, toolchain
10
10
  # ext_relative_path is relative path from build dir
11
11
  # e.g. cgi-0.3.6/ext/cgi/escape
12
12
  @ext_relative_path = ext_relative_path || File.basename(srcdir)
13
+ @features = features
13
14
  @name = @ext_relative_path
14
15
  end
15
16
 
@@ -37,7 +38,6 @@ module RubyWasm
37
38
  make_args << "AR=#{@toolchain.ar}"
38
39
  make_args << "RANLIB=#{@toolchain.ranlib}"
39
40
 
40
- make_args << "DESTDIR=#{crossruby.dest_dir}"
41
41
  make_args
42
42
  end
43
43
 
@@ -45,12 +45,15 @@ module RubyWasm
45
45
  objdir = product_build_dir crossruby
46
46
  executor.mkdir_p objdir
47
47
  do_extconf executor, crossruby
48
+
49
+ executor.system "make", "-C", objdir, *make_args(crossruby), "clean"
50
+ build_target = crossruby.target.pic? ? "install-so" : "static"
48
51
  executor.system "make",
49
52
  "-j#{executor.process_count}",
50
53
  "-C",
51
54
  "#{objdir}",
52
55
  *make_args(crossruby),
53
- "static"
56
+ build_target
54
57
  # A ext can provide link args by link.filelist. It contains only built archive file by default.
55
58
  unless File.exist?(linklist(crossruby))
56
59
  executor.write(
@@ -61,9 +64,28 @@ module RubyWasm
61
64
  end
62
65
 
63
66
  def do_extconf(executor, crossruby)
67
+ unless crossruby.target.pic?
68
+ self.do_legacy_extconf(executor, crossruby)
69
+ return
70
+ end
71
+ objdir = product_build_dir crossruby
72
+ source = crossruby.source
73
+ rbconfig_rb = Dir.glob(File.join(crossruby.dest_dir, "usr/local/lib/ruby/*/wasm32-wasi/rbconfig.rb")).first
74
+ raise "rbconfig.rb not found" unless rbconfig_rb
75
+ extconf_args = [
76
+ "-C", objdir,
77
+ "#{@srcdir}/extconf.rb",
78
+ "--target-rbconfig=#{rbconfig_rb}",
79
+ ]
80
+ extconf_args << "--enable-component-model" if @features.support_component_model?
81
+ executor.system Gem.ruby, *extconf_args
82
+ end
83
+
84
+ def do_legacy_extconf(executor, crossruby)
64
85
  objdir = product_build_dir crossruby
65
86
  source = crossruby.source
66
87
  extconf_args = [
88
+ "-C", objdir,
67
89
  "--disable=gems",
68
90
  # HACK: top_srcdir is required to find ruby headers
69
91
  "-e",
@@ -71,9 +93,6 @@ module RubyWasm
71
93
  # HACK: extout is required to find config.h
72
94
  "-e",
73
95
  %Q($extout="#{crossruby.build_dir}/.ext"),
74
- # HACK: skip have_devel check since ruby is not installed yet
75
- "-e",
76
- "$have_devel = true",
77
96
  # HACK: force static ext build by imitating extmk
78
97
  "-e",
79
98
  "$static = true; trace_var(:$static) {|v| $static = true }",
@@ -88,12 +107,13 @@ module RubyWasm
88
107
  # like "cgi/escape" instead of "escape"
89
108
  "-e",
90
109
  %Q(require "json"; File.write("#{metadata_json(crossruby)}", JSON.dump({target: $target}))),
91
- "-I#{crossruby.build_dir}"
110
+ "-I#{crossruby.build_dir}",
111
+ "--",
92
112
  ]
113
+ extconf_args << "--enable-component-model" if @features.support_component_model?
93
114
  # Clear RUBYOPT to avoid loading unrelated bundle setup
94
115
  executor.system crossruby.baseruby_path,
95
116
  *extconf_args,
96
- chdir: objdir,
97
117
  env: {
98
118
  "RUBYOPT" => ""
99
119
  }
@@ -118,7 +138,7 @@ module RubyWasm
118
138
  end
119
139
 
120
140
  class CrossRubyProduct < AutoconfProduct
121
- attr_reader :source, :toolchain
141
+ attr_reader :target, :source, :toolchain
122
142
  attr_accessor :user_exts,
123
143
  :wasmoptflags,
124
144
  :cppflags,
@@ -155,6 +175,14 @@ module RubyWasm
155
175
  executor.system "make", "rbconfig.rb", chdir: build_dir
156
176
  end
157
177
 
178
+ def need_exts_build?
179
+ @user_exts.any?
180
+ end
181
+
182
+ def need_extinit_obj?
183
+ need_exts_build? && !@target.pic?
184
+ end
185
+
158
186
  def build_exts(executor)
159
187
  @user_exts.each do |prod|
160
188
  executor.begin_section prod.class, prod.name, "Building"
@@ -168,6 +196,7 @@ module RubyWasm
168
196
  executor.mkdir_p build_dir
169
197
  @toolchain.install
170
198
  [@source, @baseruby, @libyaml, @zlib, @openssl, @wasi_vfs].each do |prod|
199
+ next unless prod
171
200
  executor.begin_section prod.class, prod.name, "Building"
172
201
  prod.build(executor)
173
202
  executor.end_section prod.class, prod.name
@@ -176,17 +205,20 @@ module RubyWasm
176
205
  configure(executor, reconfigure: reconfigure)
177
206
  executor.end_section self.class, name
178
207
 
179
- build_exts(executor)
208
+ build_exts(executor) if need_exts_build?
180
209
 
181
210
  executor.begin_section self.class, name, "Building"
182
- executor.mkdir_p File.dirname(extinit_obj)
183
- executor.system "ruby",
184
- extinit_c_erb,
185
- *@user_exts.map { |ext| ext.feature_name(self) },
186
- "--cc",
187
- toolchain.cc,
188
- "--output",
189
- extinit_obj
211
+
212
+ if need_extinit_obj?
213
+ executor.mkdir_p File.dirname(extinit_obj)
214
+ executor.system "ruby",
215
+ extinit_c_erb,
216
+ *@user_exts.map { |ext| ext.feature_name(self) },
217
+ "--cc",
218
+ toolchain.cc,
219
+ "--output",
220
+ extinit_obj
221
+ end
190
222
  install_dir = File.join(build_dir, "install")
191
223
  if !File.exist?(install_dir) || remake || reconfigure
192
224
  executor.system "make",
@@ -216,7 +248,7 @@ module RubyWasm
216
248
  end
217
249
 
218
250
  def cache_key(digest)
219
- digest << @params.target
251
+ @params.target.cache_key(digest)
220
252
  digest << @params.default_exts
221
253
  @wasmoptflags.each { |f| digest << f }
222
254
  @cppflags.each { |f| digest << f }
@@ -229,11 +261,11 @@ module RubyWasm
229
261
  end
230
262
 
231
263
  def build_dir
232
- File.join(@build_dir, @params.target, name)
264
+ File.join(@build_dir, @params.target.to_s, name)
233
265
  end
234
266
 
235
267
  def ext_build_dir
236
- File.join(@build_dir, @params.target, name + "-ext")
268
+ File.join(@build_dir, @params.target.to_s, name + "-ext")
237
269
  end
238
270
 
239
271
  def with_libyaml(libyaml)
@@ -274,14 +306,14 @@ module RubyWasm
274
306
  end
275
307
 
276
308
  def configure_args(build_triple, toolchain)
277
- target = @params.target
309
+ target = @params.target.triple
278
310
  default_exts = @params.default_exts
279
311
 
280
312
  ldflags = @ldflags.dup
281
313
  xldflags = @xldflags.dup
282
314
 
283
315
  args = self.system_triplet_args + ["--build", build_triple]
284
- args << "--with-static-linked-ext"
316
+ args << "--with-static-linked-ext" unless @params.target.pic?
285
317
  args << %Q(--with-ext=#{default_exts})
286
318
  args << %Q(--with-libyaml-dir=#{@libyaml.install_root})
287
319
  args << %Q(--with-zlib-dir=#{@zlib.install_root})
@@ -289,7 +321,7 @@ module RubyWasm
289
321
  args << %Q(--with-baseruby=#{baseruby_path})
290
322
 
291
323
  case target
292
- when "wasm32-unknown-wasi"
324
+ when /^wasm32-unknown-wasi/
293
325
  xldflags << @wasi_vfs.lib_wasi_vfs_a if @wasi_vfs
294
326
  # TODO: Find a way to force cast or update API
295
327
  # @type var wasi_sdk_path: untyped
@@ -308,8 +340,9 @@ module RubyWasm
308
340
 
309
341
  args.concat(self.tools_args)
310
342
  (@user_exts || []).each { |lib| xldflags << "@#{lib.linklist(self)}" }
311
- xldflags << extinit_obj
343
+ xldflags << extinit_obj if need_extinit_obj?
312
344
 
345
+ cflags = @cflags.dup
313
346
  xcflags = @xcflags.dup
314
347
  xcflags << "-DWASM_SETJMP_STACK_BUFFER_SIZE=24576"
315
348
  xcflags << "-DWASM_FIBER_STACK_BUFFER_SIZE=24576"
@@ -317,6 +350,7 @@ module RubyWasm
317
350
 
318
351
  args << %Q(LDFLAGS=#{ldflags.join(" ")})
319
352
  args << %Q(XLDFLAGS=#{xldflags.join(" ")})
353
+ args << %Q(CFLAGS=#{cflags.join(" ")})
320
354
  args << %Q(XCFLAGS=#{xcflags.join(" ")})
321
355
  args << %Q(debugflags=#{@debugflags.join(" ")})
322
356
  args << %Q(cppflags=#{@cppflags.join(" ")})
@@ -13,7 +13,7 @@ module RubyWasm
13
13
  end
14
14
 
15
15
  def product_build_dir
16
- File.join(@build_dir, target, "yaml-#{LIBYAML_VERSION}")
16
+ File.join(@build_dir, target.to_s, "yaml-#{LIBYAML_VERSION}")
17
17
  end
18
18
 
19
19
  def destdir
@@ -52,12 +52,14 @@ module RubyWasm
52
52
  executor.system "curl",
53
53
  "-o",
54
54
  "#{product_build_dir}/config/config.guess",
55
- "https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD"
55
+ "https://cdn.jsdelivr.net/gh/gcc-mirror/gcc@master/config.guess"
56
56
  executor.system "curl",
57
57
  "-o",
58
58
  "#{product_build_dir}/config/config.sub",
59
- "https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD"
59
+ "https://cdn.jsdelivr.net/gh/gcc-mirror/gcc@master/config.sub"
60
60
 
61
+ configure_args = self.configure_args.dup
62
+ configure_args << "CFLAGS=-fPIC" if target.pic?
61
63
  executor.system "./configure", *configure_args, chdir: product_build_dir
62
64
  executor.system "make",
63
65
  "install",
@@ -13,7 +13,7 @@ module RubyWasm
13
13
  end
14
14
 
15
15
  def product_build_dir
16
- File.join(@build_dir, target, "openssl-#{OPENSSL_VERSION}")
16
+ File.join(@build_dir, target.to_s, "openssl-#{OPENSSL_VERSION}")
17
17
  end
18
18
 
19
19
  def destdir
@@ -42,7 +42,7 @@ module RubyWasm
42
42
  --libdir=lib
43
43
  -Wl,--allow-undefined
44
44
  ]
45
- if @target == "wasm32-unknown-wasi"
45
+ if @target.triple.start_with?("wasm32-unknown-wasi")
46
46
  args.concat %w[
47
47
  -D_WASI_EMULATED_SIGNAL
48
48
  -D_WASI_EMULATED_PROCESS_CLOCKS
@@ -52,6 +52,11 @@ module RubyWasm
52
52
  -DHAVE_FORK=0
53
53
  ]
54
54
  end
55
+
56
+ if @target.pic?
57
+ args << "-fPIC"
58
+ end
59
+
55
60
  args + tools_args
56
61
  end
57
62
 
@@ -12,13 +12,13 @@ module RubyWasm
12
12
  end
13
13
  def system_triplet_args
14
14
  args = []
15
- case @target
16
- when "wasm32-unknown-wasi"
15
+ case @target.triple
16
+ when /^wasm32-unknown-wasi/
17
17
  args.concat(%W[--host wasm32-wasi])
18
18
  when "wasm32-unknown-emscripten"
19
19
  args.concat(%W[--host wasm32-emscripten])
20
20
  else
21
- raise "unknown target: #{@target}"
21
+ raise "unknown target: #{@target.triple}"
22
22
  end
23
23
  args
24
24
  end
@@ -19,7 +19,7 @@ module RubyWasm
19
19
  when "tarball"
20
20
  digest << @params[:url]
21
21
  when "local"
22
- digest << File.mtime(@params[:src]).to_i.to_s
22
+ digest << File.mtime(@params[:path]).to_i.to_s
23
23
  else
24
24
  raise "unknown source type: #{@params[:type]}"
25
25
  end
@@ -75,12 +75,12 @@ module RubyWasm
75
75
  )
76
76
  when "local"
77
77
  executor.mkdir_p File.dirname(src_dir)
78
- executor.cp_r @params[:src], src_dir
78
+ executor.ln_s File.expand_path(@params[:path]), src_dir
79
79
  else
80
80
  raise "unknown source type: #{@params[:type]}"
81
81
  end
82
82
  (@params[:patches] || []).each do |patch_path|
83
- executor.system "patch", "-p1", patch_path, chdir: src_dir
83
+ executor.system "patch", "-p1", "-i", patch_path, chdir: src_dir
84
84
  end
85
85
  end
86
86
 
@@ -12,7 +12,7 @@ module RubyWasm
12
12
  def lib_product_build_dir
13
13
  File.join(
14
14
  @build_dir,
15
- "wasm32-unknown-wasi",
15
+ "wasm32-unknown-wasip1",
16
16
  "wasi-vfs-#{WASI_VFS_VERSION}"
17
17
  )
18
18
  end
@@ -13,7 +13,7 @@ module RubyWasm
13
13
  end
14
14
 
15
15
  def product_build_dir
16
- File.join(@build_dir, target, "zlib-#{ZLIB_VERSION}")
16
+ File.join(@build_dir, target.to_s, "zlib-#{ZLIB_VERSION}")
17
17
  end
18
18
 
19
19
  def destdir
@@ -54,6 +54,8 @@ module RubyWasm
54
54
  product_build_dir,
55
55
  "--strip-components=1"
56
56
 
57
+ configure_args = self.configure_args.dup
58
+ configure_args << "CFLAGS=-fPIC" if target.pic?
57
59
  executor.system "env",
58
60
  *configure_args,
59
61
  "./configure",
@@ -0,0 +1,24 @@
1
+ module RubyWasm
2
+ # A build target representation
3
+ class Target
4
+ attr_reader :triple
5
+
6
+ def initialize(triple, pic: false)
7
+ @triple = triple
8
+ @pic = pic
9
+ end
10
+
11
+ def pic?
12
+ @pic
13
+ end
14
+
15
+ def to_s
16
+ "#{@triple}#{@pic ? "-pic" : ""}"
17
+ end
18
+
19
+ def cache_key(digest)
20
+ digest << @triple
21
+ digest << "pic" if @pic
22
+ end
23
+ end
24
+ end
@@ -4,10 +4,10 @@ module RubyWasm
4
4
 
5
5
  def initialize(
6
6
  build_dir:,
7
- revision: "251e84b89121751f79ac268629e9285082b2596d"
7
+ revision: "v0.24.0"
8
8
  )
9
9
  @build_dir = build_dir
10
- @tool_dir = File.join(@build_dir, "toolchain", "wit-bindgen")
10
+ @tool_dir = File.join(@build_dir, "toolchain", "wit-bindgen-#{revision}")
11
11
  @bin_path = File.join(@tool_dir, "bin", "wit-bindgen")
12
12
  @revision = revision
13
13
  end
@@ -18,7 +18,7 @@ module RubyWasm
18
18
 
19
19
  def self.get(target, build_dir = nil)
20
20
  case target
21
- when "wasm32-unknown-wasi"
21
+ when /^wasm32-unknown-wasi/
22
22
  return RubyWasm::WASISDK.new(build_dir: build_dir)
23
23
  when "wasm32-unknown-emscripten"
24
24
  return RubyWasm::Emscripten.new
@@ -2,6 +2,7 @@ require_relative "build/build_params"
2
2
  require_relative "build/product"
3
3
  require_relative "build/toolchain"
4
4
  require_relative "build/executor"
5
+ require_relative "build/target"
5
6
 
6
7
  class RubyWasm::Build
7
8
  # Source to build from.
@@ -36,6 +37,7 @@ class RubyWasm::Build
36
37
  toolchain:,
37
38
  build_dir:,
38
39
  rubies_dir:,
40
+ wasi_vfs: :default,
39
41
  **options
40
42
  )
41
43
  @target = target
@@ -45,7 +47,7 @@ class RubyWasm::Build
45
47
 
46
48
  @libyaml = RubyWasm::LibYAMLProduct.new(@build_dir, @target, @toolchain)
47
49
  @zlib = RubyWasm::ZlibProduct.new(@build_dir, @target, @toolchain)
48
- @wasi_vfs = RubyWasm::WasiVfsProduct.new(@build_dir)
50
+ @wasi_vfs = wasi_vfs == :default ? RubyWasm::WasiVfsProduct.new(@build_dir) : wasi_vfs
49
51
  @source = RubyWasm::BuildSource.new(src, @build_dir)
50
52
  @baseruby = RubyWasm::BaseRubyProduct.new(@build_dir, @source)
51
53
  @openssl = RubyWasm::OpenSSLProduct.new(@build_dir, @target, @toolchain)
@@ -78,11 +80,13 @@ class RubyWasm::Build
78
80
  @crossruby.cache_key(digest)
79
81
  digest << @build_dir
80
82
  digest << @rubies_dir
81
- digest << @target
83
+ @target.cache_key(digest)
82
84
  digest << @toolchain.name
83
85
  digest << @libyaml.name
84
86
  digest << @zlib.name
85
87
  digest << @openssl.name
86
- digest << @wasi_vfs.name
88
+ if wasi_vfs = @wasi_vfs
89
+ digest << wasi_vfs.name
90
+ end
87
91
  end
88
92
  end
data/lib/ruby_wasm/cli.rb CHANGED
@@ -42,7 +42,7 @@ module RubyWasm
42
42
  end
43
43
 
44
44
  def build(args)
45
- # @type var options: Hash[Symbol, untyped]
45
+ # @type var options: cli_options
46
46
  options = {
47
47
  save_temps: false,
48
48
  optimize: false,
@@ -50,14 +50,16 @@ module RubyWasm
50
50
  reconfigure: false,
51
51
  clean: false,
52
52
  ruby_version: "3.3",
53
- target_triplet: "wasm32-unknown-wasi",
53
+ target_triplet: "wasm32-unknown-wasip1",
54
54
  profile: "full",
55
55
  stdlib: true,
56
- disable_gems: false
56
+ disable_gems: false,
57
+ gemfile: nil,
58
+ patches: [],
57
59
  }
58
60
  OptionParser
59
61
  .new do |opts|
60
- opts.banner = "Usage: rbwasm componentize [options]"
62
+ opts.banner = "Usage: rbwasm build [options]"
61
63
  opts.on("-h", "--help", "Prints this help") do
62
64
  @stdout.puts opts
63
65
  exit
@@ -106,6 +108,10 @@ module RubyWasm
106
108
  options[:disable_gems] = true
107
109
  end
108
110
 
111
+ opts.on("-p", "--patch PATCH", "Apply a patch") do |patch|
112
+ options[:patches] << patch
113
+ end
114
+
109
115
  opts.on("--format FORMAT", "Output format") do |format|
110
116
  options[:format] = format
111
117
  end
@@ -116,8 +122,19 @@ module RubyWasm
116
122
  end
117
123
  .parse!(args)
118
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)
119
135
  verbose = RubyWasm.logger.level == :debug
120
136
  executor = RubyWasm::BuildExecutor.new(verbose: verbose)
137
+
121
138
  packager = self.derive_packager(options)
122
139
 
123
140
  if options[:print_ruby_cache_key]
@@ -152,10 +169,12 @@ module RubyWasm
152
169
  private
153
170
 
154
171
  def build_config(options)
155
- 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 }
156
175
  case options[:profile]
157
176
  when "full"
158
- config[:default_exts] = RubyWasm::Packager::ALL_DEFAULT_EXTS
177
+ config[:default_exts] = all_default_exts || ""
159
178
  env_additional_exts = ENV["RUBY_WASM_ADDITIONAL_EXTS"] || ""
160
179
  unless env_additional_exts.empty?
161
180
  config[:default_exts] += "," + env_additional_exts
@@ -170,12 +189,129 @@ module RubyWasm
170
189
  config
171
190
  end
172
191
 
173
- 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.1.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.4.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
174
272
  __skip__ =
175
- 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
176
305
  definition = Bundler.definition
306
+ ensure
307
+ Bundler.ui.level = old_level
177
308
  end
178
- 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
+ )
179
315
  end
180
316
 
181
317
  def do_print_ruby_cache_key(packager)
@@ -197,9 +333,9 @@ module RubyWasm
197
333
  RubyWasm.logger.info "Size: #{SizeFormatter.format(wasm_bytes.size)}"
198
334
  case options[:output]
199
335
  when "-"
200
- @stdout.write wasm_bytes.pack("C*")
336
+ @stdout.write wasm_bytes
201
337
  else
202
- File.binwrite(options[:output], wasm_bytes.pack("C*"))
338
+ File.binwrite(options[:output], wasm_bytes)
203
339
  RubyWasm.logger.debug "Wrote #{options[:output]}"
204
340
  end
205
341
  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