Linking to a library of my own code that requires 3rd party library

I have a working CMake test project that consists of my C++ code linked to a 3rd party library called DPDK, targeting Linux. I now want to separate my code into a main.cpp and my own library of my functions that require DPDK. I have two CMakeLists.txt files, here is an outline of each:

Top-level CMakeLists.txt

project(main VERSION 1.0 LANGUAGES CXX C)
set(_target_name dpdk_test)
set(_dpdk_version dpdk-stable-18.11.8)

add_subdirectory(dpdk)

add_executable(${_target_name} main.cpp )

target_include_directories(${_target_name} PRIVATE dpdk/include)
target_include_directories(${_target_name} PRIVATE /opt/dpdk/${_dpdk_version}/x86_64-native-linuxapp-gcc/include)

target_link_libraries(${_target_name} dpdk)

# Enable the -pthread compiler and linker flag
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(${_target_name}
    Threads::Threads)

’dpdk’ CMakeLists.txt

set (_lib_name "dpdk")
set(_dpdk_version dpdk-stable-18.11.8)

add_definitions(
    -DRTE_MACHINE_CPUFLAG_SSE 
<snip>)

add_library(${_lib_name} STATIC "")

target_sources(${_lib_name} 
    PRIVATE
        ThreadDpdk.cpp
<snip>
)

target_include_directories(${_lib_name} PRIVATE /opt/dpdk/${_dpdk_version}/x86_64-native-linuxapp-gcc/include)

# Enable the -pthread compiler and linker flag
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(${_lib_name}
    Threads::Threads)

# Library paths
set(_lib64_path /usr/lib64/)
set(_dpdk_path /opt/dpdk/${_dpdk_version}/x86_64-native-linuxapp-gcc/lib/)

target_link_libraries(${_lib_name}
     ${_lib64_path}libdl.so
     ${_lib64_path}libutil.so
     ${_lib64_path}libm.so
     -Xlinker -export-dynamic

     # dpdk libraries
     ${_dpdk_path}librte_flow_classify.a
     -Wl,--whole-archive
     ${_dpdk_path}librte_pipeline.a
     <snip>
     -Wl,--no-whole-archive
     ${_dpdk_path}librte_pdump.a
     <snip>
     -Wl,--whole-archive
     ${_dpdk_path}librte_acl.a
     -Wl,--no-whole-archive
      <snip>
     -Wl,--whole-archive
     ${_dpdk_path}librte_cfgfile.a 
<snip>
     -Wl,--no-whole-archive
     
     -lrt -lm  -lnuma -ldl
     ${_lib64_path}libconfig.so

     -Wl,-export
     -Dynamic 
     #-L/opt/dpdk/${_dpdk_version}/examples/l2fwd/build/lib -L/opt/dpdk/${_dpdk_version}/x86_64-native-linuxapp-gcc/lib 
     -Wl,--as-needed 
     -Wl,-Map=${_target_name}.map 
     -Wl,--cref      
     )

My question

When I build the project, the library builds ok, but linking the executable fails:

[ 60%] Built target dpdk
[ 66%] Linking CXX executable dpdk_test
/opt/dpdk/dpdk-stable-18.11.8/x86_64-native-linuxapp-gcc/lib/librte_eal.a(eal_common_devargs.o): 
In function `rte_devargs_layers_parse':
eal_common_devargs.c:(.text+0x159): undefined reference to `rte_kvargs_parse_delim'

My question is, do I need to specify the DPDK libraries in the top-level CMakeLists.txt file, or is it sufficient to specify them in the library’s CMakeLists.txt file? (main.cpp does not call DPDK functions).

I think I need this clear in my thinking before I go further.

If main does not call DPDK then you only need to link it from the library, not from main.

Also, you have a lot of flags in target_link_libraries() that should be in target_link_options() instead. I’m not sure how exactly to handle --whole-archive and --no-whole-archive in a more CMake-like way… @brad.king any thoughts?

I thing, that should be PUBLIC, than it is automatically imported with

This imports too the dependent defines and link library list … Don’t repeat yourself. So you my skip this:

Rather than --whole-archive/--no-whole-archive pairs, one could use the -force_load flag that applies only to the immediately following library. See CMake Issue 20078 for discussion of adding a first-class way to attach such a flag. Without such a feature (or possibly the workaround discussed in the issue), the only way to reliably list a sequence of flags and libraries and make sure they appear in order on the final link line is to put them directly in the target_link_libraries call for the final target that needs to have all objects from all the .a files.

Thanks for your replies. I am rather confused as, if I build with ninja -v, I see the compile commands for the source files and then a link command for the executable, which begins:

