活动公告

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

深入浅出CMake编程从基础语法到高级技巧的完整代码示例与业界最佳实践指南助您轻松掌握项目构建

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-10-1 15:40:00 | 显示全部楼层 |阅读模式

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

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

x
1. 引言

CMake是一个开源、跨平台的构建自动化工具,它使用平台无关的配置文件来生成标准的构建文件(如Unix的Makefile或Windows Visual Studio的项目文件)。CMake被广泛用于C++项目,但也支持其他语言。通过掌握CMake,开发者可以轻松管理复杂项目的构建过程,处理依赖关系,并实现跨平台开发。

本文将从CMake的基础语法开始,逐步深入到高级技巧和最佳实践,帮助读者全面掌握CMake编程。每个概念都会配有详细的代码示例,以便读者理解和应用。

2. CMake基础

2.1 CMake简介和安装

CMake(Cross-platform Make)是一个构建系统生成器,它不直接构建软件,而是生成构建文件(如Makefile或Visual Studio项目),然后使用这些构建文件来编译和链接代码。

在大多数Linux系统上,可以通过包管理器安装:
  1. # Ubuntu/Debian
  2. sudo apt-get install cmake
  3. # Fedora
  4. sudo dnf install cmake
  5. # Arch Linux
  6. sudo pacman -S cmake
复制代码

在macOS上,可以使用Homebrew:
  1. brew install cmake
复制代码

在Windows上,可以从CMake官方网站下载安装程序。

安装完成后,可以通过以下命令验证安装:
  1. cmake --version
复制代码

2.2 基本语法和命令

CMake脚本使用CMake语言编写,文件通常命名为CMakeLists.txt。CMake语言简单直观,主要由命令(函数)调用组成。

让我们从一个简单的例子开始:
  1. # 设置CMake最低版本要求
  2. cmake_minimum_required(VERSION 3.10)
  3. # 项目名称
  4. project(MyFirstProject)
  5. # 添加一个可执行文件
  6. add_executable(my_app main.cpp)
复制代码

这个简单的CMakeLists.txt文件做了三件事:

1. 指定需要的CMake最低版本
2. 定义项目名称
3. 添加一个可执行文件目标,从main.cpp源文件构建

以下是一些常用的CMake基本命令:
  1. # 设置变量
  2. set(VARIABLE_NAME value)
  3. # 获取变量值
  4. message(STATUS "Variable value: ${VARIABLE_NAME}")
  5. # 添加子目录
  6. add_subdirectory(source_dir)
  7. # 查找源文件
  8. file(GLOB SOURCES "src/*.cpp")
  9. # 条件语句
  10. if(condition)
  11.     # 执行命令
  12. elseif(condition2)
  13.     # 执行命令
  14. else()
  15.     # 执行命令
  16. endif()
  17. # 循环
  18. foreach(var IN ITEMS item1 item2 item3)
  19.     message(STATUS "Item: ${var}")
  20. endforeach()
复制代码

2.3 变量和作用域

在CMake中,变量使用set()命令设置,通过${VAR_NAME}语法引用。
  1. # 设置变量
  2. set(MY_VARIABLE "Hello World")
  3. # 引用变量
  4. message(STATUS "${MY_VARIABLE}")
  5. # 列表变量
  6. set(MY_LIST item1 item2 item3)
  7. message(STATUS "List items: ${MY_LIST}")
复制代码

CMake有几种不同的变量作用域:

1. 函数作用域:在函数内部设置的变量,默认情况下只在函数内部可见
2. 目录作用域:在CMakeLists.txt文件中设置的变量,对该文件及其子目录可见
3. 缓存变量:持久化变量,存储在CMakeCache.txt中,跨CMake运行保持不变
  1. # 设置普通变量
  2. set(LOCAL_VAR "local value")
  3. # 设置缓存变量
  4. set(CACHE_VAR "cache value" CACHE STRING "A cache variable")
  5. # 设置父作用域变量(在函数中使用)
  6. set(PARENT_VAR "parent value" PARENT_SCOPE)
复制代码

2.4 条件语句和循环

CMake支持标准的条件语句,使用if()、elseif()、else()和endif():
  1. set(MY_VARIABLE "some_value")
  2. if(MY_VARIABLE)
  3.     message(STATUS "MY_VARIABLE is set to: ${MY_VARIABLE}")
  4. endif()
  5. # 检查变量是否等于特定值
  6. if(MY_VARIABLE STREQUAL "some_value")
  7.     message(STATUS "MY_VARIABLE equals 'some_value'")
  8. elseif(MY_VARIABLE STREQUAL "other_value")
  9.     message(STATUS "MY_VARIABLE equals 'other_value'")
  10. else()
  11.     message(STATUS "MY_VARIABLE has a different value")
  12. endif()
  13. # 数值比较
  14. set(NUMBER 10)
  15. if(NUMBER GREATER 5)
  16.     message(STATUS "${NUMBER} is greater than 5")
  17. endif()
  18. # 检查是否定义了变量
  19. if(DEFINED MY_VARIABLE)
  20.     message(STATUS "MY_VARIABLE is defined")
  21. endif()
