Introduction
CMake is the most widely used build system tool in the C++ programming landscape. Troubleshooting its build errors can be frustrating for engineers new to the ecosystem, especially those in robotics using ROS middleware, the most popular framework used by robotics engineers in the field. A common challenge arises when integrating third-party CMake projects, only to encounter cryptic errors that take hours to resolve. This is partly due to CMake’s official documentation, which, while detailed, often lacks clear, practical examples.
I’ve been both a victim and a culprit of this problem! In this post, I’ll walk through a structured approach to troubleshooting CMake errors by making the best use of its modern features.
Example Overview
We’ll use a repository that integrates Boston Dynamics Spot API with the MuJoCo physics engine in a ROS 2 environment. The issue? A missing header file (cv_bridge.h
).
Note: This example is chosen solely to illustrate a common CMake build error. The repository only supports ROS 2 Humble version, but we’re attempting to build it in ROS 2 Jazzy version (and beyond), where cv_bridge
package was updated, modifying its include directory structure.
Troubleshooting Steps
1. Think of CMake Like OOP
I like to think of targets in modern CMake as similar to classes in an object-oriented programming (OOP) language with properties and access specifiers. A target’s dependencies, such as libraries and header files, function like inherited properties in OOP, affecting how they link to executables. This article explains the concept well. As with any programming language, when build or configuration errors arise, the best first step is usually to add relevant print/debug statements to gain more insight into the issue. In this example, the error message directly points to the problem, i.e., failure in a find_package
command.
2. Understanding find_package
The error occurs because CMake can’t locate cv_bridge.h
, despite the package being listed as a dependency.
The official documentation of find_package mentions that this command searches for package-specific details (libraries, include files, etc.).
The typical search modes, as listed in the official documentation, are given below. This is loosely analogous to polymorphism in OOP; the mechanism for finding a package can be seen as a common interface, with the two modes acting as classes with different functionality.
Config mode
: Uses a package-specific configuration file, providing more flexibility to the target in how the found package’s configuration is used or even modified.Module mode
: Searches CMake’s built-in modules and paths inCMAKE_MODULE_PATH
.
3. Finding Where CMake Looks for Packages
We now know that CMake may find cv_bridge
package via either mode. So, we need to determine its source considering both modes of search:
Config mode: The
<PackageName>_DIR
variable in CMakeCache.txt shows where a package was found. Alternatively, print<PackageName>_CONFIG
variable after thefind_package
statement. This is shown in the below steps.Patch applied to simulation/mujoco’s CMakeLists.txt to print
cv_bridge_CONFIG
if it was set.diff --git a/simulation/mujoco/CMakeLists.txt b/simulation/mujoco/CMakeLists.txt index 8c98978..d90d9f1 100644 --- a/simulation/mujoco/CMakeLists.txt +++ b/simulation/mujoco/CMakeLists.txt @@ -31,6 +31,8 @@ find_package(tf2 REQUIRED) find_package(tf2_ros REQUIRED) find_package(nav_msgs REQUIRED) find_package(cv_bridge REQUIRED) +message(DEBUG "cv_bridge config: ${cv_bridge_CONFIG}") + # BLASFEO Option if(NOT TARGET mujoco) add_library(mujoco SHARED IMPORTED)
Colcon build command enabling printing of CMake debug statements (applies to a ROS2 workspace only):
colcon build --symlink-install --event-handlers console_direct+ --cmake-clean-first --cmake-args " --log-level=Debug"
- Note: In a normal CMake project, the command would be of the form:
cmake --build . --log-level=Debug
- Note: In a normal CMake project, the command would be of the form:
Module mode: Print
CMAKE_MODULE_PATH
to see where CMake looks. It selects the first matching path.
4. Resolving the Issue
On my system, CMake finds cv_bridgeConfig.cmake at /opt/ros/jazzy/share/cv_bridge/cmake
.
Checking the root installation path (/opt/ros/jazzy
) for a relevant include directory (i.e., /opt/ros/jazzy/include/cv_bridge
), we see cv_bridge.h
was deprecated and replaced with cv_bridge.hpp
. Hence, updating the include statement in simulation/mujoco/src/MuJoCoMessageHandler.cpp
resolves the issue, allowing the project to build successfully in a ROS 2 Jazzy workspace.
Summary
The key takeaways from this example are:
- Understand CMake’s dependency resolution: Treat CMake scripts like an object-oriented programming language. Targets inherit dependencies, and commands like
find_package
work in different modes, similar to OOP polymorphism, affecting where libraries and include files are sourced from. - Diagnose before fixing: Always check where CMake finds packages/modules/libraries. Inspect CMakeCache.txt or
CMAKE_MODULE_PATH
, and liberally add print statements (message(DEBUG ...)
) to fix dependency issues. Extend this principle to other (yes, bigger) build issues.