将 mingw 库与 cmake 链接

Linking mingw libs with cmake

长话短说,我将 Godot 构建系统重写为 cmake(仅 windows 部分),主要是因为我想学习它,但我在使用 mingw 编译 Godot 时遇到了麻烦。当我试图编译它时,一开始一切正常,直到链接最终 exe 时,我得到了很多“未定义的引用”错误。看起来主库 (core/scene/editor/..) 看不到彼此的函数。 MSVC 构建工作正常,scons 版本也在 mingw 下编译,所以我显然只是错过了我的 cmake 版本中的一些东西。我试图在我的 cmake 脚本中删除一些 compile/linking 选项作为测试,但没有任何改变。我真的不知道如何调试这个问题,所以如果有人能在正确的方向上踢我,我会很高兴。

好吧,我终于有时间回到这个了。

问题

所以基本上问题出在对 godot 库的循环依赖上。我不认为这是问题所在,因为我被 MSVC 宠坏了(它不依赖于 link 顺序)。另外,我试图在一个规模小得多的测试项目上复制 mingw 中的循环依赖。该项目是一个 30 个库,每个库有两个函数,第一个函数打印字符串,另一个调用所有 30 个库中的所有第一个函数,因此有 30 个循环依赖库。奇怪的是,项目 link 没有问题,打印了 30^2 个字符串..

解决方案

解决方案是在所有库周围使用 -Wl,--start-group/-Wl,--end-group linking 标志。有两种方法可以做到。

第一种方法,是将所有库添加到某种列表中。这可以是全局的 属性,或某些目标上的 属性(不仅仅是一个简单的变量),因此可以从其他子目录访问它。在你形成你的库列表后,你只需 link 它到可执行文件如下

# getting all your libs from the global property..
get_property(__LIBS_LIST GLOBAL PROPERTY EXE_LIBS_LIST)
# linking all libraries to the exe..
target_link_libraries(my-exe PRIVATE -Wl,--start-group ${__LIBS_LIST} -Wl,--end-group)

这是最简单的解决方案,但要小心你 link 对你的 exe 的库的依赖性,因为看起来当 CMake 为你的 exe 创建 link 行时,它首先列出所有直接 linked 到你的 exe 的库,并且只有在它放置来自库的依赖关系的库之后才直接 linked 。基本上如果你的目标依赖树看起来像这样:

exe // your main exe file
    - lib_A // lib A linked directly to the main exe
        - lib_AA // lib AA linked to the lib_A
            - lib_AAA // lib AAA linked to the lib_AA
    - lib_B // lib B linked directly to the main exe
        - lib_BB // lib BB linked to the lib_B
            - lib_BBB // lib BBB linked to the lib_BB

您的 link exe 订单将如下所示:

// first libs linked directly to the exe 
lib_A
lib_B
// only after recursively initial libs dependencies 
lib_AA
lib_AAA
lib_BB
lib_BBB

意思是,如果您 link 您的库 target_link_libraries(my-exe PRIVATE -Wl,--start-group ${__LIBS_LIST} -Wl,--end-group),--start-group 和 --end-group 将只保护直接 linked 的库你的执行文件。我没有在文档中找到这个描述,但我发现 SO 问题几乎谈论相同的行为(CMake library linking order)。此外,正如我在 mingw 上测试的那样,库 lib_AA/lib_AAA/lib_BB/lib_BBB 是如何被 linked 的,通过 PRIVATE 或通过 INTERFACE,结果是相同的。

第二种方式,是利用 link 的递归性扩展库的依赖性 linked 直接到 exe。从我的示例中您可以看到,lib_A (lib_AA/lib_AAA) 的依赖项没有与 lib_B (lib_BB/lib_BBB) 的依赖项混合在一起。所以基本上我们可以做的是创建接口库并在之后立即连接到它 -Wl,--start-group。然后将任意数量的库添加到它的界面,并将 link global-libs 添加到您的 exe(顺序无关紧要)。最后,关闭全局库中的组

add_library(global-libs INTERFACE)
target_link_libraries(global-libs INTERFACE -Wl,--start-group)
# ...
# linking another libs, and linking global-libs to exe
# ...
target_link_libraries(global-libs INTERFACE -Wl,--end-group)

这将确保所有连接到 global-libs 的库都被 -Wl,--start-group/-Wl,--end-group.

包围

现在,理论上,CMake 应该自己处理循环依赖,通过多次将库放在 link 行中(多少次由 LINK_INTERFACE_MULTIPLICITY 控制)。但是这种方法对我不起作用(我只是错过了一些东西..)。另外,您需要声明 cmake 目标之间的依赖关系,并且使用 -Wl,--start-group/-Wl,--end-group 您可以将一个特定的接口库设置为所有具有循环依赖关系的库的持有者..