如何删除 cmake 静态库的编译依赖项?

Posted

技术标签:

【中文标题】如何删除 cmake 静态库的编译依赖项?【英文标题】:How to remove compile dependencies for cmake static libraries? 【发布时间】:2018-03-20 18:41:48 【问题描述】:

考虑这个 CMake 设置:

add_library( A STATIC modules/a/src/src1.cpp modules/a/src/src2.cpp )
target_include_directories( A PUBLIC modules/a/inc )
target_compile_definitions( A PUBLIC USING_A=1 )

add_library( B STATIC modules/b/src/src1.cpp modules/b/src/src2.cpp )
target_include_directories( B PUBLIC modules/b/inc )
target_compile_definitions( B PUBLIC USING_B_WRAPPER=1 )
target_link_libraries( B PUBLIC A )

add_library( C STATIC modules/c/src/src1.cpp )
target_include_directories( C PUBLIC modules/c/inc )
target_link_libraries( C PUBLIC B )

假设来自modules/b/inc 的标头包含来自modules/a/inc 的标头,因此B 库的任何使用者还必须将modules/a/inc 添加到其包含路径并添加USING_A=1 预处理器定义。

让我们使用Ninja 生成器(但是任何生成器都会出现问题,包括MakefileXcodeVisual Studio):

cmake -GNinja ../path/to/source

让我们构建C 库:

ninja libC.a

一切都正确构建,但是大量时间浪费在构建 libA.alibB.a 上,只是为了构建 libC.a

如果我将C'sB 的依赖更改为INTERFACE,则不会构建libA.alibB.a,但编译modules/c/src/src1.cpp 会失败,因为包含目录并编译应该继承自的定义B 及其依赖不会被继承。

有没有办法告诉 CMake 特定目标不应该编译 - 依赖于 target_link_libraries 列表中指定的特定目标(链接依赖应该保留)?我知道有时需要这些依赖项(例如,如果 A 依赖于一些自定义命令为其依赖项生成标头),但这里不是这种情况。我正在寻找一种解决方案,为每个静态库目标指定它是否应该编译依赖于另一个静态库目标,同时仍然保持链接依赖关系并从其依赖关系中获取所有正确的编译标志。

【问题讨论】:

我已经看到了这个问题,但那里的解决方案并没有解决我的问题。即解决了在构建libC.a时并行构建libA.alibB.a的问题,但是当我只需要构建libC.a时它仍然不妨碍构建libA.alibB.a。就我而言,该问题的并行化问题由 ninja 构建系统解决。 感谢您的回答。实际上它没有我目前的尝试那么难看,所以我会接受它:-)。 【参考方案1】:

目前,如果没有完整的目标级依赖关系,我无法解决问题 target_link_libraries。并且不确定事情会在不久的将来发生变化。 CMake 开发者post from the bugreport:

使用 target_link_libraries 总是足以获得排序依赖,许多项目都依赖于此。我们无法为任何目标类型更改它。

在不改变语义的情况下,Ninja 生成器可以检测到依赖项的传递闭包何时没有任何自定义命令,在这种情况下,从编译规则和自定义命令中删除对它的排序依赖项。只需要链接步骤对依赖库文件的完全依赖。在开发者邮件列表中讨论这方面的工作会更好。

That answer 在相关问题上建议在 STATIC 库之间拒绝来自target_link_libraries,这会删除一些不必要的依赖项。我已根据您的目的调整了该场景:

非自然方式

INTERFACE 库表达“编译时依赖关系”。真实库仅用于链接可执行文件或 SHARED 库。

# Compile-time dependency.
add_library( A_compile INTERFACE )
target_include_directories( A_compile PUBLIC modules/a/inc )
target_compile_definitions( A_compile PUBLIC USING_A=1 )
# Real library.
add_library( A STATIC modules/a/src/src1.cpp modules/a/src/src2.cpp )
target_link_libraries(A A_compile)

