CMake中能否将多个对象库合并为单个对象库?
1. 现象产生的原因
CMake的**对象库(OBJECT library)**设计初衷是关联源码文件、生成对应目标文件集合,而非作为“容器”合并已有对象库。当你创建LibAB.Obj时,若仅指定依赖LibA.Obj和LibB.Obj但未提供自身源码,CMake会判定这个对象库没有可编译的输入内容,因此抛出“无源码”的构建错误。
本质上,对象库的依赖关系仅用于传递编译选项、头文件路径等配置,不会自动将依赖对象库的目标文件并入当前对象库中。
2. 是否属于CMake的bug?
这不是CMake的bug,而是符合官方设计的预期行为。CMake文档明确说明,对象库的核心用途是复用同一源码集合编译出的目标文件到多个不同最终目标(比如同时生成静态库和共享库),并不支持直接合并其他对象库为新的对象库。
3. 可行的解决方案
结合你模块化重构的需求(子模块单独生成对象库用于测试/基准,最终合并为单个共享库),推荐以下实用方案:
方案1:直接在共享库中依赖子模块对象库
跳过合并对象库的步骤,直接在生成最终共享库时链接所有子模块对象库:
# 子模块对象库(保持现有定义) add_library(LibA.Obj OBJECT libA.cpp) add_library(LibB.Obj OBJECT libB.cpp) # 直接生成共享库,关联子模块对象库 add_library(LibAB SHARED) target_link_libraries(LibAB PRIVATE LibA.Obj LibB.Obj)
CMake会自动将两个对象库的目标文件打包进LibAB共享库,同时LibA.Obj和LibB.Obj依然可以单独被测试、基准测试等目标引用,完全满足快速迭代需求。
方案2:使用生成器表达式直接引用目标文件
通过$<TARGET_OBJECTS:>生成器表达式,直接将子模块对象库的目标文件包含到共享库中:
add_library(LibAB SHARED $<TARGET_OBJECTS:LibA.Obj> $<TARGET_OBJECTS:LibB.Obj> ) # 传递子模块的编译配置(如头文件、编译选项) target_link_libraries(LibAB PRIVATE LibA.Obj LibB.Obj)
该方案要求CMake版本≥3.12,优点是显式控制目标文件的包含,同时保留子模块对象库的独立性。
方案3:自定义命令合并目标文件(跨平台兼容)
手动将子模块对象库的目标文件合并为一个归档文件,再基于该归档文件构建共享库:
# 获取子模块对象库的目标文件列表 get_target_property(LIB_A_OBJS LibA.Obj OBJECTS) get_target_property(LIB_B_OBJS LibB.Obj OBJECTS) # 合并目标文件为归档文件(根据平台选择工具) if(MSVC) set(MERGE_CMD ${CMAKE_LINKER} /lib /OUT:libab_objs.lib ${LIB_A_OBJS} ${LIB_B_OBJS}) else() set(MERGE_CMD ${CMAKE_AR} rcs libab_objs.a ${LIB_A_OBJS} ${LIB_B_OBJS}) endif() add_custom_command( OUTPUT libab_objs.$<$<BOOL:MSVC>:lib>.$<$<NOT:$<BOOL:MSVC>>:a> COMMAND ${MERGE_CMD} DEPENDS LibA.Obj LibB.Obj COMMENT "Merging LibA.Obj and LibB.Obj into combined archive" ) # 基于归档文件生成共享库 add_library(LibAB SHARED) target_link_libraries(LibAB PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libab_objs.$<$<BOOL:MSVC>:lib>.$<$<NOT:$<BOOL:MSVC>>:a> )
此方案适合需要对合并过程精细控制的场景,同时保证跨平台兼容性。
方案4:伪源码hack(应急可用,不推荐长期使用)
给合并用的对象库添加一个空的源码文件,让CMake认为它有可编译内容,再链接子模块对象库:
# 创建空源码文件(比如empty.cpp,内容为空) add_library(LibAB.Obj OBJECT empty.cpp) target_link_libraries(LibAB.Obj PRIVATE LibA.Obj LibB.Obj) # 基于合并后的对象库生成共享库 add_library(LibAB SHARED $<TARGET_OBJECTS:LibAB.Obj>)
该方案属于临时 workaround,空源码文件会引入无意义的编译步骤,不推荐长期使用。
内容的提问来源于stack exchange,提问作者Alecto

