add_library核心是定义构建目标而非声明库,需指定唯一逻辑名、显式类型(static/shared等)和真实存在的源文件;类型选错或路径错误导致链接失败,object/interface库不生成二进制文件,命名冲突会引发静默覆盖。

add_library 用法核心就三件事:名字、类型、源文件
它不是“声明一个库”,而是定义一个构建目标——CMake 会据此生成 libxxx.a、xxx.dll 或其他对应产物。漏掉类型或写错源路径,cmake 不报错但链接时崩,这是新手最常踩的坑。
-
add_library第一个参数是逻辑名(如utils),不是文件名,也不带扩展名;最终生成的文件名由平台决定(Linux 是libutils.a,Windows 是utils.lib) - 类型必须显式指定或受全局变量控制:
STATIC、SHARED、MODULE、OBJECT、INTERFACE—— 省略时默认为STATIC,但依赖BUILD_SHARED_LIBS变量是否启用,行为不透明 - 源文件路径必须真实存在,且不能只写头文件(
.h);CMake 不检查头文件内容,但若路径不存在,cmake配置阶段就会失败
STATIC 和 SHARED 的关键区别不只是文件后缀
静态库把代码复制进可执行文件,动态库只存引用,运行时加载。选错类型,轻则链接失败,重则部署后程序启动报 undefined symbol 或 DLL not found。
-
STATIC库:不需要导出符号,编译快,发布简单(单个.exe),但体积大、无法热更新 -
SHARED库:CMake 自动开启POSITION_INDEPENDENT_CODE属性,确保生成位置无关代码;必须显式导出函数(如用__declspec(dllexport)或__attribute__((visibility("default")))),否则 Linux 下nm -D看不到符号,Windows 下dumpbin /exports为空 - 跨平台项目慎用
BUILD_SHARED_LIBS全局开关:它会让所有未指定类型的add_library目标按此值处理,但INTERFACE或OBJECT库不受影响,容易误判
OBJECT 和 INTERFACE 库不是“真库”,别指望看到 .a/.so 文件
它们不产出二进制库文件,纯属编译期抽象机制。误当普通库使用,会导致 target_link_libraries 报错 “target not found” 或链接器找不到符号。
-
OBJECT库:只编译源码为.o或.obj,供多个目标复用。必须用$<xxx></xxx>生成器表达式引用,直接写target_link_libraries(myapp PRIVATE xxx)会失败 -
INTERFACE库:零编译、零源码,只传递头文件路径(target_include_directories)、宏定义(target_compile_definitions)或依赖项(target_link_libraries)。常用于封装第三方头文件包,比如:add_library(fmt INTERFACE)+target_include_directories(fmt INTERFACE /path/to/fmt/include) -
MODULE库:和SHARED编译方式类似,但不参与链接,专为dlopen/LoadLibrary设计;在 macOS 上等同于SHARED,但命名建议用.dylib后缀避免混淆
EXCLUDE_FROM_ALL 不是“禁用”,而是“懒加载”
加了这个参数,make 或 ninja 默认不构建它,但只要别的目标(比如 add_executable)通过 target_link_libraries 依赖它,CMake 就会自动把它拉进来。很多人以为加了就彻底不用了,结果发现链接失败却查不到原因。
- 典型场景:测试专用工具库、文档生成辅助库、CI 中临时构建的中间产物
- 它不影响
target_link_libraries的解析,只影响默认构建目标(all)的依赖图 - 调试时可用
cmake --build . --target xxx手动构建被排除的目标,验证其是否能正常编译
add_library 定义的目标名,在整个 CMake 作用域内必须唯一。两个子目录里都定义 add_library(core STATIC ...),CMake 不报错,但后一个会覆盖前一个——链接时可能用错版本,而且 IDE(如 CLion、VS)索引会混乱。大型项目务必用命名空间前缀,比如 myproj_core、myproj_utils。