复制代码

CMake提供了几种循环结构:
  1. # foreach循环
  2. foreach(var IN ITEMS item1 item2 item3)
  3.     message(STATUS "Item: ${var}")
  4. endforeach()
  5. # 数字范围循环
  6. foreach(i RANGE 0 5)
  7.     message(STATUS "Number: ${i}")
  8. endforeach()
  9. # 步进循环
  10. foreach(i RANGE 0 10 2)
  11.     message(STATUS "Even number: ${i}")
  12. endforeach()
  13. # while循环
  14. set(i 0)
  15. while(i LESS 5)
  16.     message(STATUS "i is: ${i}")
  17.     math(EXPR i "${i} + 1")
  18. endwhile()
复制代码

3. 项目配置

3.1 项目设置

project()命令是CMake项目的基础,它设置项目名称,并可选地指定版本、语言等:
  1. # 基本项目设置
  2. project(MyProject)
  3. # 带版本信息的项目设置
  4. project(MyProject
  5.     VERSION 1.0.2
  6.     DESCRIPTION "My awesome project"
  7.     LANGUAGES CXX C
  8. )
  9. # 访问项目信息
  10. message(STATUS "Project name: ${PROJECT_NAME}")
  11. message(STATUS "Project version: ${PROJECT_VERSION}")
  12. message(STATUS "Project version major: ${PROJECT_VERSION_MAJOR}")
  13. message(STATUS "Project version minor: ${PROJECT_VERSION_MINOR}")
  14. message(STATUS "Project version patch: ${PROJECT_VERSION_PATCH}")
复制代码

3.2 查找依赖包

CMake的find_package()命令是查找和使用外部依赖的关键:
  1. # 查找Boost库
  2. find_package(Boost 1.66 REQUIRED COMPONENTS filesystem system)
  3. # 如果找到,使用Boost
  4. if(Boost_FOUND)
  5.     message(STATUS "Boost found")
  6.     message(STATUS "Boost include directories: ${Boost_INCLUDE_DIRS}")
  7.     message(STATUS "Boost libraries: ${Boost_LIBRARIES}")
  8.    
  9.     # 将Boost的include目录添加到目标
  10.     include_directories(${Boost_INCLUDE_DIRS})
  11. endif()
  12. # 查找其他常见库
  13. find_package(OpenGL REQUIRED)
  14. find_package(PkgConfig REQUIRED)
  15. pkg_check_modules(SDL2 REQUIRED sdl2)
复制代码

在现代CMake(3.0+)中,推荐使用目标属性和target_link_libraries()来处理依赖:
  1. # 创建一个可执行文件
  2. add_executable(my_app main.cpp)
  3. # 链接库
  4. target_link_libraries(my_app
  5.     PRIVATE
  6.         Boost::filesystem
  7.         Boost::system
  8.         OpenGL::GL
  9.         ${SDL2_LIBRARIES}
  10. )
  11. # 包含目录
  12. target_include_directories(my_app
  13.     PRIVATE
  14.         ${SDL2_INCLUDE_DIRS}
  15. )
复制代码

3.3 编译选项和配置

CMake允许设置各种编译选项和配置:
  1. # 设置C++标准
  2. set(CMAKE_CXX_STANDARD 17)
  3. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  4. set(CMAKE_CXX_EXTENSIONS OFF)
  5. # 编译器标志
  6. add_compile_options(-Wall -Wextra -Wpedantic)
  7. # 调试和发布模式的不同设置
  8. set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
  9. set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
  10. # 使用生成器表达式设置特定配置的编译标志
  11. target_compile_options(my_app
  12.     PRIVATE
  13.         $<$<CONFIG:Debug>:-g -O0>
  14.         $<$<CONFIG:Release>:-O3 -DNDEBUG>
  15. )
  16. # 定义宏
  17. target_compile_definitions(my_app
  18.     PRIVATE
  19.         MY_PROJECT_VERSION="${PROJECT_VERSION}"
  20.         $<$<CONFIG:Debug>:DEBUG_MODE>
  21. )
复制代码

4. 构建系统

4.1 可执行文件和库的创建
  1. # 从单个源文件创建可执行文件
  2. add_executable(my_app main.cpp)
  3. # 从多个源文件创建可执行文件
  4. add_executable(my_app
  5.     main.cpp
  6.     utils.cpp
  7.     utils.h
  8. )
  9. # 使用变量存储源文件
  10. set(SOURCES
  11.     main.cpp
  12.     utils.cpp
  13.     utils.h
  14. )
  15. add_executable(my_app ${SOURCES})
  16. # 使用file命令自动查找源文件
  17. file(GLOB SOURCES "src/*.cpp")
  18. add_executable(my_app ${SOURCES})
