活动公告

系统通知
07-14 23:24
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Qt项目转CMake构建系统的详细步骤与常见问题解决方案实战指南

SunJu_FaceMall

3万

主题

3056

科技点

3万

积分

执行版主

碾压王

积分
32876

塔罗立华奏

执行版主 发表于 2025-10-1 17:50:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

Qt是一个跨平台的C++应用程序开发框架,长期以来,Qt项目主要使用qmake作为其构建系统。然而,随着CMake在C++社区中的普及和其强大的功能,越来越多的开发者选择将Qt项目迁移到CMake构建系统。CMake提供了更强大的依赖管理、更好的IDE集成和更灵活的构建配置。

本文将详细介绍如何将Qt项目从传统的qmake构建系统转换为CMake构建系统,包括基本步骤、常见问题解决方案以及实战案例,帮助开发者顺利完成迁移过程。

CMake基础知识

在开始转换之前,我们需要了解一些CMake的基础知识:

CMake基本语法

CMake使用简单的脚本语言,主要命令包括:

• cmake_minimum_required(VERSION version): 指定CMake的最低版本要求
• project(ProjectName): 定义项目名称
• add_executable(name sources...): 添加可执行文件目标
• add_library(name sources...): 添加库目标
• target_link_libraries(target libraries...): 为目标链接库
• include_directories(dirs...): 添加包含目录
• find_package(name [version]): 查找并加载外部包

变量和作用域

CMake中的变量使用set(var value)设置,通过${var}引用。CMake有函数作用域和目录作用域,变量可以在不同作用域中传递。

控制结构

CMake提供了基本的控制结构:
  1. if(condition)
  2.   # commands
  3. elseif(condition)
  4.   # commands
  5. else()
  6.   # commands
  7. endif()
  8. foreach(var range)
  9.   # commands
  10. endforeach()
  11. while(condition)
  12.   # commands
  13. endwhile()
复制代码

Qt项目转CMake的步骤

创建CMakeLists.txt文件

在Qt项目根目录下创建CMakeLists.txt文件,这是CMake的主配置文件。

设置项目信息和最低CMake版本要求

首先,我们需要指定CMake的最低版本要求和项目信息:
  1. cmake_minimum_required(VERSION 3.5)
  2. project(MyQtApp VERSION 1.0.0 LANGUAGES CXX)
复制代码

这里我们要求CMake版本至少为3.5,因为这是支持Qt 5的最低版本。如果使用Qt 6,建议使用CMake 3.16或更高版本。

查找Qt包

使用find_package命令查找Qt包:
  1. # 对于Qt 5
  2. find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED)
  3. # 对于Qt 6
  4. find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED)
复制代码

COMPONENTS参数指定了我们需要使用的Qt模块,REQUIRED表示这些模块是必需的,如果找不到将导致错误。

添加源文件和头文件

接下来,我们需要添加项目的源文件和头文件:
  1. set(SOURCES
  2.     src/main.cpp
  3.     src/mainwindow.cpp
  4.     src/dialog.cpp
  5. )
  6. set(HEADERS
  7.     src/mainwindow.h
  8.     src/dialog.h
  9. )
  10. add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
复制代码

这里我们使用set命令定义了源文件和头文件的列表,然后使用add_executable创建可执行文件目标。

配置Qt相关设置

对于Qt项目,我们需要进行一些特殊的配置:
  1. # 对于Qt 5
  2. target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets)
  3. # 对于Qt 6
  4. target_link_libraries(${PROJECT_NAME} Qt6::Core Qt6::Gui Qt6::Widgets)
复制代码

这里我们使用target_link_libraries将Qt模块链接到我们的目标。使用Qt5::ModuleName或Qt6::ModuleName的形式可以确保正确包含头文件路径和链接库。

处理Qt特定文件

Qt项目通常包含一些特殊类型的文件,如UI文件、资源文件和翻译文件,我们需要对它们进行特殊处理。

对于UI文件,我们需要使用Qt的uic工具将它们转换为C++头文件:
  1. # 对于Qt 5
  2. qt5_wrap_ui(UI_HEADERS ${UI_FILES})
  3. # 对于Qt 6
  4. qt_wrap_ui(UI_HEADERS ${UI_FILES})
  5. # 然后将生成的头文件添加到目标中
  6. target_sources(${PROJECT_NAME} PRIVATE ${UI_HEADERS})
复制代码

对于Qt资源文件(.qrc),我们需要使用rcc工具处理:
  1. # 对于Qt 5
  2. qt5_add_resources(QRC_SOURCES ${QRC_FILES})
  3. # 对于Qt 6
  4. qt_add_resources(QRC_SOURCES ${QRC_FILES})
  5. # 然后将生成的源文件添加到目标中
  6. target_sources(${PROJECT_NAME} PRIVATE ${QRC_SOURCES})
复制代码

对于翻译文件(.ts),我们可以使用以下方式处理:
  1. # 对于Qt 5
  2. qt5_create_translation(QM_FILES ${SOURCES} ${HEADERS} ${TS_FILES})
  3. # 对于Qt 6
  4. qt_create_translation(QM_FILES ${SOURCES} ${HEADERS} ${TS_FILES})
  5. # 然后将生成的QM文件添加到目标中
  6. target_sources(${PROJECT_NAME} PRIVATE ${QM_FILES})
复制代码

设置目标属性

我们可以为目标设置一些属性,如C++标准、输出目录等:
  1. set_target_properties(${PROJECT_NAME} PROPERTIES
  2.     CXX_STANDARD 14
  3.     CXX_STANDARD_REQUIRED ON
  4.     CXX_EXTENSIONS OFF
  5.     RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
  6. )
复制代码

设置包含目录

虽然使用target_link_libraries已经自动处理了Qt头文件的包含路径,但我们可能还需要添加其他包含目录:
  1. target_include_directories(${PROJECT_NAME} PRIVATE
  2.     ${CMAKE_CURRENT_SOURCE_DIR}/src
  3. )
复制代码

安装规则

最后,我们可以定义安装规则,以便在执行make install时安装我们的应用程序:
  1. install(TARGETS ${PROJECT_NAME}
  2.     BUNDLE DESTINATION .
  3.     RUNTIME DESTINATION bin
  4. )
复制代码

常见问题及解决方案

Qt模块找不到

问题: 在运行CMake时,出现找不到Qt模块的错误。

解决方案:

1. 确保已正确安装Qt,并且Qt的bin目录在系统PATH中。
2. 如果Qt不在标准位置,可以手动设置Qt_DIR变量:set(Qt_DIR "path/to/Qt/lib/cmake/Qt5")
3. 对于Qt 6,可能需要设置Qt6_DIR:set(Qt6_DIR "path/to/Qt/lib/cmake/Qt6")
4. 使用CMake GUI或命令行参数指定Qt路径:cmake -DQt_DIR="path/to/Qt/lib/cmake/Qt5" ..
  1. set(Qt_DIR "path/to/Qt/lib/cmake/Qt5")
复制代码
  1. set(Qt6_DIR "path/to/Qt/lib/cmake/Qt6")
复制代码
  1. cmake -DQt_DIR="path/to/Qt/lib/cmake/Qt5" ..
复制代码

资源文件处理问题

问题: 资源文件(.qrc)中的文件路径不正确,导致运行时找不到资源。

解决方案:

1. 确保资源文件中的路径相对于项目根目录或CMakeLists.txt文件的位置。
2.
  1. 使用CMake的file(GLOB)命令自动收集资源文件:file(GLOB_RECURSE RESOURCES "resources/*")
  2. set(QRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc")
复制代码
3. 在资源文件中使用相对路径,并确保这些路径在构建时仍然有效。
  1. file(GLOB_RECURSE RESOURCES "resources/*")
  2. set(QRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc")
复制代码

UI文件处理问题

问题: UI文件(.ui)生成的头文件找不到或未更新。

解决方案:

1. 确保正确使用qt5_wrap_ui或qt_wrap_ui命令。
2. 将生成的UI头文件添加到目标的源文件列表中。
3.
  1. 如果UI文件位于子目录中,确保路径正确:file(GLOB UI_FILES "src/ui/*.ui")
  2. qt5_wrap_ui(UI_HEADERS ${UI_FILES})
  3. target_sources(${PROJECT_NAME} PRIVATE ${UI_HEADERS})
复制代码
4. 确保包含生成的UI头文件:#include "ui_mainwindow.h"
  1. file(GLOB UI_FILES "src/ui/*.ui")
  2. qt5_wrap_ui(UI_HEADERS ${UI_FILES})
  3. target_sources(${PROJECT_NAME} PRIVATE ${UI_HEADERS})
复制代码
  1. #include "ui_mainwindow.h"
复制代码

翻译文件处理问题

问题: 翻译文件(.ts)未更新或未正确编译为.qm文件。

解决方案:

1. 使用qt5_create_translation或qt_create_translation命令处理翻译文件。
2. 确保所有需要翻译的源文件和头文件都包含在命令中。
3.
  1. 可以创建一个自定义目标来更新翻译文件:add_custom_target(update_translations
  2.    COMMAND ${Qt5_LUPDATE_EXECUTABLE} ${SOURCES} ${HEADERS} -ts ${TS_FILES}
  3.    VERBATIM
  4. )
复制代码
4. 在构建过程中包含翻译更新步骤:add_dependencies(${PROJECT_NAME} update_translations)
  1. add_custom_target(update_translations
  2.    COMMAND ${Qt5_LUPDATE_EXECUTABLE} ${SOURCES} ${HEADERS} -ts ${TS_FILES}
  3.    VERBATIM
  4. )
复制代码
  1. add_dependencies(${PROJECT_NAME} update_translations)
复制代码

平台特定问题

问题: 在不同平台上构建时遇到特定问题。

解决方案:

1.
  1. 使用CMake的条件语句处理平台特定代码:if(WIN32)
  2.    # Windows特定设置
  3. elseif(APPLE)
  4.    # macOS特定设置
  5. elseif(UNIX)
  6.    # Linux/Unix特定设置
  7. endif()
复制代码
2.
  1. 对于macOS,可能需要设置额外的属性:if(APPLE)
  2.    set_target_properties(${PROJECT_NAME} PROPERTIES
  3.        MACOSX_BUNDLE TRUE
  4.        MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist
  5.    )
  6. endif()
复制代码
3.
  1. 对于Windows,可能需要设置图标和版本信息:if(WIN32)
  2.    set_target_properties(${PROJECT_NAME} PROPERTIES
  3.        WIN32_EXECUTABLE TRUE
  4.        LINK_FLAGS "/ENTRY:mainCRTStartup"
  5.    )
  6. endif()
复制代码
  1. if(WIN32)
  2.    # Windows特定设置
  3. elseif(APPLE)
  4.    # macOS特定设置
  5. elseif(UNIX)
  6.    # Linux/Unix特定设置
  7. endif()
复制代码
  1. if(APPLE)
  2.    set_target_properties(${PROJECT_NAME} PROPERTIES
  3.        MACOSX_BUNDLE TRUE
  4.        MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist
  5.    )
  6. endif()
复制代码
  1. if(WIN32)
  2.    set_target_properties(${PROJECT_NAME} PROPERTIES
  3.        WIN32_EXECUTABLE TRUE
  4.        LINK_FLAGS "/ENTRY:mainCRTStartup"
  5.    )
  6. endif()
复制代码

依赖管理问题

问题: 项目依赖于其他库,但在CMake中正确配置这些依赖关系很复杂。

解决方案:

1.
  1. 使用find_package查找外部依赖:find_package(Boost REQUIRED COMPONENTS filesystem system)
  2. target_link_libraries(${PROJECT_NAME} Boost::filesystem Boost::system)
复制代码
2.
  1. 对于没有CMake配置的库,手动设置包含路径和链接库:find_path(SOME_LIB_INCLUDE_DIR some_lib.h)
  2. find_library(SOME_LIB_LIBRARY some_lib)
  3. target_include_directories(${PROJECT_NAME} PRIVATE ${SOME_LIB_INCLUDE_DIR})
  4. target_link_libraries(${PROJECT_NAME} ${SOME_LIB_LIBRARY})
复制代码
3.
  1. 使用FetchContent或ExternalProject下载和构建外部依赖:include(FetchContent)
  2. FetchContent_Declare(
  3.    some_lib
  4.    GIT_REPOSITORY https://github.com/someuser/some_lib.git
  5.    GIT_TAG master
  6. )
  7. FetchContent_MakeAvailable(some_lib)
  8. target_link_libraries(${PROJECT_NAME} some_lib)
复制代码
  1. find_package(Boost REQUIRED COMPONENTS filesystem system)
  2. target_link_libraries(${PROJECT_NAME} Boost::filesystem Boost::system)
复制代码
  1. find_path(SOME_LIB_INCLUDE_DIR some_lib.h)
  2. find_library(SOME_LIB_LIBRARY some_lib)
  3. target_include_directories(${PROJECT_NAME} PRIVATE ${SOME_LIB_INCLUDE_DIR})
  4. target_link_libraries(${PROJECT_NAME} ${SOME_LIB_LIBRARY})
复制代码
  1. include(FetchContent)
  2. FetchContent_Declare(
  3.    some_lib
  4.    GIT_REPOSITORY https://github.com/someuser/some_lib.git
  5.    GIT_TAG master
  6. )
  7. FetchContent_MakeAvailable(some_lib)
  8. target_link_libraries(${PROJECT_NAME} some_lib)
复制代码

实战案例

让我们通过一个完整的示例来说明如何将Qt项目转换为CMake构建系统。假设我们有一个简单的Qt应用程序,包含以下文件结构:
  1. MyQtApp/
  2. ├── CMakeLists.txt
  3. ├── resources.qrc
  4. ├── translations/
  5. │   ├── myapp_en.ts
  6. │   └── myapp_zh_CN.ts
  7. ├── src/
  8. │   ├── main.cpp
  9. │   ├── mainwindow.cpp
  10. │   ├── mainwindow.h
  11. │   ├── mainwindow.ui
  12. │   ├── dialog.cpp
  13. │   ├── dialog.h
  14. │   └── dialog.ui
  15. └── resources/
  16.     └── icon.png
复制代码

完整的CMakeLists.txt文件
  1. cmake_minimum_required(VERSION 3.16)
  2. project(MyQtApp VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 14)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. set(CMAKE_CXX_EXTENSIONS OFF)
  7. # 查找Qt
  8. find_package(Qt6 COMPONENTS Core Gui Widgets LinguistTools REQUIRED)
  9. # 收集源文件
  10. file(GLOB_RECURSE SOURCES "src/*.cpp")
  11. file(GLOB_RECURSE HEADERS "src/*.h")
  12. file(GLOB UI_FILES "src/*.ui")
  13. set(QRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc")
  14. file(GLOB TS_FILES "translations/*.ts")
  15. # 处理UI文件
  16. qt_wrap_ui(UI_HEADERS ${UI_FILES})
  17. # 处理资源文件
  18. qt_add_resources(QRC_SOURCES ${QRC_FILES})
  19. # 处理翻译文件
  20. qt_create_translation(QM_FILES ${SOURCES} ${HEADERS} ${TS_FILES})
  21. # 创建可执行文件
  22. add_executable(${PROJECT_NAME}
  23.     ${SOURCES}
  24.     ${HEADERS}
  25.     ${UI_HEADERS}
  26.     ${QRC_SOURCES}
  27.     ${QM_FILES}
  28. )
  29. # 链接Qt库
  30. target_link_libraries(${PROJECT_NAME} Qt6::Core Qt6::Gui Qt6::Widgets)
  31. # 设置包含目录
  32. target_include_directories(${PROJECT_NAME} PRIVATE
  33.     ${CMAKE_CURRENT_SOURCE_DIR}/src
  34. )
  35. # 设置输出目录
  36. set_target_properties(${PROJECT_NAME} PROPERTIES
  37.     RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
  38. )
  39. # 创建更新翻译的自定义目标
  40. add_custom_target(update_translations
  41.     COMMAND ${Qt6_LUPDATE_EXECUTABLE} ${SOURCES} ${HEADERS} -ts ${TS_FILES}
  42.     VERBATIM
  43. )
  44. # 创建发布翻译的自定义目标
  45. add_custom_target(release_translations
  46.     COMMAND ${Qt6_LRELEASE_EXECUTABLE} ${TS_FILES}
  47.     VERBATIM
  48. )
  49. # 安装规则
  50. install(TARGETS ${PROJECT_NAME}
  51.     BUNDLE DESTINATION .
  52.     RUNTIME DESTINATION bin
  53. )
复制代码

构建和运行项目

在Windows上,可以使用以下命令构建项目:
  1. mkdir build
  2. cd build
  3. cmake -G "NMake Makefiles" ..
  4. nmake
复制代码

在Linux或macOS上,可以使用以下命令:
  1. mkdir build
  2. cd build
  3. cmake ..
  4. make
复制代码

如果需要更新翻译文件,可以运行:
  1. make update_translations
复制代码

如果需要编译翻译文件,可以运行:
  1. make release_translations
复制代码

高级技巧

自动化转换工具

对于大型项目,手动转换可能很耗时,可以考虑使用自动化工具:

1. qmake2cmake: Qt官方提供的一个工具,可以将qmake的.pro文件转换为CMakeLists.txt。qmake2cmake myproject.pro -o CMakeLists.txt
2. q2c: 一个开源的qmake到CMake的转换工具。
3. IDE支持: Qt Creator和Visual Studio等IDE提供了内置的转换支持。
  1. qmake2cmake myproject.pro -o CMakeLists.txt
复制代码

使用现代CMake特性

现代CMake(3.0+)引入了一些新特性,可以简化CMakeLists.txt:

1.
  1. 使用target_命令代替全局命令:
  2. “`cmake老式方法include_directories(include)
  3. link_libraries(somelib)
复制代码

使用target_命令代替全局命令:
“`cmake

include_directories(include)
link_libraries(somelib)

# 现代方法
   target_include_directories(mytarget PRIVATE include)
   target_link_libraries(mytarget PRIVATE somelib)
  1. 2. 使用`IMPORTED`目标:
  2.    ```cmake
  3.    add_library(somelib STATIC IMPORTED)
  4.    set_property(TARGET somelib PROPERTY IMPORTED_LOCATION path/to/libsomelib.a)
复制代码

1.
  1. 使用INTERFACE库传递编译选项:add_library(myinterface INTERFACE)
  2. target_compile_features(myinterface INTERFACE cxx_std_14)
  3. target_link_libraries(mytarget PRIVATE myinterface)
复制代码
  1. add_library(myinterface INTERFACE)
  2. target_compile_features(myinterface INTERFACE cxx_std_14)
  3. target_link_libraries(mytarget PRIVATE myinterface)
复制代码

条件编译和配置

根据不同的构建类型和平台,可能需要不同的配置:
  1. # 根据构建类型设置不同选项
  2. if(CMAKE_BUILD_TYPE STREQUAL "Debug")
  3.     add_definitions(-DDEBUG)
  4.     target_compile_options(${PROJECT_NAME} PRIVATE -O0 -g)
  5. elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
  6.     add_definitions(-DNDEBUG)
  7.     target_compile_options(${PROJECT_NAME} PRIVATE -O3)
  8. endif()
  9. # 根据Qt版本设置不同选项
  10. if(Qt6_FOUND)
  11.     # Qt 6特定设置
  12.     target_compile_definitions(${PROJECT_NAME} PRIVATE QT_VERSION_MAJOR=6)
  13. else()
  14.     # Qt 5特定设置
  15.     target_compile_definitions(${PROJECT_NAME} PRIVATE QT_VERSION_MAJOR=5)
  16. endif()
复制代码

打包和分发

使用CMake的CPack模块可以简化应用程序的打包和分发:
  1. # 包含CPack模块
  2. include(InstallRequiredSystemLibraries)
  3. include(CPack)
  4. # 设置CPack变量
  5. set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
  6. set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
  7. set(CPACK_PACKAGE_CONTACT "Your Name <your.email@example.com>")
  8. set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My Qt Application")
  9. # 根据平台设置不同的打包格式
  10. if(WIN32)
  11.     set(CPACK_GENERATOR "NSIS")
  12. elseif(APPLE)
  13.     set(CPACK_GENERATOR "DragNDrop")
  14. else()
  15.     set(CPACK_GENERATOR "DEB;RPM")
  16. endif()
复制代码

然后可以使用以下命令创建包:
  1. cpack -G NSIS  # Windows
  2. cpack -G DragNDrop  # macOS
  3. cpack -G DEB  # Linux (Debian/Ubuntu)
  4. cpack -G RPM  # Linux (Fedora/RHEL)
复制代码

总结

将Qt项目从qmake迁移到CMake构建系统可能看起来复杂,但通过遵循本文提供的步骤和解决方案,开发者可以顺利完成这一过程。CMake为Qt项目提供了更强大、更灵活的构建系统,能够更好地处理依赖管理、多平台构建和自动化测试等任务。

在迁移过程中,需要注意以下几点:

1. 确保正确设置CMake版本要求和项目信息。
2. 使用find_package正确查找Qt模块,并处理可能的路径问题。
3. 正确处理Qt特殊文件,如UI文件、资源文件和翻译文件。
4. 针对不同平台设置适当的配置和属性。
5. 使用现代CMake特性和最佳实践,保持CMakeLists.txt的简洁和可维护性。

通过这些步骤,开发者可以充分利用CMake的强大功能,为Qt项目提供更高效、更灵活的构建系统。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则