我希望最终用户可以访问我的Python代码,这样他们就可以看到并可能改进代码,因此我不感兴趣隐藏代码的选项。我发现我可以使用不同的标准Python解释器将wx应用程序(尚未尝试使用Qt)包装成捆绑包,并在安装它们时(例如,作为conda软件包安装过程的一部分)以指定的应用程序名称运行。以下是一个简短的脚本,使用执行脚本的Python创建具有指定名称的.app包。使用创建的软链接(请参见下面的pyAlias)运行脚本,如下所示:
./MyApplication.app/Contents/MacOS/MyApplication /path/MyApplication.py
这份工作做得如何。
from __future__ import division, print_function
import os,sys
appName = 'MyApplication' # name of app
scriptdir = '.'
iconfile = os.path.join(scriptdir,'MyApplication.icns') # optional icon file
if __name__ == '__main__':
wrapApp = os.path.join(scriptdir,appName+'.app','Contents')
os.makedirs(wrapApp)
wrapPy = os.path.join(wrapApp,'MacOS')
os.makedirs(wrapPy)
fp = open(os.path.join(wrapApp,'PkgInfo'),'w')
fp.write('APPL????\n')
fp.close()
fp = open(os.path.join(wrapApp,'Info.plist'),'w')
fp.write('''<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">\n<plist version="0.9">\n<dict>\n <key>CFBundleIconFile</key>\n <string></string>\n <key>CFBundlePackageType</key>\n <string>APPL</string>\n <key>CFBundleGetInfoString</key>\n <string>Created in makeApp.py (Brian Toby/QMake</string>\n <key>CFBundleSignature</key>\n <string>????</string>\n <key>CFBundleExecutable</key>\n <string>{:}</string>\n <key>CFBundleIdentifier</key>\n <string>com.continuum.python</string>\n <key>NSPrincipalClass</key>\n <string>NSApplication</string>\n</dict>\n</plist>\n'''.format(appName))
fp.close()
pyAlias = os.path.join(wrapPy,appName)
pythonpath = os.path.realpath(sys.executable)
os.symlink(pythonpath,pyAlias)
if os.path.exists(iconfile):
shutil.copyfile(iconfile,oldicon)
print('Use',pyAlias,'to run wxPython scripts')
以下是一篇较旧的答案,我保留它以供历史参考。在我的应用程序中,我使用一种混合方法,在其中创建了一个拖放的AppleScript捆绑包(如下所示),在其中放置了一个Python的软链接(命名为与我的应用程序相匹配的名称)。
在Christopher Bruns的答案基础上,以及从“如何通过Python创建Mac应用程序包”的
脚本,这是一个Python脚本,用于为用户Python脚本创建一个bundle(应用程序),它将在菜单中显示应用程序名称而不是“Python”。为此,它尝试查找已打包的Python版本,并使用应用程序的名称创建符号链接。我用wxpython脚本测试了它,但对Qt也应该适用。
用户脚本是从其原始位置运行的,而不是将其放在应用程序中。如果您想将自己的脚本(以及Python)放入捆绑包中以进行重新分发,请参见
py2app。
#!/usr/bin/env python
'''This creates an app to launch a python script. The app is
created in the directory where python is called. A version of Python
is created via a softlink, named to match the app, which means that
the name of the app rather than Python shows up as the name in the
menu bar, etc, but this requires locating an app version of Python
(expected name .../Resources/Python.app/Contents/MacOS/Python in
directory tree of calling python interpreter).
Run this script with one or two arguments:
<python script>
<project name>
The script path may be specified relative to the current path or given
an absolute path, but will be accessed via an absolute path. If the
project name is not specified, it will be taken from the root name of
the script.
'''
import sys, os, os.path, stat
def Usage():
print("\n\tUsage: python "+sys.argv[0]+" <python script> [<project name>]\n")
sys.exit()
version = "1.0.0"
bundleIdentifier = "org.test.test"
if not 2 <= len(sys.argv) <= 3:
Usage()
script = os.path.abspath(sys.argv[1])
if not os.path.exists(script):
print("\nFile "+script+" not found")
Usage()
if os.path.splitext(script)[1].lower() != '.py':
print("\nScript "+script+" does not have extension .py")
Usage()
if len(sys.argv) == 3:
project = sys.argv[2]
else:
project = os.path.splitext(os.path.split(script)[1])[0]
# find the python application; must be an OS X app
pythonpath,top = os.path.split(os.path.realpath(sys.executable))
while top:
if 'Resources' in pythonpath:
pass
elif os.path.exists(os.path.join(pythonpath,'Resources')):
break
pythonpath,top = os.path.split(pythonpath)
else:
print("\nSorry, failed to find a Resources directory associated with "+str(sys.executable))
sys.exit()
pythonapp = os.path.join(pythonpath,'Resources','Python.app','Contents','MacOS','Python')
if not os.path.exists(pythonapp):
print("\nSorry, failed to find a Python app in "+str(pythonapp))
sys.exit()
apppath = os.path.abspath(os.path.join('.',project+".app"))
newpython = os.path.join(apppath,"Contents","MacOS",project)
projectversion = project + " " + version
if os.path.exists(apppath):
print("\nSorry, an app named "+project+" exists in this location ("+str(apppath)+")")
sys.exit()
os.makedirs(os.path.join(apppath,"Contents","MacOS"))
f = open(os.path.join(apppath,"Contents","Info.plist"), "w")
f.write('''<?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>CFBundleExecutable</key>
<string>main.sh</string>
<key>CFBundleGetInfoString</key>
<string>{:}</string>
<key>CFBundleIconFile</key>
<string>app.icns</string>
<key>CFBundleIdentifier</key>
<string>{:}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>{:}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>{:}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>{:}</string>
<key>NSAppleScriptEnabled</key>
<string>YES</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
'''.format(projectversion, bundleIdentifier, project, projectversion, version)
)
f.close()
# not sure what this file does
f = open(os.path.join(apppath,'Contents','PkgInfo'), "w")
f.write("APPL????")
f.close()
# create a link to the python app, but named to match the project
os.symlink(pythonapp,newpython)
# create a script that launches python with the requested app
shell = os.path.join(apppath,"Contents","MacOS","main.sh")
# create a short shell script
f = open(shell, "w")
f.write('#!/bin/sh\nexec "'+newpython+'" "'+script+'"\n')
f.close()
os.chmod(shell, os.stat(shell).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
argv[0]
。虽然我对Mac一无所知,但我已经没有更多的想法了。 - Falmarri