活动公告

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

深入浅出CMake构建文件编写打造高效可维护的跨平台项目构建系统

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-2 21:50:01 | 显示全部楼层 |阅读模式

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

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

x
引言

CMake是一个开源、跨平台的构建自动化工具,它使用平台无关的配置文件来控制软件编译过程,并生成标准的构建文件(如Unix的Makefile或Windows Visual Studio的项目文件)。CMake的设计初衷是解决跨平台构建的问题,使开发者能够用一套构建逻辑管理不同平台上的项目构建。

在现代软件开发中,项目规模日益庞大,依赖关系复杂,跨平台需求普遍,一个高效、可维护的构建系统变得至关重要。CMake凭借其强大的功能和灵活的设计,成为了许多大型项目的首选构建工具,如OpenCV、VTK、KDE等。

本文将深入浅出地介绍CMake构建文件的编写方法,帮助读者打造高效可维护的跨平台项目构建系统。

CMake基础

CMake的基本语法

CMake脚本使用CMake语言编写,这种语言相对简单,主要由命令、变量和注释组成。命令不区分大小写,但参数是区分大小写的。以下是一个基本的CMake脚本示例:
  1. # 这是一个CMake注释
  2. cmake_minimum_required(VERSION 3.10)  # 要求CMake最低版本
  3. project(MyProject)                    # 定义项目名称
  4. # 添加一个可执行文件
  5. add_executable(my_app main.cpp)
复制代码

变量

CMake中的变量使用set()命令设置,使用${VAR}语法引用:
  1. # 设置变量
  2. set(MY_VARIABLE "Hello, CMake!")
  3. # 引用变量
  4. message(STATUS "${MY_VARIABLE}")  # 输出: -- Hello, CMake!
复制代码

CMake也有一些预定义的变量,如PROJECT_SOURCE_DIR(项目根目录)、CMAKE_CURRENT_SOURCE_DIR(当前CMakeLists.txt所在目录)等。

控制流

CMake提供了基本的控制流结构,如条件判断和循环:
  1. # 条件判断
  2. if(WIN32)
  3.     message(STATUS "Windows platform")
  4. elseif(UNIX AND NOT APPLE)
  5.     message(STATUS "Linux platform")
  6. else()
  7.     message(STATUS "Other platform")
  8. endif()
  9. # 循环
  10. set(MY_LIST item1 item2 item3)
  11. foreach(item ${MY_LIST})
  12.     message(STATUS "${item}")
  13. endforeach()
复制代码

项目结构

一个典型的CMake项目结构如下:
  1. my_project/
  2. ├── CMakeLists.txt          # 主CMake配置文件
  3. ├── src/                    # 源代码目录
  4. │   ├── CMakeLists.txt      # src子目录的CMake配置
  5. │   ├── module1/
  6. │   │   ├── CMakeLists.txt
  7. │   │   ├── module1.cpp
  8. │   │   └── module1.h
  9. │   └── module2/
  10. │       ├── CMakeLists.txt
  11. │       ├── module2.cpp
  12. │       └── module2.h
  13. ├── include/                # 公共头文件目录
  14. │   └── my_project/
  15. │       └── common.h
  16. ├── tests/                  # 测试目录
  17. │   ├── CMakeLists.txt
  18. │   └── test_module1.cpp
  19. ├── third_party/            # 第三方库
  20. │   └── some_library/
  21. └── cmake/                  # 自定义CMake模块
  22.     └── FindSomeLib.cmake
复制代码

这种结构将源代码、头文件、测试和第三方库分离开来,使项目更加清晰和易于维护。

主CMakeLists.txt

主CMakeLists.txt文件通常位于项目根目录,它负责设置项目全局配置和构建目标:
  1. # 要求CMake最低版本
  2. cmake_minimum_required(VERSION 3.10)
  3. # 定义项目名称和版本
  4. project(MyProject VERSION 1.0.0 LANGUAGES CXX)
  5. # 设置C++标准
  6. set(CMAKE_CXX_STANDARD 17)
  7. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  8. # 添加编译选项
  9. if(MSVC)
  10.     add_compile_options(/W4)
  11. else()
  12.     add_compile_options(-Wall -Wextra -Wpedantic)
  13. endif()
  14. # 添加子目录
  15. add_subdirectory(src)
  16. add_subdirectory(tests)
复制代码

子目录CMakeLists.txt

