CMake中链接其他静态库的静态库——一个可用,另一个不行。为什么?

9

背景

我有一个项目,它使用其他较小的项目。这些项目本身由其他项目组成。大部分是传统遗留代码或由管理人员规定排列的,因此将所有内容合并到一个项目中不是选项。一些库在远程共享上进行预编译。

我有两个主要的子项目让我头疼:

  • Foo 项目 是一个可执行文件和库,链接了几个静态子项目 (foo_subproject_1, foo_subproject_n)。这些子项目进一步链接到远程位置的静态库 (some_lib, some_other_lib)。Foo 项目的可执行文件可以正确地编译、链接和运行

  • Bar 项目 是一个可执行文件,链接了几个其他项目,包括 libFoo。链接失败,显示“undefined reference to” foo_subproject 函数

据我所知,这两个项目的链接指令类似。在 Stack Overflow 上查看后,发现静态库与静态库链接不应该工作,但是我对如何成功编译 Project Foo 感到困惑。

编译器是 gcc 和 g++ 4.9.2(已经检查了一些部分使用 C,一些部分使用 C++ 的 extern "C" 问题)


问题

我对 CMake add_subdirectory 的工作方式或链接器的工作方式中的一个或两个方面存在误解。有人能否请解释一下 Project Foo 如何成功运行以及 Project Bar (未能)按预期工作的原因?

更新 我仔细查看了 foo_lib.a 和 foo_runtime。

我应该从一开始就确定有些问题,因为 foo_runtime 的大小接近 100MB,而 foo_lib 只有 10KB。

nm 显示 foo_lib.a 引用了几十个符号,其中大多数未定义。与此同时,foo_runtime 引用了 所有 符号。

同样令人困惑的是,foo_subproject_1.a 同样大部分未定义。再次强调,这是我所期望看到的;但我不明白如何从中构建 foo_runtime?

我仍然不清楚为什么 some_library -> subproject -> foo_runtime 成功,但 some_library -> subproject -> foo_lib -> bar 不成功。在我的调查阶段,我期望这两个命令都会失败。


Project Foo 使用 CMake 排列如下:

cmake_minimum_required(VERSION 2.6)
project(foo)

set(FOO_SRCS
    # source and headers for main foo project
)

# Project Foo libraries are subdirectories within this project
add_subdirectory(subs/foo_subproject_1)
add_subdirectory(subs/foo_subproject_2)

# Runtime executable
add_executable(foo_runtime main.c ${FOO_SRCS})
target_link_libraries(foo_runtime foo_subproject_1 foo_subproject_2)

# Library version (static library)
add_library(foo_lib STATIC ${FOO_SRCS})
target_link_libraries(foo_lib foo_subproject_1 foo_subproject_2)

Foo项目的子目录大致具有以下结构:

cmake_minimum_required(VERSION 2.6)
project(foo_subproject_<n>)

set(FOO_SUBPROJECT_<N>_SRCS
    # source and headers for subproject
)

# foo_subproject's remote libraries are all static
add_library(some_lib STATIC IMPORTED)
set_target_properties(some_lib PROPERTIES IMPORTED_LOCATION /path/to/libsome_lib.a)

add_library(some_other_lib STATIC IMPORTED)
set_target_properties(some_other_lib PROPERTIES IMPORTED_LOCATION /path/to/libsome_other_lib.a)

include_directories(/paths/to/libs/include/)

# Static library for foo_subproject_N, links against static libs above
add_library(foo_subproject_<N> STATIC ${FOO_SUBPROJECT_<N>_SRCS})
target_link_libraries(foo_subproject_<N> some_library some_other_library)

Project Bar 的排列方式如下:

cmake_minimum_required(VERSION 2.6)
project(bar)

set(BAR_SRCS
    # source and headers for main bar project
)

# Project Bar libraries are remote from Bar's perspective
add_library(foo_lib STATIC IMPORTED)
set_target_properties(foo_lib PROPERTIES IMPORTED_LOCATION /path/to/foo/libfoo_lib.a)

include_directories(/path/to/foo/include/)

# Runtime executable
add_executable(bar main.c ${BAR_SRCS} foo_lib)

项目栏与多个错误无法链接(编译正常),错误形式如下:

bar_frobulator.cpp:123: undefined reference to 'foo_subproject_1_init_frobulation'

foo_subproject_1_init_frobulation位于foo_subproject_1中。


库的顺序可能很重要。如果库A需要来自库B的项目,则应在库A之后放置库B。 - john
@john 我认为这不是问题所在。Foo的子项目按正确顺序排列其库,但子项目之间并不相互依赖,因此对于Foo来说并不重要。Bar的库也是独立的(这里仅显示了foo_lib以简洁为例,但它的任何库都没有相互连接)。 - KidneyChris
难道你需要显式地包含foo子项目库才能得到答案吗?如果我理解得正确,那么你只是在链接foo项目而不是子项目。 - john
@john 或许是这样(但如果是真的,Bar 阶段最终会有很多东西需要链接,所以如果可以的话,我宁愿避免这种情况)。如果是真的,为什么 Foo 运行时能够工作?它没有链接到 some_library(它的子项目是这样做的),但它可以愉快地调用那些函数。为什么对于 Foo 而言这样可以运行,而对于 Bar 却不行呢? - KidneyChris
@Tsyvarev 不好意思,那是我在写问题时误按了 CMake;它确实使用了 target_link_libraries。请给我一点时间来纠正这个错误... - KidneyChris
显示剩余2条评论
2个回答

