py2appでMac OS Xのアプリを作ってみた

PythonでMac OSのアプリを作りたい。

そんな時にはpy2appを使う。

PythonではGUIアプリはTkinterなどのライブラリを使って開発できる。

私の開発環境:

  • Mac OS Catalina
  • PyCharm
  • Anaconda

この記事で詳しく説明するが、py2appはAnacondaを使っていると素直に動かないしトラップがいくつもある。

そのトラップ達を解決するためにかなり苦労したので、その過程を紹介しようと思う。

[toc]

py2appのインストール

py2appをインストールする。

公式サイトを見ると「pip」でインストールする。と書いてあるが、Anacondaのパッケージマネージャでもインストールできる。

(py2appに限らず、pipでインストールできるものはAnacondaでも大体インストールできる。)

PyCharmでAnacondaのパッケージマネージャを開き「py2app」のパッケージを検索してインストールする。

「Preferences」➡︎「Project Interpreter」➡︎ 「+」

インストールができると以下のように、Anacondaのパッケージマネージャにpy2appが追加される。

py2appを実行

setup.pyが必要なので以下のコマンドで作成する。

引数のgui.pyは私が適当に作ったGUIアプリの起動スクリプトだ。

アプリを構成する複数の.pyファイルがあったとしても、アプリを起動するためのmain関数が書かれた.pyだけを指定すれば良い。

(golden-week) MacBook-Pro:google-search $ py2applet --make-setup gui.py
Wrote setup.py

setup.pyが作成されたので中身を確認する。

"""
This is a setup.py script generated by py2applet

Usage:
    python setup.py py2app
"""

from setuptools import setup

APP = ['gui.py']
DATA_FILES = ['ranking.pkl']
OPTIONS = {}


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

DATA_FILESは何も指定されていなかったが、私の作ったアプリは「ranking.pkl」を使用するアプリなので追加している。

アプリで使用する画像やデータファイルがある場合には、DATA_FILESに記載する。

次にアプリを作成するために以下のコマンドを実行する。

(golden-week) MacBook-Pro:google-search $ python setup.py py2app -A
 :
 :
*** creating application bundle: gui ***
Copy '/opt/anaconda3/envs/golden-week/lib/python3.7/site-packages/py2app/apptemplate/prebuilt/main-x86_64' -> '/Users/username/PycharmProjects/google-search/dist/gui.app/Contents/MacOS/gui'
Done!

上記コマンドでは「-A」を指定しエイリアスモードで作成している。

エイリアスモードでは、ソースファイルなどをコピーしないためビルドするのに時間がかからない。

そもそもエイリアスモードって何だろう。

公式サイトによると、以下の通り。

エイリアスモード(-Aまたは–aliasオプション)は、ソースファイルとデータファイルをインプレースで使用するアプリケーションバンドルをビルドするようにpy2appに指示します。 スタンドアロンアプリケーションは作成されず、エイリアスモードで構築されたアプリケーションは他のマシンに移植できません。

つまり、エイリアスモードで作成されたアプリは、実行時にビルドしたマシンの中にあるファイルを参照するように作られる。

だから自分のマシン上でしか動かず、他のマシンに配布できない。

とりあえず、自分のパソコンで動けば良いのでエイリアスモードを指定する。

buildとdistと言う名前のフォルダが作成される。

Finderでフォルダ名を検索して移動する。

distの中にアプリがあるのでそれをクリックすると起動できるようになる。

これでうまくいけば良かったのだが、下記のエラーが発生した。

Pythonのランタイムが見つからないようだ。

Terminateを選択して終了する。

setup.pyを修正する

調べてみると結構このエラーで悩んでいる人が多いようだ。

以下のサイトにAnacondaでpy2appを使う方法が記載されている。

https://stackoverflow.com/questions/39379155/how-do-i-use-py2app-with-anaconda-python

いわゆる神質問である。

「Anaconda provides this DLL under a slightly different name:」

AnacondaではDLLを少し違った名前で提供している、だからsetup.pyを書き換えようね。と言うことだ。

setup.pyのOPTIONSを以下のようにした。

OPTIONS = {'argv_emulation': True,
           'plist': {
               'PyRuntimeLocations': [
                '@executable_path/../Frameworks/libpython3.7m.dylib',
                '/opt/anaconda3/lib/libpython3.7m.dylib'
               ]
           }}

4行目:相対パスで書かれているが、特にパスの変更はしなくて良い。

「libpython3.7m.dlib」のpythonのバージョンが違う場合は書き換える。

私はPython3.7を使って開発していたので3.7にしてある。