每个子目录可以有自己的CMakeLists.txt文件,用于定义该目录下的构建目标:
  1. # src/CMakeLists.txt
  2. # 添加子目录
  3. add_subdirectory(module1)
  4. add_subdirectory(module2)
  5. # 创建库
  6. add_library(my_project_lib STATIC
  7.     module1/module1.cpp
  8.     module2/module2.cpp
  9. )
  10. # 设置包含目录
  11. target_include_directories(my_project_lib
  12.     PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include
  13. )
  14. # 链接库
  15. target_link_libraries(my_project_lib
  16.     PUBLIC some_other_library
  17. )
复制代码

常用CMake命令详解

project()

project()命令用于定义项目名称,并可选地指定版本、描述、语言等:
  1. project(MyProject VERSION 1.0.0 DESCRIPTION "My awesome project" LANGUAGES CXX)
复制代码

执行此命令后,CMake会自动定义一些变量,如PROJECT_NAME、PROJECT_VERSION等。

add_executable()

add_executable()命令用于添加一个可执行文件目标:
  1. # 简单形式
  2. add_executable(my_app main.cpp)
  3. # 指定源文件列表
  4. set(SOURCES
  5.     main.cpp
  6.     utils.cpp
  7.     config.cpp
  8. )
  9. add_executable(my_app ${SOURCES})
  10. # 指定别名(CMake 3.13+)
  11. add_executable(my_app ALIAS my_app_real)
复制代码

add_library()

add_library()命令用于添加一个库目标,可以是静态库、共享库或模块库:
  1. # 静态库
  2. add_library(my_lib STATIC utils.cpp)
  3. # 共享库
  4. add_library(my_lib SHARED utils.cpp)
  5. # 模块库
  6. add_library(my_lib MODULE utils.cpp)
  7. # 接口库(仅包含接口,不生成编译文件)
  8. add_library(my_lib INTERFACE)
复制代码

target_include_directories()

target_include_directories()命令用于为目标设置包含目录:
  1. # 为my_lib目标设置包含目录
  2. target_include_directories(my_lib
  3.     PUBLIC
  4.         ${CMAKE_CURRENT_SOURCE_DIR}/include
  5.     PRIVATE
  6.         ${CMAKE_CURRENT_SOURCE_DIR}/src
  7.     INTERFACE
  8.         ${CMAKE_CURRENT_SOURCE_DIR}/api
  9. )
复制代码

这里的PUBLIC、PRIVATE和INTERFACE指定了包含目录的传递性:

• PUBLIC:当前目标使用,并且会传递给链接此目标的目标
• PRIVATE:仅当前目标使用,不传递
• INTERFACE:不用于当前目标,只传递给链接此目标的目标

target_link_libraries()

target_link_libraries()命令用于为目标链接库:
  1. # 链接库
  2. target_link_libraries(my_app
  3.     PRIVATE
  4.         some_static_lib
  5.     PUBLIC
  6.         some_shared_lib
  7.     INTERFACE
  8.         some_interface_lib
  9. )
复制代码

与target_include_directories()类似,PRIVATE、PUBLIC和INTERFACE指定了链接库的传递性。

find_package()

find_package()命令用于查找和加载外部项目:
  1. # 查找Boost库
  2. find_package(Boost 1.66 REQUIRED COMPONENTS filesystem system)
  3. # 如果找到,则使用
  4. if(Boost_FOUND)
  5.     target_include_directories(my_app
  6.         PRIVATE ${Boost_INCLUDE_DIRS}
  7.     )
  8.     target_link_libraries(my_app
  9.         PRIVATE ${Boost_LIBRARIES}
  10.     )
  11. endif()
复制代码

include_directories()与link_directories()

虽然target_include_directories()和target_link_libraries()是更现代、更推荐的做法,但在某些情况下,使用全局的include_directories()和link_directories()可能更方便:
  1. # 全局包含目录
  2. include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
  3. # 全局链接目录
  4. link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)
复制代码

add_subdirectory()

add_subdirectory()命令用于添加并构建子目录:
  1. # 添加子目录
  2. add_subdirectory(src)
  3. add_subdirectory(tests EXCLUDE_FROM_ALL)  # EXCLUDE_FROM_ALL表示默认不构建
复制代码

option()

option()命令用于定义一个布尔选项,用户可以在配置时设置:
  1. # 定义选项
  2. option(ENABLE_TESTS "Enable building tests" ON)
  3. # 根据选项执行不同操作
  4. if(ENABLE_TESTS)
  5.     add_subdirectory(tests)
  6. endif()
复制代码

条件编译和跨平台处理

CMake提供了多种方式来处理不同平台和编译器的差异,实现条件编译。

平台检测