9
请问一下,项目Foo是如何成功运作的,而项目Bar却无法按照预期工作呢?
简单来说:创建静态库不需要进行链接步骤。
细节说明:
在Foo项目中,你有一个可执行文件foo_runtime,它能够“正常”工作,因为它与适当的库链接(例如库foo_subproject_1,该库定义了符号foo_subproject_1_init_frobulation)。
来自Bar项目的可执行文件bar则没有执行该链接操作,所以会失败。这行代码
target_link_libraries(bar foo_lib)

链接使用了foo_lib,但是这个库没有定义所需的符号foo_subproject_1_init_frobulation

请注意,该行

target_link_libraries(foo_lib foo_subproject_1 foo_subproject_2)

在Foo项目中,不执行实际的链接:一般来说,构建静态库不涉及链接步骤。
给出的行只是将foo_subproject_*库的包含目录(和其他编译功能)传递到foo_lib库中。

如何使之工作

因为静态库foo_lib不跟踪其依赖关系,所以您需要将bar与一个知道它的库链接起来。例如,将foo_lib变成共享库,或者按照所引用问题How to combine several C/C++ libraries into one?的建议将foo_subproject_*库组合成存档库。
另外,您可以在Bar项目中构建Foo子项目,并且使用在Foo项目中创建的“普通”foo_lib目标,而不是创建IMPORTED foo_lib目标。在这种情况下,该行
target_link_libraries(bar foo_lib)

这意味着CMake将使用foo_subproject_*库(在CMake中已经“链接”)来实际链接bar,因为这些库已经“链接”到foo_lib中。再次强调,最后一步“链接”只对CMake有意义:文件foo_lib.a不知道需要foo_subproject_*库。


那么foo_runtime出了什么问题?如果target_link_libraries(foo_subproject_1 some_library some_other_library)没有进行链接,那么为什么target_link_libraries(foo_runtime foo_subproject_1 foo_subproject_2)可以工作呢?还是说关键在于这一行代码中的“传递包含目录(和其他编译特性)”? - KidneyChris
构建静态库(foo_subproject_1)不涉及链接步骤,构建可执行文件(foo_runtime)则需要链接步骤。 - KamilCuk
@KidneyShris:foo_runtime不是静态库,而是一个可执行文件,因此它可以正常链接。在你的问题帖子中,你直接将其与子库链接起来,这些子库定义了所有需要的符号。但如果你将其与foo_lib链接,那么CMake传播将会发挥作用,就像我回答末尾所描述的那样。 - Tsyvarev
啊哈!我想我明白了。查看了CMake文档——“默认情况下,这个签名的库依赖是可传递的。当这个目标链接到另一个目标时,链接到这个目标的库也会出现在另一个目标的链接行中。”——这是否意味着在我的子项目中包含“target_link_libraries”仅让主项目看到需要一些库例如some_library和some_other_library?foo_runtime进行链接并获取依赖项。foo_lib不进行链接,因此大多数情况下未定义? - KidneyChris
1
是的,“传递性库依赖项”在 CMake 文档中是正确的位置。实际上,foo_lib 包含这些依赖项... 但仅限于 Foo 项目中的 CMake 目标。作为静态库文件 foo_lib.a 不包含依赖项。 - Tsyvarev

2

Tsyvarev的回答很好地描述了我实际上正在做什么(而不是我认为我在做什么),让我查找正确的内容来回答我所拥有的根问题(“为什么foo_runtime可以工作,但bar_runtime不能,当两者都链接到静态库,这些静态库又链接到静态库?”)

从{{link1:target_link_libraries的CMake文档}}:

默认情况下,此签名的库依赖关系是传递的。当将此目标链接到另一个目标时,链接到此目标的库也将出现在另一个目标的链接行中。

target_link_libraries不会导致静态库foo_subproject_1foo_subproject_2的链接(静态库不会调用链接器)。它所做的是使尝试链接foo_subproject_1foo_subproject_2的任何内容的所需库列表可用。

因此,就CMake而言,我的foo_runtimefoo_lib target_link_libraries命令实际上是:

target_link_libraries(foo_runtime foo_subproject_1 some_library some_other_library foo_subproject_2 some_library some_other_library)
target_link_libraries(foo_lib foo_subproject_1 some_library some_other_library foo_subproject_2 some_library some_other_library)

foo_lib 是静态的,因此不会调用链接器foo_runtime 是可执行文件,所以会。

bar 是一个完全不同的项目,所以无法利用传递依赖关系(链接失败,因为我缺少底层库中所有符号)。

这种 target_link_libraries 的行为是意料之外的,因此整个项目的行为有些误导性(因为看起来像我在子项目级别捆绑了一堆库,然后在上层捆绑了这些库。实际上,我只是告诉上层需要哪些库而已)。

总之:

“为什么 foo_runtime 能工作,但 bar_runtime 不行,当两者都连接到链接到静态库的静态库时?”

“foo_runtime 没有连接到连接到静态库的静态库。foo_runtime 链接的静态库比你最初认为的要多。

bar_runtime 不起作用是因为你的 foo_lib 基本上是空的。”


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