概要
Pyinstaller https://github.com/pyinstaller/pyinstaller は、pythonで作ったプログラムを実行ファイルに変形してくれるありがたいライブラリのことです。
これを用いて、pythonコードをoneバイナリファイルもしくはoneフォルダにまとめた時、
別の環境にそのバイナリファイルを持ってきて実行しようとすると、動的リンク関連で怒られ、いろいろはまりました。
使った環境&コマンド
環境:
- ubuntu18.04
- python3.6.3 (pyenv環境を用いていた)
pyinstaller -D --clean --onefile --additional-hooks-dir ./ main.py
このコマンドを用いて、main.py
をonefile (バイナリファイル)にして、
他の環境でも使いたかった。(実際はこのバイナリファイルをdocker image の中に置き、dockerをもちいて実行しようとしていた)
出たエラー
| [7] Error loading Python lib '/tmp/_MEIzvSj0a/libpython3.6m.so.1.0': dlopen: libexpat.so.1: cannot open shared object file: No such file or directory
考察
so file
がないと怒られている。pyinstaller が作ったonefileは実行環境に依存しないと思っていたけど、
実際は動的リンクを行なっている??
と思って調べてみると、色々なことがわかりました。
まず、pyinstallerでoneファイルになったものでも、
https://github.com/pyinstaller/pyinstaller/wiki/FAQ
ここにあるように、
GNU/Linux
Under Linux, I get runtime dynamic linker errors, related to libc. What should I do?
The executable that PyInstaller builds is not fully static, in that it still depends on the system libc.
とのこと。つまり、動的にもちろん参照するライブラリがあるので、linuxに依存します。
ここで、次のようなことが書いてありました。
Another solution is to use a tool like StaticX to create a fully-static bundled version of your PyInstaller application. StaticX bundles all dependencies, including libc and ld.so. (Python code PyInstaller StaticX Fully-static application)
staticx
というライブラリがあり、それで完全に静的なライブラリ化するのはどうでしょう?
ということでした。それに従い、やってみました。
git レポジトリはhttps://github.com/JonathonReinhart/static
ここです。
これを使い、pyinstaller
でonefileになったものを、さらに staticx
により動的依存を減らしました。
結果、ここで作ったバイナリは、ubuntu
(開発環境と同じ)docker環境内で無事実行できることがわかりました。
ではなんで動的にリンクする必要があるのか
動的にリンクするものが多いと環境依存が大きくなってしまうような気がしますが、
なぜ動的リンクをするのでしょうか。
http://archive.linux.or.jp/JF/JFdocs/libc-intro.html
ここには、linuxシステムがオペレーションに使っているlibc
の歴史について書いてありますが、
そのなかの静的リンクと動的リンクの解説について少し引用して置きます。
static なライブラリを用いる場合は、リンカはプログラムが必要と
する部品を探し、出力する実行ファイルにその部品をコピーします。
共有ライブラリの場合は違った作業が行われます。
リンカは出力ファイルに「このプログラムが実行されるときには、
まずこれこれのライブラリがロードされていないといけませんよ」と
いったメッセージを埋め込みます。したがって明らかに共有ライブラリを
用いる方が実行ファイルのサイズは小さくなります。
また消費するメモリやディスク容量も小さくなります。 Linux における
デフォルトの振る舞いでは、共有ライブラリがあればそちらを用い、
なければ static なリンクを行います。実行ファイルを共有ライブラリ
形式にしたいのに static になってしまった場合は、正しい位置に
共有ライブラリのファイルがあって(a.outでは *.sa、 ELF では *.so です)、
それらが読み込み可能になっているかどうかをチェックしてください。
つまり、何でもかんでも環境依存しないようにするために、静的リンクをしまくるというのはダメだということです。(つまり自分の考えは浅はかでした。)
追記
one file ではなく、one folderを出力するようにpyinstallerを用いた時に、
cannot find libexpat.so
のようなエラーが出ていました。これは、libpython3.6m.so
を使う時にlibexpat.so
も動的にリンクしなければならなかったので、そのときにdockerコンテナ上にそれがないというエラーでした。
色々考えましたが、手っ取り早い解決策は、
コンテナ上で apt-get install libexpat-dev
で普通にこのlibexpat.so
をインストールしておくことでした。
まとめ
pyinstaller をlinuxで使う時は、onefileになったあとでも動的にリンクしているライブラリが存在していることを心に留めておく。
もし実行時にリンク問題で怒られることがあれば、
- staticx をつかってみるのも1つの手(ただし、静的リンクにすることでバイナリの大きさが増大することも理解しておく。)
- onefileをdockerに埋め込みたいなどの時は、linuxでonefileを作った時にはできるだけ同じような環境のdocker を構築するとストレスが少ない。
以上です。
今回はこの辺で。
おわり。