【问题标题】:"Hiding" transitive external dependencies / combining libraries with CMake“隐藏”传递外部依赖项/将库与 CMake 组合
【发布时间】:2020-12-03 20:10:26
【问题描述】:

这个问题可能部分重复,例如this question,但更多的是关于如果有更好的解决方案的话。由于这个问题结束得相当长,我用粗斜体用“+Q+”标记了具体问题

我有这样的情况,我写了一个小型库 B,它依赖于其他一些大型项目 A 拆分为许多库 A1, A2, ..., An,其中一些是我的库所依赖的,而另一些则不是。进行正确的链接有点痛苦。该库开始被其他人使用,我想避免每个人都必须经历这个可怕的链接过程,即我想将A的所有外部库编译到我的B中。假设 A 完全是外部的,即 我无法重新编译 A(在这种情况下我有,但它很复杂,我想知道我没有的情况下的选项)。

我想这一定是一件非常标准的事情,我使用过其他流行的库,我从来不需要链接它们传递依赖的所有其他库..?所以我开始寻找解决方案,虽然我找到了可行的解决方案,但大多数解决方案看起来都是一团糟,我想知道这是否真的在实践中完成,或者是否有一些惯用的方法。

如果我需要不同的情况,为了避免更多的麻烦,我想考虑静态/共享库的所有组合,即

  • A 和 B 是静态的
  • A 是静态的,B 是共享的
  • A 是共享的,B 是静态的
  • A 和 B 共享

为了给代码设置一些 MWE(CMakeLists.txt 文件中的变量 LIB1_ROOTLIB2_ROOTA/B/ 分别):

A/include/lib1.hh

struct Lib1 { void run() const; };

A/src/lib1.cc

#include <iostream>
#include <lib1.hh>
void Lib1::run() const { std::cout << "Hello from lib1\n"; }

A/CMakeLists.txt

