CMake中find_package引入库与系统库链接顺序错误解决方法
问题复现
初始CMake配置:
find_package(something) add_executable(exe main.cc) # libsomething 依赖 dlopen/dlsym 接口,手动追加dl链接 target_link_libraries(exe PRIVATE something::something dl)
编译报错输出:
g++ -o exe -ldl path/to/libsomething.a libsomething.c: undefined reference to `dlsym'
尝试调整链接顺序、重复添加dl依赖均未解决问题,CMake始终将-ldl放在libsomething.a之前,重复项会被自动去重:
# 尝试1:交替写依赖项 target_link_libraries(exe PRIVATE dl something::something dl something::something ) # 实际链接命令:g++ -o exe -ldl -ldl path/to/libsomething.a 仍然报错
# 尝试2:分多次追加链接依赖 target_link_libraries(exe PRIVATE dl) target_link_libraries(exe PRIVATE something::something) target_link_libraries(exe PRIVATE dl) # 实际链接命令:g++ -o exe -ldl -ldl path/to/libsomething.a 仍然报错
手动调整参数顺序,将-ldl放在静态库之后即可正常编译:
g++ -o exe path/to/libsomething.a -ldl
根本原因
GNU链接器处理静态库(.a文件)时默认遵循从左到右顺序解析符号的规则:链接器按参数顺序扫描库文件,遇到未解析的符号时只会向后查找,不会回溯前面已经扫描过的库。libsomething.a依赖dl库提供的dlsym/dlopen接口,因此-ldl必须放在libsomething.a之后才能正常解析符号。
CMake默认会对传入的链接项做自动去重、依赖拓扑排序,它默认将系统库dl识别为无其他依赖的底层库,会自动将其排到链接序列的靠前位置,手动重复添加dl项也会被去重逻辑过滤,无法达到调整顺序的效果。
另外你初始配置里存在两处基础笔误,需要先修正:
find_pacakge拼写错误,正确写法为find_packageadd_executable定义的目标名为exe,但target_link_libraries里填写的目标名是run,二者需要保持一致
解决方法
方案1:手动追加链接选项(快速修复)
使用target_link_options传入原始链接参数,这类参数CMake不会做重排、去重处理,会直接追加到链接命令的末尾,保证-ldl出现在所有静态库之后:
find_package(something) add_executable(exe main.cc) target_link_libraries(exe PRIVATE something::something) # 直接追加-ldl到链接命令末尾,不被CMake调整顺序 target_link_options(exe PRIVATE -ldl)
跨平台兼容场景下可以用CMake内置的CMAKE_DL_LIBS变量,自动适配不同平台的dl库名称,Windows等不需要单独链接dl库的平台变量会自动为空:
if(CMAKE_DL_LIBS) target_link_options(exe PRIVATE ${CMAKE_DL_LIBS}) endif()
方案2:补全导入目标的依赖(最优方案)
问题的本质是something::something这个静态库导入目标没有正确声明自身对dl库的依赖,CMake无法自动推导正确的链接顺序。
如果你无法修改第三方库的上游配置,可以直接在自己的CMake脚本里给导入目标补全依赖属性,CMake会自动按依赖拓扑排序,保证-ldl排在libsomething.a之后,不需要手动调整链接选项,兼容性最好:
find_package(something) # 给第三方导入目标补上对dl库的依赖 set_target_properties(something::something PROPERTIES INTERFACE_LINK_LIBRARIES "${CMAKE_DL_LIBS}" ) add_executable(exe main.cc) target_link_libraries(exe PRIVATE something::something)
如果你是libsomething的维护者,直接在库的CMake导出配置里添加dl的公开依赖即可,所有使用该库的下游项目都不需要手动处理dl的链接顺序问题:
# 写在libsomething项目的CMake配置中 target_link_libraries(something PUBLIC ${CMAKE_DL_LIBS})
内容的提问来源于stack exchange,提问作者Bigcat