复制代码
  1. # 创建静态库
  2. add_library(my_static_lib STATIC
  3.     lib_source1.cpp
  4.     lib_source2.cpp
  5. )
  6. # 创建共享库
  7. add_library(my_shared_lib SHARED
  8.     lib_source1.cpp
  9.     lib_source2.cpp
  10. )
  11. # 创建对象库(不生成归档文件,只编译为对象文件)
  12. add_library(my_object_lib OBJECT
  13.     lib_source1.cpp
  14.     lib_source2.cpp
  15. )
  16. # 创建接口库(只包含接口,不编译源文件)
  17. add_library(my_interface_lib INTERFACE)
  18. target_include_directories(my_interface_lib
  19.     INTERFACE
  20.         ${CMAKE_CURRENT_SOURCE_DIR}/include
  21. )
复制代码

4.2 目标属性

现代CMake强调使用目标属性而不是全局变量:
  1. # 创建库
  2. add_library(my_lib lib.cpp)
  3. # 设置库的包含目录
  4. target_include_directories(my_lib
  5.     PUBLIC
  6.         ${CMAKE_CURRENT_SOURCE_DIR}/include
  7.     PRIVATE
  8.         ${CMAKE_CURRENT_SOURCE_DIR}/src
  9. )
  10. # 设置库的编译定义
  11. target_compile_definitions(my_lib
  12.     PRIVATE
  13.         LIB_INTERNAL_DEFINE
  14.     PUBLIC
  15.         LIB_PUBLIC_DEFINE
  16. )
  17. # 设置库的编译选项
  18. target_compile_options(my_lib
  19.     PRIVATE
  20.         -Wall
  21.     PUBLIC
  22.         -fno-exceptions
  23. )
  24. # 链接其他库
  25. target_link_libraries(my_lib
  26.     PUBLIC
  27.         Boost::filesystem
  28.     PRIVATE
  29.         some_internal_lib
  30. )
  31. # 创建可执行文件并链接库
  32. add_executable(my_app main.cpp)
  33. target_link_libraries(my_app
  34.     PRIVATE
  35.         my_lib
  36. )
复制代码

4.3 安装规则

CMake提供了安装规则,允许用户通过make install(或类似命令)安装项目:
  1. # 安装目标
  2. install(TARGETS my_lib my_app
  3.     # 库的安装位置
  4.     LIBRARY DESTINATION lib
  5.     ARCHIVE DESTINATION lib
  6.     RUNTIME DESTINATION bin
  7.     # 公共头文件
  8.     PUBLIC_HEADER DESTINATION include
  9. )
  10. # 安装文件
  11. install(FILES
  12.     ${CMAKE_CURRENT_SOURCE_DIR}/README.md
  13.     DESTINATION doc/my_project
  14. )
  15. # 安装目录
  16. install(DIRECTORY
  17.     ${CMAKE_CURRENT_SOURCE_DIR}/include/
  18.     DESTINATION include
  19. )
  20. # 安装脚本
  21. install(SCRIPT
  22.     cmake/post_install.cmake
  23. )
  24. # 导出配置
  25. install(EXPORT MyProjectTargets
  26.     FILE MyProjectTargets.cmake
  27.     DESTINATION lib/cmake/MyProject
  28. )
复制代码

5. 高级技巧

5.1 自定义函数和宏

CMake允许定义自定义函数和宏来封装常用逻辑:
  1. # 定义一个简单的函数
  2. function(print_message msg)
  3.     message(STATUS "Custom message: ${msg}")
  4. endfunction()
  5. # 调用函数
  6. print_message("Hello from function")
  7. # 带返回值的函数
  8. function(get_project_version result_var)
  9.     set(${result_var} ${PROJECT_VERSION} PARENT_SCOPE)
  10. endfunction()
  11. # 调用函数并获取结果
  12. get_project_version(PROJ_VER)
  13. message(STATUS "Project version: ${PROJ_VER}")
  14. # 带参数的函数
  15. function(create_simple_executable name sources)
  16.     add_executable(${name} ${sources})
  17.     target_include_directories(${name}
  18.         PRIVATE
  19.             ${CMAKE_CURRENT_SOURCE_DIR}/include
  20.     )
  21.     target_compile_features(${name}
  22.         PRIVATE
  23.             cxx_std_17
  24.     )
  25. endfunction()
  26. # 使用函数创建可执行文件
  27. create_simple_executable(my_app main.cpp utils.cpp)
复制代码
  1. # 定义一个宏
  2. macro(print_macro_message msg)
  3.     message(STATUS "Macro message: ${msg}")
  4.     # 注意:宏中的变量会直接替换,而不是创建新作用域
  5.     set(msg "Modified in macro")
  6.     message(STATUS "Modified message: ${msg}")
  7. endmacro()
  8. # 调用宏
  9. set(original_msg "Original message")
  10. print_macro_message(${original_msg})
  11. message(STATUS "After macro call: ${original_msg}")  # 输出: "Modified in macro"
复制代码

5.2 生成器和表达式