CMake提供了一些预定义的变量来检测当前平台:
  1. # Windows平台
  2. if(WIN32)
  3.     # Windows特定代码
  4.     add_definitions(-DWINDOWS_PLATFORM)
  5. endif()
  6. # Unix-like系统
  7. if(UNIX)
  8.     # Unix特定代码
  9.     add_definitions(-DUNIX_PLATFORM)
  10. endif()
  11. # Linux系统
  12. if(UNIX AND NOT APPLE)
  13.     # Linux特定代码
  14.     add_definitions(-DLINUX_PLATFORM)
  15. endif()
  16. # macOS系统
  17. if(APPLE)
  18.     # macOS特定代码
  19.     add_definitions(-DMACOS_PLATFORM)
  20. endif()
复制代码

编译器检测

CMake也提供了检测编译器的方法:
  1. # MSVC编译器
  2. if(MSVC)
  3.     add_compile_options(/W4 /permissive-)
  4. endif()
  5. # GCC编译器
  6. if(CMAKE_COMPILER_IS_GNUCXX)
  7.     add_compile_options(-Wall -Wextra -Wpedantic)
  8. endif()
  9. # Clang编译器
  10. if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  11.     add_compile_options(-Wall -Wextra -Wpedantic)
  12. endif()
复制代码

生成器表达式

生成器表达式是CMake中一种强大的条件表达式,可以在构建时根据条件生成不同的值:
  1. # 根据配置类型设置不同的定义
  2. target_compile_definitions(my_lib
  3.     PRIVATE
  4.         $<$<CONFIG:Debug>:DEBUG_BUILD>
  5.         $<$<CONFIG:Release>:NDEBUG>
  6. )
  7. # 根据平台设置不同的链接库
  8. target_link_libraries(my_app
  9.     PRIVATE
  10.         $<$<PLATFORM_ID:Windows>:ws2_32>
  11.         $<$<PLATFORM_ID:Linux>:dl>
  12. )
复制代码

配置类型处理

CMake支持多种配置类型,如Debug、Release、RelWithDebInfo和MinSizeRel:
  1. # 根据配置类型设置不同的编译选项
  2. if(CMAKE_BUILD_TYPE STREQUAL "Debug")
  3.     add_compile_options(-g -O0)
  4. elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
  5.     add_compile_options(-O3 -DNDEBUG)
  6. elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
  7.     add_compile_options(-g -O2 -DNDEBUG)
  8. elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
  9.     add_compile_options(-Os -DNDEBUG)
  10. endif()
复制代码

依赖管理:查找和链接库

在复杂项目中,依赖管理是一个关键问题。CMake提供了多种方式来查找和链接外部库。

find_package()详解

find_package()是CMake中查找外部库的主要方式。它有两种模式:Module模式和Config模式。

Module模式下,CMake会查找名为Find<PackageName>.cmake的文件。这些文件通常包含查找库的逻辑:
  1. # 查找OpenCV库
  2. find_package(OpenCV REQUIRED)
  3. # 如果找到,使用它
  4. if(OpenCV_FOUND)
  5.     message(STATUS "OpenCV found: ${OpenCV_VERSION}")
  6.     target_include_directories(my_app
  7.         PRIVATE ${OpenCV_INCLUDE_DIRS}
  8.     )
  9.     target_link_libraries(my_app
  10.         PRIVATE ${OpenCV_LIBS}
  11.     )
  12. endif()
复制代码

Config模式下,CMake会查找名为<PackageName>Config.cmake或<lower-case-package-name>-config.cmake的文件。这些文件通常由库的安装过程生成:
  1. # 查找Eigen3库
  2. find_package(Eigen3 REQUIRED NO_MODULE)
  3. # 如果找到,使用它
  4. if(Eigen3_FOUND)
  5.     message(STATUS "Eigen3 found: ${Eigen3_VERSION}")
  6.     target_link_libraries(my_app
  7.         PRIVATE Eigen3::Eigen
  8.     )
  9. endif()
复制代码

自定义Find模块

如果CMake没有提供某个库的Find模块,你可以自己创建一个。例如,创建一个FindMyLib.cmake文件:
  1. # 尝试查找头文件
  2. find_path(MYLIB_INCLUDE_DIR
  3.     NAMES mylib.h
  4.     PATHS /usr/include /usr/local/include
  5. )
  6. # 尝试查找库文件
  7. find_library(MYLIB_LIBRARY
  8.     NAMES mylib
  9.     PATHS /usr/lib /usr/local/lib
  10. )
  11. # 设置变量
  12. include(FindPackageHandleStandardArgs)
  13. find_package_handle_standard_args(MyLib
  14.     DEFAULT_MSG
  15.     MYLIB_INCLUDE_DIR
  16.     MYLIB_LIBRARY
  17. )
  18. # 如果找到,创建导入目标
  19. if(MYLIB_FOUND)
  20.     add_library(MyLib::MyLib UNKNOWN IMPORTED)
  21.     set_target_properties(MyLib::MyLib PROPERTIES
  22.         INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_INCLUDE_DIR}"
  23.         IMPORTED_LOCATION "${MYLIB_LIBRARY}"
  24.     )
  25. endif()
