十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

现代CMake实战:target依赖、交叉编译与老项目改造

现代CMake实战:target依赖、交叉编译与老项目改造 1. 先搞清楚现代CMake到底新在哪里CMake 这个工具说实话口碑挺分裂的。有人觉得它是 C/C 生态里唯一能跨平台管好构建的救星也有人一看到那一堆CMakeLists.txt就想掀桌子。我身边不少做嵌入式和后端的朋友做cmake编译的时候都遇到过同一个尴尬项目能跑但没人敢动构建脚本改一行参数全崩。问题的根源其实不在 CMake 本身而在于很多人写的是十年前那种老式 CMake——靠全局变量、include_directories、link_libraries到处撒命令的写法。所谓现代 CMake核心就一句话一切以 target目标为中心。老写法是我先设置一堆全局开关然后大家各自编译现代写法是每个库、每个可执行文件都是一个独立对象它自己声明需要什么、对外暴露什么。这个转变听起来抽象但落到cmake使用教程的实操层面就是你会用target_link_libraries代替link_libraries会用target_include_directories代替include_directories然后在后面加上PRIVATE、PUBLIC、INTERFACE三个关键字把依赖关系说清楚。这篇内容我打算写得扎实一点。适合谁看如果你正在从cmake下载安装起步或者手里有个老项目想重构又或者你在嵌入式方向琢磨cmake 能不能代替 keil5这类问题那基本都能在这儿找到能直接抄的写法。我不会只给你一个模板了事而是把每个选择背后的原因讲透——为什么要这么写、不这么写会踩什么坑、include($env{idf_path}/tools/cmake/project.cmake)这种看起来吓人的写法到底在干嘛。我尽量按一个真实工程师的排查和重构思路来组织从环境、机制、实战到排查一路走下来。中间会穿插我踩过的坑和一些常规文档不会写的小技巧。你不需要看到最后才动手每一节都能独立落地。2. 环境搭建从下载安装到命令找不到的排查环境这事看起来最简单实际上cmake安装和版本问题劝退的人最多。我见过太多人卡在第一步明明装好了却提示找不到命令或者版本太老导致新语法不认。这一节把常见的几种情况一次说清楚。2.1 三大平台安装方式的差异与选择先说 Linux。这应该是最省心的Ubuntu 上一条命令搞定sudo apt update sudo apt install cmake但这里有个坑很多新手不知道apt 源里的 CMake 版本往往偏老。你要写现代 CMake特别是一些较新的target_*命令和生成器表达式老版本会直接报语法错误。网上那些ubuntu cmake banben版本的搜索多半就是被这个坑到的。装完先查一下版本cmake --version如果版本低于你需要的比如想用 3.20 以上的一些特性apt 里只有 3.16那就得考虑用 Kitware 官方仓库或者直接下预编译包。我个人更推荐新手直接用官方二进制包解压后把bin目录加进 PATH干净利落还方便以后换版本。macOS 用 Homebrew 最顺手brew install cmake一句话的事。Windows 上就复杂点官方安装包.msi和压缩包.zip两种安装包里可以选把 CMake 加入系统 PATH勾上这一步能省掉后面八成的麻烦。提示Windows 上还有一种省心方案就是直接用 Visual Studio 自带的 CMake或者用 Scoop、Chocolatey 这类包管理器。但如果你同时装了多个来源的 CMakePATH 顺序会决定你用哪个版本后面排查问题时要留意。2.2 无法将cmake项识别为cmdlet到底怎么破这个报错在 Windows PowerShell 里特别常见完整的话是cmake : 无法将cmake项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写...。看到这个先别慌它跟 CMake 好不好用没关系纯粹是系统不知道cmake.exe在哪。排查顺序我建议这么走。第一步确认到底装没装。去安装目录看看默认一般落在C:\Program Files\CMake\bin下面。找不到就说明安装那步没选对路径或者压根没装成功。第二步看 PATH。Windows 上按 Win 键搜环境变量打开编辑系统环境变量进 PATH 里找有没有C:\Program Files\CMake\bin。没有就手动加上然后一定要重开一个新的终端窗口旧窗口不会自动刷新环境变量。第三步如果 PATH 加对了还是不行用绝对路径直接调用试试 C:\Program Files\CMake\bin\cmake.exe --version能出东西就说明程序没问题是 PATH 的事。这里有个我踩过的坑有些人装的是 CMake 的 GUI 版本cmake-gui只装了图形界面没装命令行那cmake命令自然找不到。这种情况回去重装安装类型选Add CMake to the system PATH for all users问题基本就解决了。还有一种情况你用的是 Git Bash 或者 WSL那 PATH 的规则又不一样。WSL 里默认是 Linux 环境得在 Linux 侧重新装一遍别指望 Windows 装的那份能直接用。2.3 多版本共存与卸载的干净做法真正做项目的时候一台机器上好几个版本共存是常态。老项目依赖旧 CMake新项目要新的强行统一反而容易出事。我一般这么处理把不同版本的 CMake 放在不同目录下比如C:\tools\cmake-3.16和C:\tools\cmake-3.28PATH 里只放你想默认用的那个。需要切换时临时在命令行改 PATH或者写个小脚本切。Linux 上更干脆官方二进制包解压即用软链接到/usr/local/bin就行sudo ln -sf /opt/cmake-3.28/bin/cmake /usr/local/bin/cmake换版本只需要重做这个软链接不动系统包干净。卸载方面Windows 上如果是 msi 装的走控制面板卸载最稳妥卸载完记得回去 PATH 里把残留的路径删掉。zip 版的手动删目录就行。Linux 上 apt 装的用sudo apt remove cmake二进制包安装的直接删目录、删软链接。我倾向于能用二进制包就用二进制包卸载意味着删目录不留任何系统痕迹这在多台机器批量部署时太香了。注意卸载前先确认没有项目正在引用某个特定版本的 CMake。我有一次手快把默认版本卸了结果一个 CI 脚本挂了一整天最后发现就是个版本问题。养成习惯卸载前cmake --version记一下必要时先钉住版本。2.4 用一个最小工程验证环境是否真的可用装完别急着上复杂项目先跑个最小验证。建两个文件# CMakeLists.txt cmake_minimum_required(VERSION 3.16) project(hello CXX) add_executable(hello main.cpp)// main.cpp #include iostream int main() { std::cout cmake ok std::endl; return 0; }然后标准三步走cmake -S . -B build cmake --build build ./build/hello我特意用-S . -B build这种写法而不是进去build目录再cmake ..。这是现代 CMake 推荐的方式语义清晰-S指定源码目录-B指定构建目录一条命令搞定还方便脚本化。能打印出cmake ok说明环境彻底没问题了可以往下走了。3. target为核心的现代CMake核心机制环境通了接下来是理解现代 CMake 真正的内功。这一节的东西如果你能吃透那么后面无论面对多大的项目思路都不会乱。我把它拆成三块可见性关键字、生成器表达式、以及目录属性和目标属性的区别。3.1 PRIVATE、PUBLIC、INTERFACE 三个关键字的含义这三个词是现代 CMake 的灵魂理解它们就等于理解了依赖传播。打个比方你设计一个库它内部用到的依赖到底是只给自己用还是我用了用我的人也得用还是我自己不用但用我的人必须用。这三种情况分别对应 PRIVATE、PUBLIC、INTERFACE。举个最直观的例子。假设你有个库mylib它内部用了某个fmt库来格式化字符串但 fmt 只出现在.cpp实现里头文件对外不暴露 fmt 的任何类型。那么target_link_libraries(mylib PRIVATE fmt::fmt)意思是fmt 只是我 mylib 自己用的谁链接我不需要知道 fmt 的存在。反过来如果你的头文件里直接#include fmt/format.h并且暴露了 fmt 的类型那依赖就得往外传target_link_libraries(mylib PUBLIC fmt::fmt)PUBLIC 等于我自己用也传给别人。而 INTERFACE 是我自己不用但用我的人要用典型场景是纯头文件库header-only它没有编译单元但会把依赖和要求传递下去。我实测下来很多老项目最大的毛病就是所有依赖都当成全局处理导致链接顺序、重复链接、符号冲突一堆问题。用这三个关键字把边界卡死这些问题一大半会自然消失。理解它的关键是记住依赖是有方向的从谁用流向谁提供。提示如果你不确定某个依赖该用哪个问自己一句——我的公开头文件里有没有用到这个依赖的类型/宏有用 PUBLIC没用但实现需要 PRIVATE完全没有编译单元只想传依赖用 INTERFACE。3.2 生成器表达式把条件判断推迟到构建时生成器表达式generator expression是老 CMake 里几乎不存在的概念也是现代 CMake 里最能体现高级的部分。它的核心思想是有些信息在配置阶段cmake 运行时还没定得等到构建阶段真正编译时才知道。比如 Debug 和 Release 用不同路径、不同平台用不同库名。拿常见的写法举例target_include_directories(mylib PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include $INSTALL_INTERFACE:include )这里$BUILD_INTERFACE:...表示在构建这个库自身时用这个路径$INSTALL_INTERFACE:...表示当这个库被安装后、别人引用它时用这个路径。两种场景路径不一样生成器表达式正好能表达。这是现代 CMake 里发布库的标准姿势老写法是根本做不到这么干净的。再比如按配置区分target_compile_definitions(mylib PRIVATE $$CONFIG:Debug:DEBUG_BUILD )意思是只有 Debug 配置下才加DEBUG_BUILD这个宏。$CONFIG:Debug是个条件外层$...:...是满足条件就展开成冒号后面的内容。刚接触这玩意会觉得语法像天书我的建议是先抄现成的模式用多了自然就熟了。记住规律$关键字:参数是读取某种信息$条件:值是条件判断可以嵌套从内往外读。3.3 目录属性 vs 目标属性为什么后者才靠得住这是理解现代 CMake 最容易被忽略、但影响最大的一点。老写法里include_directories、add_definitions操作的是目录属性它会传染到该目录下所有子目录和后续定义的所有目标。结果就是依赖关系变得隐式、隐晦你只看某个 target 的配置根本不知道它实际用了哪些头文件路径。现代写法里target_include_directories、target_compile_definitions这些操作的是目标属性只作用于你指定的那个 target而且能通过 PUBLIC/INTERFACE 精确控制传播。对比一下老写法目录属性现代写法目标属性include_directories(inc)target_include_directories(t PRIVATE inc)影响范围整个目录及子目录影响范围仅指定 target依赖关系隐式、全局依赖关系显式、可控子目录顺序敏感与定义顺序无关为什么后者更可靠因为构建系统本质上应该是一张依赖图每个节点声明自己的输入输出。目录属性相当于往一个全局袋子里丢东西谁都能捞;目标属性相当于每个节点自带标签。项目一大前者必然乱套后者还能理清。这也解释了为什么现代 CMake 强调不要污染目录属性——一旦用了目录级命令整个工程的可维护性就直线下降。4. 实战把老项目改造成现代CMake工程理论讲完来点硬货。这一节我拿一个典型的多模块 C 项目做例子走一遍从目录设计到依赖引入的完整流程。你手里的项目无论大小思路都通用。4.1 目录结构怎么设计才合理先说结构。现代 CMake 项目我推荐这样的布局project/ ├── CMakeLists.txt ├── cmake/ │ └── helpers.cmake ├── include/ │ └── mylib/ │ └── mylib.h ├── src/ │ ├── CMakeLists.txt │ ├── mylib.cpp │ └── main.cpp └── tests/ ── CMakeLists.txt顶层CMakeLists.txt只负责工程定义和子目录接入include/专门放对外暴露的头文件src/放实现tests/放测试。这个划分的好处是include和src物理分离库使用者只需要include路径不会误把实现细节暴露出去。有经验的人可能会问那内部用的私有头文件放哪我的做法是在src/下再建一个detail/或者internal/通过PRIVATE方式加进 target对外不可见。这种公开/私有头文件分离是大型项目的基本功后面发布库的时候能省掉大量麻烦。4.2 顶层 CMakeLists.txt 的标准写法顶层文件我一般这么写cmake_minimum_required(VERSION 3.16) project(mylib VERSION 1.2.0 DESCRIPTION 一个示例库 LANGUAGES CXX) # 全局设置C 标准、默认构建类型 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING Build type FORCE) endif() # 统一输出目录方便找产物 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) add_subdirectory(src) enable_testing() add_subdirectory(tests)几个细节解释一下。set(CMAKE_CXX_STANDARD 17)是全局设标准比给每个 target 单独设省事。但如果你项目里混合了不同标准比如老模块要 C11那就得改成 target 级别设置。CMAKE_CXX_EXTENSIONS OFF是关掉编译器的 GNU 扩展强制标准行为做跨平台项目时尤其重要不然在 GCC 上能编过、Clang 上就翻车。默认构建类型的判断也值得说不设的话单配置生成器Makefile、Ninja默认是空编译出来的东西没有任何优化性能差好几倍你还以为是代码问题。这里强制默认 Release需要 Debug 时显式-DCMAKE_BUILD_TYPEDebug。4.3 库目标和可执行目标的写法src/CMakeLists.txt是关键我把库和可执行文件都写在这# 定义库目标 add_library(mylib mylib.cpp ) add_library(mylib::mylib ALIAS mylib) target_include_directories(mylib PUBLIC $BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include $INSTALL_INTERFACE:include PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/detail ) target_compile_features(mylib PUBLIC cxx_std_17) # 定义可执行目标 add_executable(app main.cpp) target_link_libraries(app PRIVATE mylib::mylib)注意add_library(mylib::mylib ALIAS mylib)这一行。这是给库起一个带命名空间的名字之后所有引用都统一用mylib::mylib。这样做有两个好处一是防止名字冲突二是将来这个库换成 find_package 引入的外部库时写法完全一样使用者无感知。这是我强烈推荐的习惯做多了项目的都知道命名空间带来的规范性有多重要。target_compile_features(mylib PUBLIC cxx_std_17)也值得说。它比直接set(CMAKE_CXX_STANDARD)更精确——它声明的是这个库要求使用 C17 特性用它的 target 会自动继承这个要求。这是现代 CMake 的按需声明思想比全局设置更精细。4.4 引入第三方依赖的正确姿势第三方库是现代 CMake 里最容易出问题的环节。主流两种方式find_package和FetchContent。find_package找系统里已装的库find_package(fmt REQUIRED) target_link_libraries(mylib PRIVATE fmt::fmt)关键是REQUIRED找不到直接报错退出而不是默默继续导致后面一堆链接错误。很多人不写 REQUIRED结果编译到一半才炸排查成本翻倍。FetchContent是配置阶段就把依赖下载进来include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.14.0 ) FetchContent_MakeAvailable(googletest) target_link_libraries(tests PRIVATE GTest::gtest_main)这两种方式怎么选我的经验是系统里稳定的依赖用find_package构建快;需要固定版本、或者 CI 环境干净想一键拉齐的用FetchContent。但FetchContent每次配置都可能触发网络请求离线环境会卡住所以我一般会加个开关或者设好本地缓存路径。注意FetchContent 的 GIT_TAG 一定要钉死到具体版本或 commit别用main。我有次偷懒用了 main结果上游改了个 API第二天 CI 全线飘红排查半天才发现是依赖自己动了。钉版本这件事一次都别省。5. 交叉编译与嵌入式场景cmake 能不能顶替 keil5这是个被问爆的问题cmake 可以代替 keil5 吗我的回答是能但要看你怎么用。CMake 本身只是构建系统的生成器它不懂芯片、不烧录、不调试。它做的是把你的源码、编译参数、链接脚本组织好交给交叉编译器比如 arm-none-eabi-gcc去干活。调试和烧录则要靠 OpenOCD、J-Link 这些工具。所以准确说是GCC CMake OpenOCD/J-Link这一整套替代了 keil5 的工作流。5.1 工具链文件的写法交叉编译的核心是工具链文件toolchain file它告诉 CMake 用哪个编译器、哪个系统根目录。我以一个 ARM Cortex-M 项目为例# arm-none-eabi.cmake set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR arm) set(TOOLCHAIN_PREFIX arm-none-eabi-) set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g) set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)CMAKE_SYSTEM_NAME Generic是裸机环境的标准写法告诉 CMake 没有操作系统。CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY这行很关键因为交叉编译器通常没法独立链接出一个可执行文件缺启动文件和链接脚本设置成静态库可以让 CMake 的编译器探测通过不然配置阶段就会失败。这个坑我见太多人栽了配置时报compiler not able to compile a simple test program八成就是这个原因。用的时候这样调用cmake -S . -B build-arm -DCMAKE_TOOLCHAIN_FILEcmake/arm-none-eabi.cmake cmake --build build-arm链接脚本通过 target 属性传进去target_link_options(firmware PRIVATE -T${CMAKE_SOURCE_DIR}/linker/stm32.ld -Wl,-Map${CMAKE_BINARY_DIR}/firmware.map --specsnano.specs )--specsnano.specs是给裸机用的精简 C 库能省不少 Flash 空间这些小参数在 keil 里是默认配好的换成 GCC 就得自己加。5.2 调试与烧录J-Link 和 OpenOCD 的接入既然换掉了 keil5调试这环得自己搭。用 J-Link 的话可以通过 J-Link 的命令行工具或者 GDB Server。常见的做法是启动 GDB Server然后用arm-none-eabi-gdb连上去JLinkGDBServer -device STM32F407VG -if SWD -speed 4000另一个终端arm-none-eabi-gdb build-arm/firmware.elf (gdb) target remote localhost:2331 (gdb) load (gdb) monitor reset如果你在搜cmake jlink相关的写法通常就是想把上面这些集成进 CMake。我的建议是别把烧录硬塞进 CMake 的构建流程里那会让构建变慢、变脆。正确做法是单独写一个 target 或者脚本比如add_custom_target(flash COMMAND JLinkExe -device STM32F407VG -if SWD -speed 4000 -autoconnect 1 -CommanderScript ${CMAKE_SOURCE_DIR}/scripts/flash.jlink DEPENDS firmware COMMENT 烧录固件到目标板 )这样cmake --build build-arm --target flash就能一键烧录但它是独立目标不影响正常构建。OpenOCD 的方式类似配好 cfg 文件后通过add_custom_target包一层即可。对比一下 keil 和这套开源方案我的实际体会是维度keil5CMake GCC OpenOCD/J-Link上手难度低开箱即用中需要自己配工具链跨平台基本限 Windows全平台CI 友好构建自动化较弱强脚本化程度高生态与成本商业授权开源免费调试体验图形化友好命令行为主可配 IDE一句话总结个人快速验证、追求开箱即用keil 更省事;团队协作、需要 CI、要跨平台现代 CMake 方案更值得投入。这不是非此即彼很多团队是开发用 keil 验证CI 用 CMake 构建各取所长。6. 常见问题与排查技巧实录这一节是我这些年踩坑攒下来的经验很多是文档里不会写、但实际天天遇到的问题。我整理成速查表加几条独家心得。6.1 高频报错速查表先上表格照着查能省一半排查时间。现象大概率原因解决方向无法将 cmake 项识别为 cmdlet命令不在 PATH加 PATH 并重开终端找不到头文件include 路径没设或用了目录级命令改用 target_include_directories链接时符号未定义依赖可见性设置错检查 PRIVATE/PUBLIC改 PUBLIC配置阶段编译器探测失败交叉编译未设 TRY_COMPILE_TARGET_TYPE设为 STATIC_LIBRARY改了 CMakeLists 没生效用了旧缓存删 build 目录或清 CMakeCache.txtinclude($env{...}) 找不到文件环境变量没设或语法不匹配检查变量名和拼写最后一行的include($env{idf_path}/tools/cmake/project.cmake)是很多人搜的写法。它来自某些嵌入式框架的工程模板本质是读取环境变量 idf_path然后 include 那个路径下的工程脚本。如果你照抄却报错九成是环境变量idf_path没设置或者路径拼接不对。排查方法先echo $env{idf_path}Windows或echo $IDF_PATHLinux确认变量有值再确认路径下真有那个文件。变量的写法跟平台有关Linux 下include($ENV{IDF_PATH}/...)Windows PowerShell 里环境变量读取也可能有差异别照搬。注意CMake 里读取环境变量用$ENV{NAME}读取普通变量用${NAME}两者别搞混。include($env{idf_path}...)这种小写写法在很多框架里能用是因为它们有自己的解析标准 CMake 里环境变量要用大写$ENV{}。抄的时候先搞清楚来源。6.2 缓存问题改了半天没反应的真凶CMake 把配置结果缓存在构建目录里这是它区别于纯 Makefile 的一个特点。好处是配置快坏处是——你改了CMakeLists.txt但结果没变或者更糟报一些莫名其妙的错。这种情况九成是缓存导致的。我的处理原则很简单遇到说不清的构建问题先删 build 目录重来一次。rm -rf build cmake -S . -B build这一步能解决我大概 60% 的诡异问题。如果不想全删也可以只删缓存文件CMakeCache.txt但有时候生成的中间产物也会残留全删最省心。CI 里我更激进每次都是干净构建目录虽然慢点但避免了一切缓存污染。有一类缓存坑特别隐蔽你之前配了-DCMAKE_BUILD_TYPEDebug后来想改 Release但没删缓存直接重配结果 CMake 没更新。正确的做法是重新配置并指定新值或者干脆删缓存。改编译选项、改工具链、改依赖路径这类操作我都会连带清一次缓存。6.3 依赖可见性引发的链接错误排查思路链接错误是最让人头大的因为编译器不告诉你是谁需要这个符号。遇到 undefined reference我按这个顺序排查第一步看这个符号属于哪个库。比如fmt::format明显来自 fmt。第二步看哪个 target 用了它可见性设置是否合理。第三步如果库只在实现里用PRIVATE但被别的 target 间接需要那就得改成 PUBLIC 往外传。第四步确认链接顺序——现代 CMake 通过 target 关系自动排序但如果用了老式link_libraries顺序就得手动管。我个人的经验是老老实实用 target_link_libraries 正确的可见性几乎不会遇到链接顺序问题。链接顺序这个东西在现代 CMake 里基本被自动处理了你一旦开始手动-l调顺序就说明哪里架构出问题了回去看看依赖声明对不对。6.4 几个能直接抄的实用小技巧最后分享几个我常用的小技巧都是能立刻用上的。第一用cmake --build build --target help看所有可用目标。这比去翻 CMakeLists 快多了尤其接手别人项目时。第二用-DCMAKE_EXPORT_COMPILE_COMMANDSON生成compile_commands.json配合 clangd、VSCode代码跳转和补全瞬间起飞cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDSON这个文件是 C 生态里做 IDE 集成的通用格式装完不用再担心头文件路径识别不了。第三用CMAKE_VERBOSE_MAKEFILE或者cmake --build build --verbose看真实的编译命令。当你不确定某个宏、某个 include 路径有没有生效时看编译命令行是最直接的验证方式比猜强一万倍。第四写库的时候尽早考虑install和export规则。很多人做完功能就不管了等到要把库发布给同事用时才发现没法find_package。现代 CMake 里install(TARGETS ... EXPORT ...)加上install(EXPORT ...)这套组合拳配合生成器表达式的$INSTALL_INTERFACE:...一次配好终身受益。我在实际带团队时最大的体会是现代 CMake 的价值不在语法多花哨而在于它逼着你把工程的依赖关系想清楚。老写法可以糊弄现代写法糊弄不了——你写不出正确的 PUBLIC/PRIVATE说明你对模块边界的理解还没到位。所以重构 CMake 的过程往往也是重新梳理项目架构的过程。这个附加价值比省下的那点打字时间值钱多了。
返回列表