はじめに
いつも何やってたか忘れてしまうので、よくやるだろう部分をメモして、将来の自分のために残したい。
Pyinstaller について
前提
こちらの「前提」項目は、以下の大項目をまとめていく上での前提状態の事で、こちらが変更されると、まとめられた項目には多少の変化が生じる。
パッケージ構成
project_root
|- .venv # virtualenv 環境
|- sample
| |- statics # 画像などの静的アセット
| | |- app_icon.png
| | |- app_icon.ico
| | |- right_arrow.svg
| | |- left_arrow.svg
| |- ui
| | |- main.ui # QtCreator/QtDesigner で作成する UI ファイル
| |- __init__.py
| |- __main__.py
| |- gui.py
| |- module_b.py
|- tests
|- build.spec
|- requirements.txt # 依存モジュールの記載
成果物
得たい成果物は次の様な構成。
今回は、sample ディレクトリ以下のパッケージを、Python 環境が無いユーザーも含めて配布する場合を考える。
- sample.exe - こちらに sample 内のファイルをすべて回収してきて起動ファイルとして固めたもの。
- sample_noconsole.exe - コンソール出力がないモード。GUI だけの挙動で十分であればこちら。
開発環境
Windows 10 / 11
Python 3.7 / 3.9 / 3.10
基本的な考え方
ここが結構重要なところ。
Pyinstaller で Python 実行環境を固める方法として、ぐぐると大体は単純な1コマンドで完了させているケースを多く見かける。単純なファイル構成だけのスクリプトであればこれで問題はない。
しかしながら、それだけでは効率的に静的アセットファイルなどを取り込めなかったり、実行ファイルのアイコンなどはデフォルトのままになってしまったりする。
また、一つのプロジェクト環境で、出力する実行ファイルを同時に複数作成したりしたい場合もあったりする。
これらをコマンドで指定するのもいいが、内容は変動することも多く、そのたびに長いコマンドを書き換えて実行するのも煩わしい上、他の誰かがそのプロジェクトフォルダを開いた際にビルド環境がどういったものなのかが分からなくなってしまう。
なので、スペックファイル (.spec) を作成し、それを編集したうえでビルドを実行するという事が基本になる。
スペックファイルは、上記手筈を簡略化したり、ビルド環境のドキュメント代わりになったりもする。
1, ビルド環境作成
まずは Python の仮想環境を整える。
ここれは色んなやり方があるので、ぶっちゃけなんでもいい。
いつもは、virtualenv を使用しているので、今回はこちらのやり方でまとめてみる。
仮想環境の作り方はいろいろあるが、他のツールだと OS の問題に起因するエラーに直面したり、仮想環境の実行ファイルが固定されなかったり、また、不要になったらプロジェクトフォルダごと削除してしまえばいいという発想に加え、Python のバッテリーインクルードの仮想環境構築手法という事もあって、結局 virtualenv を使いがち。
仮想環境の作成し、
python -m venv .venv
.venv\Scripts\activate.bat
依存モジュールをインストールする。
pip install -r requirements.txt
2, スペックファイルの作成
とりあえず、実行環境を作りながら作成する
まだ Pyinstaller の何たるかを分かっていない場合や、設定方法を忘れている場合は、とりあえずこちらを実行して、スペックファイルの作成を行う。
pyinstaller .\sample\__main__.py --one-file
スペックファイルのみを作成
ある程度 spec ファイルの作成に慣れて、最初に設定が漏れている状態で Pyinstaller を実行させ、実行ファイル等の作成を行わない場合は、こちらでスペックファイルの作成を先に行い、その作成されたスペックファイルを編集していく。
pyi-makespec .\sample\__init__.py --onefile
こちらは引数を加える事によって、以下のさらな spec ファイルの内容を書き換えた状態で作成してくれる。
詳しくは pyi-makespec --help
で参照。
スペックファイルの名前は、--one-file
で作成した場合はその .py ファイルの名前だったりが適応されているくらいなので特に決まりはない。
しかしながら、明示的に「このプロジェクトのビルドスペックファイルはこれだよ。」というのを分かりやすくするため、build.spec という名前で作成する様に心がけている。
作成される __ main __.spec ファイル。これを build.spec ファイルに名前を変更する。
中身は実質 Python ファイル。
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['sample\\__init__.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
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,
[],
exclude_binaries=True,
name='__init__',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='__init__',
)
3, スペックファイルの編集
上記コマンドを一度実行する事で、pysintaller で処理されるためのスペックファイル (.spec) が作成される。
これを編集することによって、作成される実行ファイルの名前や内包するバイナリファイルやエクストラファイルなどを含める事ができる。
実行ファイル名の編集
exe ファイルの場合。
EXE クラスの name
引数に渡す値を変更する。
exe = EXE(
# ...
name="sample"
# ...
)
追加ファイルの編集
テキストデータや、画像データ、PySide の場合 .ui ファイルなどの、Python ファイル以外のものをパッケージに含む場合に設定する。
こちらの設定を簡素化したい場合は、以下の事を守る。
- なるべく目的に分けたディレクトリ構成にする。
- なるべくパッケージに不必要なファイルを、検索対象にするディレクトリ化に配置しない。
以下、該当部の切り出し。
project_root
|- sample
| |- statics # 画像などの静的アセット
| | |- app_icon.png
| | |- app_icon.ico
| | |- right_arrow.svg
| | |- left_arrow.svg
| |- ui
| | |- main.ui # QtCreator/QtDesigner で作成する UI ファイル
spec ファイルの編集を行う。
add_datas = [
('sample/ui/*', 'sample/ui'),
('sample/statics/*', 'sample/statics')
]
a = Analyze(
datas=add_datas
)
アイコンの付与
実行ファイルに対してアイコンを付与したい場合。icon
引数にパス文字列を追加する。
exe = EXE(
# ...
icon="sample/statics/app_icon.ico"
# ...
)
コンソール出力を行わない
GUI だけの起動(ユーザービリティ的にコンソール出力を敢えて見せない)や、別途ログ出力を行っている際など、コンソール出力が必要ない場合がある。その際に設定する項目。
Exe の console
引数を False
に設定する。
exe = EXE(
# ...
console=False
# ...
)
出力項目を増やす
例えば、CLI と GUI を兼ね備えたツールだが、普段ユーザーにコンソール表示を出したくない場合もある。両方を満たすため、それぞれの実行ファイルを作成する場合は両方とも出力するような設定に書き換える必要がある。
block_cipher = None
a = Analysis(
['sample\\__main__.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
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='sample_noconsole',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
console_exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='sample',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
これをビルドすることによって、dist ディレクトリに以下の二つが作成される。
- sample_noconsole.exe
- sample.exe
4, スペックファイルを使ってビルド
スペックファイルを元にビルドを実行する。
pyinstaller build.spec
VSCode の設定
VSCode のタスク設定を次の様にしておくことで、Ctrl + B
でのビルド実行がすぐに行える。便利。
{
"version": "2.0.0",
"tasks": [
{
"label": "Pyinstaller: Build",
"type": "shell",
"windows": {
"command": ".venv\\Scripts\\pyinstaller.exe .\\build.spec",
},
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Pytest",
"type": "shell",
"windows": {
"command": ".venv\\Scripts\\pytest.exe ."
},
"problemMatcher": [],
"group": {
"kind": "test"
}
}
]
}
まとめ
大体のことは「基本的な考え方」の項目に書いが、様は「(ビルド)スペックファイル」を編集することが結構重要になってくるよ。といった感じ。公式ドキュメントにもまだまだ書かれてることもあるので、そちらを参照しながら更新していきたい。