使用 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)成功加载后,将提供以下变量:
Variable | Description |
---|---|
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)