Create MacOS App Bundle from Script
I’m going to show how to create the smallest possible MacOS app bundle we can: one that simply executes a Bash script. Then, I’m going to expand a bit on that and add an icon to it. I’ll also show how open a Terminal app window so we can see the script’s output in it.
A MacOS app bundle is a directory that is interpreted in a special way by
MacOS. It is an application name followed by the suffix .app
(e.g.
foo.app
). A minimal app bundle would look like:
$ mkdir foo.app
$ cat > foo.app/foo
#!/bin/bash -i
zenity --calendar
$ chmod u+x foo.app/foo
$ tree foo.app
foo.app
`-- foo
We create an app bundle foo.app
that contains an executable of the same
name: if you name your app bundle baz.app
, the executable should be named
baz
. You can now execute the bundle by typing open foo.app
in your
terminal, or double clicking on the bundle in Finder.
(Note that in order to display a Zenity calendar, I needed to make Bash
interactive. Leaving out the -i
flag would not display anything. I haven’t
figured out yet why that is the case, but do send me a note if you
know.)
If you want more control, add an icon, and so on, we need to get more elaborate.
Open a Terminal Window to Display the Output of a Shell Script
$ mkdir -p bar.app/Contents/{MacOS,Resources}
$ cat > bar.app/Contents/MacOS/wrapper
#!/bin/bash
script_path="$(dirname "$0")"/actual-script
open -a Terminal "$script_path"
$ cat > bar.app/Contents/MacOS/actual-script
#!/bin/bash
echo do something useful
$ chmod u+x bar.app/Contents/MacOS/{wrapper,actual-script}
Here, we created a wrapper script that will open the Terminal and execute the actual, useful script such that any output would be visible to the user.
(Important: make sure the names of the files and directories are spelled
correctly. For example, it’s Contents
(plural) and NOT Content
. Failing to
do this will make your life miserable because the app won’t work in Finder and
won’t display a useful error message. Or, when open
ing it from your terminal,
it will spit out weird errors like “executable is missing”.)
Then, add <name>.app/Contents/Info.plist
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>Bar</string>
<key>CFBundleExecutable</key>
<string>wrapper</string>
<key>CFBundleIdentifier</key>
<string>com.relentlesscoding.Bar</string>
<key>CFBundleVersion</key>
<string>1.2.3</string>
</dict>
</plist>
CFBundleExecutable
contains the name of the executable located in
<name>.app/Contents/MacOS
. Reminder: do not forget to chmod u+x <executable>
.
CFBundleIdentifier
should contain a unique identifier for your app. Fail to
make this unique, and your app will replace some other app in your Launcher.
For good measure, I added CFBundleVersion
and put the app version in there.
Incidentally, you can read all about these properties in Apple’s documentation.
Add an Icon
If you search online, you’ll find recommendations for application icons and
sizes. If you want to keep things simple, however, just download a 512x512 PNG
icon from the internet, name it whatever you want, and put it in
<name>.app/Contents/Resources
.
Then, modify <name>.app/Contents/Info.plist
to point to the icon. Here, I
added an icon bar.app/Contents/Resources/bar.png
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- ... snipped other tags... -->
<key>CFBundleIconFile</key>
<string>bar.png</string>
</dict>
</plist>
Tips
-
You can check the validity of the
Info.plist
file by runningplutil path/to/Info.plist
:$ plutil bar.app/Contents/Info.plist bar.app/Contents/Info.plist: OK
-
MacOS won’t immediately pick up changes you make to the app bundle. You can force its hand by
touch
ing the bundle:$ touch bar.app/
-
All of this was tested on MacOS Sanoma.