Embedding Python in a MacOS Application

Embedding a python application in a MacOS cocoa application via pyinstaller

Mosaic Shapes for macOS uses Python

Mosaic Shapes for macOS core is written in Python. The UI is written in Objective-C and it communicates with the nested Python application via NSTask and NSPipes.

It took a considerable amount of time, cursing, late nights to figure out how to nest a Python application in a Cocoa application due to resultant sandbox constraints and code signing issues. I couldn’t find a comprehensive solution online to solve my issues. I hope this walkthrough helps someone in the future as they sift through Google search results.

After generating your .app from pyinstaller and nesting it in XCode, calling your python app via NSTask is straightforward. However, once you’re ready to submit your application to the store, you’ll need to properly set the application’s sandbox entitlements and codesign the application. This is where the nightmare usually begins.

Download python 2.7.13 from here:

https://www.python.org/downloads/

Download pyinstaller:

http://www.pyinstaller.org/

The default shipped python version with MacOS and Hombrew/Cellar version of Python will have codesigning issues once you’ve sandbox your application. The error manifests after nesting the python application, signing it with the proper sandbox entitlements and then attempting to call your python application. You’ll receive a crash that gives an archaic codesigning error referencing Python. (StackOverflow).

Make sure you download the Python installer directly from python.org. In my case, I am using 2.7.13 (I’ve yet to test with Python 3)

After downloading and installing, double check the default Python:

guppy:~ dean$ which python

/Library/Frameworks/Python.framework/Versions/2.7/bin/python

guppy:~ dean$ python -version

Python 2.7.13

If you’ve installed it in such a way that it isn’t your default python interpreter, you can explicitly specify the python version to use in your python virtualenv

mkvirtualenv -python=/Library/Frameworks/Python.framework/Versions/2.7/bin/python

Now, package your python application (replace with your parameters):

python pyinstaller.py run.py -icon=icon-windowed.icns -clean -windowed -onefile -osx-bundle-identifier "com.mosaic.mosaicshapes"

Note: be sure you have an icon file ready, otherwise pyinstaller will default to their own icon. Additionally, replace the bundle id with your parent application’s bundle id.

Embed the resultant application in XCode Resources folder (copy and paste / drag & drop the resultant .app)

pyinstaller will generate an Info.plist file. Navigate to that file in XCode and change the Executable File property. Remove “MacOS/” and just leave the executable name. If you don’t, iTunesConnect will complain of codesigning issues during the verification page when you submit your application.

Archive your application in XCode by going to Product -> Archive. Export the Archive without re-signing the application. This is the last option: Export as a macOS App.

A directory will be created with your application.

Soon we can sign and package our application, but before that we need to configure the sandbox entitlements.

The parent entitlement:

This file is generated when you enable sandbox on XCode. Your parent’s entitlements list may differ from mine depending on the requirements of your application. Apple has pretty thorough documentation to get you through some initial violation errors.

I’ve copied this file to my codesign directory so I can reference it more easily in the the code signing script a little below. Here is the raw entitlements file for our parent that we will use to sign. In my case, I allow users to specify photos to convert to mosaics, thus I only have one property to specify for my sandbox.



<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "

<plist version="1.0">

<dict>

<key>com.apple.security.app-sandbox</key>

<true/>

<key>com.apple.security.files.user-selected.read-write</key>

<true/>

</dict>

</plist> guppy:~ dean$ cat ~/Desktop/p.entitlements version="1.0" encoding="UTF-8"? ttp://www.apple.com/DTDs/PropertyList-1.0.dtd" class="eb ic ke kf kg kh" rel="noopener nofollow">http://www.apple.com/DTDs/PropertyList-1.0.dtd "> com.apple.security.app-sandbox com.apple.security.files.user-selected.read-write

The child entitlements is what we will use to sign our python application with.

The child entitlements can ONLY contain two properties to specify the sandbox inheritance. From Apple documentation “To enable sandbox inheritance, a child target must use exactly two App Sandbox entitlement keys: com.apple.security.app-sandbox and com.apple.security.inherit . If you specify any other App Sandbox entitlement, the system aborts the child process. You can, however, confer other capabilities to a child process by way of iCloud and notification entitlements.”



<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "

<plist version="1.0">

<dict>

<key>com.apple.security.app-sandbox</key>

<true/>

<key>com.apple.security.inherit</key>

<true/>

</dict>

</plist> guppy:~ dean$ cat ~/Desktop/c.entitlements version="1.0" encoding="UTF-8"? ttp://www.apple.com/DTDs/PropertyList-1.0.dtd" class="eb ic ke kf kg kh" rel="noopener nofollow">http://www.apple.com/DTDs/PropertyList-1.0.dtd "> com.apple.security.app-sandbox com.apple.security.inherit

Now it’s time to sign and package your application to submit to the app store.

I use the following bash codesign script to sign and package my application. Be sure to alter the parameters to your needs:

“3rd Party Mac Developer Application” is used for Apple store submissions.

“Developer ID Application” is used for distributing your application outside of the Apple store. I like to sign with the “Developer ID Application” first to ensure everything installs properly prior to submitting to the store.

A .pkg file will be created from the current working directory. Now upload this package via Application Loader! If you upload the binary via XCode, it will resign the binaries which will override and exclude the entitlements we have configured for our Python application. Therefore, use Application Loader!

Quite the process right?