Qt CMake

1 阅读8分钟

 使用 CMake 构建

CMake 是一款用于简化跨不同平台开发项目的构建流程的工具。 CMake 可自动生成构建系统,如 Makefile 和 Visual Studio 项目文件。 CMake 是一个第三方工具,有自己的文档。 本主题介绍如何在 Qt 5 中使用 CMake 3.1.0。

开始使用 CMake

使用 find_package 查找 Qt 附带的库和头文件。 然后,您可以使用 target_link_libraries 命令来使用这些库和头文件,以构建基于 Qt 的库和应用程序。 例如,该命令会自动添加适当的包含目录、编译定义、位置无关代码标志,并链接到 Windows 上的 qtmain.lib 库。

构建 GUI 可执行文件

要构建 helloworld GUI 可执行文件,您需要具备以下条件:

cmake_minimum_required(VERSION 3.1.0)

project(helloworld VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

if(CMAKE_VERSION VERSION_LESS "3.7.0")
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
endif()

find_package(Qt5 COMPONENTS Widgets REQUIRED)

add_executable(helloworld
    mainwindow.ui
    mainwindow.cpp
    main.cpp
    resources.qrc
)

target_link_libraries(helloworld Qt5::Widgets)

要使 find_package 成功,CMake 必须通过以下方式之一找到 Qt 安装:

  • 将 CMAKE_PREFIX_PATH 环境变量设置为 Qt 5 安装前缀。这是推荐的方法。
  • 将 CMake 缓存中的 Qt5_DIR 设为 Qt5Config.cmake 文件的位置。

CMAKE_AUTOMOC 设置可在需要时自动运行 moc。更多详情,请参阅CMake AUTOMOC 文档。

导入库目标

每个加载的 Qt 模块都定义了一个 CMake 库目标。目标名称以 Qt5:: 开头,后跟模块名称。例如 Qt5::Core, Qt5::Gui。将目标库的名称传递给 target_link_libraries,即可使用相应的库。

注意:自 Qt 5.15 起,CMake 目标库也可作为 Qt::Core、Qt::Gui 等。这方便了编写可同时使用 Qt 5 和 Qt 6 的 CMake 代码。

导入目标创建时的配置与 Qt 配置时的配置相同。也就是说:

  • 如果 Qt 是用 -debug 开关配置的,则会创建一个带有 DEBUG 配置的导入目标。
  • 如果使用 -release 开关配置 Qt,则会创建一个具有 RELEASE 配置的导入目标。
  • 如果使用 -debug-and-release 开关配置 Qt,则会同时创建 RELEASE 和 DEBUG 配置的导入目标。

如果您的项目有自定义 CMake 编译配置,则必须将自定义配置映射到调试或发布 Qt 配置。

find_package(Qt5 COMPONENTS Core REQUIRED)

set(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_RELEASE} -fprofile-arcs -ftest-coverage")

# set up a mapping so that the Release configuration for the Qt imported target is
# used in the COVERAGE CMake configuration.
set_target_properties(Qt5::Core PROPERTIES MAP_IMPORTED_CONFIG_COVERAGE "RELEASE")

在 CMake 项目中添加库

除 Qt 库外,您还可以在项目中添加其他库。添加过程取决于库的类型和位置:

  • 系统库
  • 你自己的库
  • 第三方库

一旦您的项目成功构建并链接到所添加的库,Qt Creator 将支持代码自动补全和语法高亮显示。

添加自己的库

使用 qt_add_library 命令在 CMakeLists.txt 文件中创建一个库并与之链接,具体做法请参阅 “构建项目”。

指定该库是静态链接还是动态链接。对于静态链接的内部库,在 CMakeLists.txt 项目文件中添加 CMake: target_link_libraries命令来指定依赖关系。

添加外部库

通过外部库,Qt Creator 可以支持代码自动补全和语法高亮,就像代码是当前项目或 Qt 库的一部分一样。

Qt Creator 使用 CMake: find_package命令检测外部库。有些库是 CMake 安装时自带的。您可以在 CMake 安装的 Modules 目录中找到这些库。更多信息,请参阅 CMake: cmake-packages(7)

使用本地 CMake 查找软件包

对于有外部依赖关系的 CMake 项目,可使用 Find.cmake模块来暴露导入的目标。您可以使用预定义的 sample_find_module 代码段将示例命令添加到 .cmake 文件中。然后,您可以根据需要更改命令。

将查找模块放在 ${CMAKE_CURRENT_SOURCE_DIR}/cmake 目录中,并将目录名追加到 CMAKE_MODULE_PATH 列表变量中。例如

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

Qt 5 和 Qt 6 兼容性

Qt 5 和 Qt 6 中 CMake API 的语义在很大程度上是兼容的。 不过,在 Qt 5.14 之前,所有导入的 Qt 库目标和命令都将版本号作为名称的一部分。 这使得编写同时适用于 Qt 5 和 Qt 6 的 CMake 代码变得有些麻烦。 因此,Qt 5.15 引入了无版本的目标和命令,使编写 CMake 代码在很大程度上与不同的 Qt 版本无关。

无版本目标

除现有的导入目标外,Qt 5.15 还引入了无版本目标。 也就是说,要链接 Qt Core,既可以引用 Qt6::Core,也可以引用 Qt::Core:

find_package(Qt6 COMPONENTS Core)
if (NOT Qt6_FOUND)
    find_package(Qt5 5.15 REQUIRED COMPONENTS Core)
endif()

add_executable(helloworld
    ...
)

target_link_libraries(helloworld PRIVATE Qt::Core)

上述代码段首先尝试查找 Qt 6 安装。 如果失败,它会尝试查找 Qt 5.15 软件包。 无论使用的是 Qt 6 还是 Qt 5,我们都可以使用导入的 Qt::Core 目标。
无版本目标是默认定义的。 在第一次调用 find_package() 之前设置 QT_NO_CREATE_VERSIONLESS_TARGETS 可禁用它们。

注意:导入的 Qt::Core 目标将不具备 Qt6::Core 目标中可用的目标属性。

无版本命令

自 Qt 5.15 起,Qt 模块还为其命令提供了无版本变体。例如,现在您可以使用 qt_add_translation 来编译翻译文件,与使用 Qt 5 还是 Qt 6 无关。

在首次调用 find_package() 之前设置 QT_NO_CREATE_VERSIONLESS_FUNCTIONS,以防止创建无版本的命令。

混合使用 Qt 5 和 Qt 6

有些项目可能需要在一个 CMake 上下文中同时加载 Qt 5 和 Qt 6(但不支持在一个库或可执行文件中混合使用 Qt 版本,所以要小心)。

在这种情况下,无版本目标和命令将隐式引用通过 find_package 找到的第一个 Qt 版本。请在第一次调用 find_package 之前设置 QT_DEFAULT_MAJOR_VERSION CMake 变量,以明确版本。

支持较旧的 Qt 5 版本

如果您也需要支持比 Qt 5.15 更早版本的 Qt 5,您可以将当前版本存储在 CMake 变量中:

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)

