Skip to content

Instantly share code, notes, and snippets.

@pudquick
Last active August 29, 2015 14:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pudquick/8e8e09b8c1f5ceb5020b to your computer and use it in GitHub Desktop.
Save pudquick/8e8e09b8c1f5ceb5020b to your computer and use it in GitHub Desktop.
import sys, os, os.path, tempfile, plistlib, shutil
class TempApp(object):
def __init__(self, infoPlist_dict, app_path=None, bundle_name='TempApp', cleanup=True, app_icon=None):
# infoPlist_dict: A dict containing key values that should be set/overridden
# vs. the normal Python.app keys.
# app_path: The path to where your app should go. Example: '/usr/local/myOrgStuff'
# This directory needs to pre-exist. If app_path is left at None,
# a temporary directory will be created and used and the value of
# cleanup will be forced to True
# bundle_name: The name of your .app. This tends to be what shows in the Dock.
# Spaces in the name are ok, but keep it short.
# cleanup: If app_path is provided, cleanup set to False will leave the .app
# bundle behind rather than removing it on object destruction.
# app_icon: Set to the path of a .icns file if you wish to have a custom app icon
#
# Setup our defaults
super(type(self), self).__init__()
self.path = None
self.cleanup_parent = False
self.cleanup = cleanup
self.returncode = 0
# First we look up which python we're running with so we know which Python.app to clone
# ... We'll just cheat and use the path of 'os' which we already imported.
base_python = os.__file__.split(os.path.join('lib', 'python2'),1)[0]
python_app = os.path.join(base_python, 'Resources', 'Python.app')
app_name = '%s.app' % (os.path.basename(bundle_name))
# Now we setup where we want the new Python.app clone to go
if app_path is None:
# Dynamically generate a path and force the value of cleanup
self.cleanup = True
# Also need to cleanup the temp directory we made
self.cleanup_parent = True
app_path = tempfile.mkdtemp()
else:
# Verify the parent path exists
# Trim trailing slashes
tmp_path = os.path.normpath(app_path)
if not os.path.exists(tmp_path):
raise Exception('app_path supplied "%s" does not exist' % app_path)
elif not os.path.isdir(tmp_path):
raise Exception('app_path supplied "%s" does not appear to be a directory' % app_path)
app_path = tmp_path
if app_icon is not None:
if not os.path.exists(app_icon):
raise Exception('app_icon supplied "%s" does not exist' % app_icon)
elif not os.path.isfile(app_icon):
raise Exception('app_icon supplied "%s" does not appear to be a file' % app_icon)
self.path = os.path.join(app_path, app_name)
# Make the bundle directory
os.mkdir(self.path)
os.makedirs(os.path.join(self.path, 'Contents', 'MacOS'))
# Set up symlink contents
os.symlink(os.path.join(python_app, 'Contents', 'MacOS', 'Python'), os.path.join(self.path, 'Contents', 'MacOS', 'Python'))
os.symlink(os.path.join(python_app, 'Contents', 'PkgInfo'), os.path.join(self.path, 'Contents', 'PkgInfo'))
if app_icon is not None:
# We create a custom Resources folder and copy the .icns file to the default 'PythonInterpreter.icns' inside
os.makedirs(os.path.join(self.path, 'Contents', 'Resources'))
shutil.copyfile(app_icon, os.path.join(self.path, 'Contents', 'Resources', 'PythonInterpreter.icns'))
else:
# No app_icon provided, just use the default resources
os.symlink(os.path.join(python_app, 'Contents', 'Resources'), os.path.join(self.path, 'Contents', 'Resources'))
os.symlink(os.path.join(python_app, 'Contents', 'version.plist'), os.path.join(self.path, 'Contents', 'version.plist'))
# Grab the contents of the existing Info.plist ... yes, using plistlib - this Info.plist is so far only XML ...
original_infoPlist = plistlib.readPlist(os.path.join(python_app, 'Contents', 'Info.plist'))
# Make our changes from infoPlist_dict
original_infoPlist.update(infoPlist_dict)
# Write the contents back to the new location
plistlib.writePlist(original_infoPlist, os.path.join(self.path, 'Contents', 'Info.plist'))
def cleanup_app(self):
# Kill the process if it's still running
if self.cleanup:
# Delete the .app bundle, best effort
try:
shutil.rmtree(self.path, True)
except:
pass
if self.cleanup_parent:
# This was an auto-generated directory, remove it as well
try:
shutil.rmtree(os.path.dirname(self.path), True)
except:
pass
def __del__(self):
self.cleanup_app()
# How to use it, assuming it's saved to a file named tempapp.py
from tempapp import *
import subprocess
infoPlist_overrides = {'CFBundleName': 'Menu Name'}
myApp = TempApp(infoPlist_overrides, bundle_name='Dock Name', app_icon='/Users/mike/Desktop/tinker.icns')
output = subprocess.check_output([myApp.path + '/Contents/MacOS/Python', path_to_py_file])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment