24
Help us understand the problem. What are the problem?

posted at

updated at

Docker環境のPyInstallerでキレイにExe化する

はじめに

PyInstallerでは生成物の肥大化を防ぐため、専用の仮想環境を用意して、その中でビルドしようという話があります。
しかし、面倒です。
普段はDocker環境で開発していることが多いので、わざわざビルドのためだけに仮想環境を作りたくないですね。

ということで以下の方法により、ローカル環境を汚すことなく、最小サイズのExeファイルただ1つ吐き出させることができます。


Mac用の実行ファイルはMac上でしか生成できないらしいので(参考)、
本記事はWindowsまたはLinuxユーザ向けの情報となります。

ここではWindowsでの方法を載せますが、Linuxの場合もほぼ同じです。
下記の通り読み替えてください。

Windows Linux
pyinstaller-windows pyinstaller-linux
main.exe main

手順

※詳細は上記を参照されるとし、アレンジした使い方を述べます。


現在のディレクトリ構成は次のようになっています。

.
├── main.py
└── requirements.txt

下記のワンライナーを実行します。(WSLで実行しました。)

docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows -c \
  "pip install -r requirements.txt && \
  pyinstaller main.py --onedir --onefile --clean && \
  mv dist/main.exe main.exe && \
  rm -rf __pycache__/ build/ dist/ main.spec"

すると、Exeファイルが生成されます。

.
├── main.exe
├── main.py
└── requirements.txt

簡単な解説

1行目
docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows

cdrx/pyinstaller-windowsイメージから使い捨てのコンテナを立て、カレントディレクトリをそのコンテナにマウントします。

2行目以降
  "pip install -r requirements.txt && \
  pyinstaller main.py --onedir --onefile --clean && \
  mv dist/main.exe main.exe && \
  rm -rf __pycache__/ build/ dist/ main.spec"

全体の流れとしては、

  1. pipで依存関係をインストール
  2. PyInstallerでExe化
  3. Exeファイルの場所を移動
  4. PyInstallerによって生成されたゴミを削除

requirements.txtが不要なら、pip install -r requirements.txt && \は削除しておきます。

その他の依存関係があれば、pyinstaller ...より前でインストールします。(/src/にホスト側のカレントディレクトリをマウントしているので、わざわざインストールするモノもなかろうかと思いますが)

実はこのあたり、単に-c "/entrypoint.sh"とだけ書いてもいいのですが、個人的なこだわりでアレンジしています。

ちょっとしたコツ

開発・実行環境における環境差について

まず、当然ながらmain.pyはWindowsで動くプログラムである必要があります。
具体的には、下記のコードはDocker環境(Linux)では動くものの、Windows上では動作しません。

main.py
with open("data.json", "r") as f:
    data = json.load(f)

実際には、encodingを指定する必要があります。

main.py
with open("data.json", "r", encoding="utf-8") as f:
    data = json.load(f)

CLIアプリではひと工夫

プログラム終了時にはその旨が黒い画面に表示され続けると親切です。
急に画面が消えると、正常終了したかどうか不安になりますから。
何も工夫しなければそのまま閉じてしまうので、プログラムに一手間加えます。

main.py
def main():
    print("hello!")

if __name__ == "__main__":
    main()

    import subprocess
    subprocess.call('PAUSE', shell=True)
hello!
続行するには何かキーを押してください . . .

実行中にコンソールを表示したくない場合

GUIアプリの場合など、稀に該当すると思います。
この場合、pyinstallerの引数に--noconsoleを追加します。

docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows -c \
  "pip install -r requirements.txt && \
  pyinstaller main.py --onedir --onefile --clean --noconsole && \
  mv dist/main.exe main.exe && \
  rm -rf __pycache__/ build/ dist/ main.spec"
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
24
Help us understand the problem. What are the problem?