复制代码

FetchContent

CMake 3.11引入了FetchContent模块,允许在配置时下载和构建依赖项:
  1. include(FetchContent)
  2. # 声明要获取的内容
  3. FetchContent_Declare(
  4.     googletest
  5.     GIT_REPOSITORY https://github.com/google/googletest.git
  6.     GIT_TAG main
  7. )
  8. # 获取内容
  9. FetchContent_MakeAvailable(googletest)
  10. # 使用获取的内容
  11. target_link_libraries(my_test
  12.     PRIVATE gtest_main
  13. )
复制代码

ExternalProject

ExternalProject模块提供了更强大的外部项目管理功能,允许在构建时下载、配置、构建和安装外部项目:
  1. include(ExternalProject)
  2. # 定义外部项目
  3. ExternalProject_Add(
  4.     some_external_lib
  5.     GIT_REPOSITORY https://github.com/example/some_external_lib.git
  6.     GIT_TAG main
  7.     CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
  8.     INSTALL_DIR ${CMAKE_INSTALL_PREFIX}
  9. )
  10. # 创建导入目标
  11. add_library(some_external_lib STATIC IMPORTED)
  12. set_target_properties(some_external_lib PROPERTIES
  13.     IMPORTED_LOCATION ${CMAKE_INSTALL_PREFIX}/lib/libsome_external_lib.a
  14.     INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_INSTALL_PREFIX}/include
  15. )
  16. # 添加依赖关系
  17. add_dependencies(some_external_lib some_external_lib)
复制代码

conan支持

Conan是一个流行的C++包管理器,CMake可以与Conan集成来管理依赖:
  1. # 在项目根目录创建conanfile.txt或conanfile.py
  2. # 然后在CMakeLists.txt中添加:
  3. # 如果conan安装了依赖
  4. if(EXISTS "${CMAKE_BINARY_DIR}/conanbuildinfo.cmake")
  5.     include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
  6.     conan_basic_setup()
  7. endif()
  8. # 使用conan提供的库
  9. target_link_libraries(my_app
  10.     PRIVATE ${CONAN_LIBS}
  11. )
复制代码

安装和打包

CMake提供了安装和打包功能,使项目可以轻松地分发和部署。

install()命令

install()命令用于指定安装规则:
  1. # 安装目标
  2. install(TARGETS my_lib my_app
  3.     EXPORT my_targets
  4.     LIBRARY DESTINATION lib
  5.     ARCHIVE DESTINATION lib
  6.     RUNTIME DESTINATION bin
  7.     PUBLIC_HEADER DESTINATION include
  8. )
  9. # 安装文件
  10. install(FILES
  11.     ${CMAKE_CURRENT_SOURCE_DIR}/README.md
  12.     DESTINATION doc
  13. )
  14. # 安装目录
  15. install(DIRECTORY
  16.     ${CMAKE_CURRENT_SOURCE_DIR}/include/
  17.     DESTINATION include
  18. )
  19. # 安装脚本
  20. install(PROGRAMS
  21.     ${CMAKE_CURRENT_SOURCE_DIR}/scripts/myscript.sh
  22.     DESTINATION bin
  23. )
复制代码

导出配置

为了使其他项目可以轻松地使用你的库,可以导出配置:
  1. # 导出目标
  2. install(EXPORT my_targets
  3.     FILE MyTargets.cmake
  4.     NAMESPACE My::
  5.     DESTINATION lib/cmake/My
  6. )
  7. # 创建配置文件
  8. include(CMakePackageConfigHelpers)
  9. write_basic_package_version_file(
  10.     "${CMAKE_CURRENT_BINARY_DIR}/MyConfigVersion.cmake"
  11.     VERSION ${PROJECT_VERSION}
  12.     COMPATIBILITY AnyNewerVersion
  13. )
  14. # 安装配置文件
  15. install(FILES
  16.     "${CMAKE_CURRENT_BINARY_DIR}/MyConfigVersion.cmake"
  17.     "${CMAKE_CURRENT_SOURCE_DIR}/cmake/MyConfig.cmake"
  18.     DESTINATION lib/cmake/My
  19. )
