简体   繁体   English

“隐藏”传递外部依赖项/将库与 CMake 组合

[英]“Hiding” transitive external dependencies / combining libraries with CMake

This question may be partly duplicate, eg this question , but is more about what if any are better solutions.这个问题可能部分重复,例如这个问题,但更多的是关于如果有更好的解决方案的话。 Since this question ended up rather long, I marked specific questions with " +Q+ " in bold italic.由于这个问题结束得相当长,我用粗斜体“ +Q+ ”标记了具体问题

I have the situation that I wrote a small library B that depends on some other huge project A split into many libraries A1, A2, ..., An , some of which my library depends on and some on which it doesn't.我的情况是,我编写了一个小型库B ,它依赖于其他一些大型项目A拆分为许多库A1, A2, ..., An ,其中一些是我的库所依赖的,而另一些则不是。 It was a bit of a pain to do the proper linking.进行正确的链接有点痛苦。 The library is starting to be used by others and I would like to avoid everyone having to go through this awful linking process, ie I want to compile all the external libraries of A into my B .该库开始被其他人使用,我想避免每个人都不得不通过这个糟糕的链接过程来 go,即我想将A所有外部库编译到我的B中。 Assume A is completely external, ie that I have no way to recompile A (in this case I do, but it is complicated and I would like to know options for the case that I don't).假设A完全是外部的,即我无法重新编译A (在这种情况下我可以,但它很复杂,我想知道我没有的情况下的选项)。

I imagine this must be a very standard thing to do, I have used other popular libraries and never did I have to link all the other libraries that they transitively depend on.. ?我想这一定是一件非常标准的事情,我使用过其他流行的库,我从来不需要链接它们传递依赖的所有其他库..? So I started looking for solutions, and while I found working ones, most solutions seem like a big mess and I wonder if this is really done in practice or if there is some idiomatic way for this.所以我开始寻找解决方案,虽然我找到了可行的解决方案,但大多数解决方案似乎都是一团糟,我想知道这是否真的在实践中完成,或者是否有一些惯用的方法。

To avoid more eventual headaches if I ever need a different case, I want to consider all combinations of static/shared libraries, ie如果我需要不同的情况,为了避免更多的最终头痛,我想考虑静态/共享库的所有组合,即

  • A & B are static A & B 是 static
  • A is static, B is shared A为static,B为共享
  • A is shared, B is static A是共享的,B是static
  • A & B are shared A & B 共享

To give some MWE of the code setup (the variables LIB1_ROOT and LIB2_ROOT in the CMakeLists.txt files are A/ and B/ respectively):给出代码设置的一些 MWE( CMakeLists.txt文件中的变量LIB1_ROOTLIB2_ROOT分别是A/B/ ):

A/include/lib1.hh A/include/lib1.hh

struct Lib1 { void run() const; };

A/src/lib1.cc一个/src/lib1.cc

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

A/CMakeLists.txt 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 B/include/lib2.hh

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

B/src/lib2.cc 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应用程序/src/app.cc

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

App/CMakeLists.cc应用程序/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")

I used the pImpl pattern for B since what is the point of hiding link dependencies when I then make the user of my library dig out all the headers anyway.我对B使用了 pImpl 模式,因为当我让我的库的用户无论如何都挖掘出所有标题时,隐藏链接依赖项的意义何在。


Finally B/CMakeLists.txt (for my library) depends on the cases I mentioned above:最后B/CMakeLists.txt (对于我的库)取决于我上面提到的情况:

A & B static A & B static

B/CMakeLists.txt 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 solution is from the question I linked at the start.这个解决方案来自我一开始链接的问题。 There was also an in my opinion much better version in this answer using在我看来, 这个答案中还有一个更好的版本,使用

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

