macOS programming · · 14 min read

Using Packages to create an installer for distributing macOS apps outside of the Mac App Store

Using Packages to create an installer for distributing macOS apps outside of the Mac App Store

macOS developers will most likely be faced with the requirement to build an installer for apps they want to — or must — distribute outside of the Mac App Store (MAS). There is also substantial economic incentive to explore macOS development targeting sales outside of the MAS. In this tutorial, I’ll be explaining how to use Packages, by far one of the most popular tools for developing macOS app installers. It is used by macOS developers worldwide. I’ll provide step-by-step instructions, including many screenshots, walking you through the entire process of building installers. Very importantly, Packages is freeware.

A 2019 survey found that more macOS app developers exclusively sell outside the MAS than exclusively inside the MAS, a near majority sell both inside and outside the MAS, and 58% of macOS developers’ revenue comes from outside-the-MAS sales.

A series of tutorials

This is the third part in a four-part series of tutorials comparing the differences between distributing macOS apps inside and outside of the MAS, comparing sandboxed and non-sandboxed apps, and considering app security.

In the first tutorial in this series, I built a non-sandboxed app, discussed certificates, signed the app, notarized it, briefly talked about building an installer, signed and notarized the installer, and lastly touched on distribution of the app installer.

In my second tutorial, I examined the app sandbox and then built an app that, whether sandboxed or not sandboxed, could read and write outside of its container — and could be sold and distributed either outside the MAS or through the MAS, both with Apple’s blessings.

Finally, in Part IV, I’ll guide you step-by-step through the process of “remembering” a user’s intent regarding access to system resources that Apple considers vulnerable to attack by malicious software. Specifically, I’ll be discussing a technology called “security-scoped bookmarks.” Please check my column here on AppCoda for Part IV.

What is a package?

Packages can be used to deliver a variety of assets to any number of Macs, but this tutorial will concentrate on building a distributable that installs a macOS app onto a Mac, MacBook Pro, MacBook Air, etc. So, in essence, a package (.pkg) is an app that installs another app — plus a few optional dependencies. I’ll use the terms “package,” “installer,” and “distributable” interchangeably herein.

In this tutorial, we’ll only talk about a “flat” installer, in other words, our distributable will be one single file with a .pkg extension. You’ll see how, despite the fact that my Packages installer project is composed of multiple files, the final .pkg will only be one file. Building “bundle” installers is beyond the scope of this article.

Finally, did you know that “Packages’ distribution is built using Packages. Would you care about a solution that would not do that?” I care about and use Packages.

Creating an installer

While Packages is a very intuitive tool, if you’ve never used it before and/or never built an installer, it’s best to start with a tutorial like mine herein so you know what to expect, get an idea of the general workflow, and learn how to “build” (generate/compile) an installer, a .pkg. You can download my sample Packages project and associated assets, required for getting the most out of this tutorial. It’s a .zip file. After downloading it, extract it to a convenient folder on your Mac.

Get Packages installed on your Mac

Download the latest version of Packages from this link and install it on your Mac. Open the file you just downloaded and follow the onscreen instructions to install Packages. The latest version at the time of writing this tutorial was 1.2.9. Please keep in mind that this excellent tool is freeware. I believe Packages is the best app for creating installers for macOS. Full disclosure: I have no personal or financial relationship or interest with Packages’ author, Stéphane Sudre. I urge you to use his app and, by doing so, support his efforts.

Preparing the environment and metadata for your installer

Our installer will have a splash screen, “readme” file, and a license agreement, all contained in three separate files in my downloadable .zip. I’ll just show you how to add such files to a Packages installer. I don’t need to tell you how to create a text file or rich text file with images (.rtfd), both of which can be created with an editor like macOS’s builtin TextEdit app.

The three files I just discussed can be found in the UI_Assets folder with these names:

  1. Introduction.rtfd
  2. ReadMe.rtf
  3. License.rtf

When working in production, I urge you to store your Packages projects and user interface (UI) assets in folders that are under source control. If you’re using Git, you’d git add all your distribution-related files and folders to your repo, commit, and push.

Creating a new Packages project

All the work I’ll discuss in the following sections has already been done in my sample Packages project, so you can just follow along or you can recreate the steps I’m about to enumerate and elucidate.

We’ll start a new installer project by using the Packages Distribution template. Start Packages, go to the File menu, select the New Project… option, and the Choose a template for your project: window will pop up:

packages-project

After you select the Distribution template, click Next button. The Choose the name and location for your project: window will be displayed:

Make your Project Name meaningful, like in this example, since the app is named AppNotaryAndDistrib, I’ll call it, well, AppNotaryAndDistrib. Click the Choose… button to select a Project Directory in which to store the new project file. Save it in the AppDistribution directory in my sample that you downloaded and click the Create button.

