cmake之旅13cmake之旅13实战项目总结1 项目介绍2 项目结构3 源文件4 配置文件模板5 自定义 CMake 模块6 CMakeLists.txt 文件6.1 顶层 CMakeLists.txt6.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之旅8Modern CMake 与 target 思维cmake之旅9安装与导出cmake之旅10自动化测试与 CTestcmake之旅11交叉编译与工具链文件cmake之旅12CPack 打包与发布cmake之旅13实战项目总结cmake之旅13实战项目总结经过前面 12 篇的学习我们从最基础的手动编译一路走到了交叉编译和打包发布。这一篇作为整个系列的收官我们用一个完整的中型项目把所有知识串起来展示一个生产级CMake 项目的完整面貌。1 项目介绍我们要构建一个名为MathKit的数学工具库它包含以下特性多个功能模块加法、减法、高级数学同时提供静态库和动态库使用 configure_file 管理版本和功能开关自定义 .cmake 模块封装通用逻辑集成 Google Test 单元测试完整的 install 和 export 规则支持 find_packageCPack 打包配置交叉编译工具链文件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#includemathkit/add.hintadd(inta,intb){returnab;}src/de/de.cpp#includemathkit/de.hintde(inta,intb){returnb-a;}src/advanced/advanced.cpp#includemathkit/advanced.hlonglongpower(intbase,intexp){longlongresult1;for(inti0;iexp;i){result*base;}returnresult;}longlongfactorial(intn){if(n1)return1;longlongresult1;for(inti2;in;i){result*i;}returnresult;}app/main.cpp#includeiostream#includemathkit_config.h#includemathkit/add.h#includemathkit/de.h#ifdefMATHKIT_ENABLE_ADVANCED#includemathkit/advanced.h#endifintmain(){std::coutMATHKIT_PROJECT_NAME vMATHKIT_VERSIONstd::endl;std::coutstd::endl;std::coutadd(10, 5) add(10,5)std::endl;std::coutde(3, 10) de(3,10)std::endl;#ifdefMATHKIT_ENABLE_ADVANCEDstd::coutpower(2, 10) power(2,10)std::endl;std::coutfactorial(10) factorial(10)std::endl;#elsestd::cout高级数学模块未启用std::endl;#endif#ifdefMATHKIT_DEBUG_LOGstd::coutstd::endl;std::cout[DEBUG] 构建类型: MATHKIT_BUILD_TYPEstd::endl;#endifreturn0;}4 配置文件模板config/mathkit_config.h.in#ifndefMATHKIT_CONFIG_H#defineMATHKIT_CONFIG_H#defineMATHKIT_PROJECT_NAMEPROJECT_NAME#defineMATHKIT_VERSIONPROJECT_VERSION#defineMATHKIT_VERSION_MAJORPROJECT_VERSION_MAJOR#defineMATHKIT_VERSION_MINORPROJECT_VERSION_MINOR#defineMATHKIT_VERSION_PATCHPROJECT_VERSION_PATCH#defineMATHKIT_BUILD_TYPECMAKE_BUILD_TYPE#cmakedefineMATHKIT_ENABLE_ADVANCED#cmakedefineMATHKIT_DEBUG_LOG#endif5 自定义 CMake 模块cmake/MathKitUtils.cmakeinclude_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.inPACKAGE_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 devmathkit.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.txtmathkit_add_module(mathkit_add SOURCES add.cpp)src/de/CMakeLists.txtmathkit_add_module(mathkit_de SOURCES de.cpp)src/advanced/CMakeLists.txtmathkit_add_module(mathkit_advanced SOURCES advanced.cpp)看到了吗每个模块的 CMakeLists.txt 只有一行所有的通用逻辑都封装在mathkit_add_module函数中。这就是第五篇学的.cmake模块的威力。6.3 app 目录app/CMakeLists.txtadd_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.txtinclude(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#includegtest/gtest.h#includemathkit/add.hTEST(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#includegtest/gtest.h#includemathkit/de.hTEST(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#includegtest/gtest.h#includemathkit/advanced.hTEST(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.cmakeset(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 各种构建场景这个项目支持丰富的构建配置。以下是各种典型场景的用法场景一默认构建静态库 全功能 测试mkdirbuildcdbuild cmake..make-j$(nproc)ctest --output-on-failure ./bin/mathkit_app场景二Release 版本动态库不构建测试cmake-DCMAKE_BUILD_TYPERelease\-DMATHKIT_BUILD_SHAREDON\-DMATHKIT_BUILD_TESTSOFF\..make-j$(nproc)场景三精简版本禁用高级数学模块cmake-DMATHKIT_ENABLE_ADVANCEDOFF..make-j$(nproc)场景四安装到指定目录cmake-DCMAKE_INSTALL_PREFIX/opt/mathkit..make-j$(nproc)makeinstall场景五生成 DEB 安装包cmake-DCMAKE_BUILD_TYPERelease..make-j$(nproc)cpack-GDEB场景六ARM 交叉编译mkdirbuild-armcdbuild-arm cmake-DCMAKE_TOOLCHAIN_FILE../toolchains/toolchain-aarch64.cmake\-DMATHKIT_BUILD_TESTSOFF\..make-j$(nproc)场景七别人使用已安装的 MathKitfind_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_subdirectory和find_package两种使用方式的接口。10 结语从第一篇的g main.cpp到现在的完整项目我们走过了 CMake 学习的完整旅程。CMake 的学习曲线确实不算平缓但一旦掌握了核心概念和 Modern CMake 的思维方式它就是一个非常强大且灵活的工具。希望这个系列对你有所帮助。如果在实践中遇到问题建议参考 CMake 官方文档https://cmake.org/cmake/help/latest/以及各种优秀开源项目的 CMakeLists.txt——阅读优秀项目的构建代码是提升 CMake 技能最快的方式之一。cmake 之旅到此完结。祝你在 C 的世界里一路顺利。