Kivyで作ったアプリをNuitkaでexeファイル化した時の試行錯誤のメモの巻
はじめに
とあるデスクトップアプリをKivy/Pythonで作ってみたときに、NuitkaでWindows用にexeファイル化をしまして、その時の試行錯誤の記録です。
最初はPyInstallerでexe化しようとしていたのですが、PyInstallerでone-fileモードでexe化すると起動に
135秒かかる。
で、ファイル容量が
393 MBになってしまう、と。
おおぅ( ̄□ ̄;)!!!
一方、one-fileモードでなくて、one-directoryモードであれば15秒ほどで起動できるのですが、directoryのファイル容量が、、、
700 MB超え。
いやぁん( ̄□ ̄;)!!!
こりゃイカンということで、起動時間とファイル容量の改善が期待できると耳にしたNuitkaを使ってみることにしました。
んが、初めてのワタシがやってすんなりいくはずもなく、試行錯誤が必要でした。
なので今後のために記録を残しておきます。
というか書きなぐります。
結果としては、起動時間を6.6秒に、exeファイル容量を82 MBに改善することができました。
記事の後半にNuitkaでexe化したときと、PyInstallerでexe化したときの結果の簡単な比較表を載せてみましたよ。
実施環境
PC、OSなど
MacBook 2017 (Intel Core m3, 1.2 GHz, RAM 8GB)(Boot CampでWindowsを導入)
Windows10 Home 21H2
PowerShell version; 5.1.19041.1682
MSVC コンパイラ(cl; 14.3 )(Build Tools for Visual Studio 2022より)
Python、pyenv、pipenv、NuitkaとPyInstaller
pyenvを使って、pipenvでの仮想環境下で実施しています。
Python 3.9.12
pyenv; 2.64.11
pipenv; version 2022.4.21
Nuitka; 0.8.1
PyInstaller; 5.1
今回exe化に供したファイル
pyファイル:253 KB
これに付随して必要なkv, csv, png, ico, ttf, xmlファイルを少々(合計6.5 MB)
pyファイルでimportしている主なライブラリ
numpy; 1.22.4
pandas; 1.2.4
scipy; 1.7.3
kivy; 2.1.0
plotly; 5.8.0
メモ書き
Nuitkaでのビルドに時間がかかる
NuitkaはPythonソースをC言語ソースに変換してからコンパイルしてくれてexeファイルを作ってくれるということなのですが、これが結構時間がかかります。特にリンクの段階が。。。
Nuitka-Scons:INFO: Backend linking program ( no progress information available)
からなっかなか動かん。
ワタシの環境の場合、初回検討時でさえNuitkaをスタートして単一exeファイルができるまでに約3時間かかりました。で、足りないいろんなモジュールを拾っていって、ちゃんと動くexeファイルを作れるようになった頃にはビルドに8時間とか9時間とか平気でかかるようになりました。
ちなみにPyInstallerだと12分でexe化完了してました。
やってる処理が全然違うので単純に比較するのはアレですが。
で、一番最初に出来上がったexeファイルでアプリが起動しないものだから、試行錯誤することになったのですが、待ち時間が長いからすごーく時間がかかりました。
長い時間プログラムを走らせた後にダメだった時のガッカリ感といったら。
パワフルなマシンが欲しくなるなぁ。。。
でも使うときに起動時間が短くなったり容量が小さくなったりすることを期待しながら、諦めずに試行錯誤を続けていったらエラーを少しずつでしたがクリアしていけました。
モジュールがないと言われるので – -include-moduleで追加する
exeでアプリが起動しないのは、必要なモジュールをNuitkaがうまく拾ってくれておらず、exeを実行したときにimportでエラーが出てしまっているからのようなのでした。
ModuleNotFoundError: No module named 'xxxxxx'
(xxxxxxはモジュール名)
なので、–include-moduleオプションにて、無いと言われたモジュールを追加してビルドをやり直しました。
そうしたらまた足りないものが発見されてしまったので、追加してまたやり直しました。するとまた足りないものが発見されて、追加して、、、とまさに芋づる式にやらなきゃならなかったのですげー手間かかりました。。。これ1回で足りないものを抽出する方法ないのん?
何度も同じライブラリにおいて異なるモジュールがナイナイナイナイ言われるので、–include-moduleで絞るんじゃなくて、–include-packageでライブラリごと指定してみたら、3時間だったビルド時間が13時間に伸びてしまいました。。。
そのときに出来ていたbuildフォルダの容量が7.6 GB。うおぅ( ̄□ ̄;)!!!
(ちなみに、–include-packageを使わなかったら1.8 GB)
うおーうおーと言ってたら、コマンド履歴からうっかりまたNuitkaを走らせてしまい、出来上がったフォルダが上書きされて全ての出力済みファイルが消えてしまいました。。。
そうだ、海に夕日を眺めに行こう。。。
こうしてワタシは–include-packageオプションをそっと消したのでした。。。
さらに面倒なことには、BootCampで控えめにWindowsを入れていたワタシはストレージの空き容量が足りなくなり、パーティションの区切り直しをするはめになったのでした(つまりWindowsのインストールからやり直し)。
はい、というわけで、もうしょうがないので、
とりあえず足りてないモジュールを洗い出して補うために、–onefileオプションを一旦やめて、
–standaloneオプションに切り替えてから、–include-moduleでモジュールを追加しながらひたすらビルド&スクラップみたいな感じでしばらく格闘。
–onefileオプションをやめたら1ターン2時間くらいに抑えられることがわかりました。
networkx のせいで動かない
そしたら今度は
File "C:\Users\*****\NUITKA~1\******\networkx\utils\decorators.py", line 848, in __call__ TypeError: Nuitka doesn't support __defaults__ size changes
なるエラーに遭遇。(*****は秘密)
どうやらこれは現状(ver. 0.8.1)どうしようもないエラーらしい。
https://github.com/Nuitka/Nuitka/issues/1177
調べてみたら、networkxは今回exe化しようとしているプログラムの中で使っているkloppyというライブラリの中で呼び出されているライブラリであることがわかりました。
で、networkxはどうやらオプション的な扱いになっていたので、kloppyの中でnetworkxが読み込まれている部分を特定して潰してみることにしました。
で、ワタシのプログラムの動作確認をしてみると、networkxを読み込まずとも問題なく動くことが確認できました。
というわけでnetworkxの読み込み部分を潰した状態で、Nuitkaでのビルド時にも「–nofollow-import-to=networkx」としてやることで、先ほどのエラーを解消することができました。
ラッキー、これで続けられます。
・・・どれくらい時が経ったことでしょう。
exeファイルをダブルクリックしたらアプリが起動しました。やったね。
と、思ったのも束の間、ちゃんと動くか確認しようとしたら、ある動作をしたときに、またまたモジュールが見つからないと言われます。
でも根気良くモジュールの追加の検討を続けます。
Scipy 1.8.0でクラッシュする。1.7.3にしたら動いた。
今度は別の動作時にクラッシュしてしまう現象に悩まされました。
これがトレースバックなしで落ちやがりまして、原因を掴むのに苦労しました。
イベントビューアーを確認したらsegmentation fault (例外コード; 0xc0000005)で落ちているのはわかったのですが。
で、C言語でのお作法を知らないワタシの能力ではこれ以上のデバッグが難しく。。。
でも、実はPyInstallerでexe化したやつも、今回と同じ動作時にsegmantation faultを出してクラッシュすることがわかっていました。
PyInstallerでは、scipyの._lib._uarrayのimportのところでつまずいていて、–collect-all “scipy” としたらエラーが解決したのでした。
なので今回も同じことかもと思って怪しんでいろいろ試したのですがうまくいかない。
で、どうもscipy 1.8.0使用時におけるNuitka側のバグかもね(多分)ってことに辿り着きました。
参考;Windows+MSVC+scipy>=1.8.0 → segfault #1565
結局、ここのページに記載のある、scipyのバージョンを1.7.3にしてみたら動くようになりました。
これのせいで5日ほど余計に時間が過ぎてしまった。。。
最終的にこれでうまくいった
ケースバイケースと思いますが、どなたかの参考になるかもしれませんので、うまくいった方法を記載します。
次のようにbatファイルに記載してbatファイルを実行してNuitkaを走らせました。
python -m nuitka ^
--onefile ^
--follow-imports ^
--windows-icon-from-ico=icon.ico ^
--nofollow-import-to=networkx ^
--include-module=pandas._config.localization ^
--include-module=plotly.io ^
--include-module=plotly.graph_objs ^
--include-module=plotly.validators ^
--include-module=kivy.uix.actionbar ^
--include-module=kivy.uix.treeview ^
--include-module=kivy.uix.stacklayout ^
--include-module=kivy.uix.progressbar ^
--include-module=win32timezone ^
--include-module=scipy._lib._uarray ^
--include-data-file=".\\*png=.\\" ^
--include-data-file=".\\*ttf=.\\" ^
--include-data-file=".\\*csv=.\\" ^
--include-data-file=".\\*xml=.\\" ^
--include-data-file=".\\*kv=.\\" ^
--include-package-data=plotly ^
--include-package-data=scipy ^
--plugin-enable=numpy ^
--show-progress ^
--show-scons ^
xxxxx.py
オプションの意味するところについては公式サイトなどをご参照ください。
xxxxx.pyはpyファイル名です。
まだやり方に改善余地がありそうな気がしています。
外部ファイルを読み込むときのpathの変更
これでうまくいった!と、思ったら、exeファイルを他のディレクトリに移動しちゃうとpngとかttfとかcsvとかを拾ってくれなくなって起動できなくなることが判明。
外部ファイルを読み込むときのpathの記載を、exeファイルの中身がTempフォルダ以下に展開されたときのpathに合わせないといけなかったです。そりゃそうか。
User Manualの「Onefile: Finding files」の項に記載のある、
os.path.join(os.path.dirname(__file__), "user-provided-file.txt")
に相当するようにpathの記載を修正することで解決できました。
PyInstallerとNuitkaでのexeの作成についてざっくり比較
今回Nuitkaでexe化したものはPyInstallerでもexe化を試しているので比較してみます。
OSはWindows10です。その他マシンスペック等は既出の実施環境の項をご参照ください。
ちなみにコマンドラインでpyファイルから起動した場合の起動時間は6.3秒でした。
Nuitka | Nuitka | PyInstaller | PyInstaller | |
方法 | one file | standalone | one file | one dir |
exeファイル容量 | 82 MB | 205 MB | 393 MB | 26.4 MB |
exe起動時間* | 6.6 秒 | 9.9秒 | 135秒 | 15秒 |
ビルド時間 | 8.5時間** | 9時間** | 12分*** | 12分*** |
dist フォルダ容量 | – | 416 MB | – | 724 MB |
* 手元のストップウォッチで計測(なのでだいたいこれくらい、という数字)
** exe化成功以前のビルド時に作成されたキャッシュの利用あり
*** specファイルを指定してのexe化処理時間
exe化するパッケージやモジュールによって、また、個々の環境によって大きく変わると思われますのでご参考まで。もうちょっとexe化するやり方のチューニング余地もあるような気がしますし。
ちなみにmacOSでのPyInstallerではappファイル(ディレクトリというべきか)にバンドルできて、それだと見た目one-fileですが実質はone-directoryなので、起動時間はone-directoryとほぼ変わりませんでした。
おわりに
これで、Pythonで作ったプログラムをexe化する際の選択肢にNutkaを使うことが入りました。
すんなりうまくいけばPyInstallerよりもNuitkaの方がいいと思います。プログラムを使う側としてはファイル容量が小さくて動作が軽い方がいいですし。(体感として、PyInstallerでexe化したものよりもNuitkaでexe化したもののほうが動作が軽快な印象)
が、exe化するときにハマるときゃハマるということがわかりました。C言語がわからんワタシにはトラブルシュートの難易度も高くなります。
Nuitkaでの試行錯誤の前に、PyInstallerでexe化したものがちゃんと動作することを確認できていれば少しは効率的にできるかもしれません。ビルド時間がすごーく長くなってしまう場合は特に。
まあ、exe化したときの起動時間とファイル容量だけでなくて、費やす時間や労力も加味した上で、プログラムの使用目的や配布対象に応じてNuitkaとPyInstallerを使い分けていくのがいいのかな、と思いました。
あとは、もしかして、C言語での開発方法を学んだ方が早いんじゃ?と思いましたよ。
まあ何とかなりましたから良かったのですが。
最後まで読んでくださりありがとうございました。
おしまい。
参考にしたサイト
https://qiita.com/taka7n/items/c80daefe9e722f11dbe9
Windows+MSVC+scipy>=1.8.0 → segfault #1565
https://github.com/Nuitka/Nuitka/issues/1177
ちょっと広告です
https://business.xserver.ne.jp/
https://www.xdomain.ne.jp/
★LOLIPOP★
.tokyo
MuuMuu Domain!
ちょっと広告です
https://business.xserver.ne.jp/
https://www.xdomain.ne.jp/
★LOLIPOP★
.tokyo
MuuMuu Domain!