(動的インポートや画像ファイルなどを使用せず、モジュール(.pyファイル)のみで動かせるパッケージの場合は本記事の設定は必要ありません)
自分用にpygameを使ったゲームフレームワークのpythonパッケージを作っている https://github.com/Eleven-junichi2/auraboros のですが、
PyInstallerでそのパッケージを使ったスクリプトのWindows環境の実行ファイル化に躓いたので、解決に至るまでを共有します。
PyInstaller --windowed 実行ファイル化するスクリプト.py
Traceback (most recent call last):
File "実行ファイル化するスクリプト.py", line 1, in <module>
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "PyInstaller\loader\pyimod02_importers.py", line 352, in exec_module
File "auraboros\engine.py", line 9, in <module>
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "PyInstaller\loader\pyimod02_importers.py", line 352, in exec_module
File "auraboros\shader.py", line 12, in <module>
FileNotFoundError: [Errno 2] No such file or directory: '~\\...\\スクリプトのディレクトリ\\dist\\実行ファイルを置くディレクトリの名前\\auraboros\\default.vert <- パッケージが使用しているfile
Tracebackからわかること
- インストールしたパッケージのモジュール(pyファイル)自体は読み込めている
- どうやら、PyInstallerがdefault.vertを見つけられていない?
- site-packagesを見ると、きちんとpipインストールしたパッケージにはdefault.vertは含まれているので、パッケージのビルド時の不具合ではない
原因と解決策
ドキュメントによると、どうやらパッケージにモジュール以外の画像等のファイルを含めたい場合、hookファイルで指定しないとPyInstallerが実行ファイル化時にそのファイルを同梱してくれない模様。
例えばpygameのリポジトリを見てみると、実際に__pyinstaller/hook-pygame.pyを設定してPyInstallerにフォントファイルやdllを同梱させるようになっています。
ディレクトリ構造
ということで、https://pyinstaller-sample-hook.readthedocs.io/en/latest/ を参考に必要な設定をしてみます。
作っているパッケージはsrcレイアウトを採用していて、以下のようなディレクトリ構造。
ビルドに必要なメタデータはpyproject.tomlで管理する方式です。
プロジェクトのrootディレクトリ
├─src
│ └─auraboros
│ ├─__pyinstaller *
│ │ ├─___init__.py *
│ │ └─hook-auraboros.py *
│ │ (その他パッケージ本体の.pyファイル)
│ ├─default.vert <-今回含めたいファイル
├─pyproject.toml *
(説明に必要な部分のみ抜粋)
*が更新・新しく作成するファイル・ディレクトリ。
hookファイルを作成する
PyInstallerは実行ファイルを作成する際、pyproject.tomlやsetup.cfg(setup.py)で設定をしておくことで、"site-packages/必要な依存関係のパッケージのディレクトリ"を見に行って、そこに含まれているhook-* .pyファイルを参照します。
hook-* .pyファイルに処理を記述しておくことで、実行ファイル化の際必要なファイルを同梱してくれるので、今回は以下の用に記述し、サンプルに習って作成した__pyinstallerディレクトリに配置。
from PyInstaller.utils.hooks import collect_data_files
# collect_data_file()でパッケージ内のファイルのパスのリストを作成し、
# datas変数でPyInstallerに知らせる
datas = collect_data_files('auraboros', excludes=['__pyinstaller'])
# 第一引数にプロジェクトのパッケージ名、excludesに含めたくないディレクトリ・ファイルを記述
また、PyInstallerがhookファイルの場所を見つけられるように、同じく__pyinstallerディレクトリに__init__.pyファイルを作ります。
import os
# hookファイルのディレクトリのリストを返し、hookファイルの場所をPyInstallerに知らせます
# 今回はhookファイルを__pyinstaller/__init__.pyと同じディレクトリに置いてい
# るので、 __init__.pyファイル自身の親ディレクトリを返す
def get_hook_dirs():
return [os.path.dirname(__file__)]
pyproject.tomlを設定
(メタデータにsetup.cfg, setup.pyを使っている場合でも対応した形で記述して設定できます)
エントリポイントを設定して、__pyinstaller/ __init __.pyのget_hook_dirs()をPyInstallerに伝えます。
...
[project.entry-points.pyinstaller40]
hook-dirs = "auraboros.__pyinstaller:get_hook_dirs"
これでhookファイルの設定は完了です。後は通常通りパッケージをビルドしましょう。
ビルドしたwhlを実際にインストールして、パッケージをimportしたスクリプトを実行ファイル化する際の出力に
INFO: Loading module hook '(作成したhookファイルの名前).py'
の表示があれば、きちんとhookファイルがPyInstallerに認識されています。
その他の参考
https://hatch.pypa.io/latest/config/metadata/#entry-points
https://github.com/pypa/packaging-problems/issues/634
https://github.com/MatthewRalston/kmerdb/blob/master/pyproject.toml
https://github.com/pyinstaller/pyinstaller-hooks-contrib
https://github.com/pyinstaller/pyinstaller/issues/2294
https://github.com/jmoiron/humanize/issues/105