CMake Basics
Overview
This document explains the C++ build process, CMake, and colcon. It is meant as a practical and somewhat minimal guide targeted at users of ROS who have some experience with C++. For a broader overview of ROS packages/colcon see Colcon Notes
Compilation Process Overview
- The C++ compilation process follows the same model as the C compilation process
preprocess -> compile -> assemble -> link - Compilation lets you do the hard work of translating human-readable source code into machine code once upfront.
 - We will use the GNU C++ compiler gcc. Another popular choice on Linux is clang
- You can install 
clangon Ubuntu as well - clang generally takes the same command line arguments as gcc. Compile-time errors are explained using different language than with gcc so sometimes compiling with both compilers can help you track down an error more easily.
 - The output of 
gccis generally considered to be faster, althoughclangis closing the gap 
 - You can install 
 - Basic command-line: 
g++ -o output_file -Wall -Wext -Wpedantic -std=c++17 file1.cpp file2.cpp-Wallturns on all warnings. These help you avoid errors so always use them.-Wextturns on extra warnings. These help you avoid errors so always use them.-Wpedanticturns on warnings related to conformance with the standard. This helps ensure your code will work with multiple compilers.-std=c++17lets you use C++17 features, which are great.
 
What is CMake?
- CMake is a cross-platform scripting language primarily used to generate instructions that other build tools (such as Make, Ninja, Xcode, or Microsoft Visual Studio) can use to compile your program.
 - CMake is responsible for finding all system libraries and dependencies, determining which files constitute which executables and libraries, managing compile flags, and determining which files go where upon installation.
 - Although other tools exist, CMake is a popular choice for building C++ projects and is almost becoming a defacto standard.
 
Why do we need build tools?
- Non-trivial C++ projects require the use of multiple source files and libraries that all must be combined together
- Libraries may be located in different locations on different people's computers
 - People may want to use different compilers with different types of compiler options
 - People may want to customize your program by selecting different options at compile time
 - You may want someone to be able to run their code on a different operating system (not us) or even a computer with a different system architecture system (definitely us)
 
 - There are also a large number of options that affect the behavior of the compiler that need to be managed
 - Without a build tools, you would have to manually enter the commands to compile the program
- And not just you, everyone who wants to use your code
 
 
4.The ideal of a working project is to have your code be able to compile and do all setup related steps in a single command
- Computers are good at automating repetitive and tedious tasks!
 
Basics
File Layout
- While there is no mandated file layout, there are common best practices.
 - the below structure is an example for a project called 
project_name. - The project creates a library called 
mylibraryand an executable calledName. 
project_name
├── CMakeLists.txt
├── include
│   └── mylibrary
│       └── header.hpp
├── src
│   ├── libfile1.cpp
│   ├── libfile2.cpp
│   └── Name_main.cpp
└── tests
    └── mytest.cpp
- All 
CMakeprojects have aCMakeLists.txtin the base directory.- This file is the primary file used to setup the build system.
 
 CMakeLists.txtis responsible for:- Finding build dependencies of the project.
 - Determining compile options.
 - Determining what files must be compiled to create the libraries and executables produced by the project.
 - Providing instructions for how to install the libraries and executables.
 
- The 
include/mylibrarydirectory is there so that every project using the library can include headers by doing#include"mylibrary/header.hpp"- The project is setup to search for include files in 
include/. The directory structure means that the files themselves are undermylibrary/header.hpp 
 - The project is setup to search for include files in 
 - The 
testsdirectory holds the unit tests - Overall, as the project becomes more complex you may add more directories
- In this example, we keep all 
.cppfiles undersrcbut other projects could do it differently by, for example, splitting the locations of library.cppfiles and executable.cppfiles 
 - In this example, we keep all 
 
