PyInstallerインストール時・実行時のエラーについて

# PyInstallerとは
　PyInstallerは、作成したPythonスクリプト（.py）を実行ファイル（.exe）に変換することができる優れものツールです。実際に私が経験した実用例としては、  
『**共同研究先（Python環境無し）で、Pythonで作製した自動解析ツールを使用したい**』  
という、楽したい願望を叶えるための優秀なツールでした。本記事では、PyInstallerを使用したい、けどエラーを吐いてしまって、、、どうすればいいの、、、、と路頭に迷っている人に助け船を出すことを目的としています。PyInstallerの使い方は、実際に私が参考にした以下の記事らを見てみてください。  
- [PyInstallerでexe化@takanorimutoh(Qiita)](https://qiita.com/takanorimutoh/items/53bf44d6d5b37190e7d1)  
- [Pythonのファイルをexe化!Pyinstallerの使い方](https://watlab-blog.com/2019/04/10/pyinstaller/)

ここでは、実際に私がハマった3つのエラーをひとつずつ、どうやって回避したのかについて説明していきます。エラーの原因等について、そこをはっきりさせることが目的ではなく、あくまで**PyInstallerを使用してexeファイルを生成することが目的**なので、エラーの原因等については深く考察しません。詳しく知りたければ英語でｇｇってください。

# ハマった3つのエラー
1. そもそもPyInstallerがインストールできない
2. exeファイル実行時にexec関数が動かない(`ModuleNotFoundError: No mudule named 'pkg_resources.py2_warm'. [8352] Failed to execute script pyi_rth_pkgres`)
3. よくわからん(`qt.qpa.plugin: Could not find the Qt platform plugin "windows" in "". This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.`)


# 環境
Windows 10
Anaconda3 4.8.2
Python 3.6.5
PyInstaller 3.6

# 1.『そもそもPyInstallerがインストールできない』問題
これは非常に苦労しました。結論から言いますと、**解決しませんでした**。このエラーにはまっている人たちで、解決している人をほとんど見かけていません。かなり脱出するのが困難な沼です。  
## インストールするコマンド
`pip install pyinstaller`  
(こっちがレコメンドされています、でも最終的にinstallするときに使用したのは`conda install pyinstaller`でした、どっちがいいかは試してみてください、もしanacondaで環境構築をしているのであれば、後者がおすすめです)
## どんなエラーが発生したのか？  
この時のエラー内容をメモっていなくて、、、、ざっくり覚えているのが、**『setuptools』がかなり悪い動きをした**、ということです（`satisfies the requirement setuptools`といったような感じの内容）
## 打開策
最終的に、インストールができています現在進行形nowです。さて、どうやったのでしょう。  
  
- 1st try: anacondaのupdate  
[GitHub](https://github.com/pypa/setuptools/issues/1963)でも深く話されていたりします（調べるとホイホイ出てきます）、その中でも、このエラーを回避するためには`conda update`が有効だという旨の投稿を目撃しました、updateしましたがダメでした（update使用にも、すでに入っているsetuptoolsがanacondaと深く結びついていて、setuptoolsを切り離してupdate等を実施することができない）
- 2nd try: wheelとsetuptoolsのインストール  
これはどちらの記事に書かれていたのか忘れてしまいましたが、満足するsetuptoolsがないのならいれてやれ、ということでやってみたやつ。何の成果も得られませんでしたｧｧｧｱｱ！！！
- Final try: conda create new_env  
はい。これがインストールできないエラーを回避するに至った、最終奥義でした。  
何をしたかって、新しく仮想環境を立ち上げたんです。立ち上げた先で、`conda install pyinstaller`しました。  

（詳しく話すと、当面はPyInstallerをインストールすることは諦めていました。あがいたってエラー回避できないし、仕事進まないし。その場その場で、バッチファイルで対応していました。業務を進めていく中、全然関係ないところでseabornのupdateが必要になりました。しかしところがどっこい、updateできないんですね。環境が汚すぎて。汚いっていうのは、**pipとcondaが入り混じっている**のです。環境としては崩壊してますね。  
　そこで、condaだけで環境を構築し直そう（dockerはwindowsだとまだ面倒くさそうなので止めた）と仮想環境を新たに立ち上げました`conda create --name makeexe python==3.6`。この段階では、`pip install pyinstaller`は成功しませんでした。もしかしたら、**Anaconda環境にpipでpyinstallerは入れられない**のかもしれません。最低限必要なパッケージをインストールしました（`conda install numpy pandas seaborn matplotlib tqdm opencv pillow `）。その後、ふと思い立ってpyinstallerをconda経由でインストール`conda install pyinstaller`してみたところ、スッと入ったのです。嗚呼、いとをかし。）  

## 結局何が問題だった？
詳細は分かりませんが、**Anaconda環境にpip経由で入れることがそもそもの間違い**だったのかなと思います。結果論ですが。conda環境にpipを混ぜるな、環境破壊禁止ってことですね。身をもって痛感しました。可能な限り、インストールしたいライブラリがcondaでできるかどうか、ちゃんと確認しましょう。できるのなら、そっちでやりましょう。その方が無難です。

# 2.『exeファイル実行時にexec関数が動かない』問題
## PyInstaller実行コマンド
まず、PyInstallerのインストールには成功し、実行することもできました。PyInstallerを適用したい（exeファイル化したい）ディレクトリに移動した後に、以下のコマンドでexeファイル化を実行します（`--onefile`で、モジュールらを一つのexeファイルにまとめる；出力ファイルがexeファイルのみになる）。

```
$ cd ~~~ # main.pyが置いてあるディレクトリに移動
$ pyinstaller main.py --onefile
```
このときのディレクトリの構成は以下のようになっています。Pyinstallerを実行することで、新たに`build`と`dist`の2つのディレクトリが生成され、`dist/main.exe`と実行ファイルが生成されています。これをWクリックすることで、main.pyのプログラムが実行されます。まぁ、エラーを吐くんですけどね。ﾊﾊｯ  

```  
./
|
|- main.py (作成したプログラム)
|- config.json (main.pyで読み込むconfigファイル)
|- main.spec（pyinstallerで作成されたもの）
|- main.exe（pyinstallerでdistディレクトリ内に作成されたもの、コードの都合上configと同じディレクトリ上に設置）
|- dist/
|    |- main.exe
|
|- build/
     |- main/
          |- ~~~
```  

## どんなエラーが発生したか？
生成した`main.exe`を実行すると、以下のようなエラーを吐きました。

```
Traceback (most recent call last):
  File "site-packages\PyInstaller\loader\rthooks\pyi_rth_pkgres.py", line 13, in <module>
  File "C:\ProgramData\Anaconda3\envs\(venv you made)\lib\site-packages\PyInstaller\loader\pyimod03_iimporters,py", line 623, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\pkg_resources\__init__.py", line 86, in <module>
ModuleNotFoundError: No mudule named 'pkg_resources.py2_warm'
[8352] Failed to execute script pyi_rth_pkgres
```

`exec`が実行できません、実行するために必要な`pkg_resources.py2_warm`が無いって喚いてます。

## 解決策
[こちら](https://qiita.com/kanedaq/items/e65507878c52ad67d002)を参考に、PyInstaller実行時に生成された`main.spec`ファイル内の`hiddenimports`に、`ModuleNotFoundError`と言われたモジュールを書き加える。

```
-              hiddenimports=[],
+              hiddenimports=['pkg_resources.py2_warn'],
```

そして、**このspecファイルを使用して**再度PyInstallerでexeファイルを作成します（このとき、先ほど生成されたファイル群はspecファイル以外は捨てちゃって問題ないです、ってか捨てましょう、エラーとか上書きどうするだとかの問題の種になります）。以下のコマンドで実行します。  
`pyinstaller main.spec --onefile`  
これで、execモジュールに関するエラーは吐かなくなります。

# 3.『よくわからんけど吐いた問題』問題
先ほどの`main.spec`ファイルを使用する方法で、execモジュールに関するエラーは回避できました。しかし、実行していると、まだエラーを吐きました。どんなエラーだったのか、見てみましょう。ちなみにボクは、語彙が無さ過ぎるのでこれを『よくわからんエラー』を呼称しています。うーん、わかりやすい。  

## どんなエラーが発生したか？

```
C:\ProgramData\Anaconda3\envs\(venv you made)\lib\site-packages\PyInstaller\loader\pyimod03_iimporters,py:623:MatplotlibDeprecationWarning:
THEMATPLOTLIBDATA environment variable was deprecated in Matplotlib 3.1 and will be removed in 3.3.
  exec(bytecode, module.__dict__)
2020/03/27 10:29:55 - complete to load config.json (ここからはプログラム内での意図的な出力)
~(中略)~
2020/03/27 10:30:14 - success affine transforming.
qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
```

最初のメッセージは、同じくexecによるものです、`hiddenimport`に設定し対処済みなのでスルーでおｋ。  
次は、プログラムの進捗状況をアナウンスするための物だからおｋ（プログラム内で意図的に吐かせているメッセージです）。Affine変換まで実施した辺りで、`qt.qpa.plugin`に関する新たなエラーが発生。プラットフォームがインストールできませんだとかなんだとか。何が起こってるのかよくわかりません。  

## 解決策    
[stackoverflowに寄せられていた質問](https://stackoverflow.com/questions/47468705/pyinstaller-could-not-find-or-load-the-qt-platform-plugin-windows)の回答にあったのが、一部抜粋すると、

> There seem to be two solutions, the first one worked fine for me:
> * copy platform directory to directory of your executable. You'll find the platform directory at a location like 
    ```c:\Users\<username>\envs\<environmentname>\Library\plugins\platforms``` or
> * Upgrade to a newer version of pyqt: conda install -c anaconda pyqt
>  
> Use the second option with care: Do not try to use pip for pyqt installation if you have a conda environment, 
> this might break your conda installation: https://github.com/ContinuumIO/anaconda-issues/issues/1970
  
 と、

> I had an issue where my python code worked fine, but the compiled .exe file would provide the 
> "could not find or load the Qt platform plugin windows" problem. 
> I fixed the problem by copying the 
> ```~PyQt5\Qt\plugins\platforms``` folder from the program's directory, 
> generated by using pyinstaller --onedir main.py, to the folder holding the .exe file.
> 
> It seems that in my case the only way "helping" my program detect the required .dlls was having the platforms folder next to the main.exe. 
> Pasting the platforms folder to the program's directory after using pyinstaller --onefile main.py also makes the program work.

です。さすがstackoverflowですね。解決したいことに対する答えがもう出ている。素晴らしい。  
まず、お手頃に試せそうなものとして、`pyqt`のインストールをしてみました。コマンドは、  
`conda install -c anaconda pyqt`
  
だがしかし改善せず。どうしたstackoverflow。こんなもんか？と詰める隙も与えてくれません、次のアイデアは既に描かれているのです、あぁ、stackoverflowには勝てない。  

で、そのもう一つの手法としておすすめされている、platformディレクトリのコピーを行いました。platformは、「C:\ProgramData\Anaconda3\envs\(venv you made)\Library\plugins\platforms」にあります（自分の場合、人によって違うかも、stackoverflowでの回答内でもまた違うパスでしたね）。
この`platforms`ディレクトリを、実行したいexeファイルと同じディレクトリにコピーします。
つまり、最終的にこんなフォルダ構成になります⇓
  
```
./
|
|- main.py (作成したプログラム)
|- config.json (main.pyで読み込むconfigファイル)
|- main.spec（pyinstallerで作成され、hiddeninportsに'pkg_resources.py2_warn'を追加したもの）
|- main.exe（pyinstallerでdistディレクトリ内に作成されたもの、コードの都合上configと同じディレクトリ上に設置）
|- platforms/（コピーしてここに貼り付ける）
|   |
|   |- qdirect2d.dll
|   |- qminimal.dll
|   |- qoffscreen.dll
|   |- qwebgl.dll
|   |- qwindows.dll
|- dist/
|    |- main.exe
|
|- build/
     |- main/
          |- ~~~
```  

この状態でexeファイルを実行（リビルドの必要は無いはず）すると、最後まで正常にexeファイルが実行されます！！！！  
もしexeファイルがエラー吐いたら、再度ビルドしなおしてみてください。

# 終わりに
　PyInstallerを使用したいのにエラーが発生する、いろんなエラーが発生すると思います。それらのエラーをまとめてくれている記事ってないんですよね。よく分からず振り回されました。で、結局根本的な打開策が`conda install`の使用だったって。よくわかりませんね。  
　でも、PyInstallerは有用です。世の中にはネットから切り離されているためPythonはもちろん、プログラム言語をinstallできないPC（も脳みそも）存在するので。しかし、これの欠点としては**生成されるexeファイルが非常に重い**ことですかね。少なくとも、スクリプトと違ってメールで気楽に送ることはできません。  
　まだまだいろいろなエラーがあると思います。ぜひ、この記事を利用してください。コメント欄に書いてくだされば、そのエラーの対処法もまたこの記事に追加します。PyInstallerエラー対策マニュアルをみんなで作りたいですね。  
  
よんでいただきありがとうございました。ばいばいっ