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,
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
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
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.
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
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.