17、内置模块:CMake内置模块概览、FetchContent(外部依赖管理)、CTest(测试支持)、CPack(打包支持)

聊到CMake的内置模块,我其实挺感慨的。早些年做C++项目,依赖管理全靠手写Find脚本,测试和打包更是各搞各的。后来CMake把这些东西都收编了,说实话,省了我不少头发。

这一节我们就把CMake自带的几个核心模块捋一遍。你想想看,一个项目从拉取依赖、到编译测试、再到打包发布,CMake全给你管了。是不是挺省心的?

CMake内置模块概览

CMake自带了几十个模块,分布在Modules/目录下。我挑几个最常用的说说:

  • FetchContent:拉取外部源码,直接集成到构建中
  • CTest:测试驱动模块,帮你跑单元测试、集成测试
  • CPack:打包模块,生成安装包、压缩包、甚至DEB/RPM
  • FindXxx系列:查找系统已安装的库,比如FindOpenSSL、FindBoost
  • ExternalProject:更底层的外部项目管理,适合复杂场景

我个人习惯,能用FetchContent就不用ExternalProject。为什么?因为FetchContent更现代,集成更平滑,不会把构建搞得乱七八糟。

小提示:CMake 3.11+ 才正式支持 FetchContent。如果你还在用老版本,建议升级。我见过不少项目卡在3.10上,结果FetchContent用不了,只能手写ExternalProject,那叫一个痛苦。

FetchContent:外部依赖管理

FetchContent说白了就是帮你把别人的源码拉到你的构建树里,然后直接add_subdirectory。它比git submodule好用,也比手动下载靠谱。

来看一个实际例子。假设你的项目依赖nlohmann/json这个JSON库:

include(FetchContent)

FetchContent_Declare(
  nlohmann_json
  GIT_REPOSITORY https://github.com/nlohmann/json.git
  GIT_TAG v3.11.2
)

FetchContent_MakeAvailable(nlohmann_json)

# 现在可以直接用 json 库了
target_link_libraries(my_app PRIVATE nlohmann_json::nlohmann_json)

就这么几行,依赖就拉下来了。我曾经在一个嵌入式项目里用这个方式管理了七八个第三方库,编译脚本干净得不像话。

FetchContent还支持URL下载、SVN、甚至本地路径。我个人最常用的是GIT_REPOSITORY + GIT_TAG,因为版本锁定最可靠。

注意:FetchContent默认会把依赖的源码放到构建目录下的_deps文件夹里。如果你想让多个项目共享同一份缓存,可以设置FETCHCONTENT_BASE_DIR变量。我踩过这个坑——每个构建目录都重新下载一遍,磁盘空间直接爆炸。

CTest:测试支持

CTest是CMake自带的测试驱动工具。它不负责写测试,而是帮你组织、运行、报告测试结果。你可以用任何测试框架——Google Test、Catch2、Boost.Test,甚至自己写的测试程序。

启用CTest很简单,在顶层CMakeLists.txt里加一行:

enable_testing()

然后注册测试用例:

add_test(NAME my_unit_test COMMAND ./run_tests --gtest_output=xml)

更常见的做法是配合Google Test:

include(GoogleTest)

# 假设你有一个测试可执行文件 test_foo
add_executable(test_foo test_foo.cpp)
target_link_libraries(test_foo PRIVATE GTest::GTest GTest::Main)

# 自动发现所有 TEST() 宏
gtest_discover_tests(test_foo)

然后你就可以用ctest命令运行所有测试了:

mkdir build && cd build
cmake ..
cmake --build .
ctest --output-on-failure

CTest还支持并行测试、超时控制、标签过滤。我在一个大型项目中用过ctest -R "integration" --timeout 60 -j4,只跑集成测试,4个任务并行,超时60秒。效果不错。

核心要点:CTest本身不写测试,它只负责调度。你写测试代码,CTest帮你跑、帮你报告、帮你集成到CI里。说白了,它是个测试管家。

CPack:打包支持

CPack是CMake的打包模块。它能生成各种格式的安装包:

  • ZIP/TGZ:通用压缩包
  • DEB:Debian/Ubuntu的包格式
  • RPM:Red Hat/Fedora的包格式
  • NSIS:Windows安装程序
  • DMG:macOS磁盘映像

启用CPack也很简单。先配置安装规则:

install(TARGETS my_app DESTINATION bin)
install(FILES config.json DESTINATION etc)

然后在CMakeLists.txt末尾加上:

set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.2.3")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A cool application")
set(CPACK_GENERATOR "TGZ;DEB")

include(CPack)

打包时运行:

cpack -G DEB   # 生成 .deb 包
cpack -G TGZ   # 生成 .tar.gz 包

我曾经给一个Linux桌面应用配置过CPack,同时生成DEB和RPM包。一开始DEB包依赖写错了,安装时提示缺少libfoo。后来我加了CPACK_DEBIAN_PACKAGE_DEPENDS才搞定。嗯,这里要注意,不同打包格式的配置变量名不一样,别搞混了。

经验之谈:CPack的配置变量非常多,建议从CPACK_GENERATORCPACK_PACKAGE_VERSION开始,慢慢加。别一上来就想把所有格式都配好,容易乱。

知识体系总览

下面这张图把CMake内置模块的核心逻辑串起来了。你可以看到,从依赖管理到测试再到打包,是一条完整的流水线。

CMake内置模块知识体系 CMake 内置模块 FetchContent CTest CPack FindXxx GIT_REPOSITORY URL下载 版本锁定 测试发现 并行运行 超时控制 DEB/RPM TGZ/ZIP NSIS/DMG FindOpenSSL FindBoost FindPython 核心流程:FetchContent 拉取依赖 → CTest 验证质量 → CPack 打包发布 FindXxx 辅助查找系统库,贯穿整个构建过程

这张图把四个核心模块的关系讲清楚了。FetchContent负责「拿来」,CTest负责「验证」,CPack负责「交付」,FindXxx系列负责「查找」。各司其职,互不干扰。

我个人建议,新项目起步时先把FetchContent和CTest配好。依赖管理和测试是基本功,这两样搞定了,项目质量就有底了。CPack可以等产品稳定了再配,不急。

一句话总结:CMake内置模块把「拉代码、跑测试、打安装包」这三件事标准化了。你只要学会配置,就能省下大量重复劳动。我这些年用下来,最深的感受就是——CMake比你想象的强大,别只拿它当个编译器调用器。
公众号:蓝海资料掘金营,微信deep3321