manylinux1 wheel を作ってみる

先日の記事 で紹介した、 manylinux1 wheel を作ってみます。

manylinux1 docker image

ビルド環境を Docker image として公開してくれています。

docker pull quay.io/pypa/manylinux1_x86_64; docker pull quay.io/pypa/manylinux1_i686 しておきましょう。

なお、このイメージは CentOS 5 をベースにビルドスクリプトを実行しているだけなので、 vagrant 等で同じ環境を作るのは簡単そうです。

作業ディレクトリを作って docker 内で bash を起動します。

$ mkdir docker
$ docker run --rm -ti -v `pwd`/docker:/docker  -w /docker quay.io/pypa/manylinux1_x86_64 bash

このように実行すると、bash を抜けるときにイメージが消えますが、ボリュームに指定した docker ディレクトリの内容は残るので便利です。

wheel をビルドしてみる

/opt/python 配下に各種 Python が用意されています。 Python 2.6 と 2.7 は、 utf-16 の narrow build (cp26m, cp27m) と utf-32 の wide build (cp26mu, cp27mu) があります。

[root@c3f621332eef docker]# ls /opt/python/
cp26-cp26m  cp26-cp26mu  cp27-cp27m  cp27-cp27mu  cp33-cp33m  cp34-cp34m  cp35-cp35m

試しに msgpack の最新リリース版をビルドしてみます。

[root@89ab8fbbfbc0 docker]# curl 'https://pypi.python.org/packages/a3/fb/bcf568236ade99903ef3e3e186e2d9252adbf000b378de596058fb9df847/msgpack-python-0.4.7.tar.gz' -o msgpack-python-0.4.7.tar.gz
[root@89ab8fbbfbc0 docker]# tar xf msgpack-python-0.4.7.tar.gz
[root@89ab8fbbfbc0 docker]# cd msgpack-python-0.4.7
[root@89ab8fbbfbc0 msgpack-python-0.4.7]# /opt/python/cp35-cp35m/bin/python setup.py bdist_wheel
...
[root@89ab8fbbfbc0 msgpack-python-0.4.7]# /opt/python/cp34-cp34m/bin/python setup.py bdist_wheel
...
[root@89ab8fbbfbc0 msgpack-python-0.4.7]# /opt/python/cp27-cp27m/bin/python setup.py bdist_wheel
...

[root@89ab8fbbfbc0 msgpack-python-0.4.7]# rm -rf build
[root@89ab8fbbfbc0 msgpack-python-0.4.7]# /opt/python/cp27-cp27mu/bin/python setup.py bdist_wheel

Python 2.7 や 2.6 のビルド時は、 rm -rf build をしておかないと、 narrow / wide ビルドが違っても build/ ディレクトリ配下にできるファイルにはタグがなくて再利用されてしまうので注意が必要です。

これで dist ディレクトリに、通常の linux 用 wheel ができます。しかしこのままでは PyPI にはアップロードできません。通常の linux wheel は、ローカルや同じ環境のマシンで使い回すためにしか使えません。

manylinux1 wheel に変換する

Docker イメージには auditwheel コマンドが用意されています。このコマンドで、 wheel 内のバイナリが、決められた以外の外部ライブラリに依存してないかなどのチェックが行われます。 (auditwheel も manylinux1 もまだ新しいツールなので、チェックが通ったから安全とは限りません。たとえば上で説明した narrow / wide ビルドミスは現状ではチェックが漏れています。)

auditwheel show コマンドでチェック結果が表示され、 auditwheel repair コマンドは依存してる外部ライブラリをバンドルして RPATH を設定するなどの黒魔術を施して linux wheel を manylinux1 wheel にリネームしてくれます。 repair コマンドは wheelhouse ディレクトリに wheel を生成します。

(repair コマンドの黒魔術が必要ない場合は、 上の手順で bdist_wheel するときに -p manylinux1_x86_64 オプションを使えば良さそうです。)

[root@0018d7cfd7df dist]# auditwheel show msgpack_python-0.4.7-cp27-cp27m-linux_x86_64.whl

msgpack_python-0.4.7-cp27-cp27m-linux_x86_64.whl is consistent with
the following platform tag: "manylinux1_x86_64".

The wheel references the following external versioned symbols in
system-provided shared libraries: GLIBC_2.2.5.

The following external shared libraries are required by the wheel:
{
    "libc.so.6": "/lib64/libc-2.5.so",
    "libgcc_s.so.1": "/lib64/libgcc_s-4.1.2-20080825.so.1",
    "libm.so.6": "/lib64/libm-2.5.so",
    "libpthread.so.0": "/lib64/libpthread-2.5.so",
    "libstdc++.so.6": "/usr/lib64/libstdc++.so.6.0.8"
}


[root@c89bce940544 dist]# for i in *.whl; do auditwheel repair $i; done
Repairing msgpack_python-0.4.7-cp27-cp27m-linux_x86_64.whl
Previous filename tags: linux_x86_64
New filename tags: manylinux1_x86_64
Previous WHEEL info tags: cp27-cp27m-linux_x86_64
New WHEEL info tags: cp27-cp27m-manylinux1_x86_64

Fixed-up wheel written to /docker/msgpack-python-0.4.7/dist/wheelhouse/msgpack_python-0.4.7-cp27-cp27m-manylinux1_x86_64.whl
Repairing msgpack_python-0.4.7-cp27-cp27mu-linux_x86_64.whl
Previous filename tags: linux_x86_64
New filename tags: manylinux1_x86_64
Previous WHEEL info tags: cp27-cp27mu-linux_x86_64
New WHEEL info tags: cp27-cp27mu-manylinux1_x86_64