but I could not get to work the here-document inside CMakeLists.txt... ?但我无法在 CMakeLists.txt 中处理此处的文档...? There was an additional answer providing what I think was a CMake function to do this, but it was a huge block of code which I found a bit ridiculous for something that should be simple / standard practice / integrated into CMake?还有一个额外的答案提供了我认为是 CMake function 来执行此操作,但这是一个巨大的代码块,我发现对于应该简单/标准实践/集成到 ZDF49ADAB93B9E0C10C614F724 中的东西有点荒谬? The custom_target solution I wrote here also works, but as mentioned in other answers as well, it unpacks object files which lie around and have to be removed again, for each library I want to compile this way.我在此处编写的 custom_target 解决方案也有效,但正如其他答案中提到的那样,它解压缩了 object 文件,这些文件存在并且必须再次删除,对于我想以这种方式编译的每个库。 And still in both cases, I can only wonder what is the point of using CMake then if I have to use ar manually anyhow.仍然在这两种情况下,我只能想知道使用 CMake 有什么意义,那么无论如何我必须手动使用 ar。 +Q+ Is there no better / CMake-integrated way to "compile-in" transitive dependencies / combine static libraries? +Q+ 没有更好的/CMake 集成方式来“编译”传递依赖/组合 static 库吗?


A static, B shared A static, B 共享

As far as I found in this case, I'm out of luck if I cannot recomple A and it is not compiled as position independent code.据我在这种情况下发现,如果我不能重新编译A并且它没有被编译为 position 独立代码,我就不走运了。 The next best thing to make it work was to do just that by adding set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") to A/CMakeLists.txt .让它工作的下一个最好的事情就是通过将set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")添加到A/CMakeLists.txt来做到这一点。 Again, there seemed to be better alternatives: set(CMAKE_POSITION_INDEPENDENT_CODE ON) or set_property(TARGET lib1 PROPERTY POSITION_INDEPENDENT_CODE ON) from here , which were complete ignored in my case.. (compiling with VERBOSE=1 , there was no -fPIC flag to be seen anywhere and B did not compile)同样,似乎有更好的选择: set(CMAKE_POSITION_INDEPENDENT_CODE ON)set_property(TARGET lib1 PROPERTY POSITION_INDEPENDENT_CODE ON) from here ,在我的情况下完全忽略了..(使用VERBOSE=1编译,没有-fPIC标志在任何地方都可以看到并且B没有编译)

The B/CMakeLists.txt was easy in this case在这种情况下, 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")

and while I don't find anything wrong with this solution, according to answers I found I should need to set additional flags like set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--export-all-symbols") in order for the symbols in the static library to be found.虽然我没有发现此解决方案有任何问题,但根据我发现的答案,我应该设置额外的标志,如set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--export-all-symbols")以便 static 中的符号要找到的图书馆。 However the above works just fine, compiles, and App runs without issues?然而,上面的工作正常,编译, App运行没有问题? +Q+ Am I doing anything wrong here? +Q+ 我在这里做错了吗? Or is it maybe due to some update to CMake since these older answers?或者可能是由于这些较旧的答案对 CMake 进行了一些更新?


A shared, B static or both shared A 共享,B static 或两者共享

According to what I found here, this is basically impossible, because shared libraries are "final" in some sense.根据我在这里的发现,这基本上是不可能的,因为共享库在某种意义上是“最终的”。 I find this very strange, surely there are many libraries that do not require a project that uses them to link every single dependency of that library that happens to be a shared library?我觉得这很奇怪,肯定有很多库不需要使用它们来链接恰好是共享库的库的每个依赖项的项目? +Q+ Are there really no options in these cases? +Q+ 在这些情况下真的没有选择吗?

Yep, you're doing wrong:)是的,你做错了:)

The CMake-way is to use packages . CMake 方式是使用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 ). 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 )应该以相同的方式(使用或不使用 CMake 的助手find_dependency )找到(恕我直言,但目前它的 CMake 方式)。

The whole process gets much simpler that way:这样整个过程就变得简单多了:

  • you have/give the full control on how to build (including what version, library type static/dynamic, &etc) and where to install the libA on your developer's host and customer's machine (so dependent projects could find it)您可以/完全控制如何构建(包括什么版本、静态/动态库类型等)以及在开发人员的主机和客户的机器上安装libA的位置(以便相关项目可以找到它)
  • same for libB and App libBApp相同
  • you and any customer of your library(ies) use the well known CMake way to find dependencies and have full control over this process您和您图书馆的任何客户都使用众所周知的 CMake 方法来查找依赖项并完全控制此过程
  • and the most important let CMake do complicated things instead of you that keeps your build logic much simpler in CMakeLists.txt of all involved projects最重要的是让 CMake 代替您做复杂的事情,从而使您的构建逻辑在所有相关项目的CMakeLists.txt中变得更加简单

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM