はじめに
備忘用。
この記事では、uvとPyO3を使ってPythonからRustを呼び出すプロジェクトにおける、ビルド方法とWindows⇔WSL間のクロスコンパイルの方法について述べます。
一応、以下の記事からの続きとなります。
対象のプロジェクト
ビルド対象のプロジェクトは以下のexample-ext
プロジェクトです。
前々回(①)、前回(②)の記事で扱ったプロジェクトと同様です。
この記事の後半で、Windows⇔WSL間のクロスコンパイルを行いますが、ビルドがうまくいったかのテストとして、example_ext.main()
が実行できるかを確認します。
>>> import example_ext
>>> example_ext.main()
3
3
3.0
Hello, World!
Hello, Bob!
[2, 4, 6]
[2, '2', 6, [1, 2], ('1', 2), {'key': 'value'}]
{'text1': 3, 'text3': 4, 'text2': 2}
2
None
実行環境
Windows
Windows 11
uv 0.6.3
python 3.12.9
rustup 1.27.1
rustc 1.85.0
cargo 1.85.0
pyo3 0.22.4
※前の記事と比べて微妙に更新してます。
WSL
Ubuntu 22.04
※それ以外のツールのバージョンはWindowsと同じ
Python&Rustプロジェクトのビルド
ビルド方法の話に入る前に、example-ext
プロジェクトをローカルにクローンして、ディレクトリを移動しておきます。
> git clone https://github.com/nukipei/example-ext.git
> cd example-ext
クローン&ディレクトリ移動ができたら、早速ビルドを行います。uvではuv build
1を実行することでビルドを行います。
> uv build
ログを見ると、ビルドシステムに指定したmaturinのビルドコマンドが走っていることや、Rustのコンパイルが行われていることがわかると思います。
uv buildのログ
> uv build
Building source distribution...
Running `maturin pep517 write-sdist --sdist-directory C:\path\example-ext\dist`
🍹 Building a mixed python/rust project
🔗 Found pyo3 bindings with abi3 support for Python ≥ 3.9
From `cargo package --list --allow-dirty --manifest-path C:\path\example-ext\Cargo.toml`:
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
📦 Built source distribution to C:\path\example-ext\dist\example_ext-0.1.0.tar.gz
example_ext-0.1.0.tar.gz
Building wheel from source distribution...
Running `maturin pep517 build-wheel -i C:\Users\path\AppData\Local\uv\cache\builds-v0\.tmpJ7aONa\Scripts\python.exe --compatibility off`
🍹 Building a mixed python/rust project
🔗 Found pyo3 bindings with abi3 support for Python ≥ 3.9
Compiling target-lexicon v0.12.16
Compiling once_cell v1.20.2
Compiling proc-macro2 v1.0.93
Compiling unicode-ident v1.0.15
Compiling libc v0.2.169
Compiling autocfg v1.4.0
Compiling heck v0.5.0
Compiling cfg-if v1.0.0
Compiling unindent v0.2.3
Compiling indoc v2.0.5
Compiling memoffset v0.9.1
Compiling quote v1.0.38
Compiling pyo3-build-config v0.22.6
Compiling syn v2.0.96
Compiling pyo3-ffi v0.22.6
Compiling pyo3-macros-backend v0.22.6
Compiling pyo3 v0.22.6
Compiling pyo3-macros v0.22.6
Compiling example_ext v0.1.0 (C:\Users\path\AppData\Local\uv\cache\sdists-v8\.tmpX2fVS2\example_ext-0.1.0)
Finished `release` profile [optimized] target(s) in 17.25s
📦 Built wheel for abi3 Python ≥ 3.9 to C:\Users\path\AppData\Local\uv\cache\sdists-v8\.tmpX2fVS2\example_ext-0.1.0\target\wheels\example_ext-0.1.0-cp39-abi3-win_amd64.whl
C:\Users\path\AppData\Local\uv\cache\sdists-v8\.tmpX2fVS2\example_ext-0.1.0\target\wheels\example_ext-0.1.0-cp39-abi3-win_amd64.whl
Successfully built dist\example_ext-0.1.0.tar.gz
Successfully built dist\example_ext-0.1.0-cp39-abi3-win_amd64.whl
ビルドされたモジュールは、dist
ディレクトリ配下に出力されます。なお、出力先のディレクトリを指定したい場合は、uv build -o <path>
で指定できます。
>tree /f dist
.gitignore
example_ext-0.1.0-cp39-abi3-win_amd64.whl
example_ext-0.1.0.tar.gz
デフォルトでは、.whl
と.tar.gz
が生成されますが、片方の形式のみの生成も可能であり、uv build --wheel
で.whl
のみを、uv build --sdist
で.tar.gz
のみを生成できます。(.tar.gz
にはRustのバイナリが含まれないことに注意してください。)
example_ext-0.1.0-cp39-abi3-win_amd64.whl
を7zipなどで解凍すると、以下のようなディレクトリ構成となっているはずで、example_extプロジェクトのPythonファイルに加え、RustのWindows用のバイナリである_core.pyd
2が含まれていることが確認できます。
├─example_ext
│ _core.pyd
│ _core.pyi
│ __init__.py
│
└─example_ext-0.1.0.dist-info
entry_points.txt
METADATA
RECORD
WHEEL
Windows⇔WSL間のクロスコンパイル
Windowsでビルド⇒WSLで実行
Linux向けのコンパイル&ビルド
uvを使ってはいるものの、ビルドの際は、maturinのビルドコマンドが走っています。そこで、maturinのドキュメントの内、クロスコンパイルについて述べた箇所を3参照してみると、クロスコンパイルの方法として、dockerを利用する方法とZigを利用する方法が述べられていました。
この記事では、Zigを使ってみようかと思います。(恥ずかしながら、Zigを触ったことがなかったこともあり。。)
まず、Zigをインストールします。インストール方法はいろいろありそうですが、ここでは公式4からバイナリを含むzipを直接ダウンロードして、ローカルで解凍しました(パスも設定済)。バージョンは以下の通りです。
> zig version
0.14.0-dev.3348+8683f25d2
次に、RustのLinux向けのコンパイルに必要なライブラリやコンポーネントをダウンロードします。
この記事では、ターゲット(対象の環境)としてx86_64-unknown-linux-gnu
を指定します。
x86_64-unknown-linux-gnu
向けの諸々がダウンロードされているかは、rustup target list
を実行することで確認できます。(x86_64-unknown-linux-gnu
の行にinstalled
が記載されていれば、ダウンロード済)
> rustup target list
(省略)
x86_64-unknown-linux-gnu (installed)
x86_64-unknown-linux-gnux32
(省略)
もし、ダウンロードされていなければ、rustup target add
を実行し、ダウンロードします。
> rustup target add x86_64-unknown-linux-gnu
これで、必要な設定が完了しましたので、x86_64-unknown-linux-gnu
向けのビルドを行います。これは、以下のコマンドを実行します。
> uv build -C build-args="--target x86_64-unknown-linux-gnu --zig"
-C build-args=hogehoge
によって、maturinにビルドオプションを渡すことができます。ここでは、ビルドオプションを2つ指定しており、--target
によってターゲットを指定し、--zig
によってZigを利用してコンパイルするよう設定しています。
以下ログを見れば、maturinによるビルド時に、指定したビルドオプション(--target
と--zig
)が追加されていることがわかると思います。
uv buildのログ
> uv build -C build-args="--target x86_64-unknown-linux-gnu --zig"
Building source distribution...
Running `maturin pep517 write-sdist --sdist-directory C:\path\example-ext\dist`
🍹 Building a mixed python/rust project
🔗 Found pyo3 bindings with abi3 support for Python ≥ 3.9
From `cargo package --list --allow-dirty --manifest-path C:\path\example-ext\Cargo.toml`:
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
📦 Built source distribution to C:\path\example-ext\dist\example_ext-0.1.0.tar.gz
example_ext-0.1.0.tar.gz
Building wheel from source distribution...
Running `maturin pep517 build-wheel -i C:\Users\path\AppData\Local\uv\cache\builds-v0\.tmppJ156N\Scripts\python.exe --compatibility off --target x86_64-unknown-linux-gnu --zig`
🍹 Building a mixed python/rust project
🔗 Found pyo3 bindings with abi3 support for Python ≥ 3.9
🐍 Not using a specific python interpreter
Compiling target-lexicon v0.12.16
Compiling once_cell v1.20.2
Compiling proc-macro2 v1.0.93
Compiling unicode-ident v1.0.15
Compiling autocfg v1.4.0
Compiling libc v0.2.169
Compiling heck v0.5.0
Compiling indoc v2.0.5
Compiling unindent v0.2.3
Compiling cfg-if v1.0.0
Compiling memoffset v0.9.1
Compiling quote v1.0.38
Compiling pyo3-build-config v0.22.6
Compiling syn v2.0.96
Compiling pyo3-macros-backend v0.22.6
Compiling pyo3-ffi v0.22.6
Compiling pyo3 v0.22.6
Compiling pyo3-macros v0.22.6
Compiling example_ext v0.1.0 (C:\Users\path\AppData\Local\uv\cache\sdists-v8\.tmpcgxtaN\example_ext-0.1.0)
Finished `release` profile [optimized] target(s) in 19.67s
📦 Built wheel for abi3 Python ≥ 3.9 to C:\Users\path\AppData\Local\uv\cache\sdists-v8\.tmpcgxtaN\example_ext-0.1.0\target\wheels\example_ext-0.1.0-cp39-abi3-linux_x86_64.whl
🛠️ Using zig for cross-compiling to x86_64-unknown-linux-gnu
C:\Users\path\AppData\Local\uv\cache\sdists-v8\.tmpcgxtaN\example_ext-0.1.0\target\wheels\example_ext-0.1.0-cp39-abi3-linux_x86_64.whl
Successfully built dist\example_ext-0.1.0.tar.gz
Successfully built dist\example_ext-0.1.0-cp39-abi3-linux_x86_64.whl
先ほどと同様に、dist
ディレクトリにビルドされたモジュールが出力されますが、.whl
の名前がexample_ext-0.1.0-cp39-abi3-linux_x86_64.whl
と先ほどと異なっています。
> tree /f dist
.gitignore
example_ext-0.1.0-cp39-abi3-linux_x86_64.whl
example_ext-0.1.0.tar.gz
また、example_ext-0.1.0-cp39-abi3-linux_x86_64.whl
を解凍して中身を確認すると、バイナリの拡張子が.pyd
から.so
に変化しており、Linux向けのバイナリが生成できていることが確認できます。
├─example_ext
│ _core.so
│ _core.pyi
│ __init__.py
│
└─example_ext-0.1.0.dist-info
entry_points.txt
METADATA
RECORD
WHEEL
WSLで実行
Windowsでビルドしたexample_ext-0.1.0-cp39-abi3-linux_x86_64.whl
を、WSLで呼び出してみます。
まず、uvプロジェクトを作成します。プロジェクト名をtest_whl
としています。
> uv init test_whl
PythonのバージョンがWindowsと異なれば、事前に揃えておきます。
> cd test_whl
> uv pin 3.12
次に、生成されたpyproject.toml
と同じディレクトリにexample_ext-0.1.0-cp39-abi3-linux_x86_64.whl
を配置し、pyproject.toml
を以下のように修正します。
[project]
name = "test-whl"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = ["example_ext"]
[tool.uv.sources]
example_ext = { path = "example_ext-0.1.0-cp39-abi3-linux_x86_64.whl" }
そして、uv sync
によって仮想環境を作成し、example_ext
モジュールをインストールします。
uv sync
インストールが成功していれば、以下のようにexample_ext
モジュールを呼び出せるようになっているはずです。
> uv run python
Python 3.12.9 (main, Feb 12 2025, 14:50:50) [Clang 19.1.6 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example_ext
>>> example_ext.main()
3
3
3.0
Hello, World!
Hello, Bob!
[2, 4, 6]
[2, '2', 6, [1, 2], ('1', 2), {'key': 'value'}]
{'text1': 3, 'text3': 4, 'text2': 2}
2
None
WSLでビルド⇒Windowsで実行
次に、WSLでビルドしたモジュールをWindowsで呼びだしてみます。(Windows→WSLと流れはほとんど変わりませんので、端折りながら説明します。)
まず、rustupやuvなどWindowsでも利用したツールに加え、Windowsのバイナリ生成に必要なmingw-w64
5をインストールします。
sudo apt install g++-mingw-w64-x86-64
インストールができたら、WSLにexample_ext
プロジェクトをクローンし、ディレクトリを移動しておきます。
git clone https://github.com/nukipei/example-ext
cd example-ext
次に、rustup target list
でWindows向けのターゲットがあるかを確認し、無ければrustup target add
でダウンロードしておきます。(今回のターゲットはx86_64-pc-windows-gnu
としています。)
rustup target add x86_64-pc-windows-gnu
ここで、先ほどと異なる点として、pyproject.tomlのpyo3
のfeatures
として、generate-import-lib
6を追加します。これにより、コンパイルに必要なWindowsのPythonライブラリファイル(python3.dllやpython3x.dll)を自動で生成してくれるとのことです。
[dependencies]
# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so)
# "abi3-py39" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.9
pyo3 = { version = "0.22.4", features = ["extension-module", "abi3-py39", "generate-import-lib"] }
Pythonライブラリファイルの生成の際、中身ではpython3_dll_a
7クレートを使っているとのことです。
maturinのでドキュメント6によると、generate-import-lib
はexperimental feature
とのことなので、実際の案件で利用の際は注意してください。
最後にuv build
を実行します。オプションとして、-C build-args="--target=x86_64-pc-windows-gnu"
を設定しています。
uv build -C build-args="--target=x86_64-pc-windows-gnu"
これにより、dist
ディレクトリ配下にexample_ext-0.1.0-cp39-abi3-win_amd64.whl
が生成されます。
あとは、example_ext-0.1.0-cp39-abi3-win_amd64.whl
をWindowsにインストールすれば、example_ext.main()
を呼び出せるはずです。
WSLにZigをインストール8した上で、Windowsでビルドしたときと同様に、オプションに--zig
を指定してuv build
を実行したところ、以下のような警告が発生し、正常なモジュールを生成することができませんでした。
⚠️ Warning: Couldn't find the symbol `PyInit__core` in the native library. Python will fail to import this module. If you're using pyo3, check that `#[pymodule]` uses `_core` as module name
類似の事象を報告した、maturinのissue9ではzigのバグとのコメントもあり、WSLに関してはZigを使ったビルドを断念しました。
最後に
この記事では、ythonからRustを呼び出すプロジェクトにおける、ビルドの方法とWindows⇔WSL間のクロスコンパイルの方法について述べました。
勉強したてなので、もし、「この方法のほうが良いよ」などありましたら、コメントよろしくお願いします。
-
pydはdllと同じようなものだが、少し異なるとのこと。詳しくは、https://docs.python.org/ja/3.12/faq/windows.html#is-a-pyd-file-the-same-as-a-dll 参照。 ↩
-
インストール方法はhttps://qiita.com/kerupani129/items/5d68a80f402cbc6a623f を参考にさせていただきました。 ↩
-
https://www.maturin.rs/distribution#cross-compile-to-windows ↩ ↩2
-
インストール方法はhttps://qiita.com/bam_b0o_/items/27770ffcf0551ce9e813 を参考にさせていただきました。 ↩