CMake 快速入门

CMake is a group of tools that allow to build, test, and package applications. Just like Qt, it is available on all major development platforms. It is also supported by various IDE's, including Qt Creator.

此节将展示在 CMake 工程中,使用 Qt 的最基本方式。首先,创建基本控制台应用程序。然后,将工程扩展成 GUI 应用程序使用 Qt Widgets .

若想要知道如何采用 Qt 构建现有 CMake 工程,见文档编制 如何在命令行中采用 CMake 构建工程 .

要学习 CMake 快速入门基础,接受 采用 Cmake 构建:CMake 和 Qt 快速入门 课程在 Qt 学院。

构建 C++ 控制台应用程序

A CMake 工程是以 CMake 语言编写的文件定义。main 文件称为 CMakeLists.txt ,通常放在实际程序源代码的相同目录下。

这里是典型 CMakeLists.txt 文件用于使用 Qt 以 C++ 编写的控制台应用程序:

cmake_minimum_required(VERSION 3.16)
project(helloworld VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core)
qt_standard_project_setup()
qt_add_executable(helloworld
    main.cpp
)
target_link_libraries(helloworld PRIVATE Qt6::Core)
					

让我们浏览一下内容。

cmake_minimum_required(VERSION 3.16)
					

cmake_minimum_required() specifies the minimum CMake version that is required to successfully configure the project. See Supported CMake versions for the minimum version required by Qt.

project(helloworld VERSION 1.0.0 LANGUAGES CXX)
					

project() 设置工程名称和默认工程版本。 LANGUAGES 自变量告诉 CMake 程序是以 C++ 编写的。

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
					

Qt 6 要求编译器支持 C++ 第 2017 或更高版本。实施这是通过设置 CMAKE_CXX_STANDARD , CMAKE_CXX_STANDARD_REQUIRED 变量将使 CMake 打印错误,若编译器太旧。

find_package(Qt6 REQUIRED COMPONENTS Core)
					

这告诉 CMake 去查找 Qt 6,并导入 Core 模块。继续没有意义若 CMake 无法定位模块,因此,设置 REQUIRED 标志以使 CMake 中止 (在这种情况下)。

若成功,模块将设置一些 CMake 文档化变量在 模块变量 。此外,它还导入 Qt6::Core 目标 (使用于下文)。

For find_package to be successful, CMake must find the Qt installation. Qt Creator and qt-cmake ( qt-cmake.bat on Windows) handle this transparently.

When using vanilla CMake directly, there are different ways you can tell CMake about Qt, but the most common and recommended approach is to set the CMake cache variable CMAKE_PREFIX_PATH to include the Qt 6 installation prefix. This is a directory where Qt binaries are installed. On Linux, it usually looks something like this: "~/Qt/6.8.5/gcc_64" and you would pass it on the command line as -DCMAKE_PREFIX_PATH=$HOME/Qt/6.8.5/gcc_64 .

注意: When cross-compiling (compiling for a platform other than the one you are on, such as WebAssembly or Android) and when using vanilla cmake , set CMAKE_TOOLCHAIN_FILE 而不是 CMAKE_PREFIX_PATH . On Linux, the toolchain file (specific to a particular target platform) is typically located at a path similar to this: "~/Qt/6.8.5/wasm_singlethread/lib/cmake/Qt6/qt.toolchain.cmake" . It sets the required variables like CMAKE_PREFIX_PATH , CMAKE_FIND_ROOT_PATH ,和 QT_HOST_PATH .

qt_standard_project_setup()
					

The qt_standard_project_setup command sets project-wide defaults for a typical Qt application.

除其它事情外,此命令设置 CMAKE_AUTOMOC 变量到 ON ,指导 CMake 自动设置规则,以便 Qt 的 MOC (元对象编译器) is called transparently, when required.

qt_standard_project_setup 参考了解细节。

qt_add_executable(helloworld
    main.cpp
)
					

qt_add_executable() 告诉 CMake 想要构建的可执行文件 (因此不是库) 称为 helloworld as a target. It is a wrapper around the built-in add_executable() command, and provides additional logic to automatically handle things like linking of Qt plugins in static Qt builds, platform-specific customization of library names, and so on.

应构建目标从 C++ 源文件 main.cpp .

Typically, you do not list header files here. This is different from qmake ,需要明确列出头文件,以便处理它们通过 MOC (元对象编译器) .

为创建库,见 qt_add_library .

target_link_libraries(helloworld PRIVATE Qt6::Core)
					

