使用cmake时出现链接错误

5
我正在尝试理解为什么在编译使用CMake生成的项目时会出现链接错误。
CMakeFiles.txt 为项目中的每个文件夹构建一个静态库,然后以以下方式将它们全部链接在一起:
# root CMakeLists.txt

add_subdirectory(subfolder1)
add_subdirectory(subfolder2)
add_subdirectory(...)

add_executable(target ${SOURCES})

set(LIBRARIES
  LIB_FOO
  LIB_BAR
  ...
)

target_link_libraries(target
  ${LIBRARIES}
)

然后在每个子文件夹中,我有一个简单的CMakeLists.txt文件,如下所示:
file(GLOB FOO_SOURCE *.cpp)
add_library(LIB_FOO ${FOO_SOURCE})

现在这个代码可以正常工作并编译,但是在链接时出现了未定义的引用错误,因此我尝试调查是否所有内容都可用,看起来是这样的。实际的错误如下:

libLIB_WORLD.a(World.cpp.o): In function `World::generate(WorldGenOptions)':
World.cpp:(.text+0x803): undefined reference to `MapGenerator::MapGenerator(BlockMap*)'
World.cpp:(.text+0x837): undefined reference to `MapGenerator::generate(bool, WorldGenOptions)'

现在,MapGenerator.cpp已成为LIB_MAP的一部分,因此我检查文件是否存在并包含符号:

:~$ nm libLIB_MAP.a | grep generate
....
00000000000044dc T _ZN12MapGenerator8generateEb15WorldGenOptions

:~$ nm CMakeFiles/LIB_MAP.dir/MapGenerator.cpp.o | grep generate
....
00000000000044dc T _ZN12MapGenerator8generateEb15WorldGenOptions

所以这个符号现在已经存在了,接下来我检查它是否与 ld 正确链接:

:~$ make VERBOSE=1
/usr/bin/g++  ... libLIB_MAP.a libLIB_WORLD.a ...

因此,它实际上是与无法找到符号的其他库一起在链接阶段出现的。

我有什么微不足道的问题没注意到吗?我对CMake非常新,所以我想不出什么主意。


1
您可能需要检查为链接器提供的库的顺序。 - Steephen
@Steephen:顺序是问题末尾由VERBOSE=1写的那个,LIB_MAPLIB_WORLD之前链接。 - Jack
1
@Steephen:交换LIB_MAP和LIB_WORLD的顺序似乎解决了问题。因此,WORLD(需要MAP)在其他库之前被链接。 - Jack
@Steephen,我刚刚简化了调用,因为它链接了40-50个库和15个共享库,我想集中精力解决问题。 - Jack
@ComicSansMS 你说得对。我们就说这是我在将CMake引入一个类似规模的现有项目时使用的最后一招吧。我花了几个月时间处理库依赖关系,最终还是需要对一些第三方库进行分组。我没有像我链接的那篇文章中那样将其放入CMakeLists.txt文件本身中,而是只在需要的地方使用了我的GNU ARM交叉编译工具链,并使用了SET(CMAKE_C_LINK_EXECUTABLE "<CMAKE_LINKER> <CMAKE_C_LINK_FLAGS> -o <TARGET> --start-group <OBJECTS> <LINK_LIBRARIES> --end-group") - Florian
显示剩余2条评论
1个回答

9
这是一个库依赖问题,CMake 没有正确地对其进行建模。
你的“LIB_WORLD”引用了“LIB_MAP”的方法。这种依赖关系在您的 CMake 脚本中没有被建模。由于两者都是静态库,它们自己构建时仍然可以正常工作。(请记住,静态库本质上是一堆打包在一起的目标文件,但它们永远不会通过链接器。)
但是,一旦将它们链接到可执行文件或共享库中,您就会遇到问题。即使您的可执行文件链接了“LIB_WORLD”和“LIB_MAP”,但顺序却错误了。因此,在链接器尝试解析“LIB_WORLD”的缺失符号时,它还不知道“LIB_MAP”导出的符号,因此您遇到了错误消息。
正确的修复方法是引入对“LIB_WORLD”的依赖性:
add_library(LIB_WORLD [...])
target_link_libraries(LIB_WORLD LIB_MAP)

现在,每当您链接针对 LIB_WORLD 的内容时,您也将始终链接 LIB_MAP,并且 CMake 会确保顺序正确。(特别是,如果您的可执行文件没有直接使用来自 LIB_MAP 的方法,则可能希望将其从其 target_link_libraries 中完全删除。)
作为额外的好处,现在它允许您将 LIB_WORLD 构建为共享库,在此之前这将导致链接器错误。

你刚刚为我节省了很多时间。谢谢!这个需要被接受为正确答案。虽然这对我来说完全违反直觉。我本以为你想先链接到LIB_MAP,因为那些符号需要在后续的LIB_WORLD链接中链接...我的大脑仍然不太想理解这一点,但现在还早,也许需要更多的咖啡... - While-E
非常感谢!我花了几个小时试图自己解决它。 - Keerpich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接