复制代码

CPack打包

CMake集成了CPack工具,可以创建各种格式的安装包:
  1. # 设置CPack变量
  2. set(CPACK_PACKAGE_NAME "MyProject")
  3. set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
  4. set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My awesome project")
  5. set(CPACK_PACKAGE_VENDOR "MyCompany")
  6. # 设置包类型
  7. if(WIN32)
  8.     set(CPACK_GENERATOR "NSIS")
  9. elseif(APPLE)
  10.     set(CPACK_GENERATOR "DragNDrop")
  11. else()
  12.     set(CPACK_GENERATOR "DEB;RPM")
  13. endif()
  14. # 包含CPack
  15. include(CPack)
复制代码

然后,在构建项目后,可以运行cpack命令来创建安装包。

高级技巧和最佳实践

使用现代CMake实践

现代CMake(通常指CMake 3.0+)推荐使用基于目标的命令,而不是全局命令:
  1. # 传统方式(不推荐)
  2. include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
  3. link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)
  4. add_executable(my_app main.cpp)
  5. target_link_libraries(my_app my_lib)
  6. # 现代方式(推荐)
  7. add_executable(my_app main.cpp)
  8. target_include_directories(my_app
  9.     PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
  10. )
  11. target_link_libraries(my_app
  12.     PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/lib/libmy_lib.a
  13. )
复制代码

创建和使用函数

CMake支持创建函数来封装重复的逻辑:
  1. # 定义函数
  2. function(add_my_library name)
  3.     set(sources ${ARGN})
  4.    
  5.     add_library(${name} STATIC ${sources})
  6.     target_include_directories(${name}
  7.         PUBLIC
  8.             ${CMAKE_CURRENT_SOURCE_DIR}/include
  9.     )
  10.     target_compile_features(${name}
  11.         PUBLIC
  12.             cxx_std_17
  13.     )
  14. endfunction()
  15. # 使用函数
  16. add_my_library(my_lib
  17.     src/my_lib.cpp
  18.     src/utils.cpp
  19. )
复制代码

创建和使用宏

与函数不同,宏不会创建新的作用域,但可以用于代码生成:
  1. # 定义宏
  2. macro(configure_target target)
  3.     target_compile_features(${target}
  4.         PUBLIC
  5.             cxx_std_17
  6.     )
  7.    
  8.     if(MSVC)
  9.         target_compile_options(${target}
  10.             PRIVATE
  11.                 /W4
  12.         )
  13.     else()
  14.         target_compile_options(${target}
  15.             PRIVATE
  16.                 -Wall -Wextra -Wpedantic
  17.         )
  18.     endif()
  19. endmacro()
  20. # 使用宏
  21. add_executable(my_app main.cpp)
  22. configure_target(my_app)
复制代码

使用CMake的测试功能

CMake集成了CTest测试工具,可以方便地添加和运行测试:
  1. # 启用测试
  2. enable_testing()
  3. # 添加测试
  4. add_executable(my_test test/my_test.cpp)
  5. target_link_libraries(my_test
  6.     PRIVATE my_lib
  7.     gtest
  8. )
  9. # 添加测试用例
  10. add_test(
  11.     NAME my_test
  12.     COMMAND my_test
  13. )
  14. # 设置测试属性
  15. set_tests_properties(my_test
  16.     PROPERTIES
  17.         WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
  18. )
复制代码

使用CMake的文档生成

CMake可以与Doxygen等文档生成工具集成:
  1. # 查找Doxygen
  2. find_package(Doxygen)
  3. # 如果找到,创建文档目标
  4. if(Doxygen_FOUND)
  5.     set(DOXYGEN_INPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
  6.     set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/docs)
  7.     set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/html/index.html)
  8.    
  9.     # 配置Doxygen模板文件
  10.     configure_file(
  11.         ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Doxyfile.in
  12.         ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
  13.         @ONLY
  14.     )
  15.    
  16.     # 添加自定义目标来生成文档
  17.     add_custom_target(docs
  18.         COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
  19.         WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
  20.         COMMENT "Generating API documentation with Doxygen"
  21.         VERBATIM
  22.     )
  23.    
  24.     # 添加安装规则
  25.     install(DIRECTORY ${DOXYGEN_OUTPUT_DIR}/html
  26.         DESTINATION share/doc/my_project
  27.     )
  28. endif()
复制代码

使用CMake的格式化和静态分析

