cx_Freeze で Macアプリを作成する(3)
QuickIR を cx_Freeze でアプリ化してみよう
6. QuickIR用の setupファイル
cx_Freeze で QuickIR を Macアプリ化する setupファイル("cxfreeze_mac_setup.py" )は以下のようになる。
# -*- coding: utf-8 -*-
"""
This is a cx_Freeze setup.py script
Usage:
python cxFreeze_setup.py build
"""
import sys
import os
from cx_Freeze import setup, Executable
# Dependencies are automatically detected, but it might need fine tuning.
APPLICATION = 'QuickIR'
VERSION = '1.0.9'
if sys.platform == 'win32':
python_dir = r'C:¥Python35' # Python installed directory
include_files = [
python_dir + r'¥DLLs¥tcl86t.dll',
python_dir + r'¥DLLs¥tk86t.dll',
'images'
]
elif sys.platform == 'darwin':
include_files = [
('/Library/Frameworks/Tcl.framework/Resources/Scripts', 'tcl8.5'),
('/Library/Frameworks/Tk.framework/Resources/Scripts', 'tk8.5'),
'images'
]
else:
include_files = []
includes = ['irkit', 'resolve_irkit']
pakages = ['os', 'sys', 'tkinter', 'pybonjour']
excludes = ['logging', 'distutils',
'multiprocessing', 'pydoc_data',
'test', 'unittest',
'xml', 'xmlrpc']
build_exe_options = {'includes': includes,
'include_files': include_files,
'packages': pakages,
'excludes': excludes,
'optimize': 2}
bdist_mac_options = {'bundle_name': APPLICATION,
'iconfile': 'app.icns',
'custom_info_plist': 'Info.plist',
'include_frameworks': []}
base = None
if sys.platform == 'win32':
base = 'Win32GUI'
os.environ['TCL_LIBRARY'] = python_dir + r'¥tcl¥tcl8.6'
os.environ['TK_LIBRARY'] = python_dir + r'¥tcl¥tk8.6'
exe = [
Executable(script='QuickIR.py',
base=base,
targetName='QuickIR.exe',
icon='app.ico')
]
else:
exe = [
Executable(script='QuickIR.py',
base=base,
targetName=APPLICATION)
]
setup(name=APPLICATION,
version=VERSION,
description='QuickIR IRKit controller',
options={
'build_exe': build_exe_options,
'bdist_mac': bdist_mac_options
},
executables=exe)
Windowsと共用できるよう、以前 Windows用に作成した"cxfreeze_setup.py" に Mac用の設定を書き加え、Windows と Macで異なる部分はsys.platform によって切り分けるようにしている。cx_Freeze はマルチプラットフォームに対応しているのが特徴とは言うものの、setupファイルはプラットフォームによる違いが多いことが分る。
また、"Info.plist" は以下のようにした。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>QuickIR</string>
<key>CFBundleExecutable</key>
<string>QuickIR</string>
<key>CFBundleName</key>
<string>QuickIR</string>
<key>CFBundleGetInfoString</key>
<string>QuickIR IRKit controller 1.0.9</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleIdentifier</key>
<string>jp.ddo.y-naito.QuickIR</string>
<key>CFBundleShortVersionString</key>
<string>1.0.9</string>
<key>CFBundleVersion</key>
<string>1.0.9</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (C) 2015-2016, N@i.jp, All Rights Reserved</string>
</dict>
</plist>
7. "QuickIR.py"プログラムの修正
setupファイルはできたが、まだ"QuickIR.py" スクリプト本体の修正が残っている。先ずはmain() 関数を呼び出すif文だ。アプリ名はQuickIR なので、
if __name__ in ('__main__', 'quickir__main__', 'QuickIR__main__'):
main()
と修正する。これで cx_Freeze でアプリ化してもmain() 関数が呼び出されるようになった。
実は修正はまだある。これで作成したアプリをFinderから実行すると、一旦起動した後にすぐに終了してしまうのだ。調査にかなり苦労したのだが、この原因はinclude_files でアプリ内に取り込んだ"images" フォルダ配下の画像ファイルを読み込めない事だった。問題のプログラムは以下の部分だ。
def createToolbar(self, toolbar):
"""make toolbar"""
self.img_folder_add = PhotoImage(file='images/folder_add.gif')
self.img_folder_ren = PhotoImage(file='images/folder_rename.gif')
self.img_folder_del = PhotoImage(file='images/folder_delete.gif')
self.img_file_add = PhotoImage(file='images/file_add.gif')
self.img_file_post = PhotoImage(file='images/file_post.gif')
self.img_file_ren = PhotoImage(file='images/file_rename.gif')
self.img_file_del = PhotoImage(file='images/file_delete.gif')
"QuickIR.py" スクリプトと同じ場所に"images" フォルダがあり、その配下に画像ファイルが置かれている事を想定しており、例外の検出もしていない。でも、ちょっと待って。include_files で"images" フォルダごとアプリ内に取り込んでいるし、実際アプリ内に取り込まれていることも確認できる。
$ ls ./build/QuickIR.app/Contents/MacOS/images
IRKit.gif file_delete.gif file_rename.gif folder_delete.gif
file_add.gif file_post.gif folder_add.gif folder_rename.gif
|
Windows でEXEファイルにした時には何の問題もなかった。何故これで画像ファイルの読み込みに失敗するのだろう。
色々調べたところ、cx_Freeze で作成した Macのアプリの場合、Finderから実行するとカレントディレクトリは「/(ルート)」になるようだ。(py2app ではカレントディレクトリは"QuickIR.app/Contents/Resources" になっていた。)そのためアプリ内にある画像ファイルに辿り着けず読み込みが失敗していたのだ。ネットで検索したらドンピシャの事例が見つかった。これによると、cx_Freeze によって固められた(frozen)場合はos.path.dirname(sys.executable) を前に付けてフルパス化する必要があるということだ。固められていない場合は、os.path.dirname(__file__) でスクリプトの場所のディレクトリ名を前に付けて、同じくフルパス化する。これで cx_Freeze のアプリ実行の場合とスクリプトとして実行された場合のどちらでもファイルに辿り着けるようになる。
つまり cx_Freeze で固めるなら、include_files でアプリ内に取り込むファイルを相対パス指定でアクセスしてはいけないと言うことになる。
と言うことで、事例にあった関数を追加して画像ファイルを読み込む処理を修正する。(関数名は少し変えている。)
def find_image_file(path):
if getattr(sys, 'frozen', False):
# The application is frozen
datadir = os.path.dirname(sys.executable)
else:
# The application is not frozen
# Change this bit to match where you store your data files:
datadir = os.path.dirname(__file__)
return os.path.join(datadir, path)
〜略〜
def createToolbar(self, toolbar):
"""make toolbar"""
self.img_folder_add = PhotoImage(file=find_image_file('images/folder_add.gif'))
self.img_folder_ren = PhotoImage(file=find_image_file('images/folder_rename.gif'))
self.img_folder_del = PhotoImage(file=find_image_file('images/folder_delete.gif'))
self.img_file_add = PhotoImage(file=find_image_file('images/file_add.gif'))
self.img_file_post = PhotoImage(file=find_image_file('images/file_post.gif'))
self.img_file_ren = PhotoImage(file=find_image_file('images/file_rename.gif'))
self.img_file_del = PhotoImage(file=find_image_file('images/file_delete.gif'))
上記以外の画像ファイルを読み込む処理も同様に修正した結果、ようやく正常に起動して動作するようになった。
このように cx_Freeze は py2app, py2exe に比べて色々と癖があり、使い勝手がいまいち良くない。py2app, py2exe が使えるのであれば、それらを使用した方が楽にアプリ化、EXEファイル化できるだろう。
|