2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

uvとPyO3でPythonからRustを呼ぶ③(ビルドとクロスコンパイル)

Posted at

はじめに

備忘用。
この記事では、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 build1を実行することでビルドを行います。

> 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.pyd2が含まれていることが確認できます。

├─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を以下のように修正します。

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-w645をインストールします。

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のpyo3featuresとして、generate-import-lib6を追加します。これにより、コンパイルに必要な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_a7クレートを使っているとのことです。

maturinのでドキュメント6によると、generate-import-libexperimental 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間のクロスコンパイルの方法について述べました。

勉強したてなので、もし、「この方法のほうが良いよ」などありましたら、コメントよろしくお願いします。

  1. https://docs.astral.sh/uv/concepts/projects/build/

  2. pydはdllと同じようなものだが、少し異なるとのこと。詳しくは、https://docs.python.org/ja/3.12/faq/windows.html#is-a-pyd-file-the-same-as-a-dll 参照。

  3. https://www.maturin.rs/distribution#cross-compiling

  4. https://ziglang.org/download/

  5. インストール方法はhttps://qiita.com/kerupani129/items/5d68a80f402cbc6a623f を参考にさせていただきました。

  6. https://www.maturin.rs/distribution#cross-compile-to-windows 2

  7. https://docs.rs/python3-dll-a/latest/python3_dll_a/

  8. インストール方法はhttps://qiita.com/bam_b0o_/items/27770ffcf0551ce9e813 を参考にさせていただきました。

  9. https://github.com/PyO3/maturin/issues/922

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?