tkinter で作った GUI の実行可能ファイルを作る

## とある日

Python で subprocess を使って外部ツールを実行するスクリプトを作り、Python コマンドを叩けば実行できるものを作った。(要は、外部ツールのラッパー)

ところが、利用者はターミナルを開いて使うような人物ではないので、やっぱり GUI でポチポチできるのが嬉しい。

そして、いつものように多々ハマる日。

## tkinter (PySimpleGUI)

Python には、 **tkinter[^1]** という GUI ツールキットが標準で用意されているらしい。これを使えば仮でも GUI を作れそうだ。

ちょろちょろ Google 先生に尋ねていると [Tkinterを使うのであればPySimpleGUIを使ってみたらという話](https://qiita.com/dario_okazaki/items/656de21cab5c81cabe59) という記事に辿り着き **PySimpleGUI[^2]** という tkinter (及び、Qt, WxPython, Remi) のラッパーライブラリに出会った。

ここで tkinter 以外にも Qt, WxPython, Remi を使えば Python で GUI を作れそうだなと、色々知ることになる。(別に Python で GUI を作りたいわけではなく、手軽に Python コードを実行できる GUI が欲しいだけなので、実際はなんでも良い)

### <font color="#f00">tkinter がインストールされていない??</font>

早速 [PySimpleGUI - Jump Start](https://pysimplegui.readthedocs.io/en/latest/#jump-start) を参考に Hello World を試してみるも以下のエラーに悩むことになる。

```bash:ターミナル
$ python hello.py 
Traceback (most recent call last):
  File "hello.py", line 1, in <module>
    import PySimpleGUI as sg
  File "/Users/chibi/.pyenv/versions/3.8.2/lib/python3.8/site-packages/PySimpleGUI/__init__.py", line 2, in <module>
    from .PySimpleGUI import *
  File "/Users/chibi/.pyenv/versions/3.8.2/lib/python3.8/site-packages/PySimpleGUI/PySimpleGUI.py", line 103, in <module>
    import tkinter as tk
  File "/Users/chibi/.pyenv/versions/3.8.2/lib/python3.8/tkinter/__init__.py", line 36, in <module>
    import _tkinter # If this fails your Python may not be configured for Tk
ModuleNotFoundError: No module named '_tkinter'
```

PySimpleGUI が悪いわけではないはずなので [Tcl/Tk の Python インタフェース](https://docs.python.org/ja/3/library/tkinter.html) に記載されている以下のコマンドより、正しくインストールされているのかを確認する。

```bash:ターミナル
$ python -m tkinter
```

もちろん、正しくインストールされていないと思われるので同じエラーが表示される。

記載するのが遅れたが、本環境は以下のようになっている。

- MacOS Catalina: 10.15.4
- Python: 3.8.2
- pip: 20.0.2

そして、この問題で参考にさせていただた記事は [pyenvのpythonでtkinterを使用する方法](https://qiita.com/skyloken/items/a5f839eba1bd79cd5ef9) です。自分は pyenv を使っているため、まさにこの記事が答えでした。

brew で `tcl-tk` というものをインストールすれば良さそう。加えて、Python をビルドするときのオプションを追加して、Python を再インストールすれば良さそう。うまくインストール出来ているか、先ほどのコマンドで確認する。

```bash:ターミナル
$ python -m tkinter
```

<img width="163" alt="20200509152812.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/44eea616-10b4-b745-c5e4-e725aa733e62.png">

問題なさそう！

続いて、PySimpleGUI の Hello World を実行してみる。

<img width="442" alt="20200509153240.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/59f7c453-a50b-05eb-d83e-d0f15a95beb9.png">

問題なさそう！！

### <font color="#f00">実装した GUI を起動するには...??</font>

とりあえず、Hello World が完了したので、実際に GUI を作っていく。と言っても、機能は「 **ファイル選択ダイアログ** でファイル選択ができること」と「 **実行ボタン** で、選択したファイルパスを利用できること」の2つだけである。

実際に GUI を起動するには PySimpleGUI を使って実装した Python ファイルをダブルクリックするだけじゃダメだった。
結局、以下のように Python コマンドを入力しなくてはならない。

```bash
# GUI を実装した gui.py(仮) を実行する。
$ python gui.py
```

これでは、実行対象が **subprocess を利用している Python ファイル** から、 **PySimpleGUI を利用している Python ファイル** に変わっただけで、ターミナルからの実行からは逃れられていない。ここでやりたいことは通常の **アプリケーション** のように Dock に入れておいて、ポチッと起動できること。なのである。

## py2app

さて、再び Google 先生に頼る時間。何種類か引っかかったが **py2app[^3]** というものを利用することにしてみた。ここでは以下の記事を参考にさせていただきました。  
[py2appでMacのネイティブアプリを作る](https://code-graffiti.com/create-native-mac-application-on-py2app-in-python/)

作業の流れとしてはだいたい以下の通り。

1. pip で py2app をインストール
1. py2app でセットアップスクリプトを生成する
1. <font color="#f00">スクリプトを実行する(ビルドする)　← ここでエラー</font>

### <font color="#f00">セットアップスクリプトでエラー??</font>

```bash:ターミナル
$ python setup.py py2app
〜略〜
ValueError: '/Users/chibi/.pyenv/versions/3.8.2/lib/libpython3.8.dylib' does not exist
```

`.dylib` が無いよ〜。  
実際に見にいくと `libpython3.8.a` はあるけど `.dylib` は無い。 

[python Macでスタンドアロンアプリにする](https://medium.com/@Dorobune/python-mac%E3%81%A7%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%89%E3%82%A2%E3%83%AD%E3%83%B3%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AB%E3%81%99%E3%82%8B-py2app-9ef3057519c) を参考にさせて頂くと `setup.py` のパラメーターを弄れば OK と。。。自分の環境では Python `3.8.2` なので、一部変更しながら試してみる。

```diff:setup.py
  from setuptools import setup

  APP = ['gui.py']
  DATA_FILES = []
- OPTIONS = {}
+ OPTIONS = {
+     'argv_emulation':True,
+     'plist': {
+         'PyRuntimeLocations': [
+             '@executable_path/../Frameworks/libpython3.8m.dylib',
+             '/Users/chibi/.pyenv/versions/3.8.2/lib/libpython3.8m.dylib'
+         ]
+     }
+ }

  setup(
      app=APP,
      data_files=DATA_FILES,
      options={'py2app': OPTIONS},
      setup_requires=['py2app'],
  )
```

いや、これを試したところで、 `/Users/chibi/.pyenv/versions/3.8.2/lib/` に `.dylib` なんて無いので出来るわけがない。古いバージョンだったり、Anaconda 環境の人を見かけるけど、別の環境には `.dylib` 最初からあるのかなー。

試しに存在している `.a` を使ってみることにするが、期待も虚しくダメだった。

<details>
<summary>実際に試した setup.py (折りたたみ)</summary>
<div>

```diff:setup.py
  from setuptools import setup

  APP = ['gui.py']
  DATA_FILES = []
  OPTIONS = {
      'argv_emulation':True,
      'plist': {
          'PyRuntimeLocations': [
              '@executable_path/../Frameworks/libpython3.8m.dylib',
-             '/Users/chibi/.pyenv/versions/3.8.2/lib/libpython3.8m.dylib'
+             '/Users/chibi/.pyenv/versions/3.8.2/lib/libpython3.8.a'
          ]
      }
  }

  setup(
      app=APP,
      data_files=DATA_FILES,
      options={'py2app': OPTIONS},
      setup_requires=['py2app'],
  )
```

</div>
</details>

どうやら static library ではダメで、shared library を用意しなければいけないっぽい。

pyenv では、インストールの際に `PYTHON_CONFIGURE_OPTS="--enable-framework"` があれば良いそう。そうすると shared library として `libpython` が配置されるっぽい。tkinter と同様で pyenv 経由の場合はオプションが足りないということらしい。 `PYTHON_CONFIGURE_OPTS` は tkinter の際にも必要だった環境変数なので `--enable-framework` を追加して、もう一度 Python をインストールしなおしてみることにする。

うまくいくと `dist/xxx.app` が出来上がる。  
ちなみにこのとき、上記で触った `setup.py` はデフォルトのままで良かった。つまり `OPTIONS` も空配列のままで OK。

`xxx.app` をダブルクリックして開いてみると。。。

<img width="346" alt="20200510180322.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/dad59d18-efa8-2a24-b1b1-0ff518f7bc36.png">

こんな感じ。  
ファイル選択できて、実行ボタンがあるくらいのもの。

### <font color="#f00">GUI だとパスが通っていない??</font>

動作確認と思って実行してみると、内部で実施している `which コマンド` で特定の外部ツールが無いと言われてしまった。PATH 自体は `.bashrc` や `.bash_profile` に書いてあるので、ターミナルからであれば、パスが通った状態になっているが、GUI からだとパスが通っていない状態になっているみたい。Windows みたいに環境変数の登録ってどうやってやるんだろう？

[macOSでGUIアプリの環境変数を設定する方法探求](https://qiita.com/homu-konamilk/items/5886f3e48ffa658f4f78) を参照。

```bash:ターミナル
$ sudo launchctl config user path <設定したいPATH情報>
```

こんな感じで設定できる。再起動も必要。  
ちなみに上記の記事内にも書かれていましたが `launchctl` で設定されたパスは以下に保存されているとのこと  
`/var/db/com.apple.xpc.launchd/config/user.plist`

再起動を終えて、再度作ったツールを実行すると期待通り subprocess も動いてくれて、実行結果も期待通りのものとなった。

## まとめ

色々ハマったけど、無事期待通りに動くちょこっとツールが完成。

- GUI
    - tkinter (PySimpleGUI)
    - tcl-tk
    - Python インストール用の環境変数の設定 ( `tcl, tk 関連` )
- 実行可能ファイル
    - py2app
    - Python インストール用の環境変数の設定 ( `--enable-framework` )
- GUI の環境変数
    - launchctl

[^1]: [Tcl/Tk の Python インタフェース](https://docs.python.org/ja/3/library/tkinter.html)
[^2]: [PySimpleGUI](https://pysimplegui.readthedocs.io/en/latest/)
[^3]: [py2app](https://py2app.readthedocs.io/en/latest/index.html)