This question may be partly duplicate, e.g. 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.
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. 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, i.e. I want to compile all the external libraries of A
into my B
. Assume A
is completely external, i.e. 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).
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, i.e.
- A & B are static
- A is static, B is shared
- A is shared, B is static
- A & B are shared
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):
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")
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.
Finally B/CMakeLists.txt (for my library) depends on the cases I mentioned above:
A & B static
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 ... ? 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 ? 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. 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. +Q+ Is there no better / CMake-integrated way to "compile-in" transitive dependencies / combine static libraries?
A static, B shared
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. 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. 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)
The B/CMakeLists.txt was easy in this case
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. However the above works just fine, compiles, and App
runs without issues? +Q+ Am I doing anything wrong here? Or is it maybe due to some update to CMake since these older answers?
A shared, B static or both shared
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?