当前位置: 首页 > news >正文

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);#endif

include/mathkit/de.h:

#ifndefMATHKIT_DE_H#defineMATHKIT_DE_H/// @brief 计算 b - aintde(inta,intb);#endif

include/mathkit/advanced.h:

#ifndefMATHKIT_ADVANCED_H#defineMATHKIT_ADVANCED_H/// @brief 计算 base 的 exp 次幂(整数幂)longlongpower(intbase,intexp);/// @brief 计算阶乘longlongfactorial(intn);#endif

src/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#endif

5 自定义 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 CMaketarget_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_subdirectoryfind_package两种使用方式的接口。

10 结语

从第一篇的g++ main.cpp到现在的完整项目,我们走过了 CMake 学习的完整旅程。CMake 的学习曲线确实不算平缓,但一旦掌握了核心概念和 Modern CMake 的思维方式,它就是一个非常强大且灵活的工具。

希望这个系列对你有所帮助。如果在实践中遇到问题,建议参考 CMake 官方文档(https://cmake.org/cmake/help/latest/),以及各种优秀开源项目的 CMakeLists.txt——阅读优秀项目的构建代码是提升 CMake 技能最快的方式之一。

cmake 之旅,到此完结。祝你在 C++ 的世界里一路顺利。

http://www.cnnetsun.cn/news/1856562.html

相关文章:

  • 液压升降台设计(毕业论文+CAD图纸)
  • 2026年4月12日 AI前沿资讯速览
  • GPIO模拟8位并行总线驱动技术详解
  • 关于在VMware虚拟机中安装openEuler系统和opengauss数据库部分问题的解决。
  • A/B测试期间GPU显存爆满?:面向LLM推理场景的动态配额弹性伸缩算法与K8s CRD落地实践
  • 别再让Cursor乱改代码了!手把手教你写像维基百科一样好用的Cursor Rules
  • UE5新手避坑指南:为什么关了项目设置,游戏运行时自动曝光还在?
  • mqtt-plus 架构解析(六):多 Broker 管理,如何让一个应用同时连接多个 MQTT 服务
  • GD32H759IMT6
  • 为什么92%的企业选错推理硬件?SITS2026 2026Q1实测数据揭示:模型精度损失>0.8%的隐性成本藏在这3个硬件参数里
  • 从H5AD到空间感知scGPT:手把手复现与多任务训练实战
  • 保姆级教程:在Windows上用YOLOX+ByteTrack搞定视频多目标跟踪(附避坑指南)
  • 嵌入式MQTT开发增强工具库:PubSubClientTools深度解析
  • 手把手教你用YOLOv5s训练自己的水果识别模型(附2611张标注数据集)
  • 嵌入式Linux下华为E372 3G模块AT指令驱动开发指南
  • ESP32/ESP8266轻量Toggl时间条目API客户端
  • 搜索算法(一)
  • 时序数据压缩和模态匹配
  • 本周补题 4/5 -- 4/12
  • 嵌入式整数信号变换库:纯定点FFT/DCT实现
  • 芯片研发要的不是“听话的工具“,是敢说不的工程师
  • 东方仙盟神识训练工具专业训练-[AI人工智能(八十七)]—东方仙盟
  • ADIN1110 Arduino库深度解析:单对以太网嵌入式实践
  • 元器件失效背后的化学战争:从银离子迁移到电化学腐蚀的防护指南
  • Cron Expression与调度系统集成:Laravel、Symfony实战应用终极指南
  • 如何快速掌握Vue.draggable.next:从组件构建到事件处理的完整指南
  • 如何快速上手Flutter-WebRTC:10分钟搭建你的第一个音视频通话应用
  • 使用Alpine配置WSL ssh门户糜
  • s与Docker集成:容器化部署教程
  • 为什么92%的AI初创公司正在裸奔式发布大模型?——版权保护缺失导致融资受阻、合作终止的真实案例集(含3份被驳回的软著申报复盘)