CMake: Finding Qt 5 the “Right Way”

October 21, 2016

I work on build systems a fair bit, and this is something I thought others might benefit from. I went through quite a bit of code in our projects that did not do things the “right way”, and it wasn’t clear what that was to me at first. Qt 5 improved its integration with CMake quite significantly – moving from using the qmake command to find Qt 4 components in a traditional CMake find module to providing its own CMake config files. This meant that instead of having to guess where Qt and its libraries/headers were installed we could use the information generated by Qt’s own build system. This led to many of us, myself included, finding Qt 5 modules individually:

find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)

This didn’t feel right to me, but I hadn’t yet seen what I think is the preferred way (and the way I would recommend) to find Qt 5. It also led to either using ‘CMAKE_PREFIX_PATH’ to specify the prefix Qt 5 was installed in, or passing in ‘Qt5Core_DIR’, ‘Qt5Widgets_DIR’, etc for the directory containing each and every Qt 5 config module – normally all within a common prefix. There is a better way, and it makes many of these things simpler again:

find_package(Qt5 COMPONENTS Core Widgets REQUIRED)

This is not only more compact, which is always nice, but it means that you can simply pass in ‘Qt5_DIR’ to your project, and it will use that when searching for the components. There are many other great features in CMake that improve Qt integration, but I want to keep this post short…

11 comments to CMake: Finding Qt 5 the “Right Way”

  1. I very much agree, I have been doing this for a little while. I also believe it should be preferable because it will insure that all COMPONENTS will belong to the same Qt5 package: I once needed the component WebKit which was missing from one package, but was present in another one. With the old way, CMake automatically found each component from the first package, but also the webkit component from the second package, and I obviously got no warnings and nice linking issues later on. By looking for Qt5 like you describe, it would indeed signal that the WebKit component was missing from that first package.

  2. One more situation that I’ve found this to be helpful is when adding another Qt5 module dependency to your CMake project or re-configuring. If CMAKE_PREFIX_PATH points to a different location than the one already set by Qt5_DIR in the CMake cache, it could lead to linker issues. Looking for components from the Qt5_DIR set in the cache eliminates this.

    1. Agreed, both are harder than writing a blog post, but I will follow up in both places. The Qt documentation looks harder to update, but I am pretty sure I can take care of CMake.

  3. I’ve stumbled on this article while searching for alternatives for setting the CMAKE_PREFIX_PATH approach and I’ve given it a try, but unfortunately it does not work. Apparently cmake looks for a FindQt5.cmake file but Qt actually does not distribute it. Thus, it doesn’t work.

    1. The FindQt5.cmake is the “old way”, and it is still searched for. Qt 5 distributes CMake config files, such as Qt5Config.cmake that contain paths, macros, functions, etc. They are unfortunately a little deeper in the tree, so you would have to set Qt5_DIR to “/usr/lib64/cmake/Qt5” in my case. I use this approach on Windows, macOS, and Linux. The reduction in Qt5*_DIR variables, and the assurance that all modules come from the same Qt5 is worth it.

  4. So, I encountered something weird — on Windows, with Qt 5.11.1 and 5.11.2 at least, I was trying to figure out how cmake found the Qt cmake modules under /lib/cmake. All of the things mentioned in both the cmake docs and in the Qt docs were not true for this install.

    I do not have CMAKE_PREFIX_PATH set, I’m not explicitly passing anything on the cmake command line, the builds are all fresh, so no cmake cache files are present, and there’s no explicit cmake module paths being set in the CMakeLists.txt file.

    I investigated printing out all environment variables, and all cmake vars in the cmake project – CMAKE_PREFIX_PATH remained unset and the only references to Qt were in the PATH (the bin dir is in the path, so qmake is executable from the path).

    I also tried passsing –trace and –trace-expand to cmake, and the logs showed nothing interesting regarding Qt, except that it was definitely following into /lib/cmake/Qt5/Qt5ConfigVersion.cmake code from the find_package(Qt5 5.9 REQUIRED COMPONENTS Core Gui Widgets ) call in CMakeLists.txt.

    The nail in the coffin for me indicating that cmake itself must have logic for finding the active Qt version (likely calling qmake?) is that I tried renaming my Qt install directory to something different (it was the default C:Qt, I renamed it to C:Qt-different), and updated the PATH in the shell to reflect the change. – I then called cmake again, and, lo and behold, it still found /lib/cmake/Qt5/Qt5ConfigVersion.cmake .

    So, on Windows at least, there is logic to automatically find the Qt install if the /bin directory is in the PATH. I still haven’t traced where this is though — probably re-using the Qt3 and Qt4 support?

    1. Check the documentation of find_package Search procedure

      Step 5 mentions “Search the standard system environment variables. This can be skipped if NO_SYSTEM_ENVIRONMENT_PATH is passed. Path entries ending in /bin or /sbin are automatically converted to their parent directories:”

      So if you have qmake in PATH, then it will contain a dir ending in /bin, that part gets stripped, and as described slightly above, “lib/cmake” is appended.

      Hence having qmake in PATH allow find_package() to find the Qt5 config files.

Leave a Reply