Package Qt6 macOS App Bundle With Translation Files In CMake

Recently, 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 out and will briefly explain what I had to do.

In my WorkTracker application, I store the language files in a folder called “l10n” at the project’s root. The first thing to do is instruct CMake to copy the *.qm files to the app bundle’s “Resource” folder. I have done that before for the app icon, and the process is similar for this kind of file.

set(l10n_files
    "${CMAKE_SOURCE_DIR}/l10n/qt_de_DE.qm"
    "${CMAKE_SOURCE_DIR}/l10n/de_DE.qm"
    "${CMAKE_SOURCE_DIR}/l10n/en_US.qm"
)

set_source_files_properties(${l10n_files} PROPERTIES 
    MACOSX_PACKAGE_LOCATION "Resources/l10n")

qt_add_executable(WorkTracker MACOSX_BUNDLE 
    ${worktracker_src} 
    ${app_icon_macos} 
    ${l10n_files})
  1. Define a variable l10n_files that contains all the files.
  2. Tell CMake that these files shall end up in the app bundle, in the “Resources/l10n” folder, to be precise.
  3. Include the files in the call to the qt_add_executable function.
A macOS Finder window showing the contents of the "Resources/l10n" folder in an app bundle.

Now that the translations are part of the bundle, a minor modification to the code tells the application where to find them. The Qt documentation contains a section about using macOS APIs to determine the bundle location. That is not necessary, though. Qt also has a helpful method to achieve the same goal, QApplication::applicationDirPath().

#if defined(Q_OS_LINUX)
    // On Linux the translations can be found in /usr/share/worktracker/l10n.
    auto l10nPath = "/../share/worktracker/l10n/";
#elif defined (Q_OS_WIN)
    // On Windows the translations are in the l10n folder in the exe dir.
    auto l10nPath = "/l10n/";
#elif defined (Q_OS_MAC)
    // On OS X the data is somewhere in the bundle.
    auto l10nPath = "/../Resources/l10n/";
#endif

auto appDir = QApplication::applicationDirPath() + l10nPath;

This method returns the absolute path to the “MacOS” folder inside of the bundle, the folder where the application’s binary is located. Appending /../Resources/l10n/ first navigates up to the “Content” folder (via /..), which is more or less the bundle’s “root” directory, and from there, goes to “Resources/l10n”. Finally, the language files are loaded like on Windows, and the translation works as expected.

I hope this was helpful because I could not find much information on this specific topic.

Thank you for reading.

Package Qt6 App as macOS App Bundle With CMake

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.

Read More »

CMake C++ Custom Library on Windows “Undefined Reference” – No Error on Linux

Here is the short version with a quick setup of the situation and the fix. After that, I’ll elaborate a bit.

TL;DR

Setup

I have a custom C++ library and a separate project for tests (all based on Qt 6). The test project requires the library for execution.

Here is a short excerpt of the CMake scripts, first the library, then the tests.

project(wt2-shared VERSION 2.0.0 DESCRIPTION "WorkTracker2 Shared 
Library")

# To export symbols.
add_compile_definitions(WT2_LIBRARY)

# Snip header + source definitions

add_library(${PROJECT_NAME} ${SOURCES} ${HEADERS})

target_include_directories(${PROJECT_NAME} PUBLIC include/)
target_link_libraries(wt2-shared Qt6::Core Qt6::Sql)
project(wt2-shared-test VERSION 2.0.0 DESCRIPTION "WorkTracker2 
Shared Library Tests")

# Snip header + source definitions

add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})

target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDES})
target_link_libraries(${PROJECT_NAME} Qt6::Core Qt6::Test wt2-shared)

Error

This error only occurred on Windows, and it does not matter which toolchain I used, be it MinGW or MSVC. The result was always the same.

The following shows the MinGW error.

[100%] Linking CXX executable wt2-shared-test.exe    
CMakeFiles\wt2-shared-test.dir/objects.a(testdatasource.cpp.obj):testdatasource.cpp:(.text+0x3e5): 
    undefined reference to `__imp__ZN4Data3Sql13SqlDataSourceC1E7QString'
CMakeFiles\wt2-shared-test.dir/objects.a(testdatasource.cpp.obj):testdatasource.cpp:(.text+0x401): 
    undefined reference to `__imp__ZN4Data3Sql13SqlDataSource4loadEv'
CMakeFiles\wt2-shared-test.dir/objects.a(testdatasource.cpp.obj):testdatasource.cpp:(.text+0x514): 
    undefined reference to `__imp__ZN4Data3Sql13SqlDataSourceC1E7QString'	
CMakeFiles\wt2-shared-test.dir/objects.a(testdatasource.cpp.obj):testdatasource.cpp:(.text+0x530): 
    undefined reference to `__imp__ZN4Data3Sql13SqlDataSource4loadEv'
