cmake之旅(13)
cmake之旅(13)
- cmake之旅(13):实战项目总结
- 1 项目介绍
- 2 项目结构
- 3 源文件
- 4 配置文件模板
- 5 自定义 CMake 模块
- 6 CMakeLists.txt 文件
- 6.1 顶层 CMakeLists.txt
- 6.2 src 目录
- 6.3 app 目录
- 6.4 tests 目录
- 6.5 工具链文件
- 7 各种构建场景
- 8 知识点对照表
- 9 最佳实践总结
- 10 结语
同系列文章:
cmake之旅(1):构建的过程
cmake之旅(2):CMakeLists.txt 核心语法
cmake之旅(3):多目录项目管理
cmake之旅(4):静态库与动态库
cmake之旅(5):函数、宏与 .cmake 模块
cmake之旅(6):查找和使用第三方库
cmake之旅(7):编译选项与条件编译
cmake之旅(8):Modern CMake 与 target 思维
cmake之旅(9):安装与导出
cmake之旅(10):自动化测试与 CTest
cmake之旅(11):交叉编译与工具链文件
cmake之旅(12):CPack 打包与发布
cmake之旅(13):实战项目总结
cmake之旅(13):实战项目总结
经过前面 12 篇的学习,我们从最基础的手动编译一路走到了交叉编译和打包发布。这一篇作为整个系列的收官,我们用一个完整的中型项目把所有知识串起来,展示一个"生产级"CMake 项目的完整面貌。
1 项目介绍
我们要构建一个名为MathKit的数学工具库,它包含以下特性:
- 多个功能模块(加法、减法、高级数学)
- 同时提供静态库和动态库
- 使用 configure_file 管理版本和功能开关
- 自定义 .cmake 模块封装通用逻辑
- 集成 Google Test 单元测试
- 完整的 install 和 export 规则(支持 find_package)
- CPack 打包配置
- 交叉编译工具链文件
2 项目结构
mathkit/ ├── CMakeLists.txt# 顶层 CMakeLists.txt├── LICENSE ├── README.md ├── cmake │ ├── MathKitUtils.cmake# 自定义工具模块│ └── MathKitConfig.cmake.in# find_package 配置模板├── config │ └── mathkit_config.h.in# 配置头文件模板├── include │ └── mathkit │ ├── add.h# 加法模块头文件│ ├── de.h# 减法模块头文件│ └── advanced.h# 高级数学模块头文件├── src │ ├── CMakeLists.txt │ ├──add│ │ ├── CMakeLists.txt │ │ └── add.cpp │ ├── de │ │ ├── CMakeLists.txt │ │ └── de.cpp │ └── advanced │ ├── CMakeLists.txt │ └── advanced.cpp ├── app │ ├── CMakeLists.txt │ └── main.cpp ├── tests │ ├── CMakeLists.txt │ ├── test_add.cpp │ ├── test_de.cpp │ └── test_advanced.cpp └── toolchains └── toolchain-aarch64.cmake# ARM 交叉编译工具链3 源文件
include/mathkit/add.h:
#ifndefMATHKIT_ADD_H#defineMATHKIT_ADD_H/// @brief 计算两个整数的和intadd(inta,intb);#endifinclude/mathkit/de.h:
#ifndefMATHKIT_DE_H#defineMATHKIT_DE_H/// @brief 计算 b - aintde(inta,intb);#endifinclude/mathkit/advanced.h:
#ifndefMATHKIT_ADVANCED_H#defineMATHKIT_ADVANCED_H/// @brief 计算 base 的 exp 次幂(整数幂)longlongpower(intbase,intexp);/// @brief 计算阶乘longlongfactorial(intn);#endifsrc/add/add.cpp:
#include"mathkit/add.h"intadd(inta,intb){returna+b;}src/de/de.cpp:
#include"mathkit/de.h"intde(inta,intb){returnb-a;}src/advanced/advanced.cpp:
#include"mathkit/advanced.h"longlongpower(intbase,intexp){longlongresult=1;for(inti=0;i<exp;++i){result*=base;}returnresult;}longlongfactorial(intn){if(n<=1)return1;longlongresult=1;for(inti=2;i<=n;++i){result*=i;}returnresult;}app/main.cpp:
#include<iostream>#include"mathkit_config.h"#include"mathkit/add.h"#include"mathkit/de.h"#ifdefMATHKIT_ENABLE_ADVANCED#include"mathkit/advanced.h"#endifintmain(){std::cout<<MATHKIT_PROJECT_NAME<<" v"<<MATHKIT_VERSION<<std::endl;std::cout<<std::endl;std::cout<<"add(10, 5) = "<<add(10,5)<<std::endl;std::cout<<"de(3, 10) = "<<de(3,10)<<std::endl;#ifdefMATHKIT_ENABLE_ADVANCEDstd::cout<<"power(2, 10) = "<<power(2,10)<<std::endl;std::cout<<"factorial(10) = "<<factorial(10)<<std::endl;#elsestd::cout<<"高级数学模块未启用"<<std::endl;#endif#ifdefMATHKIT_DEBUG_LOGstd::cout<<std::endl;std::cout<<"[DEBUG] 构建类型: "<<MATHKIT_BUILD_TYPE<<std::endl;#endifreturn0;}4 配置文件模板
config/mathkit_config.h.in:
#ifndefMATHKIT_CONFIG_H#defineMATHKIT_CONFIG_H#defineMATHKIT_PROJECT_NAME"@PROJECT_NAME@"#defineMATHKIT_VERSION"@PROJECT_VERSION@"#defineMATHKIT_VERSION_MAJOR@PROJECT_VERSION_MAJOR@#defineMATHKIT_VERSION_MINOR@PROJECT_VERSION_MINOR@#defineMATHKIT_VERSION_PATCH@PROJECT_VERSION_PATCH@#defineMATHKIT_BUILD_TYPE"@CMAKE_BUILD_TYPE@"#cmakedefineMATHKIT_ENABLE_ADVANCED#cmakedefineMATHKIT_DEBUG_LOG#endif5 自定义 CMake 模块
cmake/MathKitUtils.cmake:
include_guard(GLOBAL) include(CMakeParseArguments) # 添加 MathKit 子模块的便捷函数 # 用法: # mathkit_add_module(add_lib SOURCES add.cpp) # mathkit_add_module(adv_lib SOURCES adv.cpp DEPENDS add_lib) function(mathkit_add_module MODULE_NAME) cmake_parse_arguments( ARG "" "" "SOURCES;DEPENDS" ${ARGN} ) # 根据全局选项决定库类型 if(MATHKIT_BUILD_SHARED) add_library(${MODULE_NAME} SHARED ${ARG_SOURCES}) else() add_library(${MODULE_NAME} STATIC ${ARG_SOURCES}) endif() # 设置头文件路径 target_include_directories(${MODULE_NAME} PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> ) # 设置 C++ 标准 target_compile_features(${MODULE_NAME} PUBLIC cxx_std_17) # 如果是动态库,设置版本号和 PIC if(MATHKIT_BUILD_SHARED) set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} POSITION_INDEPENDENT_CODE ON ) endif() # 处理依赖 if(ARG_DEPENDS) target_link_libraries(${MODULE_NAME} PUBLIC ${ARG_DEPENDS}) endif() endfunction()cmake/MathKitConfig.cmake.in:
@PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/MathKitTargets.cmake") check_required_components(MathKit)6 CMakeLists.txt 文件
6.1 顶层 CMakeLists.txt
# ============================================================ # MathKit - 数学工具库 # cmake之旅(13)完整实战项目 # ============================================================ cmake_minimum_required(VERSION 3.14) project(MathKit VERSION 1.2.0 DESCRIPTION "一个模块化的数学工具库" LANGUAGES CXX ) # ---- 标准模块 ---- include(GNUInstallDirs) include(CMakePackageConfigHelpers) # ---- 自定义模块 ---- list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) include(MathKitUtils) # ---- 构建选项 ---- option(MATHKIT_BUILD_SHARED "构建动态库" OFF) option(MATHKIT_ENABLE_ADVANCED "启用高级数学模块" ON) option(MATHKIT_DEBUG_LOG "启用调试日志" OFF) option(MATHKIT_BUILD_TESTS "构建单元测试" ON) option(MATHKIT_BUILD_APP "构建示例程序" ON) # ---- 默认构建类型 ---- if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "构建类型" FORCE) endif() # ---- 输出目录 ---- set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # ---- 生成配置头文件 ---- configure_file( ${CMAKE_SOURCE_DIR}/config/mathkit_config.h.in ${CMAKE_BINARY_DIR}/mathkit_config.h ) # ---- 打印构建信息 ---- message(STATUS "========================================") message(STATUS " ${PROJECT_NAME} v${PROJECT_VERSION}") message(STATUS " 构建类型: ${CMAKE_BUILD_TYPE}") message(STATUS " 库类型: $<IF:${MATHKIT_BUILD_SHARED},SHARED,STATIC>") message(STATUS " 高级数学: ${MATHKIT_ENABLE_ADVANCED}") message(STATUS " 调试日志: ${MATHKIT_DEBUG_LOG}") message(STATUS " 构建测试: ${MATHKIT_BUILD_TESTS}") message(STATUS " 构建示例: ${MATHKIT_BUILD_APP}") message(STATUS "========================================") # ---- 构建子目录 ---- add_subdirectory(src) if(MATHKIT_BUILD_APP) add_subdirectory(app) endif() if(MATHKIT_BUILD_TESTS) enable_testing() add_subdirectory(tests) endif() # ---- 安装与导出 ---- # 收集要安装的目标 set(INSTALL_TARGETS mathkit_add mathkit_de) if(MATHKIT_ENABLE_ADVANCED) list(APPEND INSTALL_TARGETS mathkit_advanced) endif() # 安装目标 install(TARGETS ${INSTALL_TARGETS} EXPORT MathKitTargets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) # 安装头文件 install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) # 安装生成的配置头文件 install(FILES ${CMAKE_BINARY_DIR}/mathkit_config.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) # 导出目标 install(EXPORT MathKitTargets FILE MathKitTargets.cmake NAMESPACE MathKit:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathKit ) # 生成版本兼容文件 write_basic_package_version_file( "${CMAKE_BINARY_DIR}/MathKitConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) # 生成包配置文件 configure_package_config_file( "${CMAKE_SOURCE_DIR}/cmake/MathKitConfig.cmake.in" "${CMAKE_BINARY_DIR}/MathKitConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathKit ) # 安装配置文件 install(FILES "${CMAKE_BINARY_DIR}/MathKitConfig.cmake" "${CMAKE_BINARY_DIR}/MathKitConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathKit ) # ---- CPack 打包 ---- set(CPACK_PACKAGE_NAME "MathKit") set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}") set(CPACK_PACKAGE_VENDOR "MathKit Team") set(CPACK_PACKAGE_CONTACT "dev@mathkit.example.com") set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}") set(CPACK_DEBIAN_PACKAGE_SECTION "devel") set(CPACK_RPM_PACKAGE_LICENSE "MIT") include(CPack)6.2 src 目录
src/CMakeLists.txt:
# 基础模块(始终构建) add_subdirectory(add) add_subdirectory(de) # 高级模块(按开关构建) if(MATHKIT_ENABLE_ADVANCED) add_subdirectory(advanced) endif()src/add/CMakeLists.txt:
mathkit_add_module(mathkit_add SOURCES add.cpp)src/de/CMakeLists.txt:
mathkit_add_module(mathkit_de SOURCES de.cpp)src/advanced/CMakeLists.txt:
mathkit_add_module(mathkit_advanced SOURCES advanced.cpp)看到了吗?每个模块的 CMakeLists.txt 只有一行,所有的通用逻辑都封装在mathkit_add_module函数中。这就是第五篇学的.cmake模块的威力。
6.3 app 目录
app/CMakeLists.txt:
add_executable(mathkit_app main.cpp) # 链接基础模块 target_link_libraries(mathkit_app PRIVATE mathkit_add mathkit_de) # 如果启用了高级模块,链接它 if(MATHKIT_ENABLE_ADVANCED) target_link_libraries(mathkit_app PRIVATE mathkit_advanced) endif() # 添加配置头文件路径 target_include_directories(mathkit_app PRIVATE ${CMAKE_BINARY_DIR}) # 安装示例程序 install(TARGETS mathkit_app RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} )6.4 tests 目录
tests/CMakeLists.txt:
include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.14.0 ) FetchContent_MakeAvailable(googletest) include(GoogleTest) # add 模块测试 add_executable(test_add test_add.cpp) target_link_libraries(test_add PRIVATE mathkit_add GTest::gtest_main) gtest_discover_tests(test_add) # de 模块测试 add_executable(test_de test_de.cpp) target_link_libraries(test_de PRIVATE mathkit_de GTest::gtest_main) gtest_discover_tests(test_de) # 高级模块测试(仅在启用时构建) if(MATHKIT_ENABLE_ADVANCED) add_executable(test_advanced test_advanced.cpp) target_link_libraries(test_advanced PRIVATE mathkit_advanced GTest::gtest_main) gtest_discover_tests(test_advanced) endif()tests/test_add.cpp:
#include<gtest/gtest.h>#include"mathkit/add.h"TEST(AddTest,PositiveNumbers){EXPECT_EQ(add(2,3),5);}TEST(AddTest,NegativeNumbers){EXPECT_EQ(add(-1,-1),-2);}TEST(AddTest,WithZero){EXPECT_EQ(add(0,5),5);}TEST(AddTest,LargeNumbers){EXPECT_EQ(add(100000,200000),300000);}tests/test_de.cpp:
#include<gtest/gtest.h>#include"mathkit/de.h"TEST(DeTest,BasicSubtraction){EXPECT_EQ(de(2,10),8);}TEST(DeTest,EqualValues){EXPECT_EQ(de(5,5),0);}TEST(DeTest,NegativeResult){EXPECT_EQ(de(10,2),-8);}tests/test_advanced.cpp:
#include<gtest/gtest.h>#include"mathkit/advanced.h"TEST(PowerTest,BasicPower){EXPECT_EQ(power(2,10),1024);}TEST(PowerTest,ZeroExponent){EXPECT_EQ(power(5,0),1);}TEST(PowerTest,OneExponent){EXPECT_EQ(power(7,1),7);}TEST(FactorialTest,BasicFactorial){EXPECT_EQ(factorial(5),120);}TEST(FactorialTest,ZeroFactorial){EXPECT_EQ(factorial(0),1);}TEST(FactorialTest,LargeFactorial){EXPECT_EQ(factorial(10),3628800);}6.5 工具链文件
toolchains/toolchain-aarch64.cmake:
set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)7 各种构建场景
这个项目支持丰富的构建配置。以下是各种典型场景的用法:
场景一:默认构建(静态库 + 全功能 + 测试)
mkdirbuild&&cdbuild cmake..make-j$(nproc)ctest --output-on-failure ./bin/mathkit_app场景二:Release 版本动态库,不构建测试
cmake-DCMAKE_BUILD_TYPE=Release\-DMATHKIT_BUILD_SHARED=ON\-DMATHKIT_BUILD_TESTS=OFF\..make-j$(nproc)场景三:精简版本(禁用高级数学模块)
cmake-DMATHKIT_ENABLE_ADVANCED=OFF..make-j$(nproc)场景四:安装到指定目录
cmake-DCMAKE_INSTALL_PREFIX=/opt/mathkit..make-j$(nproc)makeinstall场景五:生成 DEB 安装包
cmake-DCMAKE_BUILD_TYPE=Release..make-j$(nproc)cpack-GDEB场景六:ARM 交叉编译
mkdirbuild-arm&&cdbuild-arm cmake-DCMAKE_TOOLCHAIN_FILE=../toolchains/toolchain-aarch64.cmake\-DMATHKIT_BUILD_TESTS=OFF\..make-j$(nproc)场景七:别人使用已安装的 MathKit
find_package(MathKit 1.0 REQUIRED) add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE MathKit::mathkit_add MathKit::mathkit_de)8 知识点对照表
回顾一下这个项目用到了哪些篇章的知识:
| 篇章 | 知识点 | 在项目中的体现 |
|---|---|---|
| 第2篇 | 核心语法 | cmake_minimum_required, project, set, message |
| 第3篇 | 多目录管理 | add_subdirectory, target_include_directories |
| 第4篇 | 静态库与动态库 | STATIC/SHARED 选择, 版本号, 输出路径 |
| 第5篇 | 函数与 .cmake 模块 | MathKitUtils.cmake, mathkit_add_module 函数 |
| 第6篇 | 第三方库 | FetchContent 引入 Google Test |
| 第7篇 | 条件编译 | option, configure_file, target_compile_definitions |
| 第8篇 | Modern CMake | target_xxx 命令, PUBLIC/PRIVATE/INTERFACE |
| 第9篇 | 安装与导出 | install, export, find_package 配置 |
| 第10篇 | 自动化测试 | enable_testing, gtest_discover_tests |
| 第11篇 | 交叉编译 | toolchain-aarch64.cmake |
| 第12篇 | 打包发布 | CPack 配置 |
9 最佳实践总结
经过整个系列的学习,总结出以下最佳实践,希望对你今后的 CMake 项目有所帮助。
项目结构方面:头文件和源文件分离,公开头文件放在include/<项目名>/下,避免命名冲突。自定义.cmake模块放在cmake/目录下。工具链文件放在toolchains/目录下。测试代码放在tests/目录下。
CMake 编写方面:全面使用 Modern CMake 的target_xxx命令,告别全局命令。通过 PUBLIC/PRIVATE/INTERFACE 精确控制依赖传播。用function封装重复逻辑,用.cmake模块组织代码。使用GNUInstallDirs而不是硬编码安装路径。使用生成器表达式区分 BUILD_INTERFACE 和 INSTALL_INTERFACE。
配置管理方面:用option提供构建开关。用configure_file生成配置头文件。设置默认CMAKE_BUILD_TYPE。给所有选项加有意义的前缀(如MATHKIT_),避免与其他项目冲突。
质量保障方面:集成单元测试框架(如 Google Test),用ctest运行。提供BUILD_TESTS开关,让使用者可以跳过测试。编译时开启警告选项(-Wall -Wextra)。
发布方面:编写完整的install()规则。导出 CMake 配置文件,让别人能find_package。使用 CPack 生成安装包。创建 ALIAS 目标,统一add_subdirectory和find_package两种使用方式的接口。
10 结语
从第一篇的g++ main.cpp到现在的完整项目,我们走过了 CMake 学习的完整旅程。CMake 的学习曲线确实不算平缓,但一旦掌握了核心概念和 Modern CMake 的思维方式,它就是一个非常强大且灵活的工具。
希望这个系列对你有所帮助。如果在实践中遇到问题,建议参考 CMake 官方文档(https://cmake.org/cmake/help/latest/),以及各种优秀开源项目的 CMakeLists.txt——阅读优秀项目的构建代码是提升 CMake 技能最快的方式之一。
cmake 之旅,到此完结。祝你在 C++ 的世界里一路顺利。