add_executable(helloworld
    ...
)

target_link_libraries(helloworld PRIVATE Qt${QT_VERSION_MAJOR}::Core)

在这里,我们让 find_package(...) 首先尝试查找 QT 6,如果失败则查找 QT 5。如果找到了,find_package 将成功,CMake 变量 QT_VERSION_MAJOR 将被定义为 5 或 6。

然后,我们通过创建 Qt${QT_VERSION_MAJOR} 来为确定的 Qt 版本重新加载软件包。之所以需要这样做,是因为 CMAKE_AUTOMOC 希望软件包名称是 Qt5 或 Qt6,否则会打印错误信息。

我们还可以使用相同的模式来指定导入库的名称。在调用 target_link_libraries 之前,CMake 会将 Qt${QT_VERSION_MAJOR}::Widgets 解析为 Qt5::Widgets 或 Qt6::Widgets。

推荐做法

尽可能使用 CMake 命令的无版本变体。

除非必须在同一项目中支持 Qt 5 和 Qt 6,否则请使用有版本的目标。

如果必须使用无版本目标,请注意 “使用无版本目标时的陷阱”。

如果您需要支持早于 Qt 5.15 的 Qt 5 版本,或您无法控制 CMake 代码是否在定义了 QT_NO_CREATE_VERSIONLESS_FUNCTIONS 或 QT_NO_CREATE_VERSIONLESS_TARGETS 的情况下加载,请使用版本控制版本的 CMake 命令和目标。在这种情况下,您仍可通过变量确定实际命令或目标名称,从而简化代码。

使用无版本目标时的陷阱

使用无版本目标有几个缺点。

无版本目标是 ALIAS 目标,缺乏有版本目标的目标属性。

项目不得导出暴露无版本目标的目标。例如,被其他项目使用的库不得导出公开链接无版本目标的目标。否则,传递依赖关系可能会被破坏,或者该库的用户会不由自主地混合使用 Qt5 和 Qt6 目标。

Windows 中的 Unicode 支持

在 Qt 6 中,针对与 Qt 模块链接的目标,默认设置了 UNICODE 和 _UNICODE 编译器定义。这与 qmake 行为一致,但与 Qt 5 中的 CMake API 行为相比有所改变。

在目标机上调用 qt_disable_unicode_defines() 可以不设置这些定义。

find_package(Qt6 COMPONENTS Core)

add_executable(helloworld
    ...
)

qt_disable_unicode_defines(helloworld)