CMake提供了强大的生成器表达式,允许在生成时根据不同条件选择值:
  1. # 基本生成器表达式
  2. add_executable(my_app main.cpp)
  3. # 根据配置类型设置不同的定义
  4. target_compile_definitions(my_app
  5.     PRIVATE
  6.         $<$<CONFIG:Debug>:DEBUG_MODE>
  7.         $<$<CONFIG:Release>:NDEBUG>
  8. )
  9. # 根据平台设置不同的编译选项
  10. target_compile_options(my_app
  11.     PRIVATE
  12.         $<$<PLATFORM_ID:Linux>:-fPIC>
  13.         $<$<PLATFORM_ID:Windows>:/WX>
  14. )
  15. # 根据编译器设置不同的标志
  16. target_compile_options(my_app
  17.     PRIVATE
  18.         $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
  19.         $<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra>
  20.         $<$<CXX_COMPILER_ID:MSVC>:/W4>
  21. )
  22. # 条件链接
  23. target_link_libraries(my_app
  24.     PRIVATE
  25.         $<$<BOOL:${USE_OPENMP}>:OpenMP::OpenMP_CXX>
  26. )
复制代码

5.3 测试和打包

CMake支持与CTest集成进行测试:
  1. # 启用测试
  2. enable_testing()
  3. # 添加测试
  4. add_executable(my_test test_main.cpp test_utils.cpp)
  5. target_link_libraries(my_test PRIVATE my_lib)
  6. # 注册测试
  7. add_test(NAME MyTest COMMAND my_test)
  8. # 带参数的测试
  9. add_test(NAME MyTestWithArgs COMMAND my_test --verbose)
  10. # 设置测试属性
  11. set_tests_properties(MyTest PROPERTIES
  12.     WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
  13.     TIMEOUT 30
  14. )
  15. # 添加多个测试
  16. function(add_unit_test name sources)
  17.     add_executable(${name} ${sources})
  18.     target_link_libraries(${name} PRIVATE my_lib)
  19.     add_test(NAME ${name} COMMAND ${name})
  20. endfunction()
  21. add_unit_test(utils_test test_utils.cpp)
  22. add_unit_test(math_test test_math.cpp)
复制代码

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. set(CPACK_GENERATOR "TGZ;ZIP")
  8. # 设置包文件名
  9. set(CPACK_PACKAGE_FILE_NAME "myproject-${PROJECT_VERSION}")
  10. # 设置包依赖
  11. set(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-all-dev (>= 1.66)")
  12. # 包含许可证文件
  13. install(FILES LICENSE.txt DESTINATION .)
  14. # 包含CPack
  15. include(CPack)
复制代码

5.4 跨平台考虑

CMake的一个主要优势是跨平台支持。以下是一些处理跨平台差异的技巧:
  1. # 检测操作系统
  2. if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  3.     message(STATUS "Building for Linux")
  4.     add_definitions(-DLINUX)
  5. elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
  6.     message(STATUS "Building for Windows")
  7.     add_definitions(-DWINDOWS)
  8. elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
  9.     message(STATUS "Building for macOS")
  10.     add_definitions(-DMACOS)
  11. endif()
  12. # 检测处理器架构
  13. if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
  14.     message(STATUS "64-bit architecture")
  15.     add_definitions(-DARCH_64BIT)
  16. else()
  17.     message(STATUS "32-bit architecture")
  18.     add_definitions(-DARCH_32BIT)
  19. endif()
  20. # 跨平台路径处理
  21. file(TO_CMAKE_PATH "/path/to/file" FILE_PATH)
  22. file(TO_NATIVE_PATH "${FILE_PATH}" NATIVE_PATH)
  23. message(STATUS "Native path: ${NATIVE_PATH}")
  24. # 跨平台库文件扩展
  25. if(WIN32)
  26.     set(LIB_EXT ".lib")
  27. elseif(APPLE)
  28.     set(LIB_EXT ".dylib")
  29. else()
  30.     set(LIB_EXT ".so")
  31. endif()
  32. # 使用生成器表达式处理平台特定代码
  33. target_sources(my_app
  34.     PRIVATE
  35.         common.cpp
  36.         $<$<PLATFORM_ID:Linux>:linux_specific.cpp>
  37.         $<$<PLATFORM_ID:Windows>:windows_specific.cpp>
  38.         $<$<PLATFORM_ID:Darwin>:macos_specific.cpp>
  39. )
复制代码

6. 最佳实践

6.1 项目结构组织

良好的项目结构使CMake项目更易于维护和理解。以下是一个推荐的项目结构:
  1. myproject/
  2. ├── CMakeLists.txt          # 主CMakeLists.txt文件
  3. ├── README.md
  4. ├── LICENSE.txt
  5. ├── cmake/                  # CMake模块和脚本
  6. │   ├── FindSomeLib.cmake   # 自定义查找模块
  7. │   └── MyProjectUtils.cmake # 自定义CMake函数
  8. ├── include/                # 公共头文件
  9. │   └── myproject/
  10. │       ├── utils.h
  11. │       └── api.h
  12. ├── src/                    # 源文件
  13. │   ├── utils.cpp
  14. │   └── main.cpp
  15. ├── tests/                  # 测试
  16. │   ├── CMakeLists.txt
  17. │   ├── test_utils.cpp
  18. │   └── test_api.cpp
  19. ├── examples/               # 示例
  20. │   ├── CMakeLists.txt
  21. │   └── simple_example.cpp
  22. ├── docs/                   # 文档
  23. │   └── user_guide.md
  24. └── third_party/            # 第三方库
  25.     └── some_lib/
复制代码

对应的顶级CMakeLists.txt文件:
  1. cmake_minimum_required(VERSION 3.15)
  2. project(MyProject
  3.     VERSION 1.0.0
  4.     DESCRIPTION "My awesome project"
  5.     LANGUAGES CXX
  6. )
  7. # 设置C++标准
  8. set(CMAKE_CXX_STANDARD 17)
  9. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  10. set(CMAKE_CXX_EXTENSIONS OFF)
  11. # 添加cmake模块路径
  12. list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
  13. # 选项
  14. option(BUILD_TESTS "Build tests" ON)
  15. option(BUILD_EXAMPLES "Build examples" ON)
  16. # 查找依赖
  17. find_package(Boost 1.66 REQUIRED COMPONENTS filesystem system)
  18. # 添加子目录
  19. add_subdirectory(src)
  20. add_subdirectory(include)
  21. if(BUILD_TESTS)
  22.     add_subdirectory(tests)
  23. endif()
  24. if(BUILD_EXAMPLES)
  25.     add_subdirectory(examples)
  26. endif()
  27. # 打包支持
  28. include(CPack)
复制代码

6.2 模块化设计

将CMake代码模块化可以提高可重用性和可维护性:
  1. # cmake/MyProjectUtils.cmake
  2. # 设置编译器警告选项
  3. function(set_project_warnings target_name)
  4.     set(MSVC_WARNINGS
  5.         /W4
  6.         /permissive-
  7.     )
  8.     set(CLANG_WARNINGS
  9.         -Wall
  10.         -Wextra
  11.         -Wpedantic
  12.     )
  13.     set(GCC_WARNINGS
  14.         ${CLANG_WARNINGS}
  15.         -Wmisleading-indentation
  16.     )
  17.     if(MSVC)
  18.         set(WARNINGS ${MSVC_WARNINGS})
  19.     elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  20.         set(WARNINGS ${CLANG_WARNINGS})
  21.     elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
  22.         set(WARNINGS ${GCC_WARNINGS})
  23.     else()
  24.         message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}'")
  25.     endif()
  26.     target_compile_options(${target_name} PRIVATE ${WARNINGS})
  27. endfunction()
  28. # 设置C++标准
  29. function(set_project_cxx_standard target_name std_version)
  30.     set_target_properties(${target_name} PROPERTIES
  31.         CXX_STANDARD ${std_version}
  32.         CXX_STANDARD_REQUIRED ON
  33.         CXX_EXTENSIONS OFF
  34.     )
  35. endfunction()
