Python wheelはinterpreter・ABI・platformをartifact名に埋め込む
Pythonのwheelは、ビルド済みpackageの互換性をartifactそのものへ明示的に持たせる代表的な方式である。wheel filenameは基本的に{distribution}-{version}-{python tag}-{abi tag}-{platform tag}.whlという形を取り、installerはそのcompatibility tagsを使って現在のruntimeで利用できるdistributionを選べる。
linux/amd64だけではビルド済みバイナリの互換性を表せない で見た「linux/amd64だけでは足りない」という問題を、package format側で解く例とみなせる。
三つのtagは別の互換性軸を表す。
- Python tag: CPython、PyPy等のimplementationとversion
- ABI tag:
abi3、implementation-specific ABI、none等 - platform tag: OS/architectureやportable platform policy
純Python packageならpy3-none-anyのように広い互換性を宣言できる。一方、C extensionを含むpackageではCPython version/ABI/platformに応じた複数wheelが必要になる。
Linuxでは単純なlinux_x86_64だけではdistribution間のglibc等の違いを安全に表しにくいため、Python packaging ecosystemはmanylinuxを導入した。現在のspecificationではmanylinux_x_yがglibc baselineを表し、musl系にはmusllinux_x_yがある。つまりplatform tagが「OS名」だけでなく portable ABI baselineの契約 を表す。
この方式の利点は、artifact selectionをinstallerが機械的に行えることである。同じproject/versionに複数のbuildが存在しても、compatibility tagによって候補を区別できる。
一方でprebuilt binaryはbuild matrixが増えるほどcache効率が落ちるの問題がそのまま現れる。CPython version × ABI × OS × architectureごとにnative wheelを作るとrelease artifact数が増える。abi3のようなstable ABIやmanylinuxのbaselineを使うことは、互換範囲を広げてmatrixを圧縮する手段と解釈できる。
wheelはsource distribution(sdist)とも併存する。compatible wheelが存在しない場合、pip/uv等のfrontendは利用可能なsdistからlocal wheelをbuildすることがある。この構造はprebuiltがないときのsource build fallbackは同じ意味ではないに近いが、prebuilt wheelとlocal build resultがbit-for-bit同じであることを前提にはしていない。
またwheel filenameにはoptional build tagも存在する。同一distribution versionを異なるbuild environmentで再buildした結果を区別できるが、PyPA specificationはbuild numberがdistribution versionの一部ではなく外部から参照しづらいため、security fix等で新しいdistributionを出す場合はversionを上げることを推奨している。これは「source version」と「binary build identity」が必ずしも同じではないことを示す。
Condaがhost capabilityをvirtual packageとしてsolverへ入れるのに対し、wheelは主なcompatibility constraintをartifact tagへencodeする。どちらもprebuilt artifact selectionを安全にするが、solver metadataで表すか、artifact naming/formatで表すかという違いがある。