Bundling macOS Applications with Platypus

So I know that Apple is pushing all app developers to the App Store, but I’ve recently been playing around with Platypus for the Connected Learning Initiative project that I’m working on. I was curious how to bundle together a macOS application, but didn’t want to go through XCode or the Apple Developer Program.

Platypus is pretty nifty because it organizes all of the files and scripts for you, into the appropriate .app folder structure. There were a couple of things that I couldn’t find in the documentation, however, that I wound up having to figure out:

Our bundle actually consists of running two executables on program launch (one that acts as an assessment engine, another as an ePub reader). However, you can only launch one script when you open the app. I wound up creating an AppleScript to open up two instances of Terminal and running one executable per instance. And apparently spaces in Terminal have to be double-forward-slash-escaped instead of single (i.e. \\ instead of the typical command line \):

#!/bin/bash
EPUB_READER_SCRIPT=`pwd`/epub_reader_executable
EPUB_READER_SCRIPT=${EPUB_READER_SCRIPT// /\\\\ }
echo Running ePub reader from $EPUB_READER_SCRIPT
osascript -e "tell app \"Terminal\" to do script \"$EPUB_READER_SCRIPT\""
sleep 3

ASSESSMENT_ENGINE_SCRIPT=`pwd`/assessment_engine_executable
ASSESSMENT_ENGINE_SCRIPT=${ASSESSMENT_ENGINE_SCRIPT// /\\\\ }
echo Running assessment engine from $ASSESSMENT_ENGINE_SCRIPT
osascript -e "tell app \"Terminal\" to do script \"$ASSESSMENT_ENGINE_SCRIPT\""
sleep 3

/usr/bin/open -a "/Applications/Google Chrome.app" 'https://localhost:8888/'

One other challenge was finding bundled data files. I was including static files, ePubs, and JSON data files in the application bundle, since it is meant to be a standalone LMS / learning application, and I couldn’t figure out why my scripts couldn’t find the data. The Platypus documents indicate scripts in the app bundle can reference other files in the bundle relative to themselves. The directory structure inside of /Content/Resources was:

main_script.py
database.sqlite3
data_files/
    foo.json

However, because I was launching new Terminal instances, the Python scripts inside of your .app bundle had the current working directory set to the user’s home directory (Terminal default). For example, in my main_script.py file, os.getcwd() would return the user’s home directory (/Users/) instead of the app’s /Content/Resources directory.

I wound up having to do something like this in my main_script.py:

import os

ABS_PATH = os.path.dirname(os.path.abspath(__file__))
os.chdir(ABS_PATH)

in order to find and read files relative to my main script.