接触过CMake的人基本都绕不开一件事:往项目里引入第三方库。我在实际项目里用的是EnTT——游戏开发里很常见的ECS框架,header-only、只有头文件、模板满天飞,照理说“把头文件加进include path”就完事,但真跑到CMake里,才发现小小的引入流程牵涉到包管理、目标传递、构建可见性这一整套东西。这篇就完整复盘我用CMake引入EnTT的思路、写法和踩坑记录,顺带把另外几种第三方库该用什么策略也一起讲清楚。
1. 为什么“在CMake里装一个库”从来不只是加个路径
先说一个很多新手容易陷入的误区:以为引入第三方库就是把 .h 文件放到能找得到的地方,编译器能include进来就行。头文件找到了,编译能过,这只是第一层。真正的问题出现在第二步、第三步:头文件自己也include别的东西,你的编译器知道去哪里找吗?链接阶段库文件放哪?最后打包给其他人,对方的构建环境能用吗?
CMake要解决的,从来不是“找到这个库”这么简单,而是把“这个库怎么用”完整地告诉整个构建系统,包括它的头文件路径、依赖的宏定义、需要链接的库、它自己的依赖关系。这也是为什么现代CMake把库包装成“目标”(target),比如EnTT::EnTT,你在自己的工程里只需要 link 它,CMake会自动帮你把该写的编译参数全部带上。
很多人会用CMake很久都是几个老命令反复用:include_directories、link_directories、add_definitions。项目规模小的时候确实没问题,但一旦第三方库多了、版本复杂了,全局可见的路径和宏会互相污染,C++项目的构建会变成一团乱麻。引入EnTT正好是个绝佳的案例:它本身不复杂,结构简单,但你照样能把“正确做法”完整走一遍。
1.1 三种主流的引入方式,本质是三种依赖管理哲学
CMake引入第三方库,表面上是三类写法,背后其实是三种完全不同的依赖管理思路:
- find_package:系统里已经装好了这个库,CMake去“找”。适合发布时带依赖、希望通过系统包管理或vcpkg统一安装的场景。
- add_subdirectory:把第三方库源码作为子目录塞进你的工程里,一起构建。适合源码下载下来、版本锁定、要一起改源码的场景。
- FetchContent:构建时自动从GitHub或URL下载源码,然后当作子目录构建。三条路里最现代化、可复现性最好的一个,也是我强烈推荐的日常默认选项。
这三个方案没有谁绝对对,全看使用场景。我个人的判断标准很简单:项目要长期维护吗?依赖的库体积大不大?构建机器有网吗?想清楚这三个问题,方案基本就定了。
1.2 直接include_directories的问题——我最初就是被这样坑的
我不掩饰,我最初也是走那条“原始道路”的。从GitHub把EnTT拉下来,解压,然后把Src目录丢进include_directories,编译demo。
第一次确实能编译通过。恩,能跑。但写第二个文件的时候问题来了:EnTT是header-only,但它依赖C++17的特性。我在头文件里写了一个全局的宏定义,影响了EnTT的某个模板分支,结果两个cpp文件编译出来的行为不一致。排查了整整一个下午,最后发现是include_directories的“全局可见性”害的——它把路径和宏无条件给了所有目标,包括那些根本不该看到EnTT的模块。
从那时起,我就彻底转成target_link_libraries + 目标传递的写法了。现在再回头看,那一下午的排查折腾其实挺值,逼我把整个依赖传播机制搞明白了。
三种方式的对比,我用一张表总结一下。
| 引入方式 | 依赖获取时机 | 版本控制 | 适合场景 | 典型风险 |
|---|---|---|---|---|
| find_package | 配置期,需要系统已安装 | 依赖外部包管理器 | 系统级安装、稳定环境 | 找不到包、版本不全 |
| add_subdirectory | 需要手动clone或拷源码 | 代码仓库锁定 | 项目内共享源码、需要本地修改库 | 构建时间变长 |
| FetchContent | 构建时自动下载 | 锁定commit或版本号 | 需要可复现构建、不想污染系统 | 网络问题、首次下载较慢 |
2. EnTT是一个理想的教学样本:header-only库的引入逻辑
EnTT是个很有意思的库,一个头文件库,里面是极其现代的C++模板代码,实现了ECS(实体组件系统,Entity Component System)的核心能力。游戏开发里你常见的场景——把逻辑和数据进行组合,同时保持性能——EnTT就是专门解决这个的。正因为它全是模板、全是inline函数、没有.cpp要编译,所以它不产生任何静态库或动态库文件,所有“代码”都在头文件里。
恰恰是这个特性,让它在CMake的引入方式上和其他大家熟悉的库(比如OpenCV、zlib这种需要编译出二进制文件的)完全不同。它最终会被CMake描述成一个“接口目标”(INTERFACE library):没有生成产物、没有编译规则,只有一堆需要向外传递的编译参数,比如头文件路径、需要开启的C++标准。
2.1 EnTT是什么,为什么ECS框架起步就选它
EnTT的核心是ECS,一个比传统面向对象组合模式更贴合游戏逻辑的架构。你可以把实体理解成游戏里一个个“对象”的ID,组件是一块块纯数据(位置、血量、外观),系统是处理这些数据的函数逻辑。EnTT在内部把这些数据组织成紧凑的内存布局,遍历起来比传统的“对象数组”快得多。用谷歌搜“ECS game development”“EnTT performance”能看到大量游戏行业的评测,可以说EnTT是现在C++世界里ECS方向的主选方案之一。
选择EnTT做教学案例,不只是因为它本身值得学,更因为它们这一类的现代header-only库都有一个特点:在它们的GitHub仓库里,官方已经帮你搭好了一套CMake导出机制。导入路径合理,编译参数完备,你工程里一行target_link_libraries(EnTT::EnTT)就全部接上了。拿到手就能跑,跑完能拆解,非常适合用来理解CMake的依赖传递逻辑。
2.2 header-only对CMake引入意味着什么:接口目标与依赖传播
普通库(静态库)的CMake结构大概是:add_library(mylib STATIC ...),然后生成一个 .a 或 .lib 文件,别人链接时要把“二进制文件”和“头文件路径”一起告诉消费者。动态库更是多一套运行时路径的问题。
header-only库不一样,它没有二进制。所以CMake给它安排了一个特殊类型:add_library(EnTT INTERFACE)。INTERFACE的意思是“只提供接口,没有实现”,所有内容通过INTERFACE属性向下游传递。你链接了EnTT::EnTT,你就能拿到它的头文件路径和使用要求。这个“不产生文件、只传递参数”的思路,恰恰是理解现代CMake依赖管理最关键的一环。
这也是为什么EnTT官方仓库里你找不到 .a、.so 这类文件,只要把它纳进项目构建系统,它会以接口目标的形式存在。这和Eigen3那个著名的头文件库在CMake里是同一套逻辑。
2.3 EnTT官方导出的目标结构
在EnTT的CMake配置里,有一行大概长这样(具体以源码为准):
add_library(EnTT INTERFACE) add_library(EnTT::EnTT ALIAS EnTT) target_include_directories(EnTT INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> $<INSTALL_INTERFACE:include>) target_compile_features(EnTT INTERFACE cxx_std_17)注意两个信息量很大的点。其一,它用了$<BUILD_INTERFACE:和$<INSTALL_INTERFACE:这两个生成器表达式,区分“在源码路径下直接引用”和“安装到系统后再引用”两个场景。其二,它用target_compile_features声明了EnTT要求C++17。你只要链接EnTT::EnTT,CMake会自动给你的库开启C++17,不用自己手动加-std=c++17。这就是前面说的“把怎么用告诉构建系统”。
所以你在自己的工程里引入EnTT,核心就一句话:让EnTT这个目标的定义在CMake配置阶段可见,然后link它。
3. 一个能直接抄走的CMakeLists.txt:从add_subdirectory到FetchContent
先直接给结论:日常开发我推荐FetchContent,因为它版本锁定、可复现、不用手动clone源码。下面三个方案的完整写法我全都贴出来,各有各的适用场景。以EnTT版本为例,用GitHub仓库地址,但实际用的时候强烈建议锁定版本号,比如v3.12.2这种tag。
3.1 方案A:git clone + add_subdirectory
适合你已经把EnTT源码放在项目里的情况,比如公司内网不方便联网,或者你要在一个镜像仓库里锁定源码。
git clone --depth 1 --branch v3.12.2 https://github.com/skypjack/entt.git然后把整个EnTT文件夹放在你的项目目录下,CMakeLists.txt里写:
add_subdirectory(entt) add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE EnTT::EnTT)add_subdirectory会把entt目录里的CMakeLists.txt作为一个子项目加载,EnTT::EnTT这个目标就自动定义出来了。这个方案的缺点有两个大家容易遇到:第一,你把整个EnTT源码当子项目编译,虽然header-only不编译,但CMake的configure流程会被EnTT自己的测试和示例配置波及;第二,源码版本靠“放在哪”来管理,不直观,换版本等于重新clone。
3.2 方案B:FetchContent 自动化拉取(推荐)
这个方案最省事。CMake会自己处理下载、解压、加入构建的完整流程。
include(FetchContent) FetchContent_Declare( EnTT GIT_REPOSITORY https://github.com/skypjack/entt.git GIT_TAG v3.12.2 ) FetchContent_MakeAvailable(EnTT) add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE EnTT::EnTT)这里有个细节值得注意:FetchContent_MakeAvailable内部会先去检查这个依赖是否已经被add_subdirectory加入过,如果没有,它会自动下载、添加。所以它和方案A其实是可以共存的。它最大的价值是“可复现”:GIT_TAG锁定版本,无论换哪台机器,构建出来的依赖版本完全一致,不会出现“我本地好好的,你那边编译不过”这种问题。
这下不用手动clone了。CMake在首次configure时直接pull源码,自动编入构建图。这些逻辑全都是CMake官方模块实现的,不需要额外安装任何插件。第一次拉取会花点时间,之后会有缓存,除非删除build目录,否则不会重复下载。
3.3 方案C:find_package + 包管理器安装
如果团队要求统一依赖版本,希望由包管理器(vcpkg或Conan)来装EnTT,那采用find_package。
用vcpkg安装:
vcpkg install entt然后在CMakeLists.txt里:
find_package(EnTT CONFIG REQUIRED) add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE EnTT::EnTT)注意这里写的CONFIG,指的是要找一个名为EnTTConfig.cmake的配置文件。vcpkg会把整个安装包的CMake配置路径处理好,只要在configure时加上工具链参数:
cmake -B build -DCMAKE_TOOLCHAIN_FILE=[vcpkg-root]/scripts/buildsystems/vcpkg.cmake上面三个的形式在最终代码上非常相似,区别只在于目标 EnTT::EnTT 是怎么被定义出来的,一个是主动找,一个是子项目加载,一个是自动下载。这也是现代CMake一个很体贴的设计:使用方式统一,引入渠道解耦。你想从vcpkg切换成FetchContent,只改引入方式就不用改业务代码。
3.4 一个完整的小demo,验证你的引入是否成功
很多人配置完第一步不是去写业务,而是先验证“我是不是真的把这个库引进来了”。我自己的习惯是写一个最简demo,能编译通过,配置就算成功了。
#include <entt/entt.hpp> #include <cstdint> struct Position { float x; float y; }; struct Velocity { float dx; float dy; }; int main() { entt::registry registry; const auto entity = registry.create(); registry.emplace<Position>(entity, 0.0f, 0.0f); registry.emplace<Velocity>(entity, 1.0f, 0.0f); return 0; }这个demo做了三件事:创建registry、创建实体entity、给实体挂两个组件。如果这三行能编译过,说明你的引入配置没问题。
4. target_link_libraries的可见性逻辑:PUBLIC、PRIVATE、INTERFACE到底在管理什么
这一步是真正理解CMake依赖管理的分水岭。很多老教程里写的是:
include_directories("${ENTT_DIR}/src")问题在于它是“全局”的,这个目录下的所有目标,不管需不需要,都会被迫把头文件路径和宏加进去。而推荐的写法是:
target_link_libraries(my_app PRIVATE EnTT::EnTT)区别在哪?在于PRIVATE和PUBLIC这些关键字其实是给CMake的可见性传播规则用的。我给你展开说。
- PRIVATE:只对自己的编译可见,不对依赖它的下游可见。你的库内部用了EnTT,但是用在头的实现文件里,头文件的public接口没暴露任何EnTT类型,那就用PRIVATE。
- PUBLIC:既自己的编译要用,也传递给下游。你的头文件直接include了EnTT的头,下游要正常编译就必须能看到EnTT,那就用PUBLIC。
- INTERFACE:自己编译不需要(因为自己是header-only或纯头),但下游必须用。典型场景就是给接口目标添加编译参数。
这个机制的实际价值是,项目复杂以后,每个依赖的边界非常清晰。A模块用了EnTT,B模块没用到,B就不会意外拿到EnTT的头文件路径,也不会因为某个宏定义而改变编译行为。对,就是我开头踩的那个坑的反面解药。
用一句话总结:你是在“该知道的人才知道”和“所有人都知道”之间做选择。全局include_directories就是“所有人都知道”,看似方便,实际上是把依赖关系全球化了。项目里有3个以上第三方库的时候,这种全球化必然带来冲突隐患。
下面用一个很小的例子说明传递工程:
# 一个内部封装EnTT的库 add_library(engine STATIC engine.cpp engine.h) target_link_libraries(engine PUBLIC EnTT::EnTT) # 一个用engine库的target,它不需要显式找EnTT add_executable(game demo.cpp) target_link_libraries(game PRIVATE engine)因为engine把EnTT::EnTT声明成了PUBLIC,game在链接engine的同时,自动就“继承”了EnTT::EnTT的include路径和编译要求。demo.cpp里可以直接include engine.h,而engine.h若引入了entt.hpp,game也能编译通过。这个传递链路,就是现代CMake“目标”设计的精髓。学会管理可见性,比会敲命令重要得多。
为了让你有个直观的总览,我列一个对照:
| 上面的场景 | include_directories全局方案 | target_link_libraries方案 |
|---|---|---|
| game能看到EnTT吗 | 能(所有目标都能) | 能(通过传递) |
| 其他无关目标能看到吗 | 能(被迫污染) | 不能(干净隔离) |
| 修改EnTT路径需要动哪个文件 | 所有依赖它的CMakeLists | 只动传递链上的link行 |
| 宏定义影响范围 | 全局 | 按PRIVATE/PUBLIC隔离 |
5. 实际迁移EnTT时我踩过的坑与排查过程
所有CMake文章都会给你看“成功路径”,但实际一个人开发,更多的时间是在跟各种报错死磕。下面的坑都是我亲自踩过的,每一个都对应一个可笑又折腾的下午。
5.1 报错:unsupported compiler / C++17 feature check fail
我第一次用FetchContent拉完EnTT,自以为配置好了,configure那步就炸了。报错信息长得很吓人,说某个模板特性不支持,仔细一看,根因是CMake没有启用C++17。
# 错误示范:只引入,没开标准 FetchContent_MakeAvailable(EnTT) add_executable(app main.cpp) # 缺少这一行 # target_compile_features(app PRIVATE cxx_std_17) # 或 # set(CMAKE_CXX_STANDARD 17)而正常链接EnTT::EnTT的情况下,它自己会通过INTERFACE_COMPILE_FEATURES把cxx_std_17传给你的target,根本不用手动加。为什么还会踩坑?因为有时候我会忘记链接EnTT::EnTT,只是手动加了include目录。
排查思路:直接打印目标的属性,看看编译器实际以什么标准编译。
# 编译时保留中间文件,查看具体编译命令行 cmake --build build --verboseverbose输出里能看到每个源文件真实的编译参数,一目了然。这个方法很土,但排查编译器报错异常好用。
5.2 FetchContent 拉不下来:网络问题和缓存问题
第二个高频坑是FetchContent下载失败。报错通常是git clone超时或者SSL证书问题。解决思路分两层:
第一,优先换GitHub镜像或检查网络。有时公司内网屏蔽了github.com,那FetchContent就走不通。这类网络环境问题需要自己评估。如果你确定短期内无法访问外网,又想用FetchContent,可以改成URL方式直接下载源码包:
FetchContent_Declare( EnTT URL https://github.com/skypjack/entt/archive/refs/tags/v3.12.2.tar.gz URL_HASH SHA256=... )URL方式配合URL_HASH,既能锁定完整性,又绕开git协议的限制。不过注意,URL_HASH的SHA256值怎么算:
# 下载后用系统工具计算,Linux上: sha256sum entt.tar.gz第二种情况是“我明明改GIT_TAG版本了,为什么不生效”。CMake会把FetchContent的内容缓存到build/_deps目录,改版本后,旧源码还在。要么手动删除_deps/entt-src重新configure,要么直接用rm -rf build/_deps。这个缓存机制上过很多次当,做依赖升级时一定记得。
5.3 CMake GUI 和 VSCode CMake Tools 的 configure 流程
我接触过不少用VSCode做C++开发的人,装完CMake Tools插件后发现底部状态栏是空的,没有configure按钮。先确认两件事:第一,VSCode打开的项目根目录里有没有CMakeLists.txt;第二,插件认没认到编译器套件。
CMake Tools的工作原理其实很简单:它把你在VSCode底栏选的kit(编译器套件)和构建目录缓存下来,点击“Configure”时执行一次cmake构建。如果底部状态栏连“Kit选择”都没有,更常见的原因是CMake Tools没有找到任何已安装的编译器,比如只有VS但没装C++工具集,或者虽然有MinGW但路径没配。
补充一点,在Windows下用MinGW的人,configure后Runner往往卡在“无法找到nmake或MSVC”。本质是CMake默认generator是VS,要用MinGW必须显式指定:
cmake -B build -G "MinGW Makefiles" -DCMAKE_CXX_COMPILER=g++同理在CLion里如果用MinGW也是这个逻辑。工具链问题看着五花八门,根子上就一个:CMake不知道你的编译器在哪。
5.4 构建类型与strip、调试信息
还有一个很多人会踩的问题:配置release时发现可执行文件超大,里面塞满了调试信息,内存、磁盘都很吃紧。这其实和引入第三方库本身没关系,但既然工程已经配上EnTT了,逃不掉要看构建产物。在CMake里控制strip和优化级别的核心是CMAKE_BUILD_TYPE和target_link_options。
比如Release下自动strip:
if(CMAKE_BUILD_TYPE STREQUAL "Release") target_link_options(my_app PRIVATE -s) endif()-s在GCC/Clang里是移除符号表的选项,配合“Release”优化能达到很好的瘦身效果。如果你想让跨平台脚本更稳一点,也可以不写编译器专属参数,而是设置:
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG")不过我个人更推荐一个思路:区分“调试期构建”和“交付期构建”。调试期用Debug或RelWithDebInfo保留完整调试信息;交付期单独出Release+strip,一次写清楚两个配置,别在同一份上反复横跳。
这些坑看起来是“用法”问题,本质上还是对configure、build、link三个阶段各自的职责不清楚。configure阶段决定“依赖从哪里来”,build阶段决定“怎么编译”,link阶段决定“链接哪些符号”。遇到报错,先定位问题发生在哪个阶段,再对症下药。
6. 从EnTT到Eigen3、raylib、mosquitto:不同形态第三方库的引入策略
一个EnTT解决了,不代表你就掌握了所有第三方库的引入。第三方库的世界是分形态的,各自在CMake里的表现完全不同。这里我给你一个“引入策略决策表”,是我根据这几年接不同库项目总结出来的。enTT属于“header-only”这一格,另外几个常见的库也一并讲讲。
6.1 按库的形态分,比按库的功能分更实用
| 库形态 | 典型代表 | 是否产生二进制 | CMake核心策略 | 引入复杂度 |
|---|---|---|---|---|
| 纯header-only | EnTT、Eigen3、nlohmann/json | 否 | INTERFACE目标,链接即用 | 低 |
| 静态库 | spdlog、fmt | 是 | 编译产物+a链接 | 中 |
| 动态库 | mosquitto、ssl等系统库 | 是 | 需要export/import、运行时路径 | 中高 |
| 带工具链复杂依赖 | Qt、OpenCV、raylib | 视情况而定 | 最好用官方CMake config或包管理器 | 高 |
Eigen3和EnTT非常像,也是header-only。引入Eigen3时,官方给的CMake线索一般是find_package(Eigen3 CONFIG REQUIRED),找到后生成一个Eigen3::Eigen的INTERFACE target。如果不用包管理器,同样可以FetchContent拉源码。因为都是纯头文件库,这套流程跑起来毫无负担。
raylib则不太一样。它默认会编译出静态库(或动态库),库本身还带了一套自己的构建选项(例如是否开启音频模块、USE_EXTERNAL_GLFW等)。如果直接add_subdirectory它,很容易被它内部的选项配置反客为主。所以raylib更推荐用系统安装+find_package,或者用vcpkg管理的raylib。
这一段的结论是:header-only库可以“源码搬进来就能用”,编译型库则要优先考虑“用官方的CMake config模式”。遇到一个库,别急着Google“怎么引入”,先把仓库的README和CMakeLists.txt翻一遍,基本就知道它导出了什么target、支持哪些寻找方式了。
6.2 需要配置工具链的场景:MinGW、MSVC、Qt的注意事项
还有一类坑和编译环境强相关。比如Qt项目本身的CMake入口是find_package(Qt6 COMPONENTS Widgets REQUIRED),它的路径需要通过Qt的安装工具写入CMake配置,找不到时手动设置CMAKE_PREFIX_PATH:
cmake -B build -DCMAKE_PREFIX_PATH=/path/to/Qt/6.5.0/msvc2019_64如果你在Windows用MinGW,要注意Qt安装包通常分mingw_64和msvc2019_64两个版本,混装会导致编译器接口不匹配,configure能过,一编译就报一堆无法解析的外部符号。这个问题我在实际搬迁工程时遇到过:某次把msvc版Qt的CMAKE_PREFIX_PATH指给了MinGW工具链,link时报了大量LNK2019和LNK2001。解决方法是把“编译器”和“库的ABI版本”当作一套来配置,不要交叉拼装。
另外cmake与mingw搭配的一个重要经验:如果用的是"MinGW Makefiles"generator,程序对Makefile的依赖比较重,路径里有中文或空格时容易出诡异问题。推荐直接给CMake指定绝对路径、尽量用纯英文路径,这会省掉一大批莫名其妙的问题。
6.3 mosquitto这种带复杂依赖的服务类库
mosquitto这一类更复杂:它有客户端库、broker服务端,还有若干个可选的依赖(如TLS支持、WebSocket支持)。你在CMake里用find_package时,能否找到全看它安装时有没有完整导出config文件。
当初我在一个项目里集成mosquitto-2.1.2时,直接用find_package(Mosquitto REQUIRED)失败,原因很直接:我没有指定它的config文件路径,它默认去找的路径里没有。加上CMAKE_PREFIX_PATH之后才正确识别。
所以处理这种库的正确顺序是:
- 先用包管理器安装该库(vcpkg、conan或apt、brew都行),安装时要确保功能组件齐全。
- 在CMakeLists.txt里用find_package找它的config模式。
- 如果找不对,用
cmake --debug-find调试查找过程,或者手工看看安装目录下到底生成的是什么样的.cmake文件。 - 实在找不到合适的config,退回老式FindXXX.cmake模块方案,但这就要自己管理头文件路径和链接库名了,放在最后考虑。
你看到这里应该有个感受:不同形态的库,你真正要关注的东西不一样。header-only关心传递的编译选项,静态库关心链接顺序和依赖,动态库关心运行时路径和ABI兼容性,复杂库则要额外关注组件选择与工具链匹配。
6.4 一个通用的决策流程,帮你判断任何库怎么引入
最后免费送一个“遇到第三方库先干什么”的决策流程,这是我自己的方法论:
- 翻README和根目录CMakeLists.txt,确认它是header-only还是编译型,它对外导出的target叫什么名字,它的最低CMake版本和C++标准要求。
- 用库官方推荐的方式做第一版。官方说find_package就用find_package,官方说FetchContent就用FetchContent。不要在官方有明确推荐时自创一套。
- 定版本、锁版本。GIT_TAG还是URL_HASH,选一个。不做这一步,总有一天会被依赖更新教做人。
- 写一个放到CI里跑的最小demo。配置不是一次性的,换个环境、换个编译器版本,第三方库的配置很可能又出问题。CI能跑,配置才算完整。
- 隔一段时间看看库的版本公告。第三方库一旦升级,任何接口变化都可能传导到你的构建层。
这套流程不局限于某个生态,Windows、Linux、macOS都适用。我自己的习惯是把“引入第三方库”的笔记单独记一份,遇到新库就按这个清单走一遍,每周都能省下一个下午的排查时间。
回到EnTT这个例子上,如果你能完全理解这个库的引入全过程,再去看Eigen3、json库、fmt、spdlog,基本上都是一路绿灯。CMake引入第三方库这件事,说白了就是学会了“识别库形态”和“看懂目标传递”这两个核心,其他都是外围的配置细节。把它们握在手里,往后的工程都不会再被依赖问题拖住进度。