复制代码

使用自定义模块:
  1. # 在主CMakeLists.txt中
  2. include(MyProjectUtils)
  3. add_library(my_lib src/my_lib.cpp)
  4. set_project_warnings(my_lib)
  5. set_project_cxx_standard(my_lib 17)
复制代码

6.3 版本控制

良好的版本控制策略对于CMake项目很重要:
  1. # 从Git标签获取版本信息
  2. find_package(Git QUIET)
  3. if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git")
  4.     execute_process(
  5.         COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0
  6.         WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  7.         OUTPUT_VARIABLE GIT_TAG
  8.         OUTPUT_STRIP_TRAILING_WHITESPACE
  9.         ERROR_QUIET
  10.     )
  11.    
  12.     execute_process(
  13.         COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
  14.         WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  15.         OUTPUT_VARIABLE GIT_COMMIT_HASH
  16.         OUTPUT_STRIP_TRAILING_WHITESPACE
  17.         ERROR_QUIET
  18.     )
  19.    
  20.     if(GIT_TAG)
  21.         set(PROJECT_VERSION ${GIT_TAG})
  22.     endif()
  23.    
  24.     if(GIT_COMMIT_HASH)
  25.         set(PROJECT_COMMIT ${GIT_COMMIT_HASH})
  26.     endif()
  27. endif()
  28. # 配置版本头文件
  29. configure_file(
  30.     ${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in
  31.     ${CMAKE_CURRENT_BINARY_DIR}/include/version.h
  32.     @ONLY
  33. )
  34. # 确保生成的头文件被包含
  35. include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
复制代码

对应的version.h.in模板文件:
  1. #pragma once
  2. #define PROJECT_VERSION "@PROJECT_VERSION@"
  3. #define PROJECT_COMMIT "@PROJECT_COMMIT@"
  4. #define PROJECT_VERSION_MAJOR "@PROJECT_VERSION_MAJOR@"
  5. #define PROJECT_VERSION_MINOR "@PROJECT_VERSION_MINOR@"
  6. #define PROJECT_VERSION_PATCH "@PROJECT_VERSION_PATCH@"
复制代码

6.4 性能优化

CMake项目本身的性能优化也很重要,特别是对于大型项目:
  1. # 减少冗余配置
  2. # 使用CMAKE_CACHE_ARGS而不是多次调用ExternalProject_Add
  3. set(CMAKE_ARGS
  4.     -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
  5.     -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD}
  6. )
  7. # 使用目标属性而不是全局变量
  8. # 避免使用include_directories()和link_directories()
  9. target_include_directories(my_target
  10.     PRIVATE
  11.         ${CMAKE_CURRENT_SOURCE_DIR}/include
  12. )
  13. # 使用编译器预编译头
  14. if(MSVC OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  15.     target_precompile_headers(my_lib
  16.         PRIVATE
  17.             ${CMAKE_CURRENT_SOURCE_DIR}/include/pch.h
  18.     )
  19. endif()
  20. # 使用UNITY_BUILD(如果支持)加速编译
  21. if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.16)
  22.     set_target_properties(my_lib PROPERTIES
  23.         UNITY_BUILD ON
  24.         UNITY_BUILD_MODE BATCH
  25.     )
  26. endif()
  27. # 使用CMAKE_DISABLE_SOURCE_CHANGES防止源目录被修改
  28. set(CMAKE_DISABLE_SOURCE_CHANGES ON)
  29. # 使用CMAKE_DISABLE_IN_SOURCE_BUILD防止源内构建
  30. if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
  31.     message(FATAL_ERROR "In-source builds are not allowed. Please create a separate build directory.")
  32. endif()