Just before your new project file is created, Packages will try to access your contacts, thus causing macOS to go security-gaga and prompt you with this dialog:

packages-app-access

I click the Don’t Allow button because I create installers using a variety of Internet domain-based identifiers (e.g., com.domainName.pkg.AppName).

Packages will create a file named AppNotaryAndDistrib.pkgproj where .pkgproj is the extension used for project files. This screen will appear next:

Look at the left sidebar for a blue icon with the word Project next to it. Below this, you’ll also notice a list of your project’s Packages, as a project can be configured to build multiple distributables, like an Xcode project can have multiple targets. Each distributable has a yellow/brown “box” (package?) icon and, next to it, its name. In today’s tutorial, we’ll just have one package, which is the name I just assigned the project (AppNotaryAndDistrib).

Highlight the blue Project icon and we’ll go through and configure its Settings, Presentation, Requirements & Resources, and Comments tabs respectively. NOTE: You should be constantly saving changes made to this new Packages project as we go.

The project Settings tab

You can accept most of the default values on this tab, with several exceptions, but let’s go through each one. I’m referring to the last screenshot I just discussed directly above.

The Name field will contain the same value you just entered as the Project Name. I usually leave this as-is so that the installer we build later will be called [APP_NAME].pkg. Leave the Path field set to build so that the installer we compile will be placed in a folder called build in the AppDistribution project folder I created for this installer. (Note that you can change the build location to whatever you want.) Leave the Reference Folder set to Project Folder so that all project assets and distributables will be referenced relative to the project home folder we chose earlier, and thus be portable and maintainable. Leave the Format set to Flat. This means that your installer will be one (clean) single file with a .pkg extension. Finally, don’t change the Exclusions as these are files that rightfully should not be included in your installer. If you find the need to exclude certain files from your installer, e.g., Git files, this would be the place to do so.

The project Presentation tab

On the project’s Presentation tab, we’ll customize the new installer’s user interface and configure some installation options. We’ll work our way through the bulleted list of buttons, starting with Introduction going all the way down to Summary:

Let’s start by adding a splash screen. Click on the Introduction button and then click the + button. A placeholder will appear under the Custom Introduction Localizations section, with the default locality being the USA. Activate the dropdown and choose the Introduction.rtfd file in my Packages project file system’s UI_Assets folder, as shown in the last screenshot. Notice that Packages already provides terse and basic installer UI screens if you don’t want to customize.

Make sure you configure the path to Introduction.rtfd to be Relative to Project so the new .pkgproj is portable, maintainable, and self-contained. Do the same with the “readme” and license files, like so:

Packages immediately previews what my introduction file is going to look like when the installer runs:

Go ahead and use the same process to add the custom “readme” and licensing files I mentioned earlier. I urge you to always provide a license with software you distribute, but keep in mind that I am not a lawyer and this does not constitute legal advice. It is up to you to seek out legal counsel and discuss your potential liabilities before you release any software to a client and/or to the public.

The Destination Select button does not apply to us, so we’ll skip it and click on Installation Type. This is just a way to give users options as to how and where they’ll install the actual .app. We’ll keep things super simple and choose to install my sample app using the Standard Install Only protocol. You can give users more options, but I leave it to you to explore those on your own time:

Notice that I’ve highlighted Standard Install on “Catalina New”. The standard install in this case defaults to the current macOS boot partition on which we’ve got Packages running (mine is “Catalina New”). This means the .pkg will attempt to install the app on the user’s current bootable partition.

The Installation button is not relevant to our discussion and the Summary button just shows a screen telling the user that the installation succeeded. I’ll show you images of the real installer screens later.

The project Requirements & Resources tab

When you distribute a lot of software, it can make maintenance easier to have all your customers install your app the same way. This tab affords you precise control over where your app can be installed. For example, I have one customer who always ticks the “Install on startup disk only” checkbox:

There’s no need to go to the Comments tab, so I’ll skip it. We’re now done with all the Project tabs. Let’s move to the Packages tabs.

The package’s Settings tab

Remember when we started our new installer project, I pointed out that Packages is the title for the left sidebar of the app? Since a project can be configured to build multiple distributables, each one has a yellow/brown icon next to its name. Remember that we have just one package, which is the name I assigned to the project upon its creation. Click on the package icon named AppNotaryAndDistrib:

The Identifier field will be pre-filled with com.mygreatcompany.pkg.AppNotaryAndDistrib, but since this package is for my company, I renamed it. I used us.microit.pkg.AppNotaryAndDistrib. Notice how it starts with the reverse of my company website’s domain, http://microit.us. This is also known as your installer’s “tag,” just like your app has a Bundle Identifier.

