How do you use a header-only library package in ROS2?
The Problem (in short)
I'm trying to create and use a header-only library package in ROS2 Foxy. However the compilation fails with a vague error message about not being able to find the library file, and it does seem like the library object is not being created. How do I correctly structure my library and client package CMakeList files to get this working?
The Problem (in full)
I have three ROS2 Foxy packages: a library package that contains source and header files, a library package of ONLY header files, and a client library that wants to use both. E.g:
workspace/
library_package/
src/
... lots of.cpp files ....
include/
... lots of .hpp files ....
CMakeLists.txt
package.xml
header_only_library_package/
# (no src folder here)
include/
... lots of **.h** files ....
CMakeLists.txt
package.xml
client_package/
src/
includes/
... etc ...
The Error
My normal library package compiles and works with the client package just fine. No problems there. But the header only library package fails compilation with this error:
--- stderr: client_package_executable
/usr/bin/ld: cannot find -lheader_only_library_package
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/client_package.dir/build.make:326: client_package_executable] Error 1
make[1]: *** [CMakeFiles/Makefile2:84: CMakeFiles/client_package_executable.dir/all] Error 2
make[1]: *** Waiting for unfinished jobs....
This seems to be saying that ld
cannot find a library object. Compiling with debug output (colcon --level-log debug build
) shows that (what I think is the correct) library path is:
LD_LIBRARY_PATH=/workspaces/install/header_only_library_package/lib:/workspaces/install/library_package/lib:${LD_LIBRARY_PATH}
However the lib
folder doesn't exist for the header only package. So from that I am inferring that something in the header-only package CMakeLists file is wrong, and the library object is not being created.
The Code
My header_only_library_package CMakeLists file looks like this:
# In header_only_library_package:CMakeLists.txt
project(header_only_library_package)
add_library(header_only_library_package INTERFACE)
target_include_directories(header_only_library_package INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
ament_export_targets(export_header_only_library_package HAS_LIBRARY_TARGET)
install(
DIRECTORY include/
DESTINATION include/)
install(
TARGETS header_only_library_package
EXPORT export_header_only_library_package
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
ament_export_include_directories(include)
ament_package()
These commands are a hoge-podge taken from the various bits of tutorials/documentation and SO threads I've been reading. Most of it comes from the Ament CMake Documentation. The docs do an OK job of explaining what command does what, but don't really explain what commands you actually need and for which applications.
And my client_package CMakelists file looks like this:
# In client_package:CMakeLists.txt
find_package(library_package REQUIRED)
find_package(header_only_library_package REQUIRED)
... usual ROS2 library and executable building stuff ...
ament_target_dependencies(client_package_executable
library_package
header_only_library_package)
ament_target_dependencies(client_package_executable
library_package
header_only_library_package)
target_link_libraries(client_package_executable
library_package
header_only_library_package)
... usual ROS2 executable install stuff ...
And of course, my package.xml has the <depend>
tags for both the normal and header-only library packages.
I've ready my way through a lot of the Ament, CMake and ROS2 documentation, but either I missed the correct syntax I need, or haven't found it yet.
Is anyone able to tell me what the correct syntax for this is?
Actually I asked myself the same question, so I started digging a bit. Please find my MWE here. It at least works on ROS2 rolling...