Fixed-up wheel written to /docker/msgpack-python-0.4.7/dist/wheelhouse/msgpack_python-0.4.7-cp27-cp27mu-manylinux1_x86_64.whl
Repairing msgpack_python-0.4.7-cp34-cp34m-linux_x86_64.whl
Previous filename tags: linux_x86_64
New filename tags: manylinux1_x86_64
Previous WHEEL info tags: cp34-cp34m-linux_x86_64
New WHEEL info tags: cp34-cp34m-manylinux1_x86_64

Fixed-up wheel written to /docker/msgpack-python-0.4.7/dist/wheelhouse/msgpack_python-0.4.7-cp34-cp34m-manylinux1_x86_64.whl
Repairing msgpack_python-0.4.7-cp35-cp35m-linux_x86_64.whl
Previous filename tags: linux_x86_64
New filename tags: manylinux1_x86_64
Previous WHEEL info tags: cp35-cp35m-linux_x86_64
New WHEEL info tags: cp35-cp35m-manylinux1_x86_64

Fixed-up wheel written to /docker/msgpack-python-0.4.7/dist/wheelhouse/msgpack_python-0.4.7-cp35-cp35m-manylinux1_x86_64.whl

PyPI にアップロード

docker で実行していた bash を抜けると、 docker イメージは消えますが、ボリュームの中に wheel が残っています。これを twine を使ってアップロードします。

$ pip install twine
$ cd docker/msgpack-python-0.4.7/dist/wheelhouse/
$ ls
msgpack_python-0.4.7-cp27-cp27m-manylinux1_x86_64.whl  msgpack_python-0.4.7-cp34-cp34m-manylinux1_x86_64.whl
msgpack_python-0.4.7-cp27-cp27mu-manylinux1_x86_64.whl msgpack_python-0.4.7-cp35-cp35m-manylinux1_x86_64.whl
$ twine upload *.whl
Uploading distributions to https://pypi.python.org/pypi
Uploading msgpack_python-0.4.7-cp27-cp27m-manylinux1_x86_64.whl
Uploading msgpack_python-0.4.7-cp27-cp27mu-manylinux1_x86_64.whl
Uploading msgpack_python-0.4.7-cp34-cp34m-manylinux1_x86_64.whl
Uploading msgpack_python-0.4.7-cp35-cp35m-manylinux1_x86_64.whl

あとは同じ手順を i686 の方の docker でもすれば x86 対応も簡単そうです。

終わりに

msgpack は libstdc++ に依存してしまっていますが、これは PEP 513 で定義されている Core shared library に含まれているので、多分問題ないと思います。

一方 mysqlclient-python の場合、 libmysqlclient 経由で ssl などに依存していて、これは core shared library に含まれていないので、 static link や bundle が必要になります。特に ssl のようにセキュリティアップデートが頻繁にあるライブラリの場合は wheel 提供者が責任をもって追随アップデートする必要がありそうで面倒です。

Core shared library に含まれていないライブラリを利用している場合は、 PEP 513 を良く読んで、ちゃんと理解できないのであれば、 manylinux1 wheel の提供は危険です。 wheel builders という ML ができたので、そこで相談してみるのが良いと思います。

Wheel-builders Info Page

追記: sdist から wheel を作るより簡単な手順

上の手順では sdist の tar.gz を展開してから setup.py を実行していましたが、 pip wheel コマンドを使うとこの流れを自動化できます。 作業ディレクトリも毎回作り直されるので、 build ディレクトリを消し忘れて narrow / wide 互換性問題を起こすことも無いはずです。

bash-3.2# for i in cp35-cp35m cp34-cp34m cp27-cp27m cp27-cp27mu; do /opt/python/$i/bin/pip wheel --build-option='-pmanylinux1_i686' msgpack-python-0.4.7.tar.gz ; done;

/opt/_internal/cpython-3.5.1/lib/python3.5/site-packages/pip/commands/wheel.py:126: UserWarning: Disabling all use of wheels due to the use of --build-options / --global-options / --install-options.
  cmdoptions.check_install_build_global(options)
Processing ./msgpack-python-0.4.7.tar.gz
Building wheels for collected packages: msgpack-python
  Running setup.py bdist_wheel for msgpack-python ... done
  Stored in directory: /docker
Successfully built msgpack-python
...
...
Successfully built msgpack-python
bash-3.2# ls -la
total 1108
drwxr-xr-x  1 1000 ftp     238 Apr 27 08:52 .
drwxr-xr-x 39 root root   4096 Apr 27 08:46 ..
-rw-r--r--  1 1000 ftp  241090 Apr 27 08:51 msgpack_python-0.4.7-cp27-cp27m-manylinux1_i686.whl
-rw-r--r--  1 1000 ftp  241106 Apr 27 08:52 msgpack_python-0.4.7-cp27-cp27mu-manylinux1_i686.whl
-rw-r--r--  1 1000 ftp  261276 Apr 27 08:51 msgpack_python-0.4.7-cp34-cp34m-manylinux1_i686.whl
-rw-r--r--  1 1000 ftp  256676 Apr 27 08:51 msgpack_python-0.4.7-cp35-cp35m-manylinux1_i686.whl
-rw-r--r--  1 1000 ftp  126251 Apr 27 06:52 msgpack-python-0.4.7.tar.gz

ここまで行けば、 docker で bash を実行する代わりに、 wheel をビルドするスクリプトを直接実行できそうですね。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。