I left the Version number at 1.0, but please use this field to track the version history of your distributables. For On Success, meaning when the installer finishes without a problem, I usually leave it set to Do Nothing but you can also Require Restart, Require Shutdown, or Require Logout. I almost always leave Location as Embedded and you will too most of the time. Trust me — or do your own research about the other possible values. Finally, I prefer that Require admin password for installation be ticked so that only the Mac’s owner can install software. You can see that there are quite a few options you can set to customize your installer. I urge you to fiddle with these settings and learn about the possibilities.

The package’s Payload tab

Without the Payload tab, your Packages project would be useless. The payload is the app to be installed. Here’s how I use drag and drop to add my sample app bundle, AppNotaryAndDistrib.app, to our Packages project:

Remember that the app to be installed must already be signed and notarized before being added to an installer. I’ve included a signed and notarized version of my sample app in [the download for this tutorial](INSERT LINK TO MY PACKAGES PROJECT). I select the signed and notarized version of my sample app in Finder, highlight it, and drag it into Packages’ virtual Finder-like representation of the target Mac’s file system. My sample Packages project is arranged so that the target .app sits in the same folder as the project file we’re now editing. In most cases you’ll drag your app bundle into the project’s virtual /Applications folder so your payload, in most cases, an app, will be installed where most other apps are installed.

Notice that as soon as I drag my AppNotaryAndDistrib.app file into the Payload tab, a sheet entitled Choose options for adding these files: pops up asking me if I want configure my project to reference an Absolute Path or Relative Path. If you chose a Reference Style of Absolute Path, then your project will be hardcoded to look for the target app (deliverable) in a specific location on your Mac every time you build your installer. I rather use a Relative Path, that is, relative to the Packages project file. That way, as long as I Xcode-build my target app into the same location specified in my Packages project, I can always build an installer regardless of any hardcoded paths. An example is in order: Which is more supportable, this hardcoded path, /Users/andrewjaffee/Documents/path/to/the/file/target_app_name.app, or this relative path, ./target_app_name.app?

The same sheet where you specify the target path has an Ownership checkbox. I suggest you leave this alone now and just set the Reference Style as you’ll have the opportunity to scrutinize the target app’s permissions in just a moment. Just click the Finish button.

Since I don’t want to get into a discussion about macOS (Unix) file permissions, I suggest you leave all the default settings provided by Packages on this Payload tab, like so:

This is a nice summary of where and how the .pkg will install the app. The bottom line here is that the permissions should only allow a Mac owner with sufficient rights to allow the installation of a new app in /Applications and not allow just any other user to delete or tamper with apps installed in that same folder.

I leave it to you to do your own research and explore the remaining options on this tab.

Building the Packages installer

We’re now ready to create a distributable capable of installing my sample app on any compatible Mac. To create the installer/package (.pkg), which is universally understood and executed by macOS, we need to “build” the installer. Think of this process as akin to compiling an app using Xcode.

Go to the Build menu and select the Build option. You can watch the entire process live in the Build Results window that automatically pops open when you hit that Build option. I encourage you to expand all the line items in the Build Results window and inspect their contents, like so:

If any errors occur in your package project configuration, say a missing target app or dependency (e.g., license file), you’ll see them in this results dialog. The installer will be placed in the build folder next to my .pkgproj file. As you can see, Packages is a sophisticated software platform that covers all the bases.

Hopefully, the app you’re distributing will be well-accepted by your customer base. That probably means you’ll be updating your app as time goes by and you’ll be reusing your .pkgproj project file. My policy is to save and save often. I regularly save my work as I configure and build a new project and/or update an existing one. Just in case you aren’t the regularly-saving type, you’ll be prompted to save your new Packages project as soon as you hit that Build menu option.

Signing the Packages installer

In order for you to properly distribute your app so your installer runs without generating scary messages from the macOS Gatekeeper, you need to 1) sign and 2) notarize the installer. I already covered this process in great detail here on AppCoda several tutorials back. Please read the section in that tutorial entitled “Creating an installer to distribute the app,” including all its subsections. Then sign and notarize the installer you just built.

You’ve just successfully created your first macOS installer!

Running the Packages installer

Double click on the signed and notarized installer and you’ll see the following series of screens. AppNotaryAndDistrib.app, version 1.0, will be installed in your /Applications folder. Here are the steps the installer will walk you through:

Double-click on the app and in your /Applications folder and, voila, it runs:

Conclusion

Considering that a clear majority of macOS developers distribute and earn revenues on their apps outside of the Mac App Store, I would hope that I piqued your interest in Packages. You can’t beat the fact that Packages is freeware so, not only did you learn something new about macOS software today, you’re opening the doors to beneficial opportunities for yourselves. Enjoy!

Read next