Reputation: 1561
I'd like to create a liboo.so
from a libfoo.a
using CMake.
So far I have
include_directories(third-party/includes)
find_library(${thirdparty_LIBRARIES} foo PATHS third-party/lib)
add_library(boo SHARED empty.cpp)
target_link_libraries(boo ${thirdparty_LIBRARIES})
add_executable(runBoo main.cpp)
target_link_libraries(runBoo boo)
where main calls functions from libfoo.so
. But provokes the error:
main.cpp:(.text+0x26): undefined reference to `Foo::Foo()'
main.cpp:(.text+0x50): undefined reference to `Foo::sayHello(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
I'm guessing the symbols aren't added since empty.cpp
doesn't use them, but I'm not sure that's the issue and how to overcome it.
I've seen CMake: how create a single shared library from all static libraries of subprojects? , but I'd prefer to stick to lower versions of cmake for now.
I've also seen how to link static library into dynamic library in gcc but I can't get it to work and I'm using CMake anyway.
Upvotes: 2
Views: 2487
Reputation: 61575
@quimnuss's accepted CMake solution is to create a shared library whose solitary source file is an empty file and that is simply linked aginst the static library it is supposed to wrap1.
At this writing the only other answer is @cyrusbehr's. As @cyrusbehr says, the accepted answer is wrong, and it is wrong for the reason @cyrusbehr states.
@cyrusbehr's remedy's is to enclose static libraries beteween linker options
--whole-archive/--no-whole-archive
. That is right, provided the toolchain is GCC. The effect of these options to coerce the linker to link all object files in the enclosed static libraries into the target, whether it needs them or not.
It may be inferred that the OP's toolchain is GCC from incidental clues, but
that is not specified and a toolchain-neutral CMake solution exists. @cyrusbehr also carries over
from @quimnuss the kludge of adding an empty source file to the project just to provide something that satisfies
the grammar of:
add_library(<libame> SHARED <sourcefile>...)
It would be odd if CMake made this necessary and it isn't. (On Linux, it also leaves a trace in the output shared library in the
form of a local symbol named empty.cpp
, or whatever the empty file is called).
Under the hood, CMake on my Linux system delegates by default to GNU Make and the GCC toolchain and deals in ELF object files, programs and shared libraries. On other systems these internals will vary but the story is the same.
Demo that the wrong answer fails, and why.
We'll make a static library libfoobar.a
that defines functions foo
and bar
and pretend it already existed, let's say in directory ./lib
.
$ cat foo.cpp
int bar()
{
return 17;
}
$ cat bar.cpp
int foo()
{
return 13;
}
$ mkdir lib
$ g++ -c foo.cpp bar.cpp
$ ar rcs ./lib/libfoobar.a foo.o bar.o
Here are the symbol tables of the two object files in the archive:
$ readelf --demangle --syms ./lib/libfoobar.a
File: ./lib/libfoobar.a(foo.o)
Symbol table '.symtab' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.cpp
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 15 FUNC GLOBAL DEFAULT 1 bar()
File: ./lib/libfoobar.a(bar.o)
Symbol table '.symtab' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar.cpp
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 15 FUNC GLOBAL DEFAULT 1 foo()
Next we'll make a CMake project that:-
libfoobar.so
supposedly wrapping libfoobar.so
.foo
and bar
and is linked against libfoobar.so
.This is just the same sort of project that the wrong answer offers as its evidence.
$ cat main.cpp
#include <iostream>
extern int foo();
extern int bar();
int main()
{
std::cout << std::boolalpha << (foo() == bar()) << std::endl;
return 0;
}
$ cat CMakeLists.txt
cmake_minimum_required (VERSION 3.15)
project(prog)
find_library(static_foobar foobar PATHS ${CMAKE_SOURCE_DIR}/lib)
add_library(foobar SHARED empty.cpp)
target_link_libraries(foobar ${static_foobar})
add_executable(prog main.cpp)
target_link_libraries(prog foobar)
$ mkdir build
$ cd build/
$ cmake ..
-- The C compiler identification is GNU 13.2.0
-- The CXX compiler identification is GNU 13.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.5s)
-- Generating done (0.0s)
-- Build files have been written to: /home/imk/develop/so/cmake_prob/build
$ make
[ 25%] Building CXX object CMakeFiles/foobar.dir/empty.cpp.o
[ 50%] Linking CXX shared library libfoobar.so
[ 50%] Built target foobar
[ 75%] Building CXX object CMakeFiles/prog.dir/main.cpp.o
[100%] Linking CXX executable prog
[100%] Built target prog
$ ./prog
false
The program was built and it runs correctly.
That's good but doesn't answer the question.
We want to know is: Has libfoobar.a
has been wrapped in libfoobar.so
, so that prog
only depends on
libfoobar.so
. Let's zap prog
and look at it's linkage again, this time in verbose mode:
$ rm prog
$ make VERBOSE=1 prog
...[cut]...
[ 75%] Linking CXX executable prog
/usr/bin/cmake -E cmake_link_script CMakeFiles/prog.dir/link.txt --verbose=1
/usr/bin/c++ CMakeFiles/prog.dir/main.cpp.o -o prog -Wl,-rpath,/home/imk/develop/so/cmake_prob/build libfoobar.so /home/imk/develop/so/cmake_prob/lib/libfoobar.a
make[3]: Leaving directory '/home/imk/develop/so/cmake_prob/build'
[100%] Built target prog
...[cut]...
See that prog
is in fact being linked against both libfoobar.so
and the original libfoobar.a
.
The new libfoobar.so
contributes 0 definitions to the linkage because it does not define either foo
or bar
:
$ readelf --syms libfoobar.so | egrep \(foo\|bar\); echo Done
Done
That's for the reason that @cyrusbehr stated. libfoobar.so
is the same shared library we'd get with empty.cpp
as its sole source file and linking nothing to it all.
The original libfoobar.a
is still providing the definitions of foo
and bar
. If we remove libfoobar.so
(and its runpath, -rpath...
) from
the CMake linkage command:
$ /usr/bin/c++ CMakeFiles/prog.dir/main.cpp.o -o prog /home/imk/develop/so/cmake_prob/lib/libfoobar.a
$ ./prog
false
the linkage is unscathed. If we remove libfoobar.a
$ /usr/bin/c++ CMakeFiles/prog.dir/main.cpp.o -o prog -Wl,-rpath,/home/imk/develop/so/cmake_prob/build libfoobar.so
/usr/bin/ld: CMakeFiles/prog.dir/main.cpp.o: in function `main':
main.cpp:(.text+0x28): undefined reference to `foo()'
/usr/bin/ld: main.cpp:(.text+0x2f): undefined reference to `bar()'
collect2: error: ld returned 1 exit status
the linkage fails. libfoobar.so
is not a wrapper for libfoobar.a
: it's a hollow shell..
Why does CMake link prog
with both libfoobar.so
and libfoobar.a
?
Because of:
target_link_libraries(foobar ${static_foobar})
By default in CMake, dependencies of a library are inherited by any target to which the library is in turn linked,
so target prog
inherits the dependency on libfoobar.a
from libfoobar.so
.2.
But this inheritance defeats the objective of wrapping libfoobar.a
in libfoobar.so
.
For that, libfoobar.a
should be a PRIVATE
dependency of libfoobar.so
.3 And
if we do that, then regenerate the build system and rebuild (from now on our shell is always in the build
directory):
$ cat ../CMakeLists.txt
cmake_minimum_required (VERSION 3.15)
project(prog)
find_library(static_foobar foobar PATHS ${CMAKE_SOURCE_DIR}/lib)
add_library(foobar SHARED empty.cpp)
target_link_libraries(foobar PRIVATE ${static_foobar})
add_executable(prog main.cpp)
target_link_libraries(prog foobar)
$ rm -fr *
$ cmake ..
...[cut]...
$ make VERBOSE=1
...[cut]...
[100%] Linking CXX executable prog
/usr/bin/cmake -E cmake_link_script CMakeFiles/prog.dir/link.txt --verbose=1
/usr/bin/c++ CMakeFiles/prog.dir/main.cpp.o -o prog -Wl,-rpath,/home/imk/develop/so/cmake_prob/build libfoobar.so
/usr/bin/ld: CMakeFiles/prog.dir/main.cpp.o: in function `main':
main.cpp:(.text+0x28): undefined reference to `foo()'
/usr/bin/ld: main.cpp:(.text+0x2f): undefined reference to `bar()'
collect2: error: ld returned 1 exit status
..[cut]...
then the CMake linkage command turns into the one we've already seen to fail.
Demo that @cyrusbehr's fix works for GCC.
For this we'll have:
$ cat ../CMakeLists.txt
cmake_minimum_required (VERSION 3.15)
project(prog)
find_library(static_foobar foobar PATHS ${CMAKE_SOURCE_DIR}/lib)
add_library(foobar SHARED empty.cpp)
SET(static_foobar_whole_archive -Wl,--whole-archive ${static_foobar} -Wl,--no-whole-archive)
target_link_libraries(foobar PRIVATE ${static_foobar_whole_archive})
add_executable(prog main.cpp)
target_link_libraries(prog foobar)
with libfoobar.a
a PRIVATE
dependency of libfoobar.so
, but enclosed in --whole-archive/--no-whole-archive
linker options.
Rebuilding from scratch again:
$ rm -fr *
$ cmake ..
...[cut]...
$ make VERBOSE=1
...[cut]...
[ 50%] Linking CXX shared library libfoobar.so
/usr/bin/cmake -E cmake_link_script CMakeFiles/foobar.dir/link.txt --verbose=1
/usr/bin/c++ -fPIC -shared -Wl,-soname,libfoobar.so -o libfoobar.so CMakeFiles/foobar.dir/empty.cpp.o -L/home/imk/develop/so/cmake_prob/lib -Wl,-rpath,/home/imk/develop/so/cmake_prob/lib -Wl,--whole-archive -lfoobar -Wl,--no-whole-archive
...[cut]...
[100%] Linking CXX executable prog
/usr/bin/cmake -E cmake_link_script CMakeFiles/prog.dir/link.txt --verbose=1
/usr/bin/c++ CMakeFiles/prog.dir/main.cpp.o -o prog -Wl,-rpath,/home/imk/develop/so/cmake_prob/build libfoobar.so
make[2]: Leaving directory '/home/imk/develop/so/cmake_prob/build'
[100%] Built target prog
...[cut]...
--whole-archive/--no-whole-archive
in the linkage of libfoobar.so
successfully links all the definitions from libfoobar.a
into libfoobar.so
:
$ readelf --demangle --syms libfoobar.so | egrep \(foo\|bar\|dynsym\|symtab\); echo Done
Symbol table '.dynsym' contains 7 entries:
5: 00000000000010f9 15 FUNC GLOBAL DEFAULT 10 bar()
6: 0000000000001108 15 FUNC GLOBAL DEFAULT 10 foo()
Symbol table '.symtab' contains 28 entries:
10: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.cpp
11: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar.cpp
22: 0000000000001108 15 FUNC GLOBAL DEFAULT 10 foo()
24: 00000000000010f9 15 FUNC GLOBAL DEFAULT 10 bar()
Done
and makes the linkage of prog
successful with no other library mentioned:
$ ./prog
false
This solution has the shortcoming that:
SET(static_foobar_whole_archive -Wl,--whole-archive ${static_foobar} -Wl,--no-whole-archive)
is valid only for the GCC toolchain and the equivalent linker option(s) must be found for other toolchains. We'd like a toolchain-neutral CMake way.
And as I said, the empty source file leaves this vestige in the symbol table of libfoobar.so
:
$ readelf --demangle --syms libfoobar.so | egrep \(empty.cpp\|dynsym\|symtab\); echo Done
Symbol table '.dynsym' contains 7 entries:
Symbol table '.symtab' contains 28 entries:
9: 0000000000000000 0 FILE LOCAL DEFAULT ABS empty.cpp
Done
Toolchain-neutral solution without the empty source file.
For this we'll have:
$ cat ../CMakeLists.txt
cmake_minimum_required (VERSION 3.15)
project(prog)
find_library(static_foobar foobar PATHS ${CMAKE_SOURCE_DIR}/lib)
add_library(foobar SHARED ${static_foobar})
target_link_libraries(foobar PRIVATE "$<LINK_LIBRARY:WHOLE_ARCHIVE,${static_foobar}>")
set_target_properties(foobar PROPERTIES LINKER_LANGUAGE CXX )
add_executable(prog main.cpp)
target_link_libraries(prog foobar)
The empty source file is eliminated by replacing:
add_library(foobar SHARED empty.cpp)
with:
add_library(foobar SHARED ${static_foobar})
i.e. we say that the sole source file for building libfoobar.so
is libfoobar.a
. Of course libfoobar.a
is not a programming language source file, but CMake's documentary nomenclature:
[add_library(<name> [<type>] [EXCLUDE_FROM_ALL] <sources>...)](https://cmake.org/cmake/help/latest/command/add_library.html)
is a little misleading here and elsewhere where it uses <sources>...
in target-creation syntax. A <source>
need not be a programming language source file. If it does not have an absolute name (after relative path expansion) - e.g. empty.cpp
- CMake will assume it is source-code file
relative to ${CMAKE_SOURCE_DIR}
and is to be input to the target's language compiler. Otherwise it will assume the<source>
is the absolute name (after expansion) of a file that is to be input to the target's link step.
So the snippet:
add_library(foobar SHARED ${static_foobar})
target_link_libraries(foobar PRIVATE "$<LINK_LIBRARY:WHOLE_ARCHIVE,${static_foobar}>")
boils down to: ${static_foobar}
( = the full name of libfoobar.a
) is a linker input file
for target foobar
and it is library on which the target depends which is to be linked with the predefined LINK_LIBRARY
feature
WHOLE_ARCHIVE
). That feature is the one that does the right thing per toolchain to link every object file
in a static library into the target.
Since we are inputting no language source files for target foobar
, CMake is unable to guess from the extensions
of such files what language-specific variant of linkage command to use for that target - what frontend, what boilerplate linkage options. So
we have to tell it to link it as per C++ targets:
set_target_properties(foobar PROPERTIES LINKER_LANGUAGE CXX )
Rebuilding from scratch once more:
$ rm -fr *
$ cmake ..
...[cut]...
$ make VERBOSE=1
...[cut]...
[ 33%] Linking CXX shared library libfoobar.so
/usr/bin/cmake -E cmake_link_script CMakeFiles/foobar.dir/link.txt --verbose=1
/usr/bin/c++ -fPIC -shared -Wl,-soname,libfoobar.so -o libfoobar.so -Wl,--push-state,--whole-archive /home/imk/develop/so/cmake_prob/lib/libfoobar.a -Wl,--pop-state
...[cut]...
[100%] Linking CXX executable prog
/usr/bin/cmake -E cmake_link_script CMakeFiles/prog.dir/link.txt --verbose=1
/usr/bin/c++ CMakeFiles/prog.dir/main.cpp.o -o prog -Wl,-rpath,/home/imk/develop/so/cmake_prob/build libfoobar.so
make[2]: Leaving directory '/home/imk/develop/so/cmake_prob/build'
[100%] Built target prog
...[cut]...
$ ./prog
false
This goes through effectively the same @cyrusbehr's solution except that no empty.cpp.o
is redundantly linked, there was
no redundant compile step for empty.cpp
and empty.cpp
gets no name-check in the symbol table of libfoobar.so
.
The CMake-generated linker options --push-state,--whole-archive ... --pop-state
have the same effect --whole-archive ... --no-whole-archive
.
Wrapping a static library in a shared library is what the question wants to do, but is a bit of a misnomer. When a shared library or program is linked against a static library, the static library is not embedded or encapsulated in the linkage target. The static library is an archive of object files. By default the linker recursively discovers an object file in the archive that serves to resolve an undefined symbol reference that has accrued earlier in the linkage process, copies that object file out of the archive and links it individually into the target - iterating until the archive yields no more needed object files. No identifying trace of the static library is linked into the target.
CMake has implemented transitivity of library dependencies since at least v2.6, 2008. I can't readily find earlier documentation.
When the question was posted in 2013, recent versions of CMake didn't support the PRIVATE
(or PUBLIC
or INTERFACE)
properties of target_link_libraries
to control the inheritance of library dependencies. They supported
LINK_PRIVATE
and LINK_PUBLIC
properties corresponding respectively to PRIVATE
and PUBLIC
and the
target property LINK_INTERFACE_LIBRARIES
specified an optional list of inherited dependencies overriding those that would accrue to the target by default.
Upvotes: 1
Reputation: 1301
The selected answer is not correct.
You are compiling an empty source file into a shared library. You then try to link a static library to this shared library. Since the static library is not used in the code which is compiled, it will not be linked and will be ignored. You must therefore add the -whole-archive
linker flag to force the static library to be linked, so something like the following.
add_library(my_shared_lib SHARED src/empty.cpp)
SET(MY_LIB_LINK_LIBRARIES -Wl,--whole-archive my_static_library.a -Wl,--no-whole-archive)
target_link_libraries(my_shared_lib ${MY_LIB_LINK_LIBRARIES})
Upvotes: 1
Reputation: 1561
There was just a silly mistake. The corrected code is:
include_directories(third-party/includes)
find_library(thirdparty_LIBRARIES foo PATHS third-party/lib) //remove the ${}!
add_library(boo SHARED empty.cpp)
target_link_libraries(boo ${thirdparty_LIBRARIES})
add_executable(runBoo main.cpp)
target_link_libraries(runBoo boo)
Upvotes: 0