【问题标题】:How to avoid linking unnecessary static libraries with CMake?如何避免将不必要的静态库与 CMake 链接?
【发布时间】:2021-12-16 08:50:27
【问题描述】:

我有一个包含 2 个静态库和一个可执行文件的小项目。

  • utils:静态库。
  • lib1:一个静态库。 lib1 中的一些函数使用了utils 代码,但大多数没有。
  • main :使用来自lib1 的函数的可执行文件,不使用来自utils 的代码。

在一个简单的 Visual Studio 项目中,main 只会链接lib1。但由于 lib1 需要一些 utils 的 target_include_directories 来编译,lib1 的 cmake 代码包含一个 target_link_libraries 调用:

utils CMakeLists.txt:

target_include_directories(utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) # lib1 needs this

lib1:CMakeLists.txt:

target_link_libraries(lib1 PRIVATE utils) # some of lib1 code uses utils

主要:CMakeLists.txt:

target_link_libraries(main PRIVATE lib1) # this causes utils to link as well

问题是在生成的项目中,main 包含 2 个依赖项而不是 1 个。如果我手动从依赖项列表中删除 utils,一切正常。

但现在我必须编译 utils 才能编译 main。

main 仅与lib1 链接的正确方法是什么?更一般地说,如何避免使用 cmake 链接不必要的静态库?

【问题讨论】:

  • 名为utilsmisc 的库通常会成为太多好东西 的垃圾场。我建议将utils 拆分为更小的部分 - 并适当地命名生成的库。这样一来,不只是继续向它们添加功能会更容易。
  • @TedLyngmo 这只是一个虚拟案例。这种使用模式在我开始从事的一个大型项目中经常出现。试图看看如何解决这个问题。
  • 是的,我也见过很多。 “解决方案”通常是拆分臃肿的库。你可以轻松开始。将其拆分为 2 部分,以便库中仅保留 lib1 使用的函数。如果这不能更容易地为剩余的函数提供一个好的库名称,请再次拆分。

标签: c++ cmake


【解决方案1】:

我找到了解决问题的方法。在我有限的测试中,它适用于 MSVC 和 GCC。

基本上,我没有在 lib1 的 CMakeLists.txt 中使用target_link_libraries,而是使用以下函数:

function(add_interface_includes target_library src_library)    
    get_target_property(library_interface_includes ${src_library} INTERFACE_INCLUDE_DIRECTORIES)
    set_target_properties(${target_library} PROPERTIES INCLUDE_DIRECTORIES "${library_interface_includes}")
endfunction()

当我写add_interface_includes(lib1 utils) 时,我基本上模仿target_link_libraries 的行为,而不创建链接器依赖项。当main链接lib1时,如果我明确指定它只会链接utils

【讨论】:

  • 这会将src_library 的包含目录添加为target_library 的依赖项。如果只需要src_libraries 的头文件,这将正常工作。但是如果 target_library 被修改为依赖于实际的 src_library 将停止工作。我认为您需要创建两个“util”库:一个 header-only,以及一个用于实际链接的静态库。然后,您使仅标头库成为 lib1util 链接器库的依赖项。这是一个更好、更便携且 IMO 正确的解决方案。
  • 实际上,即使 utils 不是仅标头库,这也有效。我对 MSVC 的这种行为很熟悉:你只链接你使用的东西(直接或间接)。但是,如果您的代码不使用 library2(直接或间接通过 library1),那么某些 library1 使用 library2 的事实并不重要。它可能更脆弱 - 是的,但我不想为我不需要的东西买单。
  • @EladMaimoni 好的,util 不是真正的仅标头库,但您在 lib1 中使用的函数仅使用 util 的标头部分?如果是这样,我会拆分 util 并制作一个真正的仅包含这些部分的标头库。这也让每个人都更容易使用这些库。
  • @TedLyngmo 不,lib1 仅使用来自 util 的非标头函数。但是 main 不要使用 lib1 中的这些特定功能。任何库中都没有仅标头功能。而且,只要主代码不通过 lib1 间接使用任何 utils 代码,它就不必链接它。顺便说一句 - 这也适用于 GCC。
  • 啊哈,我想我终于明白了。这需要一段时间 :-) 在这种情况下,我可能会拆分 lib1
【解决方案2】:

如果utils 库的某些功能可以通过仅获取标头来使用,那么我将制作仅标头目标。

utilsCMakeLists.txt:

add_library(utils_hdr INTERFACE)
target_include_directories(utils_hdr INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

add_library(utils STATIC *.c)
target_link_libraries(utils PUBLIC utils_hdr)

lib1CMakeLists.txt:

target_link_libraries(lib1 PRIVATE utils_hdr)

mainCMakeLists.txt:

target_link_libraries(main PRIVATE lib1)

【讨论】:

  • utils 不仅仅是标题。不过谢谢。
【解决方案3】:

主要问题是静态库只不过是目标文件的存档。链接静态库就像链接目标文件本身一样。

这导致两件事:

  1. 静态库本身不链接;
  2. 您需要链接静态库的所有依赖项。

对于您的项目,main 程序必须与两个静态库链接。

【讨论】:

  • 我不明白为什么我必须链接两者。事实是我的项目在没有 utils 依赖的情况下编译得很好,因为没有代码路径指向它的目标文件之一。
  • 理想情况下,我可以找到一个命令,将额外的包含目录添加到 lib1,而无需创建依赖项。
  • @EladMaimoni 这不太可能。你最好的选择是 Ted 的建议,将库分成更小的部分。使用 CMake 之类的工具,依赖项很容易处理,并且拥有许多小型静态库不会导致运行时损失。
  • 请检查我的回答。它目前有效。您是否声称它会在某些情况下中断?
猜你喜欢
  • 2015-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
  • 1970-01-01
  • 2013-10-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多