Basic CMakeLists.txt
Below is a template for
CMakeLists.txtas well as comments describing each aspect# Lines that begin with a # are comments # set the minimum required version of cmake, usually the first line cmake_minimum_required(VERSION 3.22) # project_name sets the name of the project and causes cmake to # find the c and c++ compilers project(project_name) # Find your dependencies. # Many libraries ship with files that allow CMake to find them # Then general behavior is to call "find_package" but the options # provided are package specific. Usually there is then a CMAKE variable # That is defined to reference the library # here: we find the eigen library as per the instruction # https://eigen.tuxfamily.org/dox/TopicCMakeGuide.html find_package(Eigen3 3.3 REQUIRED NO_MODULE) # Create a library. Can specify if it is shared or static but usually # you don't need or want to. # name is the name of the library without the extension or lib prefix # name creates a cmake "target" add_library(libname src/libfile1.cpp src/libfile2.cpp) # Use target_include_directories so that #include"mylibrary/header.hpp" works # The use of the <BUILD_INTERFACE> and <INSTALL_INTERFACE> is because when # Using the library from the build directory or after installation # During build, the headers are read from the source code directory # When used from the installed location, headers are in the # system include/ directory target_include_directories(libname PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/> $<INSTALL_INTERFACE:include/>) # specify additional compilation flags for the library # Public causes the flags to propagate to anything # that links against this library target_compile_options(libname PUBLIC -Wall -Wextra -pedantic) # Enable c++17 support. # Public causes the features to propagate to anything # that links against this library target_compile_features(libname PUBLIC cxx_std_17) # Create an executable from the following source code files # The Name of the executable creates a cmake "target" add_executable(Name src/Name_main.cpp) # Use target_link_libraries to add dependencies to a "target" # (e.g., a library or executable) # This will automatically add all required library files # that need to be linked # and paths to th locations of header files target_link_libraries(Name Eigen3::Eigen libname) # install the include files by copying the whole include directory install(DIRECTORY include/mylibrary DESTINATION include) # Create a CMake Exported Target containing the lib and exe. # Also create CMake Export called projet_name-targets # The CMake Export contains files that allow other CMake projects # to find this project. It must be installed separately. install(TARGETS Name libname EXPORT project_name-targets) # The project_name-targets created by install(TARGETS) needs to be installed. # install(EXPORT ...) will generate a file called project_name-config.cmake # that contains the exported targets. # After installation this file will then be found when calling # find_package(project_name) from another cmake project # A user can then target_link_libraries(target project_name::library) # to use the libraries installed here install(EXPORT project_name-targets FILE project_name-config.cmake NAMESPACE project_name:: DESTINATION lib/cmake/${PROJECT_NAME})
After writing a
CMakeLists.txtconfigure the project and build it.cmake -B build . cmake --build build
The above commands are equivalent to the following:
mkdir build cd build cmake .. make
Doxygen
CMake has the ability to generate doxygen documentation automatically during the build
find_package(Doxygen) # Building documentation should be optional. # To build documentation pass -DBUILD_DOCS=ON when generating the build system option(BUILD_DOCS "Build the documentation" OFF) # build just because Doxygen is missing if(${DOXYGEN_FOUND} AND ${BUILD_DOCS}) # Turn the README.md into the homepage of the doxygen docs set(DOXYGEN_USE_MDFILE_AS_MAINPAGE README.md) # Tell Doxygen where to find the documentation doxygen_add_docs(doxygen include/ src/ README.md ALL) # The documentation will be in the build/html directory # The main page is build/html/index.html endif()
Unit Testing
CMake provides a framework for calling unit tests called CTest that enables coordinating tests with the build system in a testing-framework agnostic manner
include(CTest) # CTest sets BUILD_TESTING to on. To disable tests add -DBUILD_TESTING=OFF when invoking cmake if(BUILD_TESTING) # Find the Unit testing framework. In this example, Catch2 find_package(Catch2 3 REQUIRED) # A test is just an executable that is linked against the unit testing library add_executable(my_test_exe tests/mytest.cpp) target_link_libraries(my_test_exe Catch2::Catch2WithMain AnyOtherLibrariesAsNeeded) # register the test with CTest, telling it what executable to run add_test(NAME the_test_name COMMAND my_test_exe) endif()
To run the tests, enter the build/ directory and run ctest --verbose.
Advanced Installation
- If a library that you write had dependencies, sometimes the users of your library will also need these dependencies.
 - In this case, you can provide a custom 
project_name-config.cmakefile that automatically finds the required dependencies whenfind_packageis called. - Rather than making the exports file the 
project_name-config.cmakethat is run whenfind_packageis called, We instead install the exports file asproject_name-targets.cmake. We then create and install a custom file that finds the dependencies and then includes the exports file. Here is an example
CMakeLists.txtsnippet.install(DIRECTORY include/mylibrary DESTINATION include) install(TARGETS Name libname EXPORT project_name-targets) install(EXPORT project_name-targets FILE project_name-targets.cmake NAMESPACE project_name:: DESTINATION lib/cmake/${PROJECT_NAME}) configure_file(project_name-config.cmake.in project_name-config.cmake @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/project_name-config.cmake DESTINATION lib/cmake/${PROJECT_NAME})
- The 
install(EXPORT)now creates a file calledproject_name-targets.cmakewhich contains theexportedtargets You now provide a file called
project_name-config.cmake.inwith the following contents# You can access variables from CMake by using the # @var_name@ syntax. These variables will be replaced with there value # from the current CMake session (useful for configuration) include(CMakeFindDependencyMacro) find_dependency(a_dep_your_library_needs REQUIRED) # Once all dependencies are found, include the exported targets # We expect that the project_name-targets.cmake file is installed # next to project_name-config.cmake (this file) include(${CMAKE_CURRENT_LIST_DIR}/project_name-targets.cmake)
Out-Of-Source Builds
- Notice that we created a build directory outside our source directory
- This is known as an out-of-source build.
 - It is recommended to do out-of-source builds to avoid scattering
a bunch of files generated by CMake everywhere in your source tree
(a 
.gitignorenightmare!) - It is possible to invoke cmake in the same directory as the source code, which makes a mess
 I use the following code to prevent me from accidently doing that
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) message(FATAL_ERROR "No in source builds allowed. Create a separate build directory. SOURCE_DIR=${CMAKE_SOURCE_DIR} BINARY_DIR=${CMAKE_BINARY_DIR} ") endif()
 
CMake Variables
- You can set 
cmakevariables when invoking cmake.- Usually you want to 
cmake -DCMAKE_BUILD_TYPE=<Type>where<Type>is:Debug- compile with debugging options enabled (-gforgcc)Release- compile with optimizations enables (-O3forgcc)- There are other release types as well
 
 - If you don't specify a build type you get no additional compile flags
 I typically use the following code to have the build type default to
Debugif not otherwise specifiedif(NOT CMAKE_BUILD_TYPE) # If no build type is set, set one set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Type of build." FORCE) endif()
 - Usually you want to 
 - Since 
cmakegenerates Makefiles, you do not typically need to reruncmake.- Even if you modify CMakeLists.txt, the Makefiles will invoke CMake automatically
 
 - When debugging CMake problems, it can sometimes be helpful to completely delete the contents of the build directory and regenerate everything from scratch
 
Important CMake Concepts
- generation
 - Since CMake is a build-system generator (we are using 
makeas the build system), it is generating build files.- This means that you are actually using CMake Code to Generate Makefile Code to Compile C++ Code
 
 - target
 - This is anything that will ultimately be created by the build system
- For example: executables and libraries
 - When generating Makefiles, targets correspond to makefile targets
- For example if you 
add_executable(hello myworld.cpp)thenhellois a target and it can be built specifically by usingmake hello 
 - For example if you 
 
 - properties
 - various entities in CMake have properties that can be set
