"Hiding" 传递外部依赖项/将库与 CMake 结合

"Hiding" transitive external dependencies / combining libraries with CMake

这个问题可能部分重复,例如this question,但更多的是关于如果有更好的解决方案。由于这个问题很长,我把特定问题标记为“+Q+" 粗体斜体。

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

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

如果我需要不同的案例,为了避免更多最终的麻烦,我想考虑 static/shared 库的所有组合,即

给代码设置一些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")

我为 B 使用了 pImpl 模式,因为隐藏 link 依赖项有什么意义,然后我让我的库的用户挖掘出所有 headers。


最后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"
)

这个解决方案来自我 link 一开始提出的问题。我认为 中还有一个更好的版本,使用

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

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


A静态,B共享

据我在这种情况下发现,如果我无法重新完成 A 并且它没有被编译为与位置无关的代码,那我就不走运了。使它工作的下一个最佳方法是通过将 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") 添加到 A/CMakeLists.txt 来做到这一点。同样,似乎有更好的选择:来自 hereset(CMAKE_POSITION_INDEPENDENT_CODE ON)set_property(TARGET lib1 PROPERTY POSITION_INDEPENDENT_CODE ON),在我的案例中完全被忽略了..(使用 [=35= 编译],没有 -fPIC 标记可以在任何地方看到并且 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 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 静态或两者共享

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

是的,你做错了:)

CMake-way就是使用packages. Once you've made the LibA package, you just do find_package(LibA) in your B/CMakeLists.txt and also the generated LibBConfig.cmake (the package config file, so your clients would need just find_package(libB) in their App/CMakeLists.txt) ought to find (that is questionable IMHO, but currently its CMake way) its dependency the same way (using or not the CMake's helper find_dependency).

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

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