CMake

Basically Makefiles are too ancient, so we created cmake instead.

CMake is an Open Source Software software for build automation, testing, packaging and installation of software itself. It generates system build files. It supports directory hierarchies and applications that depend on multiple libraries.

Why use CMake?

Theoretically, any C++ program can be compiled with g++. However, when the program gets bigger and bigger, a project may have many folders and source files.

In these cases, using CMake will make your life a lot easier that compiling all fines together by running a long tedious command every time.

CMake is widely used for the C and C++ languages, but it may be used to build source code of other languages too.

It creates a Makefile. So to compile, you usually do cmake . which calls the current directories CMakeLists.txt file. (But I use make -C qnx/build install) Then you run using make. (Notice the build directory). It will create an executable.

The cmake-make process

The cmake process handles the relationship between the project files, while the make process actually calls g++, or gcc or qcc to compile the program.

Documentation: https://cmake.org/cmake/help/latest/index.html

Some useful features to know:

  • install
  • set
  • add_libraries
  • target_link_libraries

Why separate Build Directories

  1. Clean Codebase
    • By creating a separate build directory, you ensure that the intermediate files (e.g., object files, Makefiles, cached configurations) do not clutter your source code directory)
  2. Easy Cleanup
  • Deleting the build directory removes all compiled files without affecting the source code
  1. Multiple Build Configurations
    • You can maintain different build directories for various configurations (e.g., Debug, Release, Testing) simultaneously (I remember having a bug with this, when I specified -DCMAKE_BUILD_TYPE=Debug was different to when it was set to Release)

Compiling a CMake Project without Intermediate Files in the Source Directory

Steps to Compile:

mkdir build
cd build
cmake ..     # Run CMake to generate build files
make         # Compile project
  • mkdir build: creates build directory for build files
  • cd buid
  • cmake ..: Tells CMake to look for the CMakeLists.txt file in the parent directory and generate the build system files inside the build directory
  • make: Compiles the project based on the build system files generated by CMake

Optional Install the Project: If your CMakeLists.txt includes an install target, you can install the compiled binaries and necessary files to a specified directory:

make install

You will probably specify in which directory/folder the install will go to in the CMakeLists.txt.

static vs. shared libraries

Usually, you have files (ex: main.cpp) calling functions from other files, which we call libraries.

We divide into 2 types of libraries

  1. static libraries (“.a” extension)
  2. shared libraries (”.so” extension)

What is the difference between the 2 kinds of libraries?

The difference is that a static library will generate a copy each time it is called, and the shared library has only one copy, which saves space.

// Static Library
add_library( hello libHelloSLAM.cpp )
 
// Shared library
add_library( hello_shared SHARED libHelloSLAM.cpp )
 
...
// Link the files at the end
target_link_libraries(helloExecutable hello)

When do I need add_library?

Whenever you are including other .h files, and these interface files have implementation files (.cpp format), you need to add those so they are linked properly.

For Eigen, add_library is not needed because it doesn’t have .cpp files, only .h files.

it searches for the makefile and executes it. A makefile can look like the following:

default:
	g++ main.cpp -o out

Commands you should become super familiar with:

  • cmake_minimum_required
  • project
  • include_directories
  • add_library
  • add_executable
  • target_link_libraries

Example: Using OpenCV with CMake:

cmake_minimum_required(VERSION 2.8)
project( DisplayImage )
find_package( OpenCV REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( DisplayImage DisplayImage.cpp )
target_link_libraries( DisplayImage ${OpenCV_LIBS} )

In CMake, when you specify that a target (like an executable or a library) needs to link against another library, the relationship is often transitive.

Linkage is transitive in CMake

Yes, CMake handles transitive dependencies out-of-the-box. When you use target_link_libraries, dependencies are automatically propagated to dependent targets. Source

What if you didn’t want this behavior? There are the PRIVATE, PUBLIC, and INTERFACE keywords.

Use of PRIVATE vs. PUBLIC vs. INTERFACE

  • PRIVATE: Used when the library is only needed for the target’s implementation.
  • PUBLIC: Used when both the target and consumers of the target require the library.
  • INTERFACE: Used when only the consumers of the target need the library.

Learn more here

Important

Simply including a header file does not require linking against the library.

Modern CMake: C++ Flags

  • Disable Compiler Extensions: set(CMAKE_CXX_EXTENSIONS OFF) instructs CMake to use the standard-compliant version of the C++ language without any compiler-specific extensions. It promotes portability of your code across different compilers.

https://cliutils.gitlab.io/modern-cmake/

Instead of this:

~/package $ mkdir build
~/package $ cd build
~/package/build $ cmake ..
~/package/build $ make