"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 库的所有组合,即
- 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")
我为 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 来做到这一点。同样,似乎有更好的选择:来自 here 的 set(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
在开发者主机和客户机器上的安装位置(所以依赖项目可以找到它)
libB
和 App
相同
- 您和您图书馆的任何客户使用众所周知的 CMake 方法来查找依赖项并完全控制此过程
- 最重要的是 让 CMake 代替你做复杂的事情 这让你的构建逻辑在
CMakeLists.txt
所有涉及的项目中变得更加简单
这个问题可能部分重复,例如this question,但更多的是关于如果有更好的解决方案。由于这个问题很长,我把特定问题标记为“+Q+" 粗体斜体。
我的情况是我写了一个小库 B
,它依赖于其他一些大型项目 A
分成许多库 A1, A2, ..., An
,其中一些是我的库所依赖的和有些没有。做正确的 linking 有点痛苦。该库开始被其他人使用,我想避免每个人都必须经历这个可怕的 linking 过程,即我想将 A
的所有外部库编译到我的 [=21] =].假设 A
是完全外部的,即 我没有办法重新编译 A
(在这种情况下我这样做,但它很复杂,我想知道我不知道的情况的选项)。
我想这一定是一件非常标准的事情,我使用过其他流行的库,而且我从来没有 link 他们传递依赖的所有其他库..?所以我开始寻找解决方案,虽然我找到了可行的解决方案,但大多数解决方案看起来一团糟,我想知道这是否真的在实践中完成,或者是否有一些惯用的方法。
如果我需要不同的案例,为了避免更多最终的麻烦,我想考虑 static/shared 库的所有组合,即
- 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")
我为 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 来做到这一点。同样,似乎有更好的选择:来自 here 的 set(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
在开发者主机和客户机器上的安装位置(所以依赖项目可以找到它) libB
和App
相同
- 您和您图书馆的任何客户使用众所周知的 CMake 方法来查找依赖项并完全控制此过程
- 最重要的是 让 CMake 代替你做复杂的事情 这让你的构建逻辑在
CMakeLists.txt
所有涉及的项目中变得更加简单