An Brief Introduction to cmake

This post is exploring the basics of the cmake build system.
Author

Quantilogy

Published

January 20, 2024

A very small introduction to cmake and its application to configure, compile and execute a cpp program which requires linking with another library (openMP) in this case for parallel programming.

Typical hello world c++ program file (say main.cpp) using openMP for parallel execution.

#include <iostream>
#include <omp.h>

int main() {
    #pragma omp parallel
    {
        std::cout << "Hello from thread " << omp_get_thread_num() << ", of nthreads " << omp_get_num_threads() << std::endl;
    }
    return 0;
}

The program above can be compiled on the command line using the following command

g++ -fopenmp -o hello_omp main.cpp

So, why not just use g++?

When we ran g++ -fopenmp -o hello_omp main.cpp, we were directly telling the compiler (g++) three things:

  1. Use the OpenMP flag (-fopenmp).
  2. The output file should be named hello_omp.
  3. The input source file is main.cpp.

This is fine for one file. But imagine a real project with:

Writing and maintaining the g++ command for a complicated project would become a nightmare.

The Solution: CMake, the Build System Generator.

CMake is not a compiler. It doesn’t build your code directly. Instead, CMake is a build system generator. You give it a simple text file with high-level instructions, and it generates the complex, platform-specific build files for you (like Makefiles on Linux, or Visual Studio .sln files on Windows).

Think of it like this:

The Core Components

Your project needs two key things for CMake to work:

  1. main.cpp (Your Source Code): The c++code you want to build.
  2. CMakeLists.txt (Your “Recipe”): A plain text file that tells CMake about your project.

Let’s look at our CMakeLists.txt line by line:

1 # 1. Set the minimum required version of CMake.
2 cmake_minimum_required(VERSION 3.10)
3 
4 # 2. Declare the project name. This is important for organization.
5 project(HelloWorld)
6 
7 # 3. Find a required dependency. This is a key feature!
8 #    CMake searches the system for the OpenMP library. If it can't be
9 #    found, it will stop with an error because we said it's REQUIRED.
10 find_package(OpenMP REQUIRED)
11 
12 # 4. Define an executable target.
13 #    This says "create an executable program named 'HelloWorld'
14 #    from the source file 'main.cpp'".
15 add_executable(HelloWorld main.cpp)
16 
17 # 5. Link the executable to its dependencies.
18 #    This says "the 'HelloWorld' target needs to be linked with the
19 #    OpenMP C++ library that we found with find_package".
20 target_link_libraries(HelloWorld PUBLIC OpenMP::OpenMP_CXX)

The Two-Step CMake Workflow

This is the standard, cross-platform way to build any CMake project.

Step 1: Configuration and Generation

Command: cmake -S . -B build

Why the build directory? This is called an “out-of-source” build. It’s a best practice that keeps your clean source code separate from the messy temporary files and executables that the compiler generates. To clean up, you just delete the build folder.

After this step, the build directory contains Makefiles and other configuration files.

Step 2: Building the Code

Command: cmake --build build

The beauty of this command is that it’s generic. On Windows, it would automatically call the Microsoft compiler instead of make, but the command you type is exactly the same.

Summary

Phase Your Action What Happens
Setup Write main.cpp and CMakeLists.txt You describe your project and its dependencies at a high level.
Configure cmake -S . -B build CMake generates low-level, platform-specific Makefiles.
Build cmake --build build make (or another tool) is called to compile the code into an executable.
Run ./build/HelloWorld You run the final program.

Support my work with a coffee