/opt/rh/devtoolset-7/root/usr/bin/g++   -I../dpdk/include -I../include -I/opt/dpdk/dpdk-stable-18.11.8/x86_64-native-linuxapp-gcc/include -I../dpdk/../include -march=native -g   -pthread -std=c++1y -MD -MT CMakeFiles/dpdk_test.dir/main.cpp.o -MF CMakeFiles/dpdk_test.dir/main.cpp.o.d -o CMakeFiles/dpdk_test.dir/main.cpp.o -c ../main.cpp
[6/6] : && /opt/rh/devtoolset-7/root/usr/bin/g++  -march=native -g   
CMakeFiles/dpdk_test.dir/main.cpp.o CMakeFiles/dpdk_test.dir/Threads.cpp.o CMakeFiles/dpdk_test.dir/ThreadMon.cpp.o 
CMakeFiles/dpdk_test.dir/ThreadMSG.cpp.o CMakeFiles/dpdk_test.dir/_kbhit.cpp.o  
-o dpdk_test  
dpdk/libdpdk.a  
/usr/lib64/libdl.so  /usr/lib64/libutil.so  /usr/lib64/libm.so  -Xlinker  
-export-dynamic  /opt/dpdk/dpdk-stable-18.11.8/x86_64-native-linuxapp-gcc/lib/librte_flow_classify.a  
-Wl,--whole-archive  /opt/dpdk/dpdk-stable-18.11.8/x86_64-native-linuxapp-gcc/lib/librte_pipeline.a  
/opt/dpdk/dpdk-stable-18.11.8/x86_64-native-linuxapp-gcc/lib/librte_table.a
etc.

That link command links to libdpdk.a (my library) as expected, but it also links to all the 3rd party dpdk libraries, which I don’t expect.

Furthermore, I don’t see a linker command for libdpdk.a itself.

Any ideas please?

This is what you get if you don’t specify PRIVATE for target_link_libraries of your library - the dependencies are carried to the target that links to it.

I tried PRIVATE but the result was the same.

As my project was quite complicated I have started again with a simple example I found online.

Top level CMakeLists.txt

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

project("To Do List")

include_directories(${CMAKE_CURRENT_SOURCE_DIR})

add_subdirectory(ToDoCore)

add_executable(toDo main.cc)
target_link_libraries(toDo PRIVATE toDoCore)

Library CMakeLists.txt

set (_lib_name "toDoCore")

add_library(${_lib_name})

target_sources(${_lib_name} 
    PRIVATE
         ToDo.cc
)

target_link_libraries(${_lib_name} 
     PRIVATE ${_lib64_path}libdl.so
     PRIVATE ${_lib64_path}libutil.so
     PRIVATE ${_lib64_path}libm.so
)

Build output

$ ninja -v
[1/4] /usr/bin/c++   -I../  -MD -MT ToDoCore/CMakeFiles/toDoCore.dir/ToDo.cc.o -MF ToDoCore/CMakeFiles/toDoCore.dir/ToDo.cc.o.d -o ToDoCore/CMakeFiles/toDoCore.dir/ToDo.cc.o -c ../ToDoCore/ToDo.cc
[2/4] : && /usr/local/bin/cmake -E remove ToDoCore/libtoDoCore.a && /usr/bin/ar qc ToDoCore/libtoDoCore.a  ToDoCore/CMakeFiles/toDoCore.dir/ToDo.cc.o && /usr/bin/ranlib ToDoCore/libtoDoCore.a && :
[3/4] /usr/bin/c++   -I../  -MD -MT CMakeFiles/toDo.dir/main.cc.o -MF CMakeFiles/toDo.dir/main.cc.o.d -o CMakeFiles/toDo.dir/main.cc.o -c ../main.cc
[4/4] : && /usr/bin/c++    -rdynamic CMakeFiles/toDo.dir/main.cc.o  -o toDo  ToDoCore/libtoDoCore.a  -ldl  -lutil  -lm && :

This demonstrates that the external libraries specified in the ‘library’ CMakeLists.txt don’t get linked into that library, but get linked into the executable.

From https://stackoverflow.com/a/14480396/4940771 I read:

I think it's CMake's default behavior to not link project2 to the external library, but to link both libraries to the executable. From the book "Mastering CMake".

Since static libraries do not link to the libraries on which they depend, it is important for CMake to keep track of the libraries so they can be specified on the link line of the executable being created.

What would be the way around this (i.e. to include the libraries in the target library)? Use “ar” in some way?

I have fixed this problem by linking to shared dpdk libraries, rather than static libraries. I have unresolved symbols but will discuss that in another thread. We can close this issue now.