复制代码

7. 实际案例

7.1 简单项目示例

让我们创建一个简单的项目,包含一个库和一个可执行文件:

项目结构:
  1. simple_project/
  2. ├── CMakeLists.txt
  3. ├── include/
  4. │   └── greeter.h
  5. └── src/
  6.     ├── greeter.cpp
  7.     └── main.cpp
复制代码

include/greeter.h:
  1. #pragma once
  2. #include <string>
  3. class Greeter {
  4. public:
  5.     explicit Greeter(const std::string& name);
  6.     void greet() const;
  7. private:
  8.     std::string name_;
  9. };
复制代码

src/greeter.cpp:
  1. #include "greeter.h"
  2. #include <iostream>
  3. Greeter::Greeter(const std::string& name) : name_(name) {}
  4. void Greeter::greet() const {
  5.     std::cout << "Hello, " << name_ << "!" << std::endl;
  6. }
复制代码

src/main.cpp:
  1. #include "greeter.h"
  2. int main() {
  3.     Greeter greeter("World");
  4.     greeter.greet();
  5.     return 0;
  6. }
复制代码

CMakeLists.txt:
  1. cmake_minimum_required(VERSION 3.10)
  2. project(SimpleProject
  3.     VERSION 1.0.0
  4.     DESCRIPTION "A simple project example"
  5.     LANGUAGES CXX
  6. )
  7. # 设置C++标准
  8. set(CMAKE_CXX_STANDARD 14)
  9. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  10. # 添加库
  11. add_library(greeter_lib
  12.     src/greeter.cpp
  13. )
  14. target_include_directories(greeter_lib
  15.     PUBLIC
  16.         ${CMAKE_CURRENT_SOURCE_DIR}/include
  17. )
  18. # 添加可执行文件
  19. add_executable(greeter_app
  20.     src/main.cpp
  21. )
  22. target_link_libraries(greeter_app
  23.     PRIVATE
  24.         greeter_lib
  25. )
  26. # 安装规则
  27. install(TARGETS greeter_lib greeter_app
  28.     LIBRARY DESTINATION lib
  29.     RUNTIME DESTINATION bin
  30. )
  31. install(DIRECTORY include/
  32.     DESTINATION include
  33. )
复制代码

7.2 复杂项目示例

现在看一个更复杂的项目,包含多个库、测试和第三方依赖:

项目结构:
  1. complex_project/
  2. ├── CMakeLists.txt
  3. ├── cmake/
  4. │   └── ProjectUtils.cmake
  5. ├── include/
  6. │   └── project/
  7. │       ├── core.h
  8. │       └── utils.h
  9. ├── src/
  10. │   ├── core/
  11. │   │   ├── CMakeLists.txt
  12. │   │   └── core.cpp
  13. │   ├── utils/
  14. │   │   ├── CMakeLists.txt
  15. │   │   └── utils.cpp
  16. │   └── app/
  17. │       ├── CMakeLists.txt
  18. │       └── main.cpp
  19. ├── tests/
  20. │   ├── CMakeLists.txt
  21. │   ├── test_core.cpp
  22. │   └── test_utils.cpp
  23. ├── external/
  24. │   └── CMakeLists.txt
  25. └── docs/
  26.     └── CMakeLists.txt
复制代码

主CMakeLists.txt:
  1. cmake_minimum_required(VERSION 3.15)
  2. project(ComplexProject
  3.     VERSION 1.0.0
  4.     DESCRIPTION "A complex project example"
  5.     LANGUAGES CXX
  6. )
  7. # 添加cmake模块路径
  8. list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
  9. # 包含项目工具
  10. include(ProjectUtils)
  11. # 设置C++标准
  12. set(CMAKE_CXX_STANDARD 17)
  13. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  14. set(CMAKE_CXX_EXTENSIONS OFF)
  15. # 选项
  16. option(BUILD_TESTS "Build tests" ON)
  17. option(BUILD_DOCS "Build documentation" OFF)
  18. # 添加第三方依赖
  19. add_subdirectory(external)
  20. # 添加子目录
  21. add_subdirectory(include)
  22. add_subdirectory(src)
  23. add_subdirectory(tests)
  24. if(BUILD_DOCS)
  25.     add_subdirectory(docs)
  26. endif()
  27. # 配置和打包
  28. include(CPack)