cmake_minimum_required(VERSION 3.14)
project(A)
include_directories(include)
add_library(lib1 src/lib1.cc)
install(TARGETS lib1 DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/lib")

B/include/lib2.hh

class Lib2 {
    class Implementation;
    Implementation* impl;
public:
    Lib2();
    ~Lib2();
    void run() const;
};

B/src/lib2.cc

#include <iostream>
#include <lib1.hh>
#include <lib2.hh>
class Lib2::Implementation {
    const Lib1 m_lib1{};
public:
    void run() const { std::cout << "using lib1 from lib2: "; m_lib1.run(); }
};
Lib2::Lib2() : impl{new Implementation} {}
Lib2::~Lib2() { delete impl; };
void Lib2::run() const { impl->run(); }

App/src/app.cc

#include <lib2.hh>
int main() { Lib2 l; l.run(); }

App/CMakeLists.cc

cmake_minimum_required(VERSION 3.14)
project(App)
include_directories(include "${LIB2_ROOT}/include")
find_library(LIB2 lib2 "${LIB2_ROOT}/lib")
add_executable(app src/main.cc)
target_link_libraries(app "${LIB2}")
install(TARGETS app DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/bin")

我对@9​​87654340@ 使用了 pImpl 模式,因为当我让我的库的用户挖掘出所有标题时,隐藏链接依赖项的意义何在。


最后B/CMakeLists.txt(对于我的库)取决于我上面提到的案例:

A & B 静态

B/CMakeLists.txt

cmake_minimum_required(VERSION 3.14)
project(B)
include_directories(include "${LIB1_ROOT}/include")
find_library(LIB1 lib1 "${LIB1_ROOT}/lib")
add_library(lib2_dependent src/lib2.cc)
add_custom_target(lib2 ALL
    COMMAND ar -x "${LIB1}"
    COMMAND ar -x "$<TARGET_FILE:lib2_dependent>"
    COMMAND ar -qcs "${CMAKE_STATIC_LIBRARY_PREFIX}lib2${CMAKE_STATIC_LIBRARY_SUFFIX}" *.o
    COMMAND rm *.o
    DEPENDS lib2_dependent
    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib"
)

这个解决方案来自我一开始链接的问题。在我看来,this answer 中还有一个更好的版本,使用

ar -M <<EOM
    CREATE lib2.a
    ADDLIB lib1.a
    ADDLIB lib2_dependent.a
    SAVE
    END
EOM

但我无法在 CMakeLists.txt 中处理 here-document ...?还有一个额外的答案提供了我认为的 CMake 函数来执行此操作,但这是一个巨大的代码块,我发现对于 应该简单/标准实践/集成到其中的东西有点荒谬制作? 我在这里编写的 custom_target 解决方案也可以工作,但正如其他答案中提到的那样,它解压缩了周围的目标文件,并且必须再次删除,对于我想以这种方式编译的每个库。 仍然在这两种情况下,我只能想知道使用 CMake 有什么意义,那么无论如何我必须手动使用 ar。 +Q+ 有没有更好的/CMake 集成方式来“编译”传递依赖/组合静态库?


A 静态,B 共享

据我在这种情况下发现,如果我无法重新编译 A 并且它没有被编译为与位置无关的代码,那我就不走运了。让它工作的下一个最好的办法就是通过将set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") 添加到 A/CMakeLists.txt 来做到这一点。同样,似乎有更好的选择:set(CMAKE_POSITION_INDEPENDENT_CODE ON)set_property(TARGET lib1 PROPERTY POSITION_INDEPENDENT_CODE ON) 来自 here,在我的情况下完全忽略了..(使用 VERBOSE=1 编译,在任何地方都看不到 -fPIC 标志和 @ 987654349@没有编译)

B/CMakeLists.txt 在这种情况下很容易

cmake_minimum_required(VERSION 3.14)
project(B)
include_directories(include "${LIB1_ROOT}/include")
find_library(LIB1 lib1 "${LIB1_ROOT}/lib")
add_library(lib2 SHARED src/lib2.cc)
target_link_libraries(lib2 "${LIB1}")
install(TARGETS lib2 DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/lib")

虽然我没有发现此解决方案有任何问题,但根据我发现的答案,我需要设置额外的标志,例如 set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--export-all-symbols"),以便找到静态库中的符号。但是上面的工作正常,编译,App 运行没有问题? +Q+ 我在这里做错了吗? 或者可能是由于这些旧答案之后对 CMake 的一些更新?


A 共享、B 静态或两者共享

根据我在这里的发现,这基本上是不可能的,因为共享库在某种意义上是“最终的”。我觉得这很奇怪,肯定有很多库不需要使用它们来链接恰好是共享库的库的每个依赖项的项目? +Q+ 在这些情况下真的没有选择吗?

【问题讨论】:

  • 删除所有LIB2_ROOT LIB1_ROOT 并使用CMAKE_CURRENT_SOURCE_DIR 引用CMakeLists.txt 所在的当前目录。我认为您的帖子太宽泛了。
  • @KamilCuk 我不确定我是否理解。我会怎么做?从B/CMakeLists.txt 引用A/include/lib1.hh(这是一个不同的项目!)没有某种输入变量或硬编码(显然不好)路径?

标签: c++ cmake shared-libraries static-libraries


【解决方案1】:

是的,你做错了:)

CMake 方式是使用packages。一旦你制作了LibA 包,你只需在你的B/CMakeLists.txt 和生成的LibBConfig.cmake 中执行find_package(LibA)(包配置文件,所以你的客户只需要在他们的App/CMakeLists.txt 中的find_package(libB))应该以相同的方式(使用或不使用 CMake 的助手 find_dependency)找到(恕我直言,但目前它的 CMake 方式是有问题的)。

这样整个过程就变得简单多了:

  • 您可以/完全控制如何构建(包括什么版本、库类型静态/动态等)以及在开发人员的主机和客户的机器上安装libA 的位置(以便相关项目可以找到它)
  • libBApp 相同
  • 您和您的库的任何客户都使用众所周知的 CMake 方法来查找依赖项并完全控制此过程
  • 最重要的是让 CMake 代替你做复杂的事情,让你的构建逻辑在 CMakeLists.txt 的所有相关项目中变得更加简单

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-23
    • 2012-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-06
    • 2020-03-30
    相关资源
    最近更新 更多