CMake - Concepts and Options

CMake Concepts, Compile Options, Commands, CMake-Format

Posted by Rico's Nerd Cluster on October 1, 2023

Concepts

Hierarchy

CMake is hierarchical. That is, you have a top-level project, with multiple subprojects.

Basic Operations:

1
2
3
4
cmake_minimum_required(VERSION 3.10)
project(MyProject)

add_subdirectory(subproject1)

Properties

  • Global Properties

A global property is accessible throughout the entire project. It’s truly global, so once set in a sub project, it could be used in other subprojects. You can set a global property for compiler options, build configs, etc. Basic Operations:

1
2
3
4
# top-level project CMakeLists.txt
set_property(GLOBAL PROPERTY <property_name> <property_value>)
# Retrieving the property in sub-level CMakeLists.txt
get_property(var GLOBAL PROPERTY <property_name>)
  • Parent Scope Properties

In a sub-project, it has a reference to the parent it was launched in. So a property can be shared with parent project. Here is a blog that further explains this

1
2
# Sets FOO in the parent project, not the current scope
set(FOO <foo value> PARENT_SCOPE)

Find Package

1
find_package(PACKAGE REQUIRED)
  • Requires .cmake files. If you have a static library, do not use find_package.

Interface Library

Interface library is a library that does not create build artifacts, like .a, .o files. So you can use it to make a group of dependencies other targets can link against. Example:

1
2
3
4
5
6
7
8
9
10
add_library(my_project_dependencies INTERFACE)
target_link_libraries(my_project_dependencies INTERFACE
    ${catkin_LIBRARIES}
    ${rosbag_LIBRARIES}
    simple_robotics_cpp_utils
    ${OpenCV_LIBRARIES}
    Eigen3::Eigen
)
add_executable(orb_test src/orb_test.cpp)
target_link_libraries(orb_test my_project_dependencies)
  • Linking order: if simple_robotics_cpp_utils depends on OpenCV, it MUST come before it. Otherwise, there would be an undefined reference error to OpenCV

Compile Options

To add more compile options, one simply does SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} <OTHER OPTIONS>"). Below is a list of options that can be used

  • -wall: enable all commonly used warning messages during compilation.

Choosing a good C++ Standard - what if the rest of the system does not support your standard?

  • If the rest of the system does not support your chosen C++ standard, be aware of potential issues. It is generally possible to use different C++ standards within the same project. However, be cautious of inconsistent ABI (Application Binary Interface) issues, particularly if the package is meant to be a library.
  • If the package consists of standalone C++ modules, such as ROS nodes, mixing standards is usually acceptable as long as the modules do not share ABI-sensitive interfaces.

Debug VS Release

set(CMAKE_BUILD_TYPE Debug) will include debug information so gdb can step through it, check variable information. It’s equivalent to gcc -g ... using the gcc compiler.

  • There wouldn’t be much code optimziation in the debug mode. So the compilation time could be a bit shorter, but execution time would be longer.
  • Debug symbols include:
    • variable names
    • function names and params
    • line numbers
    • data types

set(CMAKE_BUILD_TYPE Release) not only exclude debug symbols, but will also turn on optimization, and disable assertions. This would include -O3

Commands

  • Read environment variable “CUSTOM_INSTALL_PATH”: $ENV{CUSTOM_INSTALL_PATH}/include
  • Setting a variable by concatenating two values set(VAR var1 var2)
    • set(CMAKE_PREFIX_PATH "$ENV{CUSTOM_INSTALL_PATH}" ${CMAKE_PREFIX_PATH})

Advanced Options

  • ccache is a compiler cache that caches previous compilations and detecting if the same compilation needs to be done again. It’s meant for C/C++ projects.
    • How to use it
      • Standalone: ccache gcc -o myprogram myprogram.c
      • Cmake:

        1
        2
        3
        4
        5
        6
        
          ```cmake
          find_program(CCACHE_PROGRAM ccache)
          if(CCACHE_PROGRAM)
              set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
          endif()
          ```
        
        • RULE_LAUNCH_COMPILE is a CMAKE property that specifies a command to be run before every compile.
    • How it works 1. Create a hash for each compilation using a fast cryptographic hash algorithm. Information for hashing include: - file info such as current dir, modification time - compiler name - input source file, compiler options (direct mode) 2. Look up for existing hash, which includes a cache hit or cache miss

Weird Issues

  • When /usr/include/opencv4/opencv2/core/eigen.hpp:259:29: error: expected ‘,’ or ‘...’ before ‘<’ token 259 |Eigen::Matrix<_Tp, 1, Eigen::Dynamic>& dst ). Solution:
    • In header file, make sure eigen includes come before <opencv2/core/eigen.hpp>

      1
      2
      3
      
      #include <Eigen/Core>
      #include <Eigen/Dense>
      #include <opencv2/core/eigen.hpp>
      
    • Make sure the CMake has:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      target_include_directories(${PROJECT_NAME}
      PUBLIC 
          ${OpenCV_INCLUDE_DIRS}
          ${EIGEN3_INCLUDE_DIRS}
      )
          
      target_link_libraries(executable 
          ...
          Eigen3::Eigen
      )
      

CMake-Format

  • Install pip install cmake-format
  • Configure ~/.cmake-format.py
  • Use: cmake-format -i CMakeLists.txt