复制代码

cmake/ProjectUtils.cmake:
  1. # 设置常用编译器警告
  2. function(set_target_warnings target_name)
  3.     set(MSVC_WARNINGS
  4.         /W4
  5.         /permissive-
  6.     )
  7.     set(CLANG_WARNINGS
  8.         -Wall
  9.         -Wextra
  10.         -Wpedantic
  11.     )
  12.     set(GCC_WARNINGS
  13.         ${CLANG_WARNINGS}
  14.         -Wmisleading-indentation
  15.     )
  16.     if(MSVC)
  17.         set(WARNINGS ${MSVC_WARNINGS})
  18.     elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  19.         set(WARNINGS ${CLANG_WARNINGS})
  20.     elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
  21.         set(WARNINGS ${GCC_WARNINGS})
  22.     else()
  23.         message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}'")
  24.     endif()
  25.     target_compile_options(${target_name} PRIVATE ${WARNINGS})
  26. endfunction()
  27. # 设置目标属性
  28. function(set_project_target_properties target_name)
  29.     set_target_properties(${target_name} PROPERTIES
  30.         CXX_STANDARD 17
  31.         CXX_STANDARD_REQUIRED ON
  32.         CXX_EXTENSIONS OFF
  33.     )
  34.    
  35.     set_target_warnings(${target_name})
  36. endfunction()
复制代码

src/core/CMakeLists.txt:
  1. add_library(core_lib
  2.     core.cpp
  3. )
  4. target_include_directories(core_lib
  5.     PUBLIC
  6.         ${CMAKE_CURRENT_SOURCE_DIR}/../../include
  7. )
  8. set_project_target_properties(core_lib)
  9. # 链接第三方库
  10. target_link_libraries(core_lib
  11.     PUBLIC
  12.         fmt::fmt
  13. )
复制代码

src/utils/CMakeLists.txt:
  1. add_library(utils_lib
  2.     utils.cpp
  3. )
  4. target_include_directories(utils_lib
  5.     PUBLIC
  6.         ${CMAKE_CURRENT_SOURCE_DIR}/../../include
  7. )
  8. set_project_target_properties(utils_lib)
  9. # 链接核心库
  10. target_link_libraries(utils_lib
  11.     PUBLIC
  12.         core_lib
  13. )
复制代码

src/app/CMakeLists.txt:
  1. add_executable(complex_app
  2.     main.cpp
  3. )
  4. target_include_directories(complex_app
  5.     PRIVATE
  6.         ${CMAKE_CURRENT_SOURCE_DIR}/../../include
  7. )
  8. set_project_target_properties(complex_app)
  9. # 链接所有库
  10. target_link_libraries(complex_app
  11.     PRIVATE
  12.         core_lib
  13.         utils_lib
  14. )
  15. # 安装规则
  16. install(TARGETS complex_app
  17.     RUNTIME DESTINATION bin
  18. )
复制代码

tests/CMakeLists.txt:
  1. enable_testing()
  2. add_executable(test_suite
  3.     test_core.cpp
  4.     test_utils.cpp
  5. )
  6. target_include_directories(test_suite
  7.     PRIVATE
  8.         ${CMAKE_CURRENT_SOURCE_DIR}/../include
  9. )
  10. target_link_libraries(test_suite
  11.     PRIVATE
  12.         core_lib
  13.         utils_lib
  14.         Catch2::Catch2
  15. )
  16. add_test(NAME TestSuite COMMAND test_suite)
复制代码

external/CMakeLists.txt:
  1. # 使用FetchContent下载第三方库
  2. include(FetchContent)
  3. # 添加fmt库
  4. FetchContent_Declare(
  5.     fmt
  6.     GIT_REPOSITORY https://github.com/fmtlib/fmt.git
  7.     GIT_TAG 8.1.1
  8. )
  9. FetchContent_MakeAvailable(fmt)
  10. # 添加Catch2测试框架
  11. FetchContent_Declare(
  12.     Catch2
  13.     GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  14.     GIT_TAG v3.0.1
  15. )
  16. FetchContent_MakeAvailable(Catch2)
复制代码

7.3 第三方库集成

CMake提供了多种方式来集成第三方库,以下是几种常见的方法:
  1. # 查找已安装的库
  2. find_package(Boost 1.66 REQUIRED COMPONENTS filesystem system)
  3. find_package(OpenGL REQUIRED)
  4. find_package(PkgConfig REQUIRED)
  5. pkg_check_modules(SDL2 REQUIRED sdl2)
  6. # 使用找到的库
  7. add_executable(my_app main.cpp)
  8. target_link_libraries(my_app
  9.     PRIVATE
  10.         Boost::filesystem
  11.         Boost::system
  12.         OpenGL::GL
  13.         ${SDL2_LIBRARIES}
  14. )
  15. target_include_directories(my_app
  16.     PRIVATE
  17.         ${SDL2_INCLUDE_DIRS}
  18. )
