LoginSignup
4
5

More than 1 year has passed since last update.

ビルドした自作のpythonパッケージをPyInstallerに対応させよう

Last updated at Posted at 2023-04-25

(動的インポートや画像ファイルなどを使用せず、モジュール(.pyファイル)のみで動かせるパッケージの場合は本記事の設定は必要ありません)


自分用にpygameを使ったゲームフレームワークのpythonパッケージを作っている https://github.com/Eleven-junichi2/auraboros のですが、
PyInstallerでそのパッケージを使ったスクリプトのWindows環境の実行ファイル化に躓いたので、解決に至るまでを共有します。

exeファイル実行時のエラー
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ディレクトリに配置。

__pyinstaller/hook-auraboros.py
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ファイルを作ります。

__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に伝えます。

pyproject.toml
...

[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

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5