PythonのBuild Backend
PythonのパッケージシステムはC++やRustで実装されたパッケージをサポートする仕組みを備えていますが、少し複雑なため、問題が起きた際に何が失敗しているのかを把握しづらいケースが多いです。この記事では 現在の仕様に沿って Pythonのビルドシステムの仕組みを解説します。
この記事は主に次のドキュメントを参考にしています:
ビルド配布物(Wheel)とソースコード配布物(sdist)
それぞれのツールの役割を議論するにはまずそれらの入出力を確認するのが良いでしょう。Pythonのビルドシステムは主に次の二つの配布物を扱います:
- ソースコード配布物(sdist): これはさらに二つの用途があります。
- Pythonのみで記述されたパッケージはPythonスクリプトをそのまま配布するので、ビルドは必要ではありません。単にソースコードを適切なディレクトリ構成で
tar.gzに固めたものです。 - C++やRustで記述されたPythonパッケージをユーザーが自分の環境でビルドして使いたいケースがあります。その場合もソースコード配布物として配布します。これはそのままでは使えないので、取得したインストーラがビルドを行います。
- Pythonのみで記述されたパッケージはPythonスクリプトをそのまま配布するので、ビルドは必要ではありません。単にソースコードを適切なディレクトリ構成で
- ビルド配布物(Wheel)
- Pythonのパッケージシステムでは、ユーザー環境でソースコード配布物をビルドさせるのではなく、予めビルドした成果物を配布ることができます。これはユーザー環境にビルド環境を用意する必要がなく、またビルドには多くのリソースを消費するため、ビルド済みパッケージの配布が一般的です。ただしビルドの成果物はOSやPythonのバージョンに依存するので、よくある環境向けに複数のパッケージを用意する必要があります。
Build FrontendとBackend
Pythonのパッケージシステムではビルドする必要があるソースコード配布物が配布される可能性があるので、パッケージ管理システムはビルドを実行できる必要があります。しかしこの世の全てをビルド出来るツールをパッケージ管理システムに組み込むわけにはいかないので、個々のパッケージ側が何を使ってビルドするのかを指定します。この時に指定されるビルドツールの事をBuild Backendと呼びます。
例えばRust製のPythonパッケージは典型的には次のように pyproject.toml に記述します:
[build-system]
requires = ["maturin >= 1.8.2"] # ビルドバックエンドを起動するのに必要なパッケージを記述
build-backend = "maturin" # 起動するPythonモジュールを指定
requires で指定される maturin はPythonパッケージとしての maturin であり、build-backend で指定された maturin はPythonのモジュールの事で
python -m ${build-backend で指定されたモジュール名}
と同じ挙動になるはずです。
逆に各パッケージのビルドを委任する側をBuild Frontendと呼び、これは典型的にはパッケージ管理システム、例えば pip や uv です。このFrontendとBackendの分離はPEP517で規定されています。
ややこしいことに、Build Backendとして機能するツールにはフロントエンドとしての機能を持っているものもあります。例えば maturin はCLIツールとしてビルドだけで無くてインストールやパッケージのアップロードも行えます。なので「maturin はBuild Backendツール」ではなく「maturinはBuild Backendとしても機能する」と理解するのが適切です。
Case Study
さて基礎事項を理解したところで、既存のツールについての分類をしていきましょう。
build (python -m build)
これは pip や uv のようなパッケージ管理機能とは切り離された、単純にBuild Frontendとして動作するツールです。pyproject.tomlのbuild-backend を起動します。
setuptools (setup.py)
上の仕組みが出来上がる前から使われている伝統的なツールです。61.0.0 から上の仕組みをサポートし、Build Backendとして使うことが出来ます。次のように pyproject.toml に記述します:
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
旧来の setup.py による仕組みは廃止される予定ですが、現状の pip は setup.py を見つけたらBuild Backendとして setuptools が指定されたように動作します。
scikit-build / scikit-build-core
scikit-buildはcmakeを使ったビルドをsetuptoolsと連携するためのツールでしたが、前節の通りsetuptoolsのレガシー機構は廃止される予定なので、新たにBuild Backendとして機能するscikit-build-coreが開発されています。
[build-system]
requires = ["scikit-build-core"]
build-backend = "scikit_build_core.build"
[project]
name = "scikit_build_simplest"
version = "0.0.1"
関連文献
Discussion