为了保持CMake脚本的一致性和质量,可以使用格式化和静态分析工具:
  1. # 添加格式化目标
  2. find_program(CMAKE_FORMAT cmake-format)
  3. if(CMAKE_FORMAT)
  4.     file(GLOB_RECURSE CMAKE_FILES
  5.         ${CMAKE_CURRENT_SOURCE_DIR}/*.cmake
  6.         ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
  7.     )
  8.    
  9.     add_custom_target(format-cmake
  10.         COMMAND ${CMAKE_FORMAT} -i ${CMAKE_FILES}
  11.         COMMENT "Formatting CMake files"
  12.         VERBATIM
  13.     )
  14. endif()
  15. # 添加静态分析目标
  16. find_program(CMAKE_LINT cmake-lint)
  17. if(CMAKE_LINT)
  18.     add_custom_target(lint-cmake
  19.         COMMAND ${CMAKE_LINT} ${CMAKE_FILES}
  20.         COMMENT "Linting CMake files"
  21.         VERBATIM
  22.     )
  23. endif()
复制代码

实际案例分析

让我们通过一个实际案例来综合运用前面介绍的知识。假设我们要构建一个名为”ImageProcessor”的跨平台图像处理库,包含以下功能:

1. 基本图像操作(旋转、缩放、裁剪)
2. 滤镜功能(灰度、模糊、锐化)
3. 支持多种图像格式(JPEG、PNG)
4. 提供命令行工具和GUI界面

项目结构
  1. ImageProcessor/
  2. ├── CMakeLists.txt              # 主CMake配置文件
  3. ├── README.md
  4. ├── LICENSE
  5. ├── include/                    # 公共头文件
  6. │   └── imageprocessor/
  7. │       ├── core.h
  8. │       ├── filters.h
  9. │       ├── io.h
  10. │       └── utils.h
  11. ├── src/                        # 源代码
  12. │   ├── CMakeLists.txt
  13. │   ├── core/
  14. │   │   ├── CMakeLists.txt
  15. │   │   ├── image.cpp
  16. │   │   └── operations.cpp
  17. │   ├── filters/
  18. │   │   ├── CMakeLists.txt
  19. │   │   ├── grayscale.cpp
  20. │   │   ├── blur.cpp
  21. │   │   └── sharpen.cpp
  22. │   ├── io/
  23. │   │   ├── CMakeLists.txt
  24. │   │   ├── jpeg.cpp
  25. │   │   └── png.cpp
  26. │   └── utils/
  27. │       ├── CMakeLists.txt
  28. │       └── common.cpp
  29. ├── apps/                       # 应用程序
  30. │   ├── CMakeLists.txt
  31. │   ├── cli/
  32. │   │   ├── CMakeLists.txt
  33. │   │   └── main.cpp
  34. │   └── gui/
  35. │       ├── CMakeLists.txt
  36. │       └── main.cpp
  37. ├── tests/                      # 测试
  38. │   ├── CMakeLists.txt
  39. │   ├── test_core.cpp
  40. │   ├── test_filters.cpp
  41. │   └── test_io.cpp
  42. ├── cmake/                      # CMake模块
  43. │   └── FindSomeLib.cmake
  44. ├── docs/                       # 文档
  45. │   └── Doxyfile.in
  46. └── third_party/                # 第三方库
  47.     └── stb/
  48.         └── stb_image.h
复制代码

主CMakeLists.txt
  1. # 要求CMake最低版本
  2. cmake_minimum_required(VERSION 3.15)
  3. # 定义项目
  4. project(ImageProcessor
  5.     VERSION 1.0.0
  6.     DESCRIPTION "A cross-platform image processing library"
  7.     LANGUAGES CXX
  8. )
  9. # 设置C++标准
  10. set(CMAKE_CXX_STANDARD 17)
  11. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  12. # 设置输出目录
  13. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
  14. set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
  15. set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
  16. # 添加编译选项
  17. if(MSVC)
  18.     add_compile_options(/W4)
  19. else()
  20.     add_compile_options(-Wall -Wextra -Wpedantic)
  21. endif()
  22. # 查找依赖
  23. find_package(Threads REQUIRED)
  24. find_package(PkgConfig REQUIRED)
  25. pkg_check_modules(JPEG REQUIRED libjpeg)
  26. pkg_check_modules(PNG REQUIRED libpng)
  27. # 如果是GUI应用,查找Qt
  28. option(BUILD_GUI "Build GUI application" ON)
  29. if(BUILD_GUI)
  30.     find_package(Qt5 COMPONENTS Widgets Core Gui REQUIRED)
  31. endif()
  32. # 添加子目录
  33. add_subdirectory(src)
  34. add_subdirectory(apps)
  35. option(BUILD_TESTS "Build tests" ON)
  36. if(BUILD_TESTS)
  37.     enable_testing()
  38.     add_subdirectory(tests)
  39. endif()
  40. # 配置打包
  41. include(CPack)
复制代码

src/CMakeLists.txt
  1. # 添加子目录
  2. add_subdirectory(core)
  3. add_subdirectory(filters)
  4. add_subdirectory(io)
  5. add_subdirectory(utils)
  6. # 创建库
  7. add_library(imageprocessor
  8.     # 这里不需要列出源文件,因为子目录已经添加了
  9. )
  10. # 设置包含目录
  11. target_include_directories(imageprocessor
  12.     PUBLIC
  13.         $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
  14.         $<INSTALL_INTERFACE:include>
  15.     PRIVATE
  16.         ${CMAKE_CURRENT_SOURCE_DIR}
  17. )
  18. # 链接库
  19. target_link_libraries(imageprocessor
  20.     PUBLIC
  21.         Threads::Threads
  22.     PRIVATE
  23.         ${JPEG_LIBRARIES}
  24.         ${PNG_LIBRARIES}
  25. )
  26. # 设置属性
  27. set_target_properties(imageprocessor
  28.     PROPERTIES
  29.         VERSION ${PROJECT_VERSION}
  30.         SOVERSION ${PROJECT_VERSION_MAJOR}
  31.         PUBLIC_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/../include/imageprocessor/*.h"
  32. )
  33. # 安装规则
  34. install(TARGETS imageprocessor
  35.     EXPORT ImageProcessorTargets
  36.     LIBRARY DESTINATION lib
  37.     ARCHIVE DESTINATION lib
  38.     RUNTIME DESTINATION bin
  39.     PUBLIC_HEADER DESTINATION include/imageprocessor
  40. )
  41. # 导出配置
  42. install(EXPORT ImageProcessorTargets
  43.     FILE ImageProcessorTargets.cmake
  44.     NAMESPACE ImageProcessor::
  45.     DESTINATION lib/cmake/ImageProcessor
  46. )
  47. # 创建配置文件
  48. include(CMakePackageConfigHelpers)
  49. write_basic_package_version_file(
  50.     "${CMAKE_CURRENT_BINARY_DIR}/ImageProcessorConfigVersion.cmake"
  51.     VERSION ${PROJECT_VERSION}
  52.     COMPATIBILITY AnyNewerVersion
  53. )
  54. # 安装配置文件
  55. install(FILES
  56.     "${CMAKE_CURRENT_BINARY_DIR}/ImageProcessorConfigVersion.cmake"
  57.     "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ImageProcessorConfig.cmake"
  58.     DESTINATION lib/cmake/ImageProcessor
  59. )
复制代码

src/core/CMakeLists.txt
  1. # 源文件
  2. set(CORE_SOURCES
  3.     image.cpp
  4.     operations.cpp
  5. )
  6. # 创建库
  7. add_library(imageprocessor_core STATIC ${CORE_SOURCES})
  8. # 设置包含目录
  9. target_include_directories(imageprocessor_core
  10.     PUBLIC
  11.         ${CMAKE_CURRENT_SOURCE_DIR}/../../include
  12.     PRIVATE
  13.         ${CMAKE_CURRENT_SOURCE_DIR}
  14. )
  15. # 链接到主库
  16. target_link_libraries(imageprocessor
  17.     PRIVATE
  18.         imageprocessor_core
  19. )
复制代码

apps/cli/CMakeLists.txt
  1. # 源文件
  2. set(CLI_SOURCES
  3.     main.cpp
  4. )
  5. # 创建可执行文件
  6. add_executable(imageprocessor_cli ${CLI_SOURCES})
  7. # 链接库
  8. target_link_libraries(imageprocessor_cli
  9.     PRIVATE
  10.         ImageProcessor::imageprocessor
  11. )
  12. # 安装
  13. install(TARGETS imageprocessor_cli
  14.     RUNTIME DESTINATION bin
  15. )
复制代码

apps/gui/CMakeLists.txt
  1. # 如果没有找到Qt或者没有启用GUI,则跳过
  2. if(NOT BUILD_GUI OR NOT Qt5_FOUND)
  3.     return()
  4. endif()
  5. # 源文件
  6. set(GUI_SOURCES
  7.     main.cpp
  8.     mainwindow.cpp
  9.     mainwindow.h
  10. )
  11. # Qt自动化
  12. qt5_wrap_cpp(GUI_MOC ${GUI_HEADERS})
  13. qt5_wrap_ui(UI_HEADERS ${GUI_FORMS})
  14. qt5_add_resources(QRC_SOURCES ${RESOURCES})
  15. # 创建可执行文件
  16. add_executable(imageprocessor_gui
  17.     ${GUI_SOURCES}
  18.     ${GUI_MOC}
  19.     ${UI_HEADERS}
  20.     ${QRC_SOURCES}
  21. )
  22. # 链接库
  23. target_link_libraries(imageprocessor_gui
  24.     PRIVATE
  25.         ImageProcessor::imageprocessor
  26.         Qt5::Widgets
  27.         Qt5::Core
  28.         Qt5::Gui
  29. )
  30. # 安装
  31. install(TARGETS imageprocessor_gui
  32.     RUNTIME DESTINATION bin
  33. )
复制代码

tests/CMakeLists.txt
  1. # 查找GTest
  2. find_package(GTest REQUIRED)
  3. # 测试源文件
  4. set(TEST_SOURCES
  5.     test_core.cpp
  6.     test_filters.cpp
  7.     test_io.cpp
  8. )
  9. # 创建测试可执行文件
  10. add_executable(imageprocessor_tests ${TEST_SOURCES})
  11. # 链接库
  12. target_link_libraries(imageprocessor_tests
  13.     PRIVATE
  14.         ImageProcessor::imageprocessor
  15.         GTest::gtest
  16.         GTest::gtest_main
  17. )
  18. # 添加测试
  19. add_test(
  20.     NAME imageprocessor_tests
  21.     COMMAND imageprocessor_tests
  22. )
复制代码

cmake/ImageProcessorConfig.cmake
  1. include(CMakeFindDependencyMacro)
  2. # 查找依赖
  3. find_dependency(Threads)
  4. find_dependency(PkgConfig)
  5. pkg_check_modules(JPEG libjpeg)
  6. pkg_check_modules(PNG libpng)
  7. # 包含目标
  8. include("${CMAKE_CURRENT_LIST_DIR}/ImageProcessorTargets.cmake")
复制代码

docs/Doxyfile.in
  1. # Doxygen配置文件
  2. PROJECT_NAME           = "ImageProcessor"
  3. PROJECT_NUMBER         = @PROJECT_VERSION@
  4. OUTPUT_DIRECTORY       = @CMAKE_CURRENT_BINARY_DIR@/docs
  5. INPUT                  = @CMAKE_CURRENT_SOURCE_DIR@/include @CMAKE_CURRENT_SOURCE_DIR@/src
  6. RECURSIVE              = YES
  7. GENERATE_HTML          = YES
  8. GENERATE_LATEX         = NO
复制代码

构建和使用

构建这个项目的步骤如下:
  1. # 创建构建目录
  2. mkdir build && cd build
  3. # 配置项目
  4. cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DBUILD_GUI=ON
  5. # 构建项目
  6. cmake --build . --config Release
  7. # 运行测试
  8. ctest --output-on-failure
  9. # 安装
  10. cmake --install . --prefix /usr/local
  11. # 创建包
  12. cpack -G DEB  # 或 CPack -G NSIS, CPack -G DragNDrop 等
复制代码

在其他项目中使用这个库:
  1. # 查找ImageProcessor
  2. find_package(ImageProcessor REQUIRED)
  3. # 使用它
  4. target_link_libraries(my_app
  5.     PRIVATE
  6.         ImageProcessor::imageprocessor
  7. )
复制代码

总结

CMake是一个功能强大、灵活的跨平台构建工具,通过合理使用CMake,我们可以构建高效、可维护的项目构建系统。本文介绍了CMake的基础知识、常用命令、条件编译、依赖管理、安装打包等内容,并通过一个实际案例展示了如何综合运用这些知识。

在实际项目中,以下是一些关键的最佳实践:

1. 使用现代CMake实践,优先使用基于目标的命令而不是全局命令。
2. 合理组织项目结构,将源代码、头文件、测试和应用程序分离开来。
3. 使用条件编译和生成器表达式处理跨平台差异。
4. 使用find_package()或FetchContent管理依赖,避免硬编码路径。
5. 导出目标配置,使其他项目可以轻松使用你的库。
6. 使用测试框架(如CTest)确保代码质量。
7. 使用CPack创建安装包,简化分发过程。

通过遵循这些实践,你可以构建出高效、可维护的跨平台项目构建系统,使你的项目更加专业和易于管理。

CMake的学习曲线可能较陡,但一旦掌握,它将成为你开发工具箱中不可或缺的工具。希望本文能够帮助你更好地理解和使用CMake,构建出优秀的软件项目。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

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

本版积分规则