5行目:libpython3.7.dlibのある場所を絶対パスで記載する。

パスは人それぞれなので、自分のパソコンの環境でファイルの存在を確認する。

現時点でAnacondaをインストールした場合には、/opt/anaconda3/lib/の下にライブラリがあると思う。

念のためターミナルを起動してファイルの存在を確認した。

再度実行する。

(golden-week) MacBook-Pro:google-search $ rm -rf dist/ build/
(golden-week) MacBook-Pro:google-search $ python setup.py py2app -A

うまくいくとdist配下にアプリが生成されている。

ちなみに、ドラッグ&ドロップして下記のようにタスクバーに追加しておくといつでも起動できるようになる。

pythonコードを修正すると自動的にアプリの方も更新される。

だからコードを書き換えるたびにsetup.pyを実行する必要はない。

アイコンやアプリ名を設定してみる

デフォルトのアイコンだと味気がないので試しにアイコンを設定してみた。

アイコンを拾ってくるには以下のサイトが良い。

http://www.iconarchive.com/

setup.py

from setuptools import setup

APP = ['main.py']
APP_NAME = "RankChecker"
DATA_FILES = []

OPTIONS = {'argv_emulation': True,
           'iconfile': '/Users/hoge/PycharmProjects/google-search/icon/Jamespeng-Movie-Ranking.icns',
           'plist': {
            'CFBundleName': APP_NAME,
            'CFBundleDisplayName': APP_NAME,
            'CFBundleGetInfoString': "Making RankChecker",
            'CFBundleIdentifier': "",
            'CFBundleVersion': "0.1.0",
            'CFBundleShortVersionString': "0.1.0",
            'NSHumanReadableCopyright': "Copyright © 2020, Nokkun, All Rights Reserved",
            'PyRuntimeLocations': [
                '@executable_path/../Frameworks/libpython3.7m.dylib',
                '/opt/anaconda3/lib/libpython3.7m.dylib'
               ]
           }
           }

上記のように、バージョン情報、著作権情報などを記載してビルドすると以下のようなアプリができる。

DATA_FILESの設定には注意が必要

アプリ内で表示する画像ファイルやデータを設定する場合には注意が必要だ。

ディレクトリを作ってその中に画像ファイルを入れるとうまく動かない。

例えば、以下のようにDATA_FILESにiconディレクトリと.pngファイルを設定する。

from setuptools import setup

APP = ['main.py']
APP_NAME = "RankChecker"
DATA_FILES = ['icon/ranking-icon.png']

OPTIONS = {'argv_emulation': True,
           'iconfile': 'icon/Jamespeng-Movie-Ranking.icns',
           'plist': {
            'CFBundleName': APP_NAME,
            'CFBundleDisplayName': APP_NAME,
            'CFBundleGetInfoString': "Making RankChecker",
            'CFBundleIdentifier': "",
            'CFBundleVersion': "0.1.0",
            'CFBundleShortVersionString': "0.1.0",
            'NSHumanReadableCopyright': "Copyright © 2020, Nokkun, All Rights Reserved",
            'PyRuntimeLocations': [
                '@executable_path/../Frameworks/libpython3.7m.dylib',
                '/opt/anaconda3/lib/libpython3.7m.dylib'
               ]
           }
           }

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

なぜかそのまま実行すると以下のエラーが発生する。

Open ConsoleをしてもPythonのエラーが出てこないため原因が不明だったが、以下のサイトに解決方法が書かれていた。

https://y-naito.ddo.jp/index.php?categ=top&year=2020&month=5&id=1477186158#restable

どうやらpy2appでは「dist➡︎RankChecker.app➡︎Contents➡︎Resources」の中にコピー元のディレクトリが作成されない不具合がある。

(ソフトのバグだなこれ。)

だから手動でiconディレクトリを生成する必要がある。

iconディレクトリを手動で作りその中にpng画像を移す必要がある。

ディレクトリ内にあるファイルを指定する時には気をつけよう。

 

画像ファイル以外も同様だ。

私の作っているアプリではアプリ内でpklファイルを作成する処理がある。

そのファイルの保存先が/data/hoge.pklになっているとデータ書き込みが行われない。

なぜならpy2appで生成されたアプリ内ではpklファイルを書き込むためのdataディレクトリが存在しないからだ。

同様にdataディレクトリを手動で作る必要がある。

ABOUTこの記事をかいた人

個人アプリ開発者。Python、Swift、Unityのことを発信します。月間2.5万PVブログ運営。 Twitter:@yamagablog