collect2.exe: error: ld returned 1 exit status
mingw32-make[2]: *** 
[wt2-shared-test\CMakeFiles\wt2-shared-test.dir\build.make:142: 
wt2-shared-test/wt2-shared-test.exe] Error 1

Solution

The add_library definition in the CMakeLists.txt was incomplete.
To make it work, I added SHARED because I want a shared library.

add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS})

Continue reading, though, to get the full picture. There is more to it than just making the library a shared one.

Read More »

CMake CLI Parameter “generator-name” Usage

This topic shouldn’t even require a blog post, but I find the CMake CLI usage rather odd when it comes to specifying a generator. Here’s a shortened "-h" output.

> cmake -h
Usage

cmake [options] <path-to-source>
cmake [options] <path-to-existing-build>
cmake [options] -S <path-to-source> -B <path-to-build>

Specify a source directory to (re-)generate a build system for it in the
current working directory.  Specify an existing build directory to
re-generate its build system.

Options
-S <path-to-source>          = Explicitly specify a source directory.
-B <path-to-build>           = Explicitly specify a build directory.
-C <initial-cache>           = Pre-load a script to populate the cache.
-D <var>[:<type>]=<value>    = Create or update a cmake cache entry.
-U <globbing_expr>           = Remove matching entries from CMake cache.
-G <generator-name>          = Specify a build system generator.
-T <toolset-name>            = Specify toolset name if supported by
                                generator.
-A <platform-name>           = Specify platform name if supported by
                                generator.

    ...snip...

Generators

The following generators are available on this platform (* marks default):
Visual Studio 16 2019        = Generates Visual Studio 2019 project files.
                                Use -A option to specify architecture.
Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
                                Optional [arch] can be "Win64" or "ARM".

    ...snip...

Borland Makefiles            = Generates Borland makefiles.
* NMake Makefiles              = Generates NMake makefiles.
NMake Makefiles JOM          = Generates JOM makefiles.
MSYS Makefiles               = Generates MSYS makefiles.
MinGW Makefiles              = Generates a make file for use with
                                mingw32-make.
Unix Makefiles               = Generates standard UNIX makefiles.

    ...snip...
    
Kate - Ninja                 = Generates Kate project files.
Kate - Unix Makefiles        = Generates Kate project files.
Eclipse CDT4 - NMake Makefiles
                            = Generates Eclipse CDT 4.0 project files.
Eclipse CDT4 - MinGW Makefiles
                            = Generates Eclipse CDT 4.0 project files.
Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.

These generator options do not look like valid parameter values to the "-G" option. But they are. So, if you want to compile on Windows using MinGW, you have to use this.

> cmake -S ../src -B ./ -G "MinGW Makefiles"

Or, if you prefer Visual Studio project files:

> cmake -S ../ -B ./ -G "Visual Studio 15 2017 Win64"

This "syntax" looks weird, and it tripped me for about 10 minutes until I found a sample and understood how it works.

CMake on Windows 10 with Qt5 Error: CMAKE_PREFIX_PATH

Quick one: Set the proper CMAKE_PREFIX_PATH value for Qt5 development on Windows 10 with MinGW and CMake.

Here is the lovely error you get from CMake.

CMake Error at CMakeLists.txt:13 (find_package):
 By not providing "FindQt5.cmake" in CMAKE_MODULE_PATH this project 
has
 asked CMake to find a package configuration file provided by "Qt5", 
but
 CMake did not find one.

 Could not find a package configuration file provided by "Qt5" with 
any of
 the following names:

 Qt5Config.cmake
 qt5-config.cmake

 Add the installation prefix of "Qt5" to CMAKE_PREFIX_PATH or set 
"Qt5_DIR"
 to a directory containing one of the above files. If "Qt5" provides a
 separate development package or SDK, be sure it has been installed.

CMake asks you to tell it where to find the Qt5 *.cmake configuration files. One option is to set the CMAKE_PREFIX_PATH variable. The Qt5 documentation has the following to say about that:

The easiest way to use CMake is to set the CMAKE_PREFIX_PATH environment variable to the install prefix of Qt 5.

Now, what the hell is the "install prefix"? It turns out it is the path where you can find the compiler-specific Qt binaries, include files and the other stuff. In my case, using MinGW, it is located at "C:\Apps\Qt\5.14.2\mingw73_64". If you are using the MSVC binaries, select the corresponding directory, probably something like "C:\Apps\Qt\5.14.2\msvc_2017".