🪄

Rust/PyO3拡張のwasm wheelを作る

2024/01/13に公開

前回はC拡張をwasmにビルドしてPyodideで使う方法をまとめました。今回はRustで書いたPyO3拡張をwasmにビルドしてPyodideで使う方法をまとめます。今回のコードも前回と同じく以下のリポジトリにあります。

https://github.com/termoshtt/pyodide-wasm-wheel-example

環境構築

今回直接使うことになるツールは通常のRust/PyO3プロジェクトと同様 maturin です

https://github.com/PyO3/maturin

これが pyodide-build とほぼ同等の作業、つまりPyO3のプロジェクトをビルドしてpyodide用のwheelを作ってくれます。pyodide-buildは裏でEmscriptenのCコンパイラemccを呼び出していましたが、 maturin はRustコンパイラのwasm32-unknown-emscriptenターゲットを使います。なので rustup でこのターゲットをインストールしておきます。このターゲットはRustのnightlyビルドでのみ利用可能です(stableにもインストールできますが、実行するとNightlyの機能を使っているので失敗します)。

rustup toolchain install nightly
rustup target add --toolchain nightly wasm32-unknown-emscripten

RustコンパイラはさらにリンカとしてEmscriptenのコンパイラ emcc を使います。なので前回と同様emsdkをインストールします。

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install 3.1.45
./emsdk activate 3.1.45
source emsdk_env.sh

emsdkのバージョンは前回とあわせておきました。別バージョンだとどうなるのかは検証していません。

Rust/PyO3拡張のビルド

まずは maturin をインストールします。pipで用意するのが簡単でしょう

pip install maturin

PyO3プロジェクトを作るには maturin new を使います。

maturin new -b pyo3 rust-extension

これで rust-extension というディレクトリが作られます。このディレクトリで maturin build とすると通常のwheelがビルドされますが、今回はwasm用にビルドします。まずはnightlyコンパイラを使うので rust-toolchain を書き換えます。

echo "nightly" > rust-extension/rust-toolchain

toolchainファイルについてはrustupのドキュメントを見てください。これでwasm wheelがビルドできます。

cd rust-extension
maturin build --release -o dist --target wasm32-unknown-emscripten -i python3.11

この時インタプリタを -i python3.11 のように指定する必要があることに注意してください。これはおそらく普段はPythonのランタイムがあるのでそこから自動的に取得していますが、今回はpyodideのランタイムはこの段階で存在していないので明示的に指定する必要があるのでしょう。-o distはwheelの出力先です。ここにwheelが出来上がります。

dist
└── rust_extension-0.1.0-cp311-cp311-emscripten_3_1_45_wasm32.whl

Node.jsでテストする

出来上がったwheelをGitHub Pages等にアップロードすることで前回みたようにJupyterLiteから使うことができますが、今回は別の方法でテストしましょう。PyodideはブラウザだけでなくNode.jsでも使うことができるので、これを使ってみましょう。

https://pyodide.org/en/stable/usage/index.html#node-js
https://www.npmjs.com/package/pyodide

まず適当に npm 環境を用意しておき、 pyodide をインストールします。

npm install pyodide

これで node_modulespyodide がインストールされて次のように使うことができます

test.js
const { loadPyodide } = require("pyodide");

async function hello_python() {
  let pyodide = await loadPyodide();
  return pyodide.runPythonAsync("1+1");
}

hello_python().then((result) => {
  console.log("Python says that 1+1 =", result);
});
node ./test.js  # => Python says that 1+1 = 2

このJavaScriptのAPIのリファレンスは以下にあります

https://pyodide.org/en/stable/usage/api/js-api.html

これはローカルのファイルシステムにあるwheelも読み込むことができます。

https://github.com/termoshtt/pyodide-wasm-wheel-example/blob/7bf00889fe2b5286c41267af21ff92805707d81f/test_rust.js

これはCIでテストするのに便利です。

GitHubで編集を提案

Discussion