# Compile-time dependency.
add_library( B_compile INTERFACE )
target_include_directories( B_compile PUBLIC modules/b/inc )
target_compile_definitions( B_compile PUBLIC USING_B_WRAPPER=1 )
target_link_libraries( B_compile PUBLIC A_compile )
# Real library
add_library( B STATIC modules/b/src/src1.cpp modules/b/src/src2.cpp )
target_link_libraries(B B_compile)    

# Final STATIC library.
add_library( C STATIC modules/c/src/src1.cpp )
target_include_directories( C PUBLIC modules/c/inc )
target_link_libraries( C PUBLIC B_compile )

# ...
# Creation executable or non-STATIC library, linked with C
add_executable(my_exe ...)
# Need to manually list libraries, "compile-time linked" to C.
target_link_libraries(my_exe C B A)

因为对于可执行或非STATICtarget_link_libraries 使用真实 STATIC 库,这些STATIC 库甚至会在此类可执行/共享库的目标文件之前构建。

【讨论】:

我不确定这是否能回答我的问题。在我的例子中,C 使用来自B 的函数,它使用来自A 的函数,因此存在明显的链接依赖性(即当可执行链接到C 时,它还必须链接到BA)。但是,在为C 构建源时,为什么我们首先需要为AB 构建源?它们之间不应该存在编译依赖关系。对此有一个ancient bug report,但我不知道它是如何解决的。 我将针对我的用例进行一些更新:我将介绍我的 target_link_libraries 版本,如果目标是静态库并且依赖项是,它将仅依赖于库的 _compile 版本静态库,对于共享库和可执行文件,它将递归收集所有真正的依赖项并直接添加它们。谢谢你的回答! 很好,这可能是一种让 CMake 使用方法更“自然”的方法。看来,您的情况还需要target_include_directoriestarget_compile_definitions_compile 版本。同时,我更新了可能发布的介绍,因为它成为一个完整的答案(不仅仅是代码)。所以有些 cmets 可以安全移除。【参考方案2】:

多年来,我们一直在使用以下函数将静态库相互链接,而不会导致它们之间的构建顺序依赖关系。虽然并不完美,但我们对它总体上还是很满意的。

function(target_link_static_libraries target)
     if(BUILD_STATIC_LIBS_IN_PARALLEL)
        target_link_libraries($target INTERFACE $ARGN)
        foreach(lib $ARGN)
            target_include_directories($target PUBLIC $<TARGET_PROPERTY:$lib,INTERFACE_INCLUDE_DIRECTORIES>)
            target_include_directories($target SYSTEM PUBLIC $<TARGET_PROPERTY:$lib,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>)
            target_compile_definitions($target PUBLIC $<TARGET_PROPERTY:$lib,INTERFACE_COMPILE_DEFINITIONS>)
            target_compile_options($target PUBLIC $<TARGET_PROPERTY:$lib,INTERFACE_COMPILE_OPTIONS>)
        endforeach()
    else()
        target_link_libraries($target PUBLIC $ARGN)
    endif()
endfunction()

已知缺点:

不会自动应用 PIC 等布尔传递属性。 生成依赖关系图时不显示依赖关系。 当查找依赖项发生错误时,它通常会吐出数百行错误消息,因为我们基本上推迟了对库是否存在的检查,直到它被传播到数十个目标。

我们将非常感谢您对上述任何内容的改进或修复。

【讨论】:

感谢您的回答。与此同时,我已经将我的项目迁移到使用柯南 C++ 包管理器,它基本上为我解决了这个问题(我为每个静态库都有一个单独的柯南包,并将它们托管在 Artifactory CE for C++ 的私有实例上)。

以上是关于如何删除 cmake 静态库的编译依赖项?的主要内容,如果未能解决你的问题,请参考以下文章

使用 cmake 如何静态链接一些库和动态链接其他库?

如何删除依赖项目中相同依赖源的重复项

如何从 CMake 或 make 输出编译依赖项?

CMake和静态库顺序

CMAKE如何编译具有不同标志的静态/对象库

使用 CMake 将几个静态库合并为一个