使用 CMake 入门

CMake 是一组工具,允许构建、测试和打包应用程序。就像 Qt 一样,它可在所有主要开发平台上使用。它还得到各种 IDE 的支持,包括 Qt Creator

在本节中,我们将展示在 CMake 项目中使用 Qt 的最基本方法。首先,我们创建一个基本的控制台应用程序。然后,我们将项目扩展为一个使用 Qt Widgets 的 GUI 应用程序。

如果您想了解如何使用 Qt 建立现有的 CMake 项目,请参阅有关如何在命令行上使用 CMake 构建项目的文档。

要了解使用 CMake 的基础知识,请参加 Qt Academy 中的 “使用 CMake 构建:Qt 和 CMake 入门” 课程。

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

一个 CMake 项目由 CMake 语言编写的文件定义。主文件名为 CMakeLists.txt,通常与实际程序源文件放在同一目录中。

以下是一个典型的,使用 C++ 和 Qt 编写的控制台应用程序的 CMakeLists.txt 文件。

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() 指定了应用程序所需的最小 CMake 版本。Qt 本身至少需要 CMake 版本 3.16,但在 Apple 平台上或静态构建 Qt(在 Qt for iOSQt 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++ 版本 17 或更高版本的编译器。通过设置 CMAKE_CXX_STANDARDCMAKE_CXX_STANDARD_REQUIRED 变量来强制执行此要求,如果编译器太旧,CMake 将打印错误信息。

find_package(Qt6 REQUIRED COMPONENTS Core)

这告诉 CMake 查找 Qt 6 并导入 Core 模块。如果 CMake 查找不到模块,则没有继续的必要,因此我们设置了 REQUIRED 标志,以便在这种情况下 CMake 报错。

如果成功,该模块将设置一些在 模块变量 中记载的 CMake 变量。此外,它还将导入我们下面使用的 Qt6::Core 目标。

为了使 find_package 成功,CMake 必须找到 Qt 安装。你可以用不同的方法告诉 CMake 关于 Qt 的信息,但最常见和推荐的方法是将 CMake 缓存变量 CMAKE_PREFIX_PATH 设置为包含 Qt 6 安装前缀。请注意,《a href="https://doc.qt.ac.cn/qtcreator/index.html" translate="no">Qt Creator 将为你透明地处理这个问题。

qt_standard_project_setup()

qt_standard_project_setup 命令设置典型 Qt 应用程序的全局默认项目。

在众多功能中,此命令将 CMAKE_AUTOMOC 变量设置为 ON,这会指示 CMake 自动设置规则,以便在需要时透明地调用 Qt 的 元对象编译器 (moc)

有关详细信息,请参阅 qt_standard_project_setup 的参考。

qt_add_executable(helloworld
    main.cpp
)

qt_add_executable() 告诉 CMake 我们需要构建一个名为 helloworld 的可执行文件(而不是库)作为目标。它是内置的 add_executable() 命令的包装器,并提供额外的逻辑来自动处理静态 Qt 构建中 Qt 插件的链接、为特定平台定制库名称等。

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

通常,你不需要在这里列出头文件。这与 qmake 不同,在 qmake 中,需要显式列出头文件,以便它们被 元对象编译器 (moc) 处理。

有关创建库的信息,请参阅 qt_add_library

target_link_libraries(helloworld PRIVATE Qt6::Core)

最后,target_link_libraries 告诉 CMake,helloworld 可执行文件使用了 Qt Core,原因是它引用了上面的 find_package() 调入的 Qt6::Core 目标。这不仅会将正确的参数添加到链接器,还会确保将正确的包含目录、编译器定义传递给 C++ 编译器。PRIVATE 关键字对于可执行目标不是绝对必要的,但是指定它是良好的做法。如果 helloworld 是库而不是可执行文件,那么应该指定 PRIVATEPUBLIC (如果库在头文件中提到 Qt6::Core,则为 PUBLIC;否则为 PRIVATE)。

构建 C++ 图形用户界面应用程序

在上一节中,我们展示了简单控制台应用程序的 CMakeLists.txt 文件。现在,我们将创建一个使用 Qt Widgets 模块的 GUI 应用程序。

这是整个项目文件

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 替换为 Widgets。这将定位 Qt6Widgets 模块,并提供我们稍后要链接的 Qt6::Widgets 目标。

请注意,应用程序仍然会链接到 Qt6::Core,因为 Qt6::Widgets 依赖于它。

qt_standard_project_setup()

除了 CMAKE_AUTOMOC 之外,qt_standard_project_setup 还将 CMAKE_AUTOUIC 变量设置为 ON。这将自动创建规则以在 .ui 源文件上调用 Qt 的 用户界面编译器 (uic)

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 文档

项目结构

包含一个以上目标的工程项目将受益于清晰的项目文件结构。我们将使用 CMake 的子目录功能

随着我们计划用更多目标扩展项目,我们将应用程序的源文件移动到一个子目录中,并在其中创建一个新的CMakeLists.txt

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

顶级CMakeLists.txt包含整体项目设置,以及find_packageadd_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)