最后, target_link_libraries 告诉 CMake helloworld 可执行文件利用 Qt Core 通过引用 Qt6::Core 目标,导入通过 find_package() 调用 (见上文)。这不仅将正确自变量添加到链接器,且还确保将正确包括目录、编译器定义传递给 C++ 编译器。 PRIVATE 关键字对于可执行目标不是严格必要的,但指定它是良好实践。若 helloworld 是库而不是可执行文件,那么 PRIVATE or PUBLIC 应该指定 ( PUBLIC 若库提到的任何东西来自 Qt6::Core 在其头中, PRIVATE 否则)。

构建 C++ GUI 应用程序

last section we showed the CMakeLists.txt file for a simple console application. We will now create a GUI application that uses the Qt Widgets 模块。

这是完整工程文件:

cmake_minimum_required(VERSION 3.16)
project(helloworld VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
qt_standard_project_setup()
qt_add_executable(helloworld
    mainwindow.ui
    mainwindow.cpp
    main.cpp
)
target_link_libraries(helloworld PRIVATE Qt6::Widgets)
set_target_properties(helloworld PROPERTIES
    WIN32_EXECUTABLE ON
    MACOSX_BUNDLE ON
)
					

让我们回顾一下所做的改变。

find_package(Qt6 REQUIRED COMPONENTS Widgets)
					

find_package 调用,我们替换 Core with Widgets 。这会定位 Qt6Widgets 模块并提供 Qt6::Widgets 目标,我们稍后链接到。

注意,应用程序仍会链接 Qt6::Core ,因为 Qt6::Widgets 从属它。

qt_standard_project_setup()
					

除了 CMAKE_AUTOMOC , qt_standard_project_setup 设置 CMAKE_AUTOUIC 变量到 ON 。这会自动创建规则以援引 Qt 的 uic (用户界面编译器) on .ui 源文件。

qt_add_executable(helloworld
    mainwindow.ui
    mainwindow.cpp
    main.cpp
)
					

我们添加 Qt Widgets Designer 文件 ( mainwindow.ui ) 及其相应 C++ 源文件 ( mainwindow.cpp ) 到应用程序目标的源。

In specific cases, such as the example below where the include directive uses a relative path, qt_add_ui can be used to generate the ui_calculatorform.h file instead of relying on AUTOUIC .

When to prefer qt_add_ui over AUTOUIC

#include "src/files/ui_mainwindow.h"
					
qt_add_ui(calculatorform SOURCES mainwindow.ui INCLUDE_PREFIX src/files)
					

qt_add_ui is used, no need to pass mainwindow.uiqt_add_executable 命令。

target_link_libraries(helloworld PRIVATE Qt6::Widgets)
					

target_link_libraries 命令,我们链接到 Qt6::Widgets 而不是 Qt6::Core .

set_target_properties(helloworld PROPERTIES
    WIN32_EXECUTABLE ON
    MACOSX_BUNDLE ON
)
					

最后,设置应用程序目标特性,效果如下:

  • 阻止 Windows 创建控制台窗口。
  • 在 macOS 创建应用程序捆绑。

CMake 文档编制 for more information about these target properties.

结构化工程

Projects that contain more than just one target will benefit from a clear project file structure. We will use CMake's 子目录特征 .

由于我们计划采用更多目标扩展工程,所以将应用程序源文件移到子目录并创建新的 CMakeLists.txt in there.

<project root>
├── CMakeLists.txt
└── src
    └── app
        ├── CMakeLists.txt
        ├── main.cpp
        ├── mainwindow.cpp
        ├── mainwindow.h
        └── mainwindow.ui
					

顶层 CMakeLists.txt 包含整体工程设置, find_package and add_subdirectory 调用:

cmake_minimum_required(VERSION 3.16)
project(helloworld VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
qt_standard_project_setup()
add_subdirectory(src/app)
					

Variables that are set in this file are visible in subdirectory project files.

应用程序工程文件 src/app/CMakeLists.txt 包含可执行目标:

qt_add_executable(helloworld
    mainwindow.ui
    mainwindow.cpp
    main.cpp
)
target_link_libraries(helloworld PRIVATE Qt6::Widgets)
set_target_properties(helloworld PROPERTIES
    WIN32_EXECUTABLE ON
    MACOSX_BUNDLE ON
)
					

Such a structure will make it easy to add more targets to the project such as libraries or unit tests.

注意: Add your project build directory to the list of excluded directories of any anti-virus application that runs on your system.

构建库

随着工程增长,可能想要将部分应用程序代码转换成用于应用程序且可能单元测试的库。此节展示如何创建这种库。

我们的应用程序目前将业务逻辑直接包含在 main.cpp 。我们将代码提取到的新静态库称为 businesslogic 在子目录 "src/businesslogic" as explained in the 上一章节 .

为简单起见,库仅仅包含一个 C++ 源文件及其相应头文件,头文件包括在应用程序的 main.cpp :

<project root>
├── CMakeLists.txt
└── src
    ├── app
    │   ├── ...
    │   └── main.cpp
    └── businesslogic
        ├── CMakeLists.txt
        ├── businesslogic.cpp
        └── businesslogic.h
					

让我们查看一下库工程文件 ( src/businesslogic/CMakeLists.txt ).

qt_add_library(businesslogic STATIC
    businesslogic.cpp
)
target_link_libraries(businesslogic PRIVATE Qt6::Core)
target_include_directories(businesslogic INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
					

让我们浏览一下内容。

qt_add_library(businesslogic STATIC
    businesslogic.cpp
)
					

The add_library command creates the library businesslogic 。稍后,将让应用程序链接到此目标。

The STATIC keyword denotes a static library. If we wanted to create a shared or dynamic library, we would use the SHARED 关键词。

target_link_libraries(businesslogic PRIVATE Qt6::Core)
					

We have a static library and don't actually have to link other libraries. But as our library uses classes from QtCore ,添加依赖链接到 Qt6::Core 。这会压入必要 QtCore include paths and preprocessor defines.

target_include_directories(businesslogic INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
					

The library API is defined in the header file businesslogic/businesslogic.h 。通过调用 target_include_directories ,确保绝对路径 businesslogic directory is automatically added as an include path to all targets using our library.

This frees us in main.cpp from using relative paths to locate businesslogic.h 。取而代之,可以仅仅编写

#include <businesslogic.h>
					

最后,必须将库的子目录添加到顶层工程文件中:

add_subdirectory(src/app)
add_subdirectory(src/businesslogic)
					

使用库

To use the library we created in the 上一章节 ,我们指导 CMake 链接到它:

target_link_libraries(helloworld PRIVATE
    businesslogic
    Qt6::Widgets
)
					

This ensures that businesslogic.h is found when main.cpp is compiled. Furthermore, the businesslogic static library will become a part of the helloworld 可执行文件。

在 CMake 术语中,库 businesslogic specifies usage requirements (include 路径) 库 (应用程序) 的每个消费者都必须满足。 target_link_libraries command takes care of that.

添加资源

We want to display some images in our application, so we add them using the Qt 资源系统 .

qt_add_resources(helloworld imageresources
    PREFIX "/images"
    FILES logo.png splashscreen.png
)
					

The qt_add_resources command automatically creates a Qt resource containing the referenced images. From the C++ source code, you can access the images by prepending the specified resource prefix:

logoLabel->setPixmap(QPixmap(":/images/logo.png"));
					

The qt_add_resources command takes as the first argument either a variable name or a target name. We recommend to use the target-based variant of this command as shown in the example above.

添加翻译

Translations of strings in a Qt project are encoded in .ts files. These .ts files are compiled into binary .qm files, which are then loaded by the Qt application at run time. See Qt 国际化 了解细节。

This section describes how to add a German and French translation to the helloworld 应用程序。

Specify both languages with qt_standard_project_setup :

qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES de fr)
					

Then call qt_add_translations on the target that shall load the .qm files:

qt_add_translations(helloworld)
					

On the first configuration, this command creates the files helloworld_de.ts and helloworld_fr.ts source directory of the project. These files will contain the translated strings and are supposed to be put under version control.

The command also creates build system rules to automatically generate .qm files from the .ts files. By default, the .qm files are embedded into a resource and are accessible under the "/i18n" resource prefix.

To update the entries in the .ts file, build the update_translations 目标:

$ cmake --build . --target update_translations
					

To trigger the generation of the .qm files manually, build the release_translations 目标:

$ cmake --build . --target release_translations
					

对于更多信息有关如何影响处理 .ts files and the embedding into a resource, see the qt_add_translations documentation .

The qt_add_translations command is a convenience wrapper. For more fine-grained control of how .ts files and .qm files are handled, use the underlying commands qt_add_lupdate and qt_add_lrelease .

延伸阅读

官方 CMake 文档编制 是使用 CMake 的宝贵资源。

官方 CMake 教程 covers common build system tasks.

书籍 专业 CMake:实践指南 提供 CMake 最相关特征的很好介绍。