はじめに
- ruby.wasmバイナリに3rd party製のgem組み込んだ際のトラブルシュートのログを書き残しておきます。
- 2024年6月現在のHACKです。今後の開発が進めば不要になると想定しています。
- こちらのスライドの補足記事です。
前提
下記PRにより、Bundlerと ruby_wasm
gemを利用して独自のruby.wasmバイナリが作成できるようになっています。
$ 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
解法
ひとまずこのトラブルを回避してみましょう。
ruby.wasmでRailsを動かす対応たちの中に解法のヒントがあります。
ビルド時に組み込まない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)
実行結果(パッケージ化成功!)
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が使えるようになりました!
おわりに
そうして出来上がったのがこちらです。
Service Worker上でrubyを動かしてymlをパースするwasmバイナリが完成しました。
example
のブロックを読んでダミーのレスポンスを返すことも可能! 今後はスキーマから自動生成するようにしたい所。
本記事の内容はいずれ不要になるHACKかと思いますが、四苦八苦した履歴として書き残しておきます。