2
$\begingroup$

Good afternoon!

We are a company that has a pretty big robotic framework and we are currently working in a transition into ROS2. We are first creating the structure that all of our packages will follow. Until now, these were separate modules in the same CMake project. Now, as we are starting to test the transition we encountered an issue.

Explain the problem

As we are moving all of our framework, we have our own logger that is being used in our development. Transitioning into the ROS2 logger in all of our modules is not feasible right now. Both loggers use Spdlog under the hood (ROS2 logger and our logger). The conversion of this module is correct and everything works great downstream. However when we try to create a package that has a dependency in ROS (for the creation of a ROS node) we encounter some linker errors.

This error appears only when linking executables that depend on ROS2. If we compile executables that exclusively depend on our modules (No ROS2 dependencies) then everything works correctly

Our Spdlog installation is built from source with version v1.9.2, while I believe ROS uses the binary installation of libspdlog-dev which has version v1.5.0. I've tried to upgrade and downgrade our version with no effect and also simply installing the ROS version to no effect.

All of these linker errors refer to the fmt library which Spdlog depends on.

Linker Error

This is the linker error in question. It's repeated in the terminal quite a few times. I've changed the names of the home paths and files names but the rest is as shown.

/path/ros2_galactic/ros2-linux/include/spdlog/fmt/bundled/format.h:1051: undefined reference to `fmt::v7::detail::assert_fail(char const*, int, char const*)'
/usr/bin/ld: CMakeFiles/ProjectSample.dir/samples/ProjectSample.cpp.o: in function `spdlog::details::elapsed_formatter<spdlog::details::null_scoped_padder, std::chrono::duration<long, std::ratio<1l, 1000000l> > >::format(spdlog::details::log_msg const&, tm const&, fmt::v7::basic_memory_buffer<char, 250ul, std::allocator<char> >&)':
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:34: undefined reference to `fmt::v7::detail::basic_data<void>::digits'
/usr/bin/ld: CMakeFiles/ProjectSample.dir/samples/ProjectSample.cpp.o: in function `std::make_unsigned<long>::type fmt::v7::detail::to_unsigned<long>(long)':
/path/ros2_galactic/ros2-linux/include/spdlog/fmt/bundled/core.h:326: undefined reference to `fmt::v7::detail::assert_fail(char const*, int, char const*)'
/usr/bin/ld: CMakeFiles/ProjectSample.dir/samples/ProjectSample.cpp.o: in function `fmt::v7::detail::count_digits(unsigned long)':
/path/ros2_galactic/ros2-linux/include/spdlog/fmt/bundled/format.h:924: undefined reference to `fmt::v7::detail::basic_data<void>::zero_or_powers_of_10_64_new'
/usr/bin/ld: CMakeFiles/ProjectSample.dir/samples/ProjectSample.cpp.o: in function `spdlog::details::elapsed_formatter<spdlog::details::null_scoped_padder, std::chrono::duration<long, std::ratio<1l, 1l> > >::format(spdlog::details::log_msg const&, tm const&, fmt::v7::basic_memory_buffer<char, 250ul, std::allocator<char> >&)':
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:34: undefined reference to `fmt::v7::detail::basic_data<void>::digits'
/usr/bin/ld: CMakeFiles/ProjectSample.dir/samples/ProjectSample.cpp.o: in function `fmt::v7::detail::count_digits(unsigned long)':
/path/ros2_galactic/ros2-linux/include/spdlog/fmt/bundled/format.h:924: undefined reference to `fmt::v7::detail::basic_data<void>::zero_or_powers_of_10_64_new'
/usr/bin/ld: CMakeFiles/ProjectSample.dir/samples/ProjectSample.cpp.o: in function `fmt::v7::detail::format_decimal_result<char*> fmt::v7::detail::format_decimal<char, unsigned long>(char*, unsigned long, int)':

It seems to come from "/path/ros2_galactic/ros2-linux/include/spdlog/fmt/bundled/", thus we guessed the problem comes from the Spdlog installed from ROS. I was wondering if someone has faced this issue and/or maybe knows why it's caused or has any idea on how to solve it.

Tried solutions

As I've said, we tried installing different versions of spdlog and removing ours just to have the ROS2 one but the error persists.

We've also tried making changes to the CMake but the linkage seems correct and follows the instructions of spdlog. I would prefer not to show the CMake files unless needed as they are sensitive and can't be shared as they are.

EDIT

Hello, to try to understand a bit better my structure I've added the basic CMake structure of the packages that I'm doing

cmake_minimum_required(VERSION 3.11...3.24)

