CMake 快速入门

CMake 是一组允许构建、测试及打包应用程序的工具。就像 Qt,它可用于所有主流开发平台。它还被各种 IDE 所支持,包括 Qt Creator .

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

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

构建 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 the application requires. Qt itself requires at least CMake version 3.16. If you use a Qt that was built statically - the default in Qt for iOS and Qt for WebAssembly - 需要 CMake 3.21.1 或更高版本。

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,并导入 核心 模块。继续没有意义若 CMake 无法定位模块,因此,设置 REQUIRED 标志以使 CMake 中止 (在这种情况下)。

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

For find_package 要取得成功, CMake 必须找到 Qt 安装。有不同方式可以告诉 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. Note that Qt Creator 会透明地处理这种情况。

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 extend it to 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 调用,我们替换 核心 with 小部件 。这会定位 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 Designer 文件 ( mainwindow.ui ) 及其相应 C++ 源文件 ( mainwindow.cpp ) 到应用程序目标的源。

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.

构建库

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

我们的应用程序目前将业务逻辑直接包含在 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. See Qt 国际化 了解细节。

To add .ts files to your project, use the qt_add_translations 命令。

The following example adds a German and a French translation file to the helloworld 目标:

qt_add_translations(helloworld
    TS_FILES helloworld_de.ts helloworld_fr.ts)
					

This 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 最相关特征的很好介绍。

构建采用 CMake 在命令行构建工程