复制代码
  1. include(FetchContent)
  2. # 声明要获取的项目
  3. FetchContent_Declare(
  4.     googletest
  5.     GIT_REPOSITORY https://github.com/google/googletest.git
  6.     GIT_TAG release-1.11.0
  7. )
  8. # 获取内容并使其可用
  9. FetchContent_MakeAvailable(googletest)
  10. # 现在可以使用GTest
  11. add_executable(my_test test_main.cpp)
  12. target_link_libraries(my_test
  13.     PRIVATE
  14.         gtest
  15.         gtest_main
  16. )
复制代码
  1. include(ExternalProject)
  2. # 设置外部项目
  3. ExternalProject_Add(
  4.     external_lib
  5.     GIT_REPOSITORY https://github.com/example/external_lib.git
  6.     GIT_TAG master
  7.     SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/external_lib-src"
  8.     BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/external_lib-build"
  9.     CMAKE_ARGS
  10.         -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
  11.         -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
  12.     INSTALL_COMMAND ""
  13. )
  14. # 创建导入的目标
  15. add_library(external_lib::external_lib STATIC IMPORTED)
  16. set_target_properties(external_lib::external_lib PROPERTIES
  17.     IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/external_lib-build/libexternal_lib.a"
  18.     INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/external_lib-src/include"
  19. )
  20. # 添加依赖关系
  21. add_dependencies(external_lib::external_lib external_lib)
  22. # 使用外部库
  23. add_executable(my_app main.cpp)
  24. target_link_libraries(my_app
  25.     PRIVATE
  26.         external_lib::external_lib
  27. )
复制代码
  1. # 需要先安装conan并创建conanfile.txt或conanfile.py
  2. # 在CMakeLists.txt中
  3. if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
  4.     message(STATUS "Downloading conan.cmake...")
  5.     file(DOWNLOAD
  6.         https://github.com/conan-io/cmake-conan/raw/v0.15/conan.cmake
  7.         "${CMAKE_BINARY_DIR}/conan.cmake"
  8.     )
  9. endif()
  10. include(${CMAKE_BINARY_DIR}/conan.cmake)
  11. conan_cmake_run(REQUIRES
  12.     fmt/8.1.1
  13.     catch2/3.0.1
  14.     BASIC_SETUP
  15.     BUILD missing)
  16. # 现在可以使用conan安装的库
  17. add_executable(my_app main.cpp)
  18. target_link_libraries(my_app
  19.     PRIVATE
  20.         CONAN_LIBS
  21. )
复制代码

8. 总结与展望

CMake是一个强大而灵活的构建系统,它为C++项目提供了跨平台构建能力。通过本文,我们详细介绍了CMake的基础语法、项目配置、构建系统、高级技巧和最佳实践,并通过实际案例展示了如何应用这些知识。

掌握CMake的关键点包括:

1. 理解现代CMake的理念:使用目标属性而不是全局变量,使用target_*命令而不是全局命令。
2. 良好的项目结构:合理组织源文件、头文件、测试和文档,使项目易于维护。
3. 模块化设计:将常用的CMake代码封装为函数和宏,提高代码重用性。
4. 跨平台考虑:使用CMake提供的工具处理不同平台之间的差异。
5. 依赖管理:熟练使用find_package、FetchContent和ExternalProject_Add等工具管理项目依赖。

理解现代CMake的理念:使用目标属性而不是全局变量,使用target_*命令而不是全局命令。

良好的项目结构:合理组织源文件、头文件、测试和文档,使项目易于维护。

模块化设计:将常用的CMake代码封装为函数和宏,提高代码重用性。

跨平台考虑:使用CMake提供的工具处理不同平台之间的差异。

依赖管理:熟练使用find_package、FetchContent和ExternalProject_Add等工具管理项目依赖。

随着C++生态系统的发展,CMake也在不断演进。未来,我们可以期待CMake在以下方面的改进:

1. 更好的包管理支持:与C++包管理器(如conan、vcpkg)的更紧密集成。
2. 更快的构建速度:通过改进生成器和构建系统的性能,减少构建时间。
3. 更强大的语言特性:CMake语言可能会增加更多现代编程语言的特性,使其更易于使用。
4. 更好的IDE集成:与各种IDE的更紧密集成,提供更好的开发体验。

更好的包管理支持:与C++包管理器(如conan、vcpkg)的更紧密集成。

更快的构建速度:通过改进生成器和构建系统的性能,减少构建时间。

更强大的语言特性:CMake语言可能会增加更多现代编程语言的特性,使其更易于使用。

更好的IDE集成:与各种IDE的更紧密集成,提供更好的开发体验。

通过持续学习和实践,开发者可以充分利用CMake的强大功能,高效地管理复杂项目的构建过程。希望本文能帮助读者深入理解CMake,并在实际项目中应用这些知识。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

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

本版积分规则