【发布时间】: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_ROOT 和 LIB2_ROOT 是 A/ 和 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")
我对@987654340@ 使用了 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_ROOTLIB1_ROOT并使用CMAKE_CURRENT_SOURCE_DIR引用CMakeLists.txt所在的当前目录。我认为您的帖子太宽泛了。 -
@KamilCuk 我不确定我是否理解。我会怎么做?从
B/CMakeLists.txt引用A/include/lib1.hh(这是一个不同的项目!)没有某种输入变量或硬编码(显然不好)路径?
标签: c++ cmake shared-libraries static-libraries