Porting a Cross-Platform C++ Application to Mac

An article about building C++ software for macOS might seem out of the ordinary for Seiden Group, a company that focuses on IBM i, but we have a good reason: we’ve been enhancing open source tools for PHP and Python. One of these is KCachegrind, an open source visualization tool that integrates with PHP’s Xdebug’s powerful application profiler and Python’s cprofile.

While KCachegrind and its cousin QCachegrind are easily available for Linux, only very outdated builds were available for Windows, and none at all for macOS. To rectify the situation, we’ve been making enhancements; providing QCachegrind builds for the community; and documenting how to use QCachegrind with Xdebug.

We’ve learned how to build cross-platform applications that work well on a Mac. This article documents many of the challenges we faced and solutions we found.

Building Qt applications on Mac

The program we ported uses Qt, a cross-platform framework that includes a GUI library. By using cross-platform Qt tooling, such as Qt Creator, we kept our implementation consistent with other platforms, especially when we didn’t need special Mac features. This approach made some tasks on the Mac side harder, because Apple would prefer everyone use Xcode and their own libraries for everything, but everything Xcode does can be achieved by Qt’s included command-line tools.

We are also sticking with Qt long-term-support releases because they’re known good versions that are supported for longer than other versions. Unfortunately, the Qt LTSes haven’t yet been updated for distribution changes that apply to newer versions of macOS like the bleeding-edge releases have, nor can they handle some issues with the upcoming macOS release Big Sur. (These may have been fixed by the time this article has been published.) Thankfully, we can work around these issues.

KCachegrind is a KDE application. KDE is a Linux desktop that comes with its own application suite. KDE also provides additional libraries for Qt. While these are useful, KDE’s libraries are a large dependency to ship for non-Linux platforms. To avoid this dependency, we chose an alternative version in the source code called QCachegrind, which depends only on the Qt framework that KDE is built on top of. We ship this on Windows and macOS, and it’s functionally equivalent.

Building the application itself was easy and worked out of the box. The harder part was getting it to work like a Mac application with a drag and drop install.

What’s in a Mac UI?

The Mac has long had its own traditions and expectations of how a user interface works. The Qt framework can handle some differences automatically, but for others, you will have to handle them yourself. Even if Qt handles a Mac specific behaviour for you, sometimes you might be better off doing it your own way to be more idomatic on a Mac.

One of the biggest differences between Windows and macOS is how menus work. In a Mac menu bar, there are multiple differences:

  • The application menu contains about dialogs, preferences, and quitting the application. On other platforms, preferences can go anywhere.
  • The edit menu is consistently available and works on every text editing control.
  • The window menu is always present. It contains a list of all the open windows in the application, as well as commands to manage them.

Window management is also different. On a Mac, applications can stay open with no windows, keeping a menu bar presence. By contrast, an application ends without a window on Windows.

Another detail is the “proxy icon” on the title bar, which provides a representation of the file. You can drag it as if it were from a Finder window, or right-click it for access to parent folders. Qt can handle this with a function call before you set the window’s title.

While we haven’t yet been able to make all the changes we’d like for a good Mac experience (no edit menu or persisting without a window), we’ve significantly improved it with minimal code changes. Qt has some facilities to make this easier. For example, Qt automatically moves menu items tagged with specific Mac behaviours to their proper place on a Mac, without having to move it in code.

What’s in a Mac app?

Unlike Linux with its package managers or Windows with its setup executables, the Mac tends to prefer “.app” distribution. The application and anything it needs are packaged into a single folder. The Finder then makes this look and work like a single file. We want users to be able to download the application, with nothing else required.

KCachegrind uses GraphViz to render call graphs. We’ve built a statically linked (all non-system dependencies built-in) version of this, and include it in the bundle. This way, users don’t have to install GraphViz themselves. For KCachegrind to invoke the included copy, we just modified it to look for GraphViz in the bundle folder.

For packaging Qt into the application, Qt comes with the “macdeployqt” tool. While it can do signing and packaging for you, we needed a more complicated setup. This is to handle both how we need to signing new Mac apps with old Qt and how we package more binaries into the bundle.

Mac applications also have a property list containing metadata about the application. Qt can generate one for you from the qmake build script, or have one that it substitutes values in from. One of the values is the bundle ID – keep this handy!

Signing and distributing: gritty details

Mac applications nowadays must be signed with a valid developer ID certificate. A developer account is only $99 a year, so it’s not a significant expense. Also make sure you have a DUNS number if you’re an organization.

We couldn’t use the Mac App Store because the license agreement there conflicts with the open source license that KCachegrind uses. Instead, we have to notarize the application, which involves sending it to Apple’s servers. If you don’t do this, macOS will give your user warning messages when they attempt to launch the app. Even worse, they’ll need to go through a more imvolved process to get around that screen.

macOS unknown application warning

macOS unknown application warning

This also involves using a different kind of certificate to sign applications with; specifically, the “Developer ID Application” certificate. The other kinds of certificates are intended for other platforms, the Mac App Store, or different kinds of packaging. Attempting to use them will result in confusion when you attempt notarization. Only the primary account holder in an organization can create a “Developer ID Application” certificate; if you’re not that person, then you’ll need to get them to make the certificate and export it to you, keys included.

Once you have the certificate, you can run the following command to know what identifier you need to use:

Actually signing

With a newer Qt, deploying can be a one step command:

With the older Qt, we’ll still use macdeployqt, but forgo automatic packaging and signing. We let it put Qt into the bundle, but handle signing ourselves. So, let’s put Qt into the package:

Then, sign the application and everything in it:

We’ll need something to put the application into, so it can be a single file. It’s a bit difficult to download a directory over the internet, after all. You can use either a disk image (DMG) or Zip file. In this example, we’ll use a Zip file.

Making a Zip file to pack the application into? While it’s tempting to use the built-in zip command to automate this, the Zip files it generates don’t play nice with Apple’s notarization service. Instead, you need to use the “ditto” command, or Apple will reject your application.

Next comes notarization. When it finishes submitting, we can check the progress. When it finishes, the tool will tell you when you run it again, or you can check your email. Be sure to substitute your bundle ID, and remember you could use the –password flag if you wish:

Then we can staple the ticket. This isn’t strictly necessary, but helps when the user is offline. For a Zip file, you need to staple the bundle, then make a new one.

You can then zip the application up again, and it’s ready for distribution!

We hit a lot of roadblocks with distributing a Mac application versus other platforms, so documenting the steps helped us. If it helped you, let us know!

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.