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


とある日

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

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

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

tkinter (PySimpleGUI)

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

ちょろちょろ Google 先生に尋ねていると Tkinterを使うのであればPySimpleGUIを使ってみたらという話 という記事に辿り着き PySimpleGUI2 という tkinter (及び、Qt, WxPython, Remi) のラッパーライブラリに出会った。

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

tkinter がインストールされていない??

早速 PySimpleGUI - Jump Start を参考に Hello World を試してみるも以下のエラーに悩むことになる。

ターミナル
$ 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 インタフェース に記載されている以下のコマンドより、正しくインストールされているのかを確認する。

ターミナル
$ python -m tkinter

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

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

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

そして、この問題で参考にさせていただた記事は pyenvのpythonでtkinterを使用する方法 です。自分は pyenv を使っているため、まさにこの記事が答えでした。

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

ターミナル
$ python -m tkinter

問題なさそう!

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

問題なさそう!!

実装した GUI を起動するには...??

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

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

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

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

py2app

さて、再び Google 先生に頼る時間。何種類か引っかかったが py2app3 というものを利用することにしてみた。ここでは以下の記事を参考にさせていただきました。

py2appでMacのネイティブアプリを作る

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

  1. pip で py2app をインストール
  2. py2app でセットアップスクリプトを生成する
  3. スクリプトを実行する(ビルドする) ← ここでエラー

セットアップスクリプトでエラー??

ターミナル
$ 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でスタンドアロンアプリにする を参考にさせて頂くと setup.py のパラメーターを弄れば OK と。。。自分の環境では Python 3.8.2 なので、一部変更しながら試してみる。

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 を使ってみることにするが、期待も虚しくダメだった。


実際に試した setup.py (折りたたみ)
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'],
  )


どうやら 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 をダブルクリックして開いてみると。。。

こんな感じ。

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

GUI だとパスが通っていない??

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

macOSでGUIアプリの環境変数を設定する方法探求 を参照。

ターミナル
$ 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