ルニラボ

lni_T の長文置き場

ruby.wasmバイナリにgemを組み込むときのトラブル回避HACK(2024/06版)

はじめに

  • ruby.wasmバイナリに3rd party製のgem組み込んだ際のトラブルシュートのログを書き残しておきます。
  • 2024年6月現在のHACKです。今後の開発が進めば不要になると想定しています。
  • こちらのスライドの補足記事です。

前提

下記PRにより、Bundlerと ruby_wasm gemを利用して独自のruby.wasmバイナリが作成できるようになっています。

github.com

$ bundle init
Writing new Gemfile to /tmp/tmp.ZeVCP05HyB/Gemfile
$ bundle add ruby_wasm
$ bundle add rainbow
$ bundle exec rbwasm build -o ruby.wasm
(snip)
INFO: Packaging gem: rainbow-3.1.1
INFO: Packaging setup.rb: bundle/setup.rb
INFO: Size: 51.17 MB

これによりPure Rubyなgemをruby.wasmバイナリ内に組み込むことが可能です。

問題

しかし、Ruby製のgemであっても、参照しているgemにC-Extensionなgemが含まれていた場合、現在正式リリースされているツールたちではビルドに失敗してしまうようです。

こちらは試しに openapi3_parser を追加したときの実行結果です。

$ bundle add openapi3_parser
$ bundle exec rbwasm build -o ruby.wasm
bundler: failed to load command: rbwasm (/Users/********/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/bin/rbwasm)

先日のRubyKaigi 2024でも紹介されていましたが、現状はクロスコンパイル対応を進めている途中のようです。

https://speakerdeck.com/kateinoigakukun/rubygems-on-ruby-dot-wasm?slide=31

github.com

解法

ひとまずこのトラブルを回避してみましょう。
ruby.wasmでRailsを動かす対応たちの中に解法のヒントがあります。

github.com

ビルド時に組み込まないgemを指定する

どうやらRubyWasm::Packagerの定数を書き換えるHACKが存在するようです。

先人にならって以下のようなビルドスクリプトを作ることで、パッケージ化に失敗するgemをパッケージ対象から除外できました。

#!/usr/bin/env ruby

require "bundler/setup"

require "ruby_wasm"
require "ruby_wasm/cli"

# Add excluded gems
RubyWasm::Packager::EXCLUDED_GEMS << "commonmarker"

RubyWasm::CLI.new(stdout: $stdout, stderr: $stderr).run(ARGV)

https://github.com/lnit/ruby_wasm_openapi_mockserver/blob/a8a0f3aecd7ded328547d37b105c2c1d287f11a7/bin/rbwasm#L14-L17

実行結果(パッケージ化成功!)

INFO: Packaging gem: rb_sys-0.9.97
INFO: Packaging gem: openapi3_parser-0.10.0
INFO: Packaging setup.rb: bundle/setup.rb
INFO: Size: 51.6 MB

※公式仕様ではないので利用は自己責任で!

ダミーファイルを設置する

パッケージ対象から除外しても、それを参照するコードは除外できません。
requireされているコードが残っている以上、実行時にLoadErrorが発生してしまいます。

$ cat <<EOS > app.rb
require "/bundle/setup"
require "openapi3_parser"
EOS
$ wasmtime run --dir ./::/ ruby.wasm app.rb
<internal:/usr/local/lib/ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require': cannot load such file -- commonmarker (LoadError)

次はこれを回避します。

どうやらMarkdown関連の処理に使用されているgemのようです。 今回はymlのパーサーが欲しいだけなので、この機能は使いません。

ということで、ダミーのファイルを与えてLoadErrorくんには見逃してもらいましょう。

Load対象と同じ名前の空っぽのファイルを用意し、LOAD_PATHの先頭に追加します。

$LOAD_PATH.unshift File.expand_path("stubs", __dir__)

これでLoadErrorも起きず、無事wasmランタイム上でopenapi3_parser gemが使えるようになりました!

おわりに

そうして出来上がったのがこちらです。

ruby-wasm-oas-mock.lnilab.net

Service Worker上でrubyを動かしてymlをパースするwasmバイナリが完成しました。 example のブロックを読んでダミーのレスポンスを返すことも可能! 今後はスキーマから自動生成するようにしたい所。

本記事の内容はいずれ不要になるHACKかと思いますが、四苦八苦した履歴として書き残しておきます。