The Qt documentation contains all the necessary pieces to create a macOS app bundle. Some steps require CMake configuration, while others require manual labor, i.e., terminal commands. Ideally, you, the developer, want to automate the whole thing and not enter the commands every time you build a release.
You can do that with CMake, and this How-To will show you what to do. I am taking my WorkTracker application as an example since it isn’t just a little toy with an executable binary. It is a fully functional application I use daily at work (albeit on Windows) with icon resources, language files, and several Qt libraries and plugins.
Note: I will not elaborate on the language file topic, as it requires code changes to find the translations in the bundle file. This post focuses on automating the app-bundle creation and setting an application icon.
You can view the complete CMakeLists.txt file on GitHub. I focus on the macOS-specific additions here.
Every App Needs an Icon
Going in the order I opted for in the CMakeList.txt, let me start with the icon first. macOS expects the icon to be in the ICNS format. The file has several variants of the app’s icon in different resolutions. You put them all in a folder and use Apple’s iconutil
to convert the folder into an ICNS file.
% pwd
/Users/rlo/Code/WorkTracker/icon
% ls icon.iconset
icon_128x128.png icon_16x16.png icon_256x256.png icon_32x32.png icon_512x512.png
icon_128x128@2x.png icon_16x16@2x.png icon_256x256@2x.png icon_32x32@2x.png icon_512x512@2x.png
% iconutil -c icns icon.iconset
iconutil
turns this…

… into this.

You can find a textual representation of my screenshots in another person’s GitHub repo as a markdown file. That is where I learned how to do it.
Now that an ICNS file exists, it is time to tell CMake what to do with it. Qt’s documentation covers this part very well.
set(MACOSX_BUNDLE_ICON_FILE icon.icns)
set(app_icon_macos "${CMAKE_SOURCE_DIR}/icon/icon.icns")
set_source_files_properties(${app_icon_macos} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
qt_add_executable(WorkTracker MACOSX_BUNDLE ${worktracker_src} ${app_icon_macos})
The first command ensures the name of the icon file ends up in the app bundle’s Info.plist
file.
<!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>CFBundleIconFile</key>
<string>icon.icns</string>
...
</dict>
</plist>
Then the absolute path is stored in a variable that CMake uses to copy the icon to its bundle location in the “Resources” folder (MACOSX_PACKAGE_LOCATION
) and, lastly, to set it as the executable’s icon.
Bundling the App Into a Bundled App Bundle With Max DMG
CMake needs to know that it is supposed to create an app bundle.
set_target_properties(WorkTracker PROPERTIES
MACOSX_BUNDLE ON
)
It looks simple enough, but this is not the end of the story, unfortunately. You get your bundle, but it will not run because it has no idea where to find the required Qt frameworks (unless you statically link everything into your executable). It likely works on your dev box but not on a clean macOS where no Qt frameworks are installed.
FYI: Something similar exists for Windows deployments.
set_target_properties(WorkTracker PROPERTIES
WIN32_EXECUTABLE ON
)
According to the Qt documentation (scroll down a bit), this prevents GUI applications from showing a console window.
Okay, so CMake creates a bundle without frameworks. So how do you solve this problem? You can take a bunch of manual steps explained in the documentation or reduce them to a single one and utilize Qt’s macdeployqt tool. This is the point I hinted at earlier, where you preferably include this step in the build tool instead of doing it manually every time.
Before running the utility in CMake, you need a few convoluted steps to obtain its location in the filesystem.
get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION)
get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY)
find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${_qt_bin_dir}")
First, you get the absolute path to the qmake
executable using the get_target_property() function with the IMPORTED_LOCATION parameter. Then, you extract just the path component with get_filename_component() (or the equivalent cmake_path() call that replaces the one I used). With _qt_bin_dir
in hand, it is time to find macdeployqt
and finally run it.
add_custom_command(TARGET WorkTracker POST_BUILD
COMMAND "${MACDEPLOYQT_EXECUTABLE}"
ARGS "WorkTracker.app" "-dmg"
COMMENT "Execute macdeployqt to create macOS bundle")
Execution of macdeployqt
is done after CMake creates the app bundle. As you can see, I also let it build a DMG file to distribute the application.
I learned this technique here.
For Science
I was curious how the different CMake steps affect the outcome of the generated app bundle. Hence, I conducted an experiment and built the app four times:
- Without both the MACOSX_BUNDLE ON option and the macdeployqt tool.
- With MACOSX_BUNDLE ON but without the macdeployqt tool.
- Without the MACOSX_BUNDLE ON option but with the macdeployqt tool.
- With both the MACOSX_BUNDLE ON option and the macdeployqt tool.
The results are surprising.

I did not go to great lengths to compare the outcomes. From only looking at the bundle’s contents, it appears that setting or not setting MACOSX_BUNDLE ON
does not make a difference. However, it could very well be that some hidden compiler, linker, or bundler options are set that somehow affect the outcome. Additionally, the macdeployqt
utility does not seem to care whether the option mentioned above is ON or OFF. The effect apparently is the same.
Either way, I’ll set all the options just to be sure. The more, the merrier, right?
I hope this was helpful. Thank you for reading.
[…] I wrote about how you can create a macOS app bundle with CMake for a Qt6 application. I omitted the inclusion of translation files, which also required code changes. Well, I figured it […]
LikeLike