How to Script macOS Command Line Tool Notarization and Packaging for Distribution

A few posts back, I talked about the script I use to package macOS apps that I distribute outside of the Mac App Store. That script is designed to simplify the complex process of signing and notarizing not only the app itself but also the installer package its ships within. This is all made necessary by the ever more rigorous, annoying but necessary security provisions Apple is applying to macOS.

Is it possible to use a similar script for command-line applications? I’m talking about full applications, here, not text-based shell scripts. It is possible, but there are couple of extra challenges to face first.

Challenge number one: how do you sign a single-file command line tool? macOS app bundles — apps that you double-click to launch, in other words — are really folders containing multiple items, one of which is a folder called _CodeSignature which contains a file CodeResources. This is where the app’s code signature lives.

Challenge number two: notarization under macOS requires access to the bundle ID usually stored in the app’s Info.plist file. Again, that file fits neatly into the app bundle described above, but is not something you can fit into a single-file binary, surely?

Actually, you can. With the right settings, Xcode, Apple’s macOS development environment, will append the Info.plist data to a command line tool binary. And it’ll do the same with the code signature.

We’ll use pdfmaker as an example. Its a command-line tool and its source code lives in a file called main.swift. I also have an Info.plist file in the project too, and this contains the tool’s bundle ID, com.bps.pdfmaker.

Command-line tools need only basic Info.plist entries, but they must have a bundle ID

We add the Info.plist data to the binary by going to Xcode’s Build Settings for the project’s Target, and locating the setting Create Info.plist Section in Binary (you can use the search field to zero in on it; it’s in the Packaging group) and making its value Yes:

How you set Xcode to add Info.plist data to the end of the file

Sorting code-signing out is just a matter of setting this to Manual. In the Signing group look for the setting Code Signing Style and set its value to Manual. You also need to set up the usual signing details: Team and Code Signing Identity. You have to set the Hardened Runtime setting to Yes, too.

Unlike regular apps, command line tools need to be compiled manually too if you want to sign and notarize them. Xcode usually makes build products hard to find, but we can subvert that with another setting: Installation Build Products Location, which is in the Deployment group. Change its value to $SRCROOT/build/pkgroot, which will be expanded by Xcode to the project directory.

Tell Xcode where to put the compiled app

It’s a good idea at this point to add build/ to your .gitignore file if, like mine, your project directory is under version control and you don’t want it included in the remote.

Now you can leave Xcode now, open Terminal, navigate to your project folder and then run:

xcodebuild clean install

This, and the remainder of the process, is entirely command-line operated, so it’s totally scriptable. Check out my packcli.zsh script in my scripts repo. It builds the app using the above command, creates a package from the build and sends the package off to Apple for notarization. The notarization process implicitly covers both the installer package and its command-line tool payload.

The installer will place the tool in the /usr/local/bin directory ready to be run.

Like the app bundle script, this one polls Apple’s notarization server to find out when notarization is done. It then staples the notarization ticket to the binary. You can now distribute your .pkg file as you prefer: on its own or within a .dmg file.

This script requires more input than the app-oriented one: you’ll need to enter the tool’s bundle ID, name and version. Like the other script, you will have to enter both your Apple ID user name, for which the password is securely stored on your Mac’s keychain (see the previous post in this series for details). You’ll need to provide the name of your Installer Distribution certificate too.