CMake 变量参考

模块变量

使用 find_package 加载的 Qt 模块会设置各种变量。

注意:您很少需要直接访问这些变量。 链接模块等常见任务应通过每个模块定义的库目标来完成。

例如,find_package(Qt5 COMPONENTS Widgets)(Qt5 COMPONENTS Widgets)成功加载后,将提供以下变量:

VariableDescription
Qt5Widgets_COMPILE_DEFINITIONS编译定义列表,用于根据该库进行编译。
Qt5Widgets_DEFINITIONS对照库构建时使用的定义列表。
Qt5Widgets_EXECUTABLE_COMPILE_FLAGS根据程序库构建可执行文件时使用的标志字符串。
Qt5Widgets_FOUND一个布尔值,用于描述是否成功找到模块。
Qt5Widgets_INCLUDE_DIRS针对程序库构建时使用的 include 目录列表。
Qt5Widgets_LIBRARIES模块导入目标的名称: Qt5::Widgets
Qt5Widgets_PRIVATE_INCLUDE_DIRS针对程序库构建和使用私有 Qt API 时要使用的私有 include 目录列表。
Qt5Widgets_VERSION_STRING包含模块版本的字符串。

对于使用 find_package 找到的所有软件包,这些变量都有对应的变量;它们区分大小写。

安装变量

此外,还有一些变量与特定软件包无关,而是与 Qt 安装本身有关。

QT_DEFAULT_MAJOR_VERSION一个整数,用于控制 Qt 5 和 Qt 6 混合项目中 qt_ 命令转发的 Qt 版本。 需要在调用相应的 find_package() 之前将其设置为 5 或 6。 如果设置为 5,以 qt_ 开始的命令将调用以 qt5_ 开始的对应版本。 如果设置为 6,则会调用以 qt6_ 开始的对应命令。 如果未设置,第一次 find_package 调用将定义默认版本。 此功能在 Qt 5.15 中添加。
QT_LIBINFIX当 Qt 使用 -libinfix 配置时,用于保存库名中使用的后缀的字符串。
QT_NO_CREATE_VERSIONLESS_FUNCTIONS自 Qt 5.15 起,模块不仅定义以 qt5_ 开头的命令,还定义以 qt_ 开头的命令。 你可以在 find_package 之前设置 QT_NO_CREATE_VERSIONLESS_FUNCTIONS,以防止出现这种情况。
QT_NO_CREATE_VERSIONLESS_TARGETS自 Qt 5.15 起,模块不仅定义以 Qt5:: 开头的目标,还定义以 Qt:: 开头的目标。 你可以在 find_package 之前设置 QT_NO_CREATE_VERSIONLESS_TARGETS,以防止出现这种情况。
QT_VISIBILITY_AVAILABLE在 Unix 上,一个布尔值,用于说明 Qt 库和插件在编译时是否使用了 -fvisibility=hidden。 这意味着只导出选定的符号。

CMake 命令参考

Qt5::Core

qt5_add_big_resources将大型二进制资源编译成目标代码
qt5_add_binary_resources从 Qt 资源文件列表中创建 RCC 文件
qt5_add_resources将二进制资源编译成源代码
qt5_generate_moc在输入文件上调用 moc
qt5_import_plugins为静态 Qt 构建指定要导入的自定义插件集
qt5_wrap_cpp从源代码创建 .moc 文件
qt_add_big_resources将大型二进制资源编译成目标代码
qt_add_binary_resources从 Qt 资源文件列表中创建 RCC 文件
qt_add_resources将二进制资源编译成源代码
qt_generate_moc在输入文件上调用 moc
qt_import_plugins为静态 Qt 构建指定要导入的自定义插件集
qt_wrap_cpp从源代码创建 .moc 文件

Qt5::DBus

qt_add_dbus_adaptor为 D-Bus 接口生成适配器类
qt_add_dbus_interface为 D-Bus 接口描述文件生成实现接口的 C++ 源代码
qt_add_dbus_interfaces为 D-Bus 接口描述文件生成实现接口的 C++ 源代码
qt_generate_dbus_interface从头文件生成 D-Bus 接口

Qt5::LinguistTools

qt5_add_translation将 Qt Linguist .ts 文件编译成 .qm 文件
qt5_create_translation设置 Qt Linguist 翻译工具链

Qt5::RemoteObjects

qt5_generate_repc从 Qt Remote Objects .rep 文件创建 C++ 类型

Qt5::Widgets

qt5_wrap_ui为 .ui 文件创建源代码
qt_wrap_ui为 .ui 文件创建源代码

CMake命令

cmake-variables(7) — CMake 3.30.4 Documentation

CMake 变量

cmake-variables(7) — CMake 3.30.4 Documentation

CMake Command Reference | Build with CMake 5.15.17 (qt.io)