- For targets, you can set properties using functions that begin with 
target_settarget_set_include_directories(targetname list_of_directories_where_include_files_are_found)target_set_compile_options()target_link_libraries(target lib1target lib2target)a bit of a misnomer (it should really be calledtarget_link_target) this will not only link libraries lib1target and lib2target intotargetbut also add their include paths and some other options to target For example, if lib1target has include files in/home/user/include,targetnamewill be able to include those files as well.- Directory vs Local
- CMake commands starting with 
add(such asadd_compile_optionsgenerally apply to all CMake files in the current directory or any sub directories. (They technically set properties on these directories) - CMake commands starting with 
targetapply only to the target that is specified. They can optionally be transferred to other targets usingtarget_link_libraries Modern CMake Best practices favor using
targetlevel properties- Allows finer control
 - Passing on these options to dependencies with 
target_link_librariesmeans that when executable target_a depends on library_X which depends on library_Y which 
depends on library_Z,
target_ais compiled with all the necessary include file paths and library paths just by doingtarget_link_libraries(target_a library_X)
 - CMake commands starting with 
 
- Directory vs Local
 
 
 - For targets, you can set properties using functions that begin with 
 
Basic CMake Syntax
${VARIABLE}gets the value of a variable. This is a direct-string replacement- In most project, you want to avoid variables in 
CMakeas much as possible and instead list files directly 
- In most project, you want to avoid variables in 
 - Functions in cmake can have a type of named argument, usually in capital letters.
So for example 
message(FATAL_ERROR "The value of VARIABLE is ${VARIABLE}")will cause a fatal cmake error and print The value of VARIABLE is <variable value here>. (this is a decent cmake debugging technique. message(WARNING "The value of VARIABLE is ${VARIABLE}")~ will just print a warning 
Dependencies
- If you ever get an error about 
find_packagecould not findXthen this likely means you are missing a dependency- If you are lucky, that package might be available for 
Ubuntuand you can install it - If using ROS2, you can use 
rosdep install --from-paths src --ignore-src -r -yto install all dependencies of all packages in the source space 
 - If you are lucky, that package might be available for 
 - You may occasionally encounter C++ packages that are not ROS packages and are not distributed with Ubuntu, meaning that you will need to compile them yourself
 - Due to the popularity of CMake, there is a good chance that this project will be using CMake as it's build system
 - By specifying 
-DCMAKE_PREFIX_PATH=/home/user/location_where_to_installand-DCMAKE_INSTALL_PREFIX=/home/user/location_where_to_installyou are able to- Compile the package(s) you want
 - Install it to the location you want (
-DCMAKE_INSTALL_PREFIXtells CMake to treat the specified path as the base location for all installations rather than the default (which is/) without needing root privileges or affecting the rest of your system - Use the installed package in other CMake packages (
-DCMAKE_PREFIX_PATHtells CMake to treat the specified directory as if it were/when looking for files) 
 
Useful compile options
Local vs Global
In CMake you can set options for the compilation both globally and on a per-target level.
- The advantage of setting options globally is that they are used for all targets and can be changed in one place.
 - The advantage of setting options locally is that you can use different options for different targets. Additionally, the options can be passed on to targets that depend on the existing target.
 - There is some debate about whether setting compiler options and flags globally or locally is best. The 
CMakecommunity (in my view) favors the local approach, whereas ROS favors the global approach. 
A Compromise Approach
- One compromise is to create a CMake target (call it 
options_target) with no source files, that just carries the compiler options.- By linking your targets to 
options_targetwithtarget_link_libraries, they inherit all options provided tooptions_target - This way you have not defined anything globally (so different targets can have different options if needed), but all options commonly used are defined in the same place.
 
 - By linking your targets to 
 
C++ Standard
Setting the C++ standard has its own syntax in CMake. This is because every compiler has different options, but CMake is cross-platform and provides a common standard-setting method across all compilers.
- In advanced usage, CMake can compute the standard needed, given particular language features that are desired.
 
Locally (Per-Target)
To set the standard for a given target
# use c++17 when compiling targetName target_compile_features(targetName PUBLIC cxx_std_17) # don't use gnu extensions set_target_properties(targetName PROPERTIES CXX_EXTENSIONS OFF)
PUBLICmeans the features are used by the target and anything that depends on it- Other possibilities are 
PRIVATEwhich means just use the flags directly for the target not dependencies andINTERFACEwhich means just used by dependencies but not the target itself - In other words, 
PUBLIC = PRIVATE + INTERFACE 
Globally (all targets)
These settings can also be done globally.
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF)
Standards And ROS
- Technically ROS 2 only supports C++17
 - However, the compiler that ships with 
Ubuntu 22.04mostly supportsC++20fully and ROS humble is officially supported on Ubuntu 22.04 - C++ standards are very backwards compatible (as in older C++ code will usually compile with a newer standard enabled)
 - We will use C++17 in this class
 - C++20 came out in 2020, and you may experiment with it if you would like to.
- Some key features of C++20 are:
- Concepts (which will benefit you without even needing to know what they are!)
 - Modules (which is not enabled with just C++20 as it is still not fully implemented/tested in 
gcc) - Ranges (this standard makes working with 
vectorsandalgorithmsmuch easier and more intuitive) 
 
 - Some key features of C++20 are:
 
Compile Options
Locally (Per-Target)
- Use 
target_compile_options(target -Wall -Wextra)to add flags (in this case-Wall -Wextra) to the build 
Globally (all targets)
- Global compile options are added with 
add_compile_options(-Wall -Wextra)(-Wall -Wextrain this case). 
Relationship with ament and colcon
colconis a build tool and can compile projects written with multiple build systemscolconcan automatically detect and compile plain CMake projects based on the presence of aCMakeLists.txt- A 
package.xmlis optional, but can be included to provide dependency information tocolconto ensure the packages in the workspace are compiled in the correct order - such a package should specify 
cmakeas the<export><build_type>, rather thanament_cmake 
- A 
 - An ament_cmake package is actually just a 
CMakeLists.txtfile that happens to callament_cmakefunctions - It is possible to invoke 
cmakedirectly on anament_cmake_ package rather than using =colcon The
amentfunctions make it easy to generate custom interface types and also simplifies handling ofROS 2packages- Lets try it!
 
mkdir -p ws/src cd ws/src ros2 pkg create --build-type ament_cmake testpkg cd .. mkdir build cd build cmake ../src/testpkg make # nothing will happen since we didn't add any files
Ament provides convenience functions for library installation:
Installation of ament_libraries
amentmakes it easier to install libraries than standard cmakeAfter creating the libraries call
ament_export_targets(my_library-targets HAS_LIBRARY_TARGET) ament_export_dependencies(dependency_of_library) install(DIRECTORY include/ DESTINATION include) install(TARGETS my_library EXPORT my_library-targets)
- See Ament Guide for more information
 
ROS IDL Generation
- ROS provides custom cmake functions for generating messages, services, and actions.
 - Everything related to IDL in ROS is in the rosidl repository
- In particular rosidl_cmake contains the CMake functionality (which is documented inside the source files)
 - The 
package.xmlmust contain<member_of_group>rosidl_interface_packages</member_of_group> 
 - The package is for generating code from IDL files (e.g. 
.srv,.msg, and.action) files is calledrosidl_default_generators.- This package includes code for generating code from ROS IDL files in several default programming languages (e.g., Python, C++).
 
 - After making 
rosidl_default_generatorsavailable, the next step is to generate the code usingrosidl_generate_interfaces rosidl_generate_interfaces(${PROJECT_NAME} msgfiles... servicefiles...)- By default, this will create a library named 
${PROJECT_NAME}, which is what the library must be named in order to work - However, it also creates a 
cmaketarget named${PROJECT_NAME}, so this call will fail if you have another cmake target (such as a node) with the same name as your project - Instead, you can call this as 
rosidl_generate_interfaces(<any_name_you_want_here> msgfiles... servicefiles... LIBRARY_NAME ${PROJECT_NAME})- Here, the library will still have the proper name (based on 
${PROJECT_NAME}), but the name of the target can be any valid CMake target name 
 - Here, the library will still have the proper name (based on 
 
- By default, this will create a library named 
 - The target created by 
rosidl_generate_interacesis a "utility" target and cannot be used intarget_link_libraries- To get a target suitable for use with 
target_link_librariescall rosidl_get_typesupport_target.cmake rosidl_get_type_support_target(<OUTVAR> <target> "string")<OUTVAR>is the name of a cmake variable for storing the target name<target>is the name of the target created byrosidl_generate_interfaces- "string" this should be 
"rosidl_typesupport_cpp"to get the typesupport library for C++. 
 - To get a target suitable for use with 
 - The value stored in the 
<OUTVAR>from therosidl_get_type_support_targetcall can be referenced when usingtarget_link_libraries Here is a complete example:
rosidl_generate_interfaces(myservices "srv/ThisService.srv" LIBRARY_NAME ${PROJECT_NAME}) rosidl_get_typesupport_target(cpp_typesupport_target myservices "rosidl_typesupport_cpp") ament_export_dependencies(rosidl_default_runtime) add_executable(mynode myfile.cpp) target_link_libraries(mynode ${cpp_typesupport_target})
Types of build
- With 
colconyou can create three types of installation:- The default: all build files are copied to the install space and all packages have their own sub-folder
 - This keeps packages isolated for easier debugging, but it also means that the PATH's needed to run your ROS program become longer (need a new PATH entry per project)
 --merge-install: Everything is copied, but they are all installed to a common root- Then, only some locations in the 
install spaceneed to be in your path 
- Then, only some locations in the 
 --symlink-install: This option allows you to directly edit python files in the source space and have the changes take effect immediately in the install space without re-running colcon- For C++ you need to compile after every change you make anyway, so this option is not useful.
 
 
CMake Practices
- All projects should include, at a minimum, the following compile flags:
-WallEnable all warnings-WextraEnable extra warnings-Wpedanticconform to the C++ standard- ROS 2 automatically adds these options to the 
CMakeLists.txttemplate it generates! 
 - When not using 
colconcreate a build directory and usecmakefrom that directory (out of source build).- Don't run 
cmakein a directory that contains code, it will make a mess 
 - Don't run 
 To set C++ version options globally
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF)
- Setting using 
set(CMAKE_CXX_STANDARD 17)is still globally applied but it also is compiler independent 
- Setting using 
 - Don't use 
GLOBto find files (I didn't discuss this but you will find it on many websites).- Some examples use 
file(GLOB *.cpp)to find all source files - Since this code executes only when CMake generates the build files, new source files will not be detected
 - The proper way to include files for compilation is to explicitly record each file
 
 - Some examples use 
 - Don't directly set 
CMAKE_CXX_FLAGSorCMAKE_C_FLAGS(I didn't discuss here there is a lot information out there about these)- This can overwrite important settings set elsewhere
 - It globally changes the compile flags for every project
 - If you are not careful it is not platform independent
 
 - Don't make a hierarchy of CMakeLists.txt files in each sub-directory
- This used to be best practice for CMake, but now it is better to do everything for a project in a top-level CMakeLists.txt
 
 
Build Process as Directed Acyclic Graph
- When compiling code, there are often items that depend on other items:
- For example, 
main.cppmay call a function defined inlibrary.cpp - If you change 
library.cpp, you need to recompilemain.cppalso- However, if 
main.cppchanges,library.cppdoes not care 
 - However, if 
 
 - For example, 
 - Dependencies can be viewed as Directed Acyclic Graph (DAG)
- Each file is a node in the graph
 - If B depends on A, then an edge exists from A to B
 - If a node changes, follow the arrows and compile everything along all paths
 - Although technically you could create circular dependencies in C++ you should avoid doing this
at all costs.
- Adds complexity and brittleness to the build process
 - If A depends on B and B depends on A, then chances are A and B should actually be one unit
 - Alternatively, create a new dependency C that both B and A depend on
 
 
 makeis a tool that reads a description of your project and the file dependencies and compiles it- You tell it the DAG describing the dependencies and only what is necessary gets compiled
 
Cross Compilation
- Cross-compilation is the process of compiling code for one system on a different (and incompatible) system
- For example, compiling code meant to run on a raspberry pi on your PC
- This is hugely advantageous because compilation is significantly faster on a modern PC than on a raspberry pi (seconds versus hours).
 
 
 - For example, compiling code meant to run on a raspberry pi on your PC
 - To specify a cross-compiler, use 
cmake -DCMAKE_TOOLCHAIN_FILE=<toolchain>, where<toolchain>is a special cmake file that is used to tellcmakeabout an alternative compiler - Since a 
toolchainfile creates settings that are created really early in thecmakeinvocation, it must be set when callingcmake: it will not have the desired effect if set from within yourCMakeLists.txt - I have provided a 
cmaketoolchain file to allow you to cross-compile for the raspberry pi. 
ROS 1 and Catkin
- These notes are left for reference in case you encounter a ROS 1 project.
 catkin_make,catkin_make_isolated, andcatkin toolsare the ROS 1 build toolscolcon, however, can also build ROS 1 packages and that is what is recommended- All ROS 1 packages, however, use 
catkinCMake files which follow a specific form - One big difference between ROS 1 and ROS 2 is that 
ament_cmaketries to hew much more closely to standardCMakefiles thanROS 1did. 
A Deep Dive into a Catkin CMakeLists.txt
Preamble, same as a normal
# minimum cmake Version 3.9, rather outdated cmake_minimum_required(VERSION 3.9) # name of project, must match package.xml project(my_project)
- Notice that catkin is found just like any other package would be (because a main component of catkin is a cmake code)
 The COMPONENTS part is because catkin sets everythign up so that ROS packages can be brought in as if they were a piece of the catkin cmake package
find_package(catkin REQUIRED COMPONENTS roscpp)Next, find other packages that are non-ros, just as you would in a regular cmake file
find_package(Boost REQUIRED COMPONENTS system)For ROS messages, services, etc to work, C++/python files encapsulating those messages must be created at compile time (that is, when you run make). These functions are provided by catkin to build the messages files into python and C++ code. This part
add_message_files() add_service_files() add_action_files() generate_messages() generate_dynamic_reconfigure_options()
catkin_package is used to let other packages know how to use your package as a dependency
## INCLUDE_DIRS: use if package contains header files ## LIBRARIES: libraries created here that dependent projects need ## CATKIN_DEPENDS: catkin_packages dependent projects need ## DEPENDS: system dependencies of this project that dependent projects also need catkin_package( # INCLUDE_DIRS include # LIBRARIES tmp # CATKIN_DEPENDS other_catkin_pkg # DEPENDS system_lib )
- The 
INCLUDE_DIRSis where your package's header files are stored. This let's other catkin packages use these header files - The 
LIBRARIESare for anything you created withadd_librarythat you want other catkin_packages to be able to use - The 
CATKIN_DEPENDSis for any catkin package (added to thefind_package(catkin REQUIRED COMPONENTS ...)line that packages that use your project need - The 
DEPENDSline is for any non-catkin package (found viafind_package) that a project that uses your project needs 
- The 
 Rather than using
target_include_directorieson individual targets,catkinsets include directories that are used by all targets in your package globally. The${catkin_INCLUDE_DIRS}variable is populated with directories set incatkin_packagein all of the packages found byfind_package(catkin REQUIRED COMPONENTS)include_directories( include ${catkin_INCLUDE_DIRS} )- Targets (i.e., libraries and executables) are added as usual
 - Target names are always prefixed by ${PROJECT_NAME} to avoid name conflicts.
${PROJECT_NAME}is a CMAKE variable that is filled in by the call toproject()at the beginning of the filecatkin_makeactually turns all the packages in your workspace into a single giant cmake package, and a single package can't have duplicate target names- If you force the users to use either 
catkin_make_isolatedorcatkin_toolsthis usage is unnecessary 
 - In the model presented here, the library name is the same as the catkin_package name. Many times there is a 1-1 correspondance between a C++ library and a ROS package, but there need not be.
 If setting flags and standards globally, you would use
target_compile_featuresandtarget_compile_optionsadd_library(${PROJECT_NAME} src/${PROJECT_NAME}/tmp.cpp) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) target_compile_options(${PROJECT_NAME} -Wall -Wextra)
- This line says that your library (called 
${PROJECT_NAME}) depends on (i.e., will be compiled after) all the targets (i.e., libraries) that the current package exports (viacatkin_package) and targets required by anything found withfind_package(catkin REQUIRED COMPONENTS). It also ensures that any messages/services that are required are built first
add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
- Adding an executable (e.g., a node) is similar to adding a library, but includes a few extra lines.
 - I recommend grouping these lines together for all nodes that you add
 - The CMake target name is prefixed with 
${PROJECT_NAME}to avoid conflicts with other packages. - The actual name of the node is changed to remove with 
${PROJECT_NAME}prefix withset_target_properties - The node is made to depend on all the imported 
catkinpackages and libraries so it is compiled after them. The executable is also linked against any libraries that were exported by the catkin_packages we have included, and any other non-catkin libraries we need
add_executable(${PROJECT_NAME}_node src/tmp_node.cpp) set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) target_link_libraries(${PROJECT_NAME}_node ${catkin_LIBRARIES})
- Add libraries from system dependencies (i.e., those found with 
find_package(Library)) totarget_link_libraries - Add local compile options and features with 
target_compile_featuresandtarget_compile_options - The installation phase is usually not used during development but becomes important if, for example, you wanted to created a binary version of your package so other people could use it without needing to compile your code.
 To install python scripts, cmake copies them from the source directory to an installation directory
install(PROGRAMS scripts/my_python_script DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} )
To install your nodes list them on the line below:
install(TARGETS ${PROJECT_NAME}_node RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} )
To install libraries list them below
install(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} )
- The header files for your library must also be explicitly installed.
 The version in
catkin_pkgonly copies files with a.hextension. I remove this restriction as its unnecessary and I endC++headers with.hppinstall(DIRECTORY include/${PROJECT_NAME}/ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} )
Resources
- Official Documentation
- Often the latest version has better documentation than earlier versions but is still applicable
 
 - Introduction to Modern CMake
 - https://rix0r.nl/blog/2015/08/13/cmake-guide/
 - https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
 - https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1
 - https://www.slideshare.net/DanielPfeifer1/cmake-48475415
 - 19 Reasons Why Cmake Is actually awesome