DBedrenko
DBedrenko

Reputation: 5039

How to link app against a library that depends on another library?

I wrote a shared library mylib1 that uses a 3rd-party shared library, in my case libtinyxml2 (for this problem the 3rd-party library is irrelevant: it can be any library). And I wrote an application app1 which depends on mylib1.

Building mylib1 succeeds, but building app1 fails, presumably because the linker was not told to link against libtinyxml2 when linking app1.

What am I missing?

[I][~/Programs/cmake-jenkins/big_proj/src/build]$ make
[ 25%] Building CXX object mylib1/CMakeFiles/mylib1.dir/src/my_functions.cpp.o
[ 50%] Linking CXX shared library libmylib1.so
[ 50%] Built target mylib1
[ 75%] Building CXX object app1/CMakeFiles/app1.dir/src/main.cpp.o
[100%] Linking CXX executable app1
../mylib1/libmylib1.so: undefined reference to `tinyxml2::StrPair::Reset()'
../mylib1/libmylib1.so: undefined reference to `tinyxml2::StrPair::~StrPair()'
collect2: error: ld returned 1 exit status
make[2]: *** [app1/CMakeFiles/app1.dir/build.make:97: app1/app1] Error 1
make[1]: *** [CMakeFiles/Makefile2:402: app1/CMakeFiles/app1.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

Folder structure

.
├── app1
│   ├── CMakeLists.txt
│   ├── src
│   │   ├── include
│   │   │   └── functions_p.hpp
│   │   └── main.cpp
├── CMakeLists.txt
└── mylib1
    ├── CMakeLists.txt
    ├── src
    │   ├── include
    │   │   └── my_functions.hpp
    │   └── my_functions.cpp

./CMakeLists.txt

# Top level CMakeLists.txt
cmake_minimum_required(VERSION 3.8.2)

add_subdirectory(app1)
add_subdirectory(mylib1)

app1/CMakeLists.txt

project("app1" CXX)

set(APP1_SOURCE_FILES src/main.cpp )
add_executable(app1 ${APP1_SOURCE_FILES})

target_include_directories(app1
    PRIVATE src/include)
target_link_libraries(app1 mylib1)

mylib1/CMakeLists.txt

project("lib1" CXX)

find_library(TINYXML_LIB tinyxml2 REQUIRED)

set(MYLIB1_SOURCE_FILES src/my_functions.cpp )
set(MYLIB_INCLUDE_FILES src/include/my_functions.hpp)
add_library(mylib1 SHARED ${MYLIB1_SOURCE_FILES} ${MYLIB1_INCLUDE_FILES} )

target_include_directories(mylib1
    PUBLIC src/include)
# I have checked that TINYXML_LIB is set, so the library IS found.
target_link_libraries(mylib1 PUBLIC ${TINYXML_LIB})

The readelf output shows the library has a dependency to libtinyxml2.so.6, and this file exists as /usr/lib/libtinyxml2.so.6 on my system.

[I][~/Programs/cmake-jenkins/big_proj/src/build]$ readelf -d mylib1/libmylib1.so
Dynamic section at offset 0x16cd0 contains 29 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libtinyxml2.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000e (SONAME)             Library soname: [libmylib1.so]

Running the generated Makefile in verbose mode shows that indeed when app1 is linked, it does not link against libtinyxml2:

[ 75%] Linking CXX executable app1
cd /home/danb/Programs/cmake-jenkins/big_proj/src/build/app1 && /usr/bin/cmake -E cmake_link_script CMakeFiles/app1.dir/link.txt --verbose=1
/usr/bin/c++     CMakeFiles/app1.dir/src/main.cpp.o  -o app1 -Wl,-rpath,/home/danb/Programs/cmake-jenkins/big_proj/src/build/mylib1 ../mylib1/libmylib1.so

Upvotes: 1

Views: 1357

Answers (3)

DBedrenko
DBedrenko

Reputation: 5039

There must be something wrong with the tinyxml2 library or my installation of it (but it's from the official ArchLinux repository), because I've replaced it with 2 other libraries, and there were no issues building app1.

I even tried to link app1 manually and it would still give the same undefined reference errors:

/usr/bin/c++     CMakeFiles/app1.dir/src/main.cpp.o  -o app1 -Wl,-rpath,/home/danb/Programs/cmake-jenkins/big_proj/src/build/mylib1 ../mylib1/libmylib1.so -ltinyxml2 -L /usr/lib

I also tried using find_package() instead of find_library(), because libtinyxml2 comes with a CMake module, but the problem remained.

What's funny is that I don't even make use of tinyxml2::StrPair::Reset(). This is all that the library implements:

#include <tinyxml2.h>

bool is_xml_empty() {
    tinyxml2::StrPair pair;
    char* a;
    char* b;
    pair.Set(a, b, 0);
    return pair.Empty();
}

And yet! I can build a software (encfs) that depends on tinyxml2, and it builds just fine! This indicates my installation of the library is fine.

Upvotes: 0

Th. Thielemann
Th. Thielemann

Reputation: 2824

CMake implements this feature in the modules. See CMake Modules If necessary modules create a variable FOOMODULE_LIBRARIES. This variable is a list of

The libraries needed to use ...

Thus you can add this dependency by:

  1. Providing a module script for mylib1
  2. Use find_package(mylib1) in project app1

Example:

find_package (mylib1)
include_directories(${mylib1_INCLUDE_DIRS})

add_executable(app1 main.cpp)
target_link_libraries(app1 ${mylib1_LIBRARIES})

Note: This makes mylib1 independent from app1. You have to do extra work to trigger a build of app1 for any change of mylib1. See CMake Tutorials at http://www.th-thielemann.de/development

Upvotes: 1

stanthomas
stanthomas

Reputation: 1181

External references are resolved when the final application is linked so include libtinyxml2 in target_link_libraries in app1/CMakeLists.txt.

Upvotes: 1

Related Questions