在此文件中设置的变量在子目录项目文件中可见。

应用程序的项目文件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
)

这种结构将使得向项目中添加更多目标(如库或单元测试)变得容易。

注意:将您的项目构建目录添加到系统上运行的任何防病毒应用程序排除目录列表中。

构建库

随着项目的增长,您可能希望将应用程序代码的一部分转换为库,该库由应用程序和可能的单元测试使用。本节展示了如何创建此类库。

我们的应用程序目前将业务逻辑直接放在main.cpp中。根据前一个章节的解释,我们将代码提取到新静态库businesslogic中,位于子目录"src/businesslogic"中。

为了简化,库只包含一个 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
)

add_library 命令创建了库 businesslogic。稍后,我们将让应用程序链接到这个目标。

STATIC 关键字表示静态库。如果我们要创建共享或动态库,我们将使用 SHARED 关键字。

target_link_libraries(businesslogic PRIVATE Qt6::Core)

我们有一个静态库,实际上不需要链接其他库。但是,由于我们的库使用来自 QtCore 的类,我们添加了对 Qt6::Core 的链接依赖。这将引入必要的 QtCore 包含路径和预处理器定义。

target_include_directories(businesslogic INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

库 API 定义在头文件 businesslogic/businesslogic.h 中。通过调用 target_include_directories,我们确保将 businesslogic 目录的绝对路径自动添加为所有使用我们的库的目标的包含路径。

这使得我们可以在 main.cpp 中免于使用相对路径来定位 businesslogic.h。相反,我们只需写下:

#include <businesslogic.h>

最后,我们必须将库的子目录添加到顶级项目文件中。

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

使用库

为了使用在上一节中创建的库,我们指示 CMake 链接到它。

target_link_libraries(helloworld PRIVATE
    businesslogic
    Qt6::Widgets
)

这确保在编译 main.cpp 时找不到 businesslogic.h。此外,businesslogic 静态库将成为 helloworld 可执行文件的一部分。

在CMake术语中,库businesslogic指定了使用要求(包含路径),每个使用我们库(应用程序)的客户都必须满足。target_link_libraries命令负责处理这一部分。

添加资源

我们希望在应用程序中显示一些图像,所以我们使用Qt资源系统添加它们。

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

qt_add_resources命令会自动创建一个包含引用图像的Qt资源。从C++源代码中,您可以通过添加指定的资源前缀来访问图像

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

qt_add_resources命令的第一个参数可以是变量名或目标名。我们推荐使用上述示例中显示的目标变体命令。

添加翻译

Qt项目中字符串的翻译以.ts文件编码。详细信息请参阅使用Qt进行国际化

要将.ts文件添加到您的项目中,请使用qt_add_translations命令。

以下示例将德语和法语翻译文件添加到helloworld目标

qt_add_translations(helloworld
    TS_FILES helloworld_de.ts helloworld_fr.ts)

这会创建构建系统规则,自动从.ts文件生成.qm文件。默认情况下,.qm文件嵌入到资源中,可以在"/i18n"资源前缀下访问。

要更新.ts文件中的条目,构建update_translations目标

$ cmake --build . --target update_translations

要手动触发.qm文件的生成,构建release_translations目标

$ cmake --build . --target release_translations

有关如何影响.ts文件的处理以及嵌入到资源中的信息,请参阅qt_add_translations文档

qt_add_translations命令是一个便利包装器。为了对.ts文件和.qm文件的处理有更精细的控制,请使用底层命令qt_add_lupdateqt_add_lrelease

进一步阅读

官方的CMake文档是使用CMake的重要资源。

官方的CMake教程涵盖了常见的构建系统任务。

书籍Professional CMake:实用的指南提供了对最相关CMake特性和使用方法的精彩介绍。

© 2024 Qt公司有限。本文件中包含的文档贡献的版权属于各自的所有者。本文件中提供的文档是根据由自由软件基金会发布的GNU自由文档许可协议版本1.3许可的。Qt及其相关标志是芬兰及其它国家/地区的Qt公司有限或其它国家的商标。所有其他商标属于其各自的所有者。