CMake:修改共享库时的冗余链接

时间:2018-04-29 08:52:26

标签: c++ cmake

我在一个由几十个共享库组成的项目上工作,每个库都有许多相关的单元测试。许多库也依赖于其他库,因为某些特定功能的库将使用来自一个更常见的库的代码。最后,当然还有依赖于库的生产可执行文件。

毫无疑问,某些核心公共库的API(头文件)的更改应该触发几乎整个系统的主要重新编译。但通常只有实现中的变化,并且编译的唯一文件是修改后的.cxx,理论上只需要链接修改后的lib - 由于动态链接,不需要重新链接任何其他内容。但是CMake继续前进并且无论如何都要这样做:在重新链接lib后,它重新链接与该lib相关的所有单元测试。然后,它重新链接该lib的依赖关系树中的所有lib以及所有单元测试。最后,它重新链接生产可执行文件。由于项目的规模,这需要很多宝贵的时间。

我使用基于this最小示例的简单项目重现了此行为(为简洁而删除了注释,并将lib更改为共享)。我的系统是英特尔PC上的Ubuntu 16,我的CMake版本是3.5.1。

从一个空目录开始并创建这些文件:

的CMakeLists.txt

cmake_minimum_required (VERSION 2.8.11)
project (HELLO)
add_subdirectory (Hello)
add_subdirectory (Demo)

演示/的CMakeLists.txt

add_executable (helloDemo demo.cxx)
target_link_libraries (helloDemo LINK_PUBLIC Hello)

演示/ demo.cxx

#include "hello.h"
int main() { hello(); }

您好/的CMakeLists.txt

add_library (Hello SHARED hello.cxx)
target_include_directories (Hello PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

您好/ hello.h

void hello();

您好/ hello.cxx

#include <stdio.h>
void hello() { printf("hello!\n"); }

现在运行命令:

mkdir build
cd build
cmake ../
make

您现在可以执行Demo/helloDemo并查看hello!

现在,再次touch Hello / hello.cxx和make。您将看到helloDemo可执行文件已重新链接(“Linking CXX executable helloDemo”)。即使修改了hello.cxx以打印不同的字符串,重新链接的可执行文件仍然是二进制相同的,所以实际上重新连接是不必要的。

有没有办法阻止这些冗余的构建操作?

2 个答案:

答案 0 :(得分:1)

摘要

  • 下面没有适当的解决方案。
  • 修补CMake会产生一个有效的解决方案;但这些变化很可能会引入错误。
  • Bazel没有相同的问题(经过测试),对于您的特定用例可能会明显加快。

旅程

使用Ninja生成器,生成的build.ninja文件(运行cmake -G Ninja ..)具有以下部分。本节清楚地显示了错误:CMake在Hello/libHello.dylib上添加了一个隐式依赖,但只有Order-Only-Dependency就足够了。

接下来是完整部分,但请阅读下面的说明,请向右滚动:

#############################################
# Link the executable Demo/helloDemo

build Demo/helloDemo: CXX_EXECUTABLE_LINKER__helloDemo Demo/CMakeFiles/helloDemo.dir/demo.cxx.o | Hello/libHello.dylib || Hello/libHello.dylib
  LINK_LIBRARIES = -Wl,-rpath,/Users/myuser/devel/misc/stackoverflow/q50084885/ninja/Hello     Hello/libHello.dylib
  OBJECT_DIR = Demo/CMakeFiles/helloDemo.dir
  POST_BUILD = :
  PRE_LINK = :
  TARGET_FILE = Demo/helloDemo
  TARGET_PDB = helloDemo.dbg

我在macOS上,因为Linux将所有*.dylib读为*.so

注意第一条非注释行: build Demo/helloDemo: ...。忍者语法如下: build <output>: <rule> <input> | <implicit input> || <order-only-pre-requisite>

<rule>CXX_EXECUTABLE_LINKER_helloDemoHello/libHelly.dylib既是隐式输入,也是仅订单先决条件。

手动编辑生成的build.ninja并删除隐式输入,但不是仅限订单的先决条件,解决了问题!

修补CMake

使用以下修补程序修补v3.11.1(对于此特定示例)。但是,在没有深入了解CMake源代码且单元测试失败的情况下完成。(其中一个失败的测试是BuildDepends并且只有在没有补丁的情况下才会失败!)

diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index f4faf47a2..bdbf6b948 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -239,7 +239,8 @@ cmNinjaDeps cmNinjaTargetGenerator::ComputeLinkDeps() const
 {
   // Static libraries never depend on other targets for linking.
   if (this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY ||
-      this->GeneratorTarget->GetType() == cmStateEnums::OBJECT_LIBRARY) {
+      this->GeneratorTarget->GetType() == cmStateEnums::OBJECT_LIBRARY ||
+      this->GeneratorTarget->GetType() == cmStateEnums::EXECUTABLE ) {
     return cmNinjaDeps();
   }

此补丁会生成生成的代码,其改变与我所说的手动完全相同。

所以,这似乎有效。

进一步参考的踪迹

试图摆脱构建顺序依赖

这里的问题类似于我们的问题:在编译目标X时,使用对象O和库依赖关系L,在编译对象O之前不需要等待L构建。

  • https://cmake.org/Bug/view.php?id=14726#c35023

  • https://cmake.org/Bug/view.php?id=13799

      

    target_link_libraries在目标及其依赖项之间添加传递链接依赖项和构建顺序依赖项。在许多(如果不是大多数)情况下,构建顺序依赖性不是必需的,并导致覆盖约束依赖图,这限制了构建的并行执行。

         

    [Brad King]:仅供参考,我们默认拥有这些依赖项的原因是因为库的构建规则可能具有自定义命令来生成标题或源,然后由链接到它的目标使用。这甚至适用于非链接目标,如静态库。此外,单个目标内的编译和链接步骤之间没有目标级排序依赖关系的分离。因此,链接(共享库和exes)的任何目标必须对其链接依赖项具有顺序依赖性。

         

    当然,如果一个项目作者想要承担责任,我认为没有理由不选择跳过这种依赖,至少对于静态库。一种方法是添加目标属性以覆盖目标级排序依赖性。这样就可以使静态库依赖于任何内容或依赖于其实现依赖性的子集:

  • https://cmake.org/pipermail/cmake-developers/2014-June/010708.html

      

    我的观点是没有理由等待建立b.cc.o和prog.cc.o;   它们可以与a.cc.o同时构建。

         

    因此我想知道为什么libA.so被添加为b.cc.o的仅订单依赖   什么时候CMake处理这个?

进一步参考

答案 1 :(得分:1)

事实证明答案在LINK_DEPENDS_NO_SHARED属性中。在我的示例中,所需要做的就是将以下行添加到Demo / CMakeLists.txt文件中:

set_target_properties(helloDemo PROPERTIES LINK_DEPENDS_NO_SHARED true)

这将防止helloDemo在其依赖项之一更新时重新链接-如果该依赖项是共享库。

在更复杂的系统上,其中一些库也依赖于其他库,将此设置也添加到它们的配置中会很有用。

感谢CMake邮件列表的Craig Scott的帮助,该邮件已保存在this link