I would rather end users have access to my Python code so they and see and maybe improve on the code, so I am not interested in options that hide the code. I have found that I can get wx applications (have not tried with Qt) to run with a specified application name using different standard Python interpreters by wrapping them into a bundle as I install them (for example, as part of the conda package install process). Here is short script that creates an .app bundle with a specified name using the Python executing the script. Running the script using the created soft link (see pyAlias, below) as in
./MyApplication.app/Contents/MacOS/MyApplication /path/MyApplication.py
does the job.
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')
Below is an older answer, which I am leaving for any historical value. In my application now, I use a hybrid between these where I create a drag-and-drop AppleScript bundle (as below) where I place a soft link to Python (named to match my app) as above.
Building on Christopher Bruns answer, as well as the script from "How to create Mac application bundle for Python script via Python", here is a Python script that creates a bundle (app) for an user Python script, which will show the app name rather than "Python" in the menus. To do this, it tries to locate a bundled version of Python, and symlinks to that with the name of the app. I tested it out with a wxpython script, but it should work for Qt as well.
The user script is run from its original location rather than placing it in the app. If you want to place your script(s) into a bundle (along with python) for redistribution, see py2app instead.
#!/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]
. I know next to nothing about macs though, so I'm all out of ideas. – Lyonnaise