cx_Freeze で Macアプリを作成する(1)
前回は Windows EXEファイルを作ったが、今回は Macアプリ作成を試してみたところ、これも色々とハマった
前回は Windowsの EXEファイルを作成するために cx_Freeze を使用したが、今度は Macのアプリを作成するために使用してみたところ、これにも色々とハマリポイントがあった。そこで、この件も備忘録として書いておこうと思う。例によって試行錯誤した様子も書き残しているので、長文になるが御容赦願いたい。
結論から言ってしまえば、Pythonスクリプトを Macアプリにするのであれば py2app の方が使いやすいと思う。cx_Freeze は必要なファイルが取り込まれなかったり、かと思えば使われない多くのパッケージを取り込み、作成されたアプリ実行環境にも多くのファイルがあって整理されていない印象を受ける。
1. Hello World
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
def main():
print('Hello World!', __name__)
main()
お馴染の Hello Worldプログラムを、今回は"Sample.py" というファイル名で用意。あえて大文字小文字混在のファイル名にしたのは、先ず Windows の際にハマった__name__ 特殊属性の値を確認したいためだ。Windows と同じであれば、__name__ は小文字化されたコマンド、アプリケーション名が__main__ の前に付いた文字列'sample__main__' になるはずだ。
2. setupスクリプト
たぶんこれが一番シンプルな cx_Freeze の setupスクリプトだろう。Macアプリ作成専用なので、Windows用の Win32GUIベースにするif文も省いてしまった。
また、cx_Freeze.Executable() 関数にはtargetName 引数(作成する実行可能モジュール(コマンド、アプリケーション)名)も与えていない。この場合、作成される実行可能モジュール(コマンド、アプリケーション)名はscript 引数に指定されたファイル名から拡張子".py" を除いた名前(即ち"Sample" )になるようだ。
# -*- coding: utf-8 -*-
import sys
from cx_Freeze import setup, Executable
setup(name='Sample',
executables=[Executable(script='Sample.py', base=None)])
3. buildして実行してみる
$ python3 setup.py build
running build
running build_exe
creating directory build/exe.macosx-10.6-intel-3.5
〜略〜
|
ビルドが成功すると"build" ディレクトリ下に"exe.macosx-10.6-intel-3.5" などと言った名前のディレクトリが作られ、そこに実行可能モジュール(コマンド)とその実行環境が作成される。問題なくビルドできた様子なので、実行してみる。
$ ./build/exe.macosx-10.6-intel-3.5/Hello
Hello World! Sample__main__
|
この結果を見ると、Windows の EXEファイルにした時には__name__ が「'小文字化されたEXEファイル名' + '__main__' 」だったのに対し、Mac の場合には「'大文字小文字が区別されたコマンド、アプリ名' + '__main__' 」になるようだ。そのため、以前 Windowsの際に実施した以下の対処では Macの場合にmain() 関数が呼び出されなくなってしまう。
if __name__ == '__main__' or __name__ == 'sample__main__':
main()
そこで、このif文を以下のように修正する。
if __name__ in ('__main__', 'sample__main__', 'Sample__main__'):
main()
これで、Pythonスクリプトとして実行される場合と、cx_Freeze で作成した Windows EXEファイル、Macアプリの場合にも対応できるようになった。
4. tkinterを使ったGUIプログラムをMacアプリ化する
今度は tkinter を使った GUIプログラムだ。再び cx_Freeze のときに使用した簡単なサンプルプログラム"SimpleTkApp.py" を使って試してみよう。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
from tkinter import Tk, Label, Button, BOTTOM
except ImportError:
from Tkinter import Tk, Label, Button, BOTTOM
def main():
root = Tk()
root.title('Button')
Label(text='I am a button').pack(pady=15)
Button(text='Button').pack(side=BOTTOM)
root.mainloop()
if __name__ in ('__main__', 'simpletkapp__main__', 'SimpleTkApp__main__'):
main()
作成するMacアプリ名を"SimpleTkApp" とする想定で、main() 呼び出しのif文の条件判定を修正してある。これを Macアプリ化する setupファイルは以下のようになる。
# -*- coding: utf-8 -*-
import sys
from cx_Freeze import setup, Executable
application = 'SimpleTkApp'
includes = []
include_files = []
packages = ['os', 'sys', 'tkinter']
excludes = []
build_exe_options = {
'includes': includes,
'include_files': include_files,
'packages': packages,
'excludes': excludes
}
exe = [
Executable(script='SimpleTkApp.py', base=None, targetName=application)
]
setup(name=application,
version='1.0.0',
description='Simple tkinter application',
options={'build_exe': build_exe_options},
executables=exe)
先ずはコマンドとして buildして実行してみよう。
問題なく実行できるようだ。では、これを Macアプリにしてみよう。Macアプリを作るにはbuild コマンドの代わりにbdist_mac コマンドを指定するだけだ。
$ rm -rf build
$ python3 setup.py bdist_mac
running bdist_mac
running build
running build_exe
creating directory build/exe.macosx-10.4-ppc-3.5
〜略〜
$ ls -l ./build/
drwxr-xr-x 3 nai admin 102 12 18 10:31 SimpleTkApp-1.0.0.app
drwxr-xr-x 8 nai admin 272 12 18 10:31 exe.macosx-10.6-intel-3.5
|
一旦 buildを行った後、Macのアプリ形式にパックしなおすようだ。Finderでも"build" フォルダ配下に"SimpleTkApp-1.0.0" というアプリケーションが見える。
だが、このアプリケーションをダブルクリックして起動させてみても、先のようにボタンのあるウィンドウは表示されず終了してしまう。
そこで、アプリケーション内にあるSimpleTkApp コマンドを直接実行してみる。
$ ./build/SimpleTkApp-1.0.0.app/Contents/MacOS/SimpleTkApp
Traceback (most recent call last):
File "<frozen importlib._bootstrap>", line 969, in _find_and_load
File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 664, in _load_unlocked
File "<frozen importlib._bootstrap>", line 634, in _load_backward_compatible
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/cx_Freeze/initscripts/__startup__.py", line 12, in <module>
__import__(name + "__init__")
File "<frozen importlib._bootstrap>", line 969, in _find_and_load
File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 664, in _load_unlocked
File "<frozen importlib._bootstrap>", line 634, in _load_backward_compatible
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/cx_Freeze/initscripts/Console.py", line 21, in <module>
scriptModule = __import__(moduleName)
File "<frozen importlib._bootstrap>", line 969, in _find_and_load
File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 664, in _load_unlocked
File "<frozen importlib._bootstrap>", line 634, in _load_backward_compatible
File "SimpleTkApp.py", line 19, in <module>
main()
File "SimpleTkApp.py", line 11, in main
root = Tk()
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/tkinter/__init__.py", line 1868, in __init__
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
_tkinter.TclError: Can't find a usable tk.tcl in the following directories:
/Users/nai/work/sample/build/SimpleTkApp-1.0.0.app/Contents/MacOS/tk
/Library/Frameworks/Tcl.framework/Versions/8.5/Resources/Scripts/tk8.5
/Library/Frameworks/Tcl.framework/Versions/8.5/Resources/Scripts/tk8.5/Resources/Scripts
/Library/Frameworks/Tcl.framework/Versions/8.5/Resources/tk8.5
/Library/Frameworks/Tcl.framework/Versions/8.5/Resources/tk8.5/Resources/Scripts
/Users/nai/work/sample/build/SimpleTkApp-1.0.0.app/Contents/lib/tk8.5
/Users/nai/work/sample/build/SimpleTkApp-1.0.0.app/Contents/lib/tk8.5/Resources/Scripts
~/Library/Tcl/tk8.5 ~/Library/Tcl/tk8.5/Resources/Scripts /Library/Tcl/tk8.5
/Library/Tcl/tk8.5/Resources/Scripts /System/Library/Tcl/tk8.5 /System/Library/Tcl/tk8.5/Resources/Scripts
~/Library/Frameworks/tk8.5
~/Library/Frameworks/tk8.5/Resources/Scripts /Library/Frameworks/tk8.5
/Library/Frameworks/tk8.5/Resources/Scripts /System/Library/Frameworks/tk8.5
/System/Library/Frameworks/tk8.5/Resources/Scripts
/Library/Tcl/teapot/package/macosx10.5-i386-x86_64/lib/tk8.5
/Library/Tcl/teapot/package/macosx10.5-i386-x86_64/lib/tk8.5/Resources/Scripts
/Library/Tcl/teapot/package/tcl/lib/tk8.5 /Library/Tcl/teapot/package/tcl/lib/tk8.5/Resources/Scripts
/Users/nai/work/sample/build/SimpleTkApp-1.0.0.app/lib/tk8.5
/Users/nai/work/sample/build/SimpleTkApp-1.0.0.app/Contents/library
This probably means that tk wasn't installed properly.
$
|
いきなりTk() で失敗していた。原因は"Can't find a usable tk.tcl" ("tk.tcl" ファイルが見つからない)ことのようだ。出力された検索先ディレクトリのリストを見ると、作成したアプリケーション内だけでなく、MacOSシステムの方も探している様子だ。しかし、"tk.tcl" は Tkのファイルだろう?検索先ディレクトリに Tkフレームワーク("/Library/Frameworks/Tk.framework" 等)を含んでいないのはどういう訳だろう?MacOSシステムの Tcl/Tk インストール場所は Tclフレームワークに纏められているという事だろうか?
実際の macOS システムを調べてみよう。macOS 10.12.1 Sierra の場合、"/System/Library/Frameworks" 配下に、
- Tcl.framework
- Tk.framework
に分けて置かれていた。(バージョンは Ver.8.5 のようだ。)ActiveTcl も同様に"/Library/Frameworks" 配下に分けて置かれている。優先的に使用されるのは"/Library/Frameworks" の方だ。そして"tk.tcl" ファイルは"Tk.framework/Resources/Scripts/tk.tcl" にある。
どうも検索先ディレクトリの設定(リスト)に問題があるように思えるのだが、これを何処で設定しているのかが分らない。また Macのアプリ形式にしたときだけ検索先ディレクトリがおかしくなるというのも解せない。実行時に "TCL_LIBRARY", "TK_LIBRARY" 環境変数を設定したり setupファイルに記述してもみたが、症状は全く変わらなかった。
検索先のディレクトリパスを見ると、末端が"tk8.5" または"tk8.5/Resources/Scripts" というパス名になっている物が多い。それなら、"tk.tcl" ファイルがあるディレクトリの内容を取り込んでしまえば良いのではないかな?ファイル、フォルダの取り込みは setupスクリプトの'include_files' に指定すれば良い。色々と試行錯誤した結果、setupスクリプトはこのようになった。
# -*- coding: utf-8 -*-
import sys
from cx_Freeze import setup, Executable
application = 'SimpleTkApp'
includes = []
include_files = [
('/Library/Frameworks/Tcl.framework/Resources/Scripts', 'tcl8.5'),
('/Library/Frameworks/Tk.framework/Resources/Scripts', 'tk8.5')
]
packages = ['os', 'sys', 'tkinter']
excludes = []
build_exe_options = {
'includes': includes,
'include_files': include_files,
'packages': packages,
'excludes': excludes
}
exe = [
Executable(script='SimpleTkApp.py', base=None, targetName=application)
]
setup(name=application,
version='1.0.0',
description='Simple tkinter application',
options={'build_exe': build_exe_options},
executables=exe)
これで Macアプリを作成し Finderから実行したところ、やっと正常に動作するようになった。
見ての通り"Tk.framework/Resources/Scripts" だけでなく、"Tcl.framework/Resources/Scripts" も取り込んでいる。両方とも取り込まないと正常に動作しないようだ。Windows では Tcl/Tk の DLL を取り込む必要があったところが、Mac では Tcl/Tk スクリプトを取り込むようになった格好だ。
また、Tcl/Tk スクリプトの複写元には"/Library/Frameworks" (ActiveTcl)を指定しているので、ActiveTclをインストールしていない場合は"/System/Library/Frameworks" を複写元にする必要があるだろう。こうして出来上がった Macアプリのサイズは、実に35MB以上にもなっていた。(おそらく使われないパッケージも取り込んでいるのだろう。)
|