ccmakelinkerros2colcon

How to link c files and header files correctly in CMakeList in ROS2?


I am new to ROS2 and do not have any prior knowledge of Cmake.

I have a ROS2 package that looks like this:

├── CMakeLists.txt
├── include
│   └── package_name
│       ├── bmi088.h
│       ├── bmi08x_defs.h
│       ├── bmi08x.h
│       ├── rpi_bmi088.h
│       └── rpi_i2c.h
├── package.xml
├── scripts
├── src
│   ├── bmi088.c
│   ├── imu_data_reader.cpp
│   ├── rpi_bmi088.c
│   └── rpi_i2c.c
└── package_name
    └── __init__.py

I got the sensor drivers from a github repo and tested the code by running the makefile that was included in the repo. The code worked perfectly, but when implementing in ROS2, I got the following error when building with colcon:

Starting >>> package_name
--- stderr: package_name                                                               
/usr/bin/ld: CMakeFiles/imu_data_reader.dir/src/imu_data_reader.cpp.o: in function `main':
imu_data_reader.cpp:(.text+0x12a): undefined reference to `rpi_bmi088_init(rpi_bmi088_t*, char const*, int, int, bmi08x_cfg const*, bmi08x_cfg const*)'
/usr/bin/ld: imu_data_reader.cpp:(.text+0x139): undefined reference to `rpi_bmi088_get_sensor_time(rpi_bmi088_t*)'
/usr/bin/ld: imu_data_reader.cpp:(.text+0x17f): undefined reference to `rpi_bmi088_get_accel(rpi_bmi088_t*, double*, double*, double*)'
/usr/bin/ld: imu_data_reader.cpp:(.text+0x1db): undefined reference to `rpi_bmi088_get_gyro(rpi_bmi088_t*, double*, double*, double*)'
collect2: error: ld returned 1 exit status
gmake[2]: *** [CMakeFiles/imu_data_reader.dir/build.make:154: imu_data_reader] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:148: CMakeFiles/imu_data_reader.dir/all] Error 2
gmake[1]: *** Waiting for unfinished jobs....
gmake: *** [Makefile:146: all] Error 2
---
Failed   <<< package_name [0.42s, exited with code 2]

Summary: 0 packages finished [0.70s]
  1 package failed: package_name
  1 package had stderr output: package_name

I know that this error comes from not declaring dependencies or falsely compiling, so I probably messed up with the CMakeLists file:

cmake_minimum_required(VERSION 3.8)
project(package_name)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclpy REQUIRED)

# include directories
include_directories(include)

# CPP Nodes:
add_executable(imu_data_reader src/imu_data_reader.cpp)
ament_target_dependencies(imu_data_reader rclcpp)

# Link libraries:
add_library(rpi_bmi088 src/rpi_bmi088.c)
add_library(bmi088 src/bmi088.c)
add_library(rpi_i2c src/rpi_i2c.c)
target_link_libraries(imu_data_reader rpi_bmi088 bmi088 rpi_i2c)

install(TARGETS
  imu_data_reader
  DESTINATION lib/${PROJECT_NAME}
)

# Install Python modules
ament_python_install_package(${PROJECT_NAME})

# Install Python executables
install(PROGRAMS
  DESTINATION lib/${PROJECT_NAME}
)

ament_package()

My question is: how does one correctly compile header files with their corresponding .c files?

In my source files, I am including the header files #include "package_name/rpi_bmi088.h" where the functions are declared, but they are not being linked to the corresponding .c file in this case src/rpi_bmi088.c where the functions are implemented.

In case it is also important, I will include my package.xml file:

  ...package_info...


  <buildtool_depend>ament_cmake</buildtool_depend>
  <buildtool_depend>ament_cmake_python</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>rclpy</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

UPDATE 1:

After implementing michael's answer:

extern "C" 
{
    #include "package_name/rpi_bmi088.h"
}

I now get a different but similar error:

Starting >>> package_name
--- stderr: package_name                             
/usr/bin/ld: libbmi088.a(bmi088.c.o): warning: relocation against `bmi08x_config_file' in read-only section `.text'
/usr/bin/ld: librpi_bmi088.a(rpi_bmi088.c.o): in function `rpi_bmi088_init':
rpi_bmi088.c:(.text+0x119): undefined reference to `bmi08a_get_regs'
/usr/bin/ld: rpi_bmi088.c:(.text+0x14c): undefined reference to `bmi08g_get_regs'
/usr/bin/ld: rpi_bmi088.c:(.text+0x183): undefined reference to `bmi08a_set_power_mode'
/usr/bin/ld: rpi_bmi088.c:(.text+0x195): undefined reference to `bmi08a_set_meas_conf'
/usr/bin/ld: rpi_bmi088.c:(.text+0x1a7): undefined reference to `bmi08a_get_power_mode'
/usr/bin/ld: rpi_bmi088.c:(.text+0x200): undefined reference to `bmi08g_set_power_mode'
/usr/bin/ld: rpi_bmi088.c:(.text+0x212): undefined reference to `bmi08g_set_meas_conf'
/usr/bin/ld: librpi_bmi088.a(rpi_bmi088.c.o): in function `rpi_bmi088_get_accel':
rpi_bmi088.c:(.text+0x2a0): undefined reference to `bmi08a_get_data'
/usr/bin/ld: librpi_bmi088.a(rpi_bmi088.c.o): in function `rpi_bmi088_get_gyro':
rpi_bmi088.c:(.text+0x384): undefined reference to `bmi08g_get_data'
/usr/bin/ld: librpi_bmi088.a(rpi_bmi088.c.o): in function `rpi_bmi088_get_sensor_time':
rpi_bmi088.c:(.text+0x46e): undefined reference to `bmi08a_get_sensor_time'
/usr/bin/ld: libbmi088.a(bmi088.c.o): in function `bmi088_init':
bmi088.c:(.text+0x18): undefined reference to `bmi08a_init'
/usr/bin/ld: bmi088.c:(.text+0x2d): undefined reference to `bmi08g_init'
/usr/bin/ld: libbmi088.a(bmi088.c.o): in function `bmi088_apply_config_file':
bmi088.c:(.text+0x66): undefined reference to `bmi08x_config_file'
/usr/bin/ld: bmi088.c:(.text+0x76): undefined reference to `bmi08a_write_config_file'
/usr/bin/ld: libbmi088.a(bmi088.c.o): in function `bmi088_configure_data_synchronization':
bmi088.c:(.text+0x14b): undefined reference to `bmi08a_set_meas_conf'
/usr/bin/ld: bmi088.c:(.text+0x166): undefined reference to `bmi08g_set_meas_conf'
/usr/bin/ld: bmi088.c:(.text+0x1a0): undefined reference to `bmi08a_write_feature_config'
/usr/bin/ld: libbmi088.a(bmi088.c.o): in function `bmi088_configure_anymotion':
bmi088.c:(.text+0x29a): undefined reference to `bmi08a_write_feature_config'
/usr/bin/ld: libbmi088.a(bmi088.c.o): in function `bmi088_get_synchronized_data':
bmi088.c:(.text+0x32c): undefined reference to `bmi08a_get_regs'
/usr/bin/ld: bmi088.c:(.text+0x35c): undefined reference to `bmi08a_get_regs'
/usr/bin/ld: bmi088.c:(.text+0x402): undefined reference to `bmi08g_get_data'
/usr/bin/ld: libbmi088.a(bmi088.c.o): in function `bmi088_set_data_sync_int_config':
bmi088.c:(.text+0x44f): undefined reference to `bmi08a_set_int_config'
/usr/bin/ld: bmi088.c:(.text+0x475): undefined reference to `bmi08a_set_int_config'
/usr/bin/ld: bmi088.c:(.text+0x49b): undefined reference to `bmi08g_set_int_config'
/usr/bin/ld: bmi088.c:(.text+0x4c1): undefined reference to `bmi08g_set_int_config'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
gmake[2]: *** [CMakeFiles/imu_data_reader.dir/build.make:154: imu_data_reader] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:148: CMakeFiles/imu_data_reader.dir/all] Error 2
gmake[1]: *** Waiting for unfinished jobs....
gmake: *** [Makefile:146: all] Error 2
---
Failed   <<< package_name [0.23s, exited with code 2]

Summary: 0 packages finished [0.34s]
  1 package failed: package_name
  1 package had stderr output: package_name

Note that:
imu_data_reader.cpp includes only rpi_bmi088.h
rpi_bmi088.h includes bmi08x.h
rpi_bmi088.c includes rpi_bmi088.h, rpi_i2c.h and bmi088.h
bmi08x.h includes bmi08x_defs.h which doesn't include anything
rpi_i2c.c includes rpi_i2c.h which doesn't include anything
bmi088.h also includes bmi08x_defs.h
bmi088.c includes bmi08x.h and bmi088.h

So maybe it's a dependency problem?

UPDATE 2:

I finally solved my problem with the following changes in the CMakeLists.txt file:

# Add and link libraries:

add_library(rpi_bmi088 SHARED src/rpi_bmi088.c)
add_library(bmi088 SHARED src/bmi088.c)
add_library(rpi_i2c SHARED src/rpi_i2c.c)

target_link_libraries(imu_data_reader rpi_bmi088 rpi_i2c bmi088)

So I was on the right track but I needed to add the SHARED keyword.

UPDATE 3:

Actually only 'colcon build' works but running the node I get:

colcon_ws/install/package_name/lib/package_name/imu_data_reader: error while loading shared libraries: librpi_bmi088.so: cannot open shared object file: No such file or directory
[ros2run]: Process exited with failure 127

So now I'm clueless again


Solution

  • What solved the problem:

    # Adding libraries:
    add_library(rpi_bmi088 STATIC src/rpi_bmi088.c)
    add_library(bmi088 STATIC src/bmi088.c)
    add_library(rpi_i2c STATIC src/rpi_i2c.c)
    
    # Linking libraries:
    target_link_libraries(rpi_bmi088 rpi_i2c bmi088)
    target_link_libraries(imu_data_reader rpi_bmi088)
    
    # Install CPP executables:
    install(TARGETS
      imu_data_reader
      rpi_bmi088
      bmi088
      rpi_i2c
      DESTINATION lib/${PROJECT_NAME}
    )