Pythonで、実行可能なバイナリを作成するにはどうしたらいいのかな?と思って調べてみたのですが、PyInstallerというものを
使えばよさそうです。
PyInstaller Quickstart — PyInstaller bundles Python applications
GitHub - pyinstaller/pyinstaller: Freeze (package) Python programs into stand-alone executables
Pythonスクリプトを単一実行ファイルにする方法 - Qiita
python3 - Python のプログラムを実行可能バイナリにコンパイルするには? - スタック・オーバーフロー
PyInstallerとは
Pythonで書かれたアプリケーションを、スタンドアロンで実行可能な形式にまとめることができるツールです。
Python 2.7、3.4〜3.7で動作し、マルチプラットフォームで動作しますが、PyInstaller自体を実行するプラットフォームとは
異なるプラットフォーム向けのバイナリは作成することができません。クロスプラットフォーム向けにバイナリを作成することは
できないので、例えば手持ちがWindows環境で、Linux環境向けのバイナリを作成しようと思ったら、LinuxをVMなどで用意する
必要があるということです。
前置きはこれくらいにして、さっそく試してみましょう。
環境
今回の環境は、こちら。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.2 LTS Release: 18.04 Codename: bionic $ python3 -V Python 3.6.7
仮想環境も利用することにします。
$ python3 -m venv venv $ . venv/bin/activate
お題とサンプルプログラム、実行可能バイナリの作成
Pythonで、起動スクリプトとは別のスクリプト、外部ライブラリを使用した簡単なアプリケーションを作成して、それを
PyInstallerで実行可能なバイナリにまとめてみます。
まずは、PyInstallerのインストール。
$ pip3 install pyinstaller
バージョン。
$ pip3 freeze ... PyInstaller==3.4
外部ライブラリとしては、requestsとBeatiful Soupを使うことにします。
$ pip3 install requests beautifulsoup4 $ pip3 freeze ... beautifulsoup4==4.7.1 ... requests==2.21.0 ...
これらのライブラリを使って、このブログのトップページを取得して、タイトルだけを返すような関数を作成します。 func.py
from bs4 import BeautifulSoup import requests def get_title(url): response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') return soup.title.text
作成した関数を利用する、起動スクリプト。
app.py
from func import get_title title = get_title('https://kazuhira-r.hatenablog.com/') print("https://kazuhira-r.hatenablog.com/'s title = {}".format(title))
確認。
$ python3 app.py https://kazuhira-r.hatenablog.com/'s title = CLOVER🍀
これで、準備は完了です。
では、PyInstallerでバイナリを作成します。「pyinstaller」コマンドに、起動スクリプトを引数に渡して実行。
$ pyinstaller app.py
「dist」というディレクトリができるので、この中身を見ると実行可能ファイルやSOファイルが並んでいます。
$ ll -h dist/app 合計 12M drwxrwxr-x 3 xxxxx xxxxx 4.0K 4月 24 23:39 ./ drwxrwxr-x 3 xxxxx xxxxx 4.0K 4月 24 23:39 ../ -rwxr-xr-x 1 xxxxx xxxxx 22K 10月 22 2018 _bz2.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 147K 10月 22 2018 _codecs_cn.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 155K 10月 22 2018 _codecs_hk.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 27K 10月 22 2018 _codecs_iso2022.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 267K 10月 22 2018 _codecs_jp.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 135K 10月 22 2018 _codecs_kr.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 111K 10月 22 2018 _codecs_tw.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 30K 10月 22 2018 _hashlib.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 73K 10月 22 2018 _json.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 33K 10月 22 2018 _lzma.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 56K 10月 22 2018 _multibytecodec.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 6.2K 10月 22 2018 _opcode.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 118K 10月 22 2018 _ssl.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 1.7M 4月 24 23:39 app* -rw-rw-r-- 1 xxxxx xxxxx 754K 4月 24 23:39 base_library.zip drwxrwxr-x 2 xxxxx xxxxx 4.0K 4月 24 23:39 certifi/ -rwxr-xr-x 1 xxxxx xxxxx 66K 1月 30 2017 libbz2.so.1.0* -rwxr-xr-x 1 xxxxx xxxxx 2.5M 12月 6 00:59 libcrypto.so.1.1* -rwxr-xr-x 1 xxxxx xxxxx 199K 12月 20 2017 libexpat.so.1* -rwxr-xr-x 1 xxxxx xxxxx 151K 6月 29 2017 liblzma.so.5* -rwxr-xr-x 1 xxxxx xxxxx 4.5M 10月 22 2018 libpython3.6m.so.1.0* -rwxr-xr-x 1 xxxxx xxxxx 288K 5月 16 2017 libreadline.so.7* -rwxr-xr-x 1 xxxxx xxxxx 424K 12月 6 00:59 libssl.so.1.1* -rwxr-xr-x 1 xxxxx xxxxx 167K 5月 23 2018 libtinfo.so.5* -rwxr-xr-x 1 xxxxx xxxxx 115K 5月 23 2017 libz.so.1* -rwxr-xr-x 1 xxxxx xxxxx 32K 10月 22 2018 readline.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 16K 10月 22 2018 resource.cpython-36m-x86_64-linux-gnu.so* -rwxr-xr-x 1 xxxxx xxxxx 25K 10月 22 2018 termios.cpython-36m-x86_64-linux-gnu.so*
また、カレントディレクトリを見ると、「dist」ディレクトリ以外には起動スクリプト名.spec」というファイルと「build」ディレクトリが
作成されています。
$ ll 合計 36 drwxrwxr-x 6 xxxxx xxxxx 4096 4月 24 23:39 ./ drwxrwxr-x 14 xxxxx xxxxx 4096 4月 24 23:13 ../ drwxrwxr-x 2 xxxxx xxxxx 4096 4月 24 23:39 __pycache__/ -rw-rw-r-- 1 xxxxx xxxxx 156 4月 24 23:38 app.py -rw-rw-r-- 1 xxxxx xxxxx 950 4月 24 23:39 app.spec drwxrwxr-x 3 xxxxx xxxxx 4096 4月 24 23:39 build/ drwxrwxr-x 3 xxxxx xxxxx 4096 4月 24 23:39 dist/ -rw-rw-r-- 1 xxxxx xxxxx 183 4月 24 23:39 func.py drwxrwxr-x 6 xxxxx xxxxx 4096 4月 24 23:13 venv/
実行してみましょう。
$ dist/app/app https://kazuhira-r.hatenablog.com/'s title = CLOVER🍀
動きましたね。他のスクリプトや外部ライブラリの情報まで見て、ビルドしてくれるようです。
ただ、この方法でのビルドでは、「dist」ディレクトリ配下のファイルをまるごと使う必要があり、例えばエントリとなる
実行可能ファイルだけを持ち出しても、ライブラリが足りずにエラーになります。
$ ./app [12] Error loading Python lib '/tmp/libpython3.6m.so.1.0': dlopen: /tmp/libpython3.6m.so.1.0: cannot open shared object file: No such file or directory
これを避けるには、完全に単一のバイナリとしてビルドする必要があります。
What PyInstaller Does and How It Does It — PyInstaller 3.4 documentation
単一のバイナリを作成するには、「--onefile」オプションを指定します。
$ pyinstaller --onefile app.py
今度は、「dist」ディレクトリ内に作成されるファイルがひとつになります。
$ ll dist 合計 6072 drwxrwxr-x 2 xxxxx xxxxx 4096 4月 24 23:48 ./ drwxrwxr-x 6 xxxxx xxxxx 4096 4月 24 23:48 ../ -rwxr-xr-x 1 xxxxx xxxxx 6207248 4月 24 23:48 app*
この生成方法だと、単一のファイルを配布するだけで実行可能になります。
$ ./app https://kazuhira-r.hatenablog.com/'s title = CLOVER🍀
なお、このようなビルド時の指定はspec拡張子のファイルに保存されているので
app.spec
# -*- mode: python -*- block_cipher = None a = Analysis(['app.py'], pathex=['/path/to/application-directory'], binaries=[], datas=[], hiddenimports=[], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='app', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, runtime_tmpdir=None, console=False )
仮にdistディレクトリなどをなくしても、specファイルから同じオプションで再作成することができます。
$ pyinstaller app.spec
こんな感じで。