project(
    moduleName
    VERSION 1.0.0
    DESCRIPTION "Add a short description of your module"
    LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

# Declare options
option(BUILD_TESTS "Build the Tests of the module" ON)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    set(COMPILER_FLAGS -Wall -Wextra -Wpedantic -Wshadow -pthread)
endif()

if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Debug")
endif()

include(GNUInstallDirs)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmakeutilities/CMakeFunctions.cmake)

if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
add_compile_options(${COMPILER_FLAGS} -O -g)
else()
    add_compile_options(${COMPILER_FLAGS} -O2)
endif()

set(EXPORT_NAME "${PROJECT_NAME}_export")

find_package(ament_cmake REQUIRED)

################
# Dependencies #
################

find_package(dependency1 REQUIRED)
find_package(dependency2 REQUIRED)
find_package(dependency3 REQUIRED)

set(MODULE_DEPENDENCIES
    dependency1
    dependency2
    dependency3
)

#################
# Build library #
#################

add_library(${PROJECT_NAME} SHARED
./src/ModuleSource.cpp)
target_include_directories(${PROJECT_NAME}
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
    PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}
)

###################
# Install Library #
###################

# Linking
ament_target_dependencies(${PROJECT_NAME} ${MODULE_DEPENDENCIES})

set_target_properties(${PROJECT_NAME}
PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}
)

# Install Library Target
install(
    TARGETS ${PROJECT_NAME}
EXPORT ${EXPORT_NAME}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

# Install Headers
install(
    DIRECTORY ${PROJECT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

###############
# Executables #
###############

add_executable(ModuleSample samples/ModuleSample.cpp)
target_link_libraries(ModuleSample ${PROJECT_NAME})

install(
    TARGETS ModuleSample
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}
)

##############
# Test Suite #
##############

if((BUILD_TESTING OR BUILD_TESTS) AND LIBRARY_HAS_TESTS)
    find_package(ament_lint_auto REQUIRED)
    find_package(ament_cmake_gmock REQUIRED)
    #ament_lint_auto_find_test_dependencies()

    ament_add_gmock(ut-${PROJECT_NAME}
    ./tests/ModuleTests.cpp)
target_link_libraries(ut-${PROJECT_NAME} ${PROJECT_NAME})
endif()

##################
# Export Targets #
##################

ament_export_dependencies(${MODULE_DEPENDENCIES})
ament_export_targets(${EXPORT_NAME} HAS_LIBRARY_TARGET)
ament_export_include_directories(include)

ament_package()

Edit 2

Hello! The full structure would be something like the following. I have two packages. The first one is my own custom logger and the CMake structure is as follows:

cmake_minimum_required(VERSION 3.11...3.24)

project(
    customLogger
    VERSION 1.0.0
    DESCRIPTION "Add a short description of your module"
    LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

# Declare options
option(BUILD_TESTS "Build the Tests of the module" ON)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    set(COMPILER_FLAGS -Wall -Wextra -Wpedantic -Wshadow -pthread)
endif()

if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Debug")
endif()

include(GNUInstallDirs)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmakeutilities/CMakeFunctions.cmake)

if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
add_compile_options(${COMPILER_FLAGS} -O -g)
else()
    add_compile_options(${COMPILER_FLAGS} -O2)
endif()

set(EXPORT_NAME "${PROJECT_NAME}_export")

find_package(ament_cmake REQUIRED)

################
# Dependencies #
################

find_package(spdlog REQUIRED)

#################
# Build library #
#################

add_library(${PROJECT_NAME} SHARED
./src/ModuleSource.cpp)
target_include_directories(${PROJECT_NAME}
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
    PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}
)

###################
# Install Library #
###################

# Linking
ament_target_dependencies(${PROJECT_NAME} spdlog)

set_target_properties(${PROJECT_NAME}
PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}
)

# Install Library Target
install(
    TARGETS ${PROJECT_NAME}
EXPORT ${EXPORT_NAME}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

# Install Headers
install(
    DIRECTORY ${PROJECT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

###############
# Executables #
###############

add_executable(ModuleSample samples/ModuleSample.cpp)
target_link_libraries(ModuleSample ${PROJECT_NAME})

install(
    TARGETS ModuleSample
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}
)

##############
# Test Suite #
##############

if((BUILD_TESTING OR BUILD_TESTS) AND LIBRARY_HAS_TESTS)
    find_package(ament_lint_auto REQUIRED)
    find_package(ament_cmake_gmock REQUIRED)
    #ament_lint_auto_find_test_dependencies()

    ament_add_gmock(ut-${PROJECT_NAME}
    ./tests/ModuleTests.cpp)
target_link_libraries(ut-${PROJECT_NAME} ${PROJECT_NAME})
endif()

##################
# Export Targets #
##################

ament_export_dependencies(${MODULE_DEPENDENCIES})
ament_export_targets(${EXPORT_NAME} HAS_LIBRARY_TARGET)
ament_export_include_directories(include)

ament_package()

The CMake from the second package is identical to the first one except that it depends on the custom logger:

find_package(customLogger REQUIRE)

ament_target_dependencies(${PROJECT_NAME} customLogger)

If, instead of:

ament_target_dependencies(${PROJECT_NAME} spdlog)

We write:

target_link_libraries(${PROJECT_NAME} spdlog::spdlog)

Then we get a different linker error:

/ros2_galactic/ros2-linux/include/spdlog/logger.h:168: undefined reference to `spdlog::details::log_msg::log_msg(spdlog::source_loc, fmt::v7::basic_string_view<char>, spdlog::level::level_enum, fmt::v7::basic_string_view<char>)'
/usr/bin/ld: /path/ros2_galactic/ros2-linux/include/spdlog/logger.h:168: undefined reference to `spdlog::details::log_msg::log_msg(spdlog::source_loc, fmt::v7::basic_string_view<char>, spdlog::level::level_enum, fmt::v7::basic_string_view<char>)'
/usr/bin/ld: /path/ros2_galactic/ros2-linux/include/spdlog/logger.h:168: undefined reference to `spdlog::details::log_msg::log_msg(spdlog::source_loc, fmt::v7::basic_string_view<char>, spdlog::level::level_enum, fmt::v7::basic_string_view<char>)'

Still, it seems to be finding the spdlog installation of ROS2 instead of the one we use.

Changing the find package into:

find_package(spdlog 1.9.2 REQUIRED)

makes it so that it finally finds our installation instead of the ROS2 one but downstream it does not seem to find it anymore and it keeps throwing the same linker errors.

$\endgroup$
2
  • $\begingroup$ Welcome to Robotics, Jorge Playán. You're saying this is a linker error, but you're not providing the CMakeLists.txt file or the target_link_libraries definition in that file. How are you linking? $\endgroup$
    – Chuck
    Commented Nov 11, 2022 at 18:25
  • $\begingroup$ Hello Chuck, thanks for commenting! I'm linking with ament_target_dependencies(), I've also tried with target_link_libraries but had the same result. I'll try to edit my original question with an scheme of my CMake structure. $\endgroup$ Commented Nov 15, 2022 at 10:36

3 Answers 3

2
$\begingroup$

I don't see where you're actually referencing spdlog in your CMakeLists.txt file. I would expect an entry like the following:

# spdlog
find_package(spdlog 1.9.2 REQUIRED)

target_link_libraries (your_project_name ${catkin_LIBRARIES} spdlog::spdlog)
$\endgroup$
2
  • $\begingroup$ Hello! Thanks for the help and sorry for the late reply. I've updated the question with a second edit explaining a bit more the linking between the two packages. I do it as explained in the documentation but still the liker error keeps appearing. $\endgroup$ Commented Nov 21, 2022 at 10:22
  • $\begingroup$ In your second edit you mentioned that this works. If you're forcing the specific version, you'll need to force that specific version in all downstream packages too. This specific find_package invocation won't effect future ones in other packages, they will find the default version too. $\endgroup$
    – Tully
    Commented Nov 22, 2022 at 0:11
1
$\begingroup$

From the answer above from Chuck I see you got at least the basic capabilities working. However I'd like to suggest that you may want to consider an alternative approach. Relying on two different versions of a binary library in potentially the same compiled unit is likely to be a fragile balance and take a lot of work to maintain. I suggest considering an alternative approach.

A much more robust mechanism for doing this is to setup your environment with the alternative version and compile everything against that version. This will avoid any potential conflict and very hard to debug ABI mismatches down the line. You're somewhat getting lucky that you're getting linker errors at the moment. Even more insidious would be ABI incompatibilities which would manifest themselves as crashes which the debugger might not even be able to backtrace correctly if you force different versions of the same library into compile units.

$\endgroup$
1
$\begingroup$

After some trial an error and going through the suggestions that people made, I managed to at least make it work. It seems that when I try to link to my own spdlog installation (not the ros version), it finds it but links to it incorrectly. If I link to both, spdlog and rclcpp, then it find both installations and links correctly to the one I need.

I'm not sure why this happens or what could be the solution but in the end I wrote something like:

find_package(spdlog 1.9.2 REQUIRED)
find_package(rclcpp REQUIRED)

ament_target_dependencies(my_project rclcpp spdlog)

Using this, the program compiles and links correctly.

Thank you all for the help, and if someone knows why this could be happening please feel free to comment and give your insight.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.