How to link shared library in Ament Tool ROS - c++

I am building my packages with ROS ament tools. structure is as below
LibA (build as shared library)
LibB (depends on LibA & build as shared library)
AppB (depends on LinB)
in package.xml of LibB. I specified dependency as ...
package.xml (under dir LibB)
<build_depend>libA</build_depend>
in package.xml of AppB. I specified dependency as ...
package.xml (under dir AppB)
<build_depend>libA</build_depend>
<build_depend>libB</build_depend>
Now build order is correct as ..
# Topological order
- libA
- libB
- AppB
Now issue starts when bulding AppB, and it can't find link for LibA and LibB.
Question
How can I link LibA to LibB ? (what syntax should I use)
How can I link LibA,LibB to AppB ?

Perhaps you should use ament_export in LibA CMakeLists.txt, this page from ROS2 should help
# CMakeLists.txt for LibA
add_library(LibA SHARED src/LibA.cpp)
ament_target_dependencies(LibA rclcpp)
ament_export_interfaces(export_LibA HAS_LIBRARY_TARGET)
ament_export_libraries(LibA)
ament_export_include_directories(include)
ament_export_dependencies(
ament_cmake
rclcpp
)
install(
TARGETS LibA
EXPORT export_LibA
LIBRARY DESTINATION lib/${PROJECT_NAME}
ARCHIVE DESTINATION lib/${PROJECT_NAME}
RUNTIME DESTINATION lib/${PROJECT_NAME}
)
install(
DIRECTORY include/
DESTINATION include
)
and then in CMakeLists.txt from LibB you should have
# CMakeLists.txt for LibB
find_package(LibA REQUIRED)
add_library(LibB SHARED src/LibB.cpp)
target_include_directories(LibB PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
ament_target_dependencies(LibB
rclcpp
LibA
)
ament_export_libraries(LibB)
ament_export_include_directories(include)
ament_export_dependencies(
rclcpp
LibA
)
install(
TARGETS LibB
EXPORT export_LibB
LIBRARY DESTINATION lib/${PROJECT_NAME}
ARCHIVE DESTINATION lib/${PROJECT_NAME}
RUNTIME DESTINATION lib/${PROJECT_NAME}
)
install(
DIRECTORY include/
DESTINATION include
)
To use LibA and LibB with AppB, I think you need to use ament_target_dependencies with LibB as argument and then use add_executable() function
I think you could change the <build_depend> tag for <depend>

Related

Why CMake does not propagate the PUBLIC include directories between libraries?

I have a C++ project with three shared libraries, let's call them libA, libB and libC. libA is dependant to both libB and libC. All of these three libraries are located inside a folder called utilities. Here is what I have:
Root CMakeLists.txt file:
cmake_minimum_required (VERSION 3.20) # Required because of policy CMP0074
project(Test VERSION 2022)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
file(TO_CMAKE_PATH $ENV{SHARED_PATH} SHARED_PATH)
add_subdirectory (utilities)
for libB I have the CMakeLists.txt like this:
add_library(libB SHARED
inc/libB.h
inc/libBExport.h
src/libB.cpp
)
target_include_directories(libB
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc
PUBLIC ${SHARED_PATH}/inc
)
target_compile_definitions(libB
PRIVATE LIBB_DLL)
and the same goes for libC:
add_library(libC SHARED
inc/libC.h
inc/libCExport.h
src/libC.cpp
)
target_include_directories(libC
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc
PUBLIC ${SHARED_PATH}/inc
)
target_compile_definitions(libC
PRIVATE LIBC_DLL)
Now, the CMakeLists.txt file for libA is like this:
add_library(libA SHARED
inc/libA.h
inc/libAExport.h
src/libA.cpp
)
target_include_directories(libA
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc
PUBLIC ${SHARED_PATH}/inc
)
target_link_directories(libA
PUBLIC libB
PUBLIC libC
)
target_compile_definitions(libA
PRIVATE LIBA_DLL)
Now, I generate the Visual Studio solution file using cmake:
> cmake -G "Visual Studio 16 2019" -A x64
Everything goes fine. But when opening the solution with VS2019, libB and libC are compiling fine, but libA cannot be compiled with this error:
D:\code\proj\utilities\inc\libA.h(5,10): fatal error C1083: Cannot open include file: 'libB.h': No such file or directory
D:\code\proj\utilities\inc\libA.h(9,10): fatal error C1083: Cannot open include file: 'libC.h': No such file or directory
Can someone explain me why the the PUBLIC include directories of libB and libC are not inherited to libA?
Instead of the following:
target_link_directories(libA
PUBLIC libB
PUBLIC libC
)
You should use target_link_libraries().
target_link_libraries(libA
PUBLIC libB
PUBLIC libC
)
This will setup a dependency between the libraries which sets the include path, compiler settings, additional link directories, and additional link libraries.

CMake Include paths - library that depend on external library

Take for example this project structure
project
CMakeLists.txt
main.cpp
libA
src.cpp
CMakeLists.txt
libB
foo.h
foo.cpp
CMakeLists.txt
src.cpp and main.cpp:
#include "foo.h"
.
.
.
I need both src.cpp and main.cpp to have libB in their include path, what I've tried to do is:
libB/CMakeLists.txt:
add_library(libB SHARED
src.cpp)
libA/CMakeLists.txt:
add_library(libA SHARED
foo.cpp)
target_link_libraries(libA libB)
project/CMakeLists.txt:
add_subdirectory(libA)
add_subdirectory(libB)
add_executable(App main.cpp)
target_include_directories(App PUBLIC libB)
target_link_libraries(App libA libB)
And yet I get an error that
src.cpp: fatal error: foo.h: No such file or directory
In the top-level:
cmake_minimum_required(VERSION 3.21)
project(project)
option(BUILD_SHARED_LIBS "Build shared libs instead of static" ON)
add_subdirectory(libA)
add_subdirectory(libB)
add_executable(App main.cpp)
target_link_libraries(App PRIVATE libA libB)
In libA:
add_library(libA src.cpp)
# Use PUBLIC below if libA exposes libB's types in its API.
target_link_libraries(libA PRIVATE libB)
In libB:
add_library(libB foo.cpp)
target_include_directories(
libB PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
)
PUBLIC and INTERFACE properties on targets propagate to their linkees. Thus, when we set up the include directories on libB, any target linking to it will get its source directory added it its include path.
We guard the include directory with $<BUILD_INTERFACE:...> so that if you ever install() the library somewhere it might be re-used in another CMake build, it won't remember details of your specific file system.

CMake 2.8.12 with nested dependencies, resulting in multiple 'inclusion'

I have widely-separated dirs, each with a CMakeLists.txt. I'd like to keep them independently-buildable. Two are libs; libA, libB. libB uses libA. exeC uses libA and libB.
When I organize them via add_subdirectory(), as in:
libB (CMakeLists.txt):
add_subdirectory($DIRA somebuild)
target_linked_libraries(libB PUBLIC libA)
exeC (CMakeLists.txt):
add_subdirectory($DIRA somebuild)
add_subdirectory($DIRB somebuild)
target_linked_libraries(exeC PUBLIC libB libA)
and then building exeC, CMake complains about the target name of libA not being unique (CMP0002). If I squelch that warning, I get funky results as the docs warn against.
When I organize them via: add_library(), and add_dependencies() as in:
libB (CMakeLists.txt):
add_library(libA SHARED IMPORTED)
set_target_properties(libA PROPERTIES IMPORTED_LOCATION $DIRA)
add_dependencies(libB libA)
target_linked_libraries(libB PUBLIC libA)
exeC (CMakeLists.txt):
add_library(libA SHARED IMPORTED)
set_target_properties(libA PROPERTIES IMPORTED_LOCATION $DIRA)
add_dependencies(exeC libA)
add_library(libB SHARED IMPORTED)
set_target_properties(libB PROPERTIES IMPORTED_LOCATION $DIRB)
add_dependencies(exeC libB)
target_linked_library(exeC PUBLIC libA libB)
It doesn't seem like the dependencies propagate - when I build exeC, it chokes on not finding libA and libB there, as indeed the docs warn about, although I thought I saw some other doc that said they would build if the required libs were all based on CMake. I'm using CMake 2.8.12.
Is it possible to have nested inclusions like this, or is that not The CMake Way?
edit: it further turns out, that one solution - relying on the transitivity of add_subdirectory(), isn't enough because my case is actually diamond-shaped, I have libB1 and libB2 who each 'include' libA and are included by exeC. It looks like include guards may be the way to go. I'll acknowledge #Drop's answer as correct, if I get this working.

CMake linking 2 libraries with relative path

my project structure:
...
CMakeLists.txt
src/
project/
libA/
CMakeLists.txt
libA.h
libA.cpp
libB/
CMakeLists.txt
libB.h
libB.cpp
build/
CMakelists.txt
project(project)
# set some global dirs
set(LIB_A_DIR "src/project/libA")
set(LIB_B_DIR "src/project/libB")
# include header directories
include_directories(${LIB_A_DIR})
include_directories(${LIB_B_DIR})
# generating linkable libraries
add_subdirectory(${LIB_A_DIR})
add_subdirectory(${LIB_B_DIR})
src/project/libA/CMakeLists.txt
# define libA sources
set(LIB_A_SOURCES libA.cpp libA.h)
# generate libA library
add_library(libA SHARED ${LIB_A_SOURCES})
src/project/libB/CMakeLists.txt
# define libB sources
set(LIB_B_SOURCES libB.cpp libB.h)
# generate libB library
add_library(libB SHARED ${LIB_B_SOURCES})
# link libraries
target_link_libraries(libB PRIVATE libA)
now if i run cmake .. && make from the build folder any function call made from libB works just fine. But as soon as i want to move this file structure the link breaks.
For example when i did cp -r build/ build_copy/ and clear the build directory with rm -rf build/* any code that includes libB throws a error while loading shared libraries: liblibB.so: cannot open shared object file: No such file or directory
how do i get my link to use the relative file path?
side note: i need them to be 2 seperate shared libraries combined with a link
You need to set the runtime-path of libA to where it is installed. This is done with the -rpath linker option.
Since the C++ frontend g++ or clang++ will be used when linking, you need to use the -Wl flag to pass the -rpath option to the linker.
You can set flag with the LINK_FLAGS target property for libB with the set_target_properties command:
set_target_properties(libB
PROPERTIES LINK_FLAGS "-Wl,-rpath,/path/to/libA/installation")
as in CMake linking 2 libraries with relative path comment:
Assign a variable with the absolute path of the relative path to the library:
get_filename_component(LIB_A_PATH ../lib/liba.a ABSOLUTE)
Then use the absolute path to link the lib (here statically):
add_library( lib_a STATIC IMPORTED )
set_target_properties(lib_a PROPERTIES IMPORTED_LOCATION ${LIB_A_PATH})
target_link_libraries(${PROJECT_NAME} lib_a)

Various libs in subfolders, how do I export them to the parent?

I have a code folder structure like this:
project/
CMakeLists.txt
src/
project.cpp
lib/
libA/
CMakeLists.txt
src/
libA.cpp
include/
libA.h
libB/
CMakeLists.txt
src/
libB.cpp
include/
libB.h
Now, libA is totally standalone, so I have a CMakeLists.txt similar to this:
cmake_minimum_required(VERSION 2.8)
project ( libA )
set ( LIBA_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include PARENT_SCOPE )
include_directories ( include/ )
file (GLOB LIBA_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}
src/libA.cpp
)
add_library ( libA STATIC ${LIBA_SOURCES} )
However, libB depends on libA. How can I export the libA.a output path to be used for linking to libB, and the corresponding include path?
And furthermore, how do I pass that data to the parent CMakeLists.txt for the main project?
I haven't done this personally, so this answer may only be a starting point, but Qt uses cmake and is able to tie all the include directories and library dependencies into each 'module' (library). If you accomplish that, then you can simply do
add_executable(myExe ...)
target_link_libraries(myExe libB)
to the executable in your main cmake project.
Looking through the Qt scripts, it appears what you need to set (for each library) is:
add_library(libA ...)
set_target_properties(libA PROPERTIES
"INTERFACE_LINK_LIBRARIES" "${LIBRARIES_LIBA_DEPENDS_ON}"
"INTERFACE_INCLUDE_DIRECTORIES" "${LIBA_INCLUDE_DIRECTORIES}"
)
then for libB
add_library(libB ...)
set_target_properties(libB PROPERTIES
"INTERFACE_LINK_LIBRARIES" "libA"
"INTERFACE_INCLUDE_DIRECTORIES" "${LIBB_INCLUDE_DIRECTORIES}"
)
The cool thing about this method is that when done properly, you can chain together as many libraries as you like without having to worry about the combinatorial explosion of dependencies and include dirs.
Taking #epicbrew and #NicolasHolthaus answers/comments I just wanted to show how the complete example would look in a newer version of CMake and discuss some of the implications:
project/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project( project )
add_subdirectory( lib/libB )
add_subdirectory( lib/libA )
add_executable( project src/project.cpp )
target_link_libraries( project libB )
project/lib/libA/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project( libA )
add_library( libA STATIC src/libA.cpp include/libA.h )
target_include_directories( libA PUBLIC include )
project/lib/libB/CMakeLists.txt
add_library( libB STATIC src/libB.cpp include/libB.h )
target_include_directories( libB PUBLIC include )
target_link_libraries( libB libA )
As you can see
You need to know in the main CMakeLists.txt file that you also need libA. I was deliberately reversing the add_subdirectory() calls to show that the order doesn't matter when using target_link_libraries() (sort of allows target forward declarations).
You don't need the local include_directories() call anymore it's an implicit part of the target_include_directories().
With target_include_directories(... PUBLIC ...) those include paths are self-propagating.
You don't need to add the ${CMAKE_CURRENT_SOURCE_DIR} prefixes, those are the defaults.
I just added the source/header files directly to the add_executable()/add_library() calls for compactness of this example code. Normally I also would place those in local variables.
libB is not standalone, but you could change that e.g. by adding add_subdirectory( ../libA libA ) to its CMakeLists.txt file. Obviously you then have to remove add_subdirectory( lib/libA ) from the main CMakeLists.txt.
If you want to optimize the CMake processing a little you should not repeat the project() commands. If you still want libA to be self-sufficient you could add if ( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR )/endif() around the cmake_minimum_required(VERSION 3.0)/project( libA ) calls.
CMake knows about all of the targets that you've built, so there's no need to export information up to the parent. You simply tell CMake that libB links against libA and CMake takes care of putting the correct path on the linker command line. So just specify libA as a link dependency of libB in your libB/CMakeLists.txt:
target_link_libraries(libB libA)
For the include path you either need to set variables in the parent along the lines of the following in the top level project/CMakeLists.txt:
set(LIBA_INCLUDES libA/include)
set(LIBB_INCLUDES libB/include)
Then reference these in the subprojects CMakeList.txt files.
Or, you could use a relative path in your libB/CMakeLists.txt file:
include_directories(../libA/include)