🐆 2 min, 🐌 5 min

C++: cmake

Yesterday we looked into the compiler. But once our programs start to grow, we need to use slightly more powerful tools to manage all the mess.

For the sake of this tutorial create first main.cpp with the following content:

#include 

int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}

It should be clear what the above code is by now. If not consult the older posts.

So instead of running lengthy terminal commands like:

g++ -o my_project main.cpp -I/path/to/headers -std=c++11 -g

We can write a CMakeLists.txt file and use that to specify how our C++ executable should be compiled.

How a cmake file should look?

Let's create a CMakeLists.txt file with the following content (that's the minimal amount that should be in the file):

cmake_minimum_required(VERSION 3.17)
project(my_project)

set(CMAKE_CXX_STANDARD 20)

add_executable(my_project main.cpp)

So what does each line do?

With:

cmake_minimum_required(VERSION 3.17)

we specify which minimum cmake version we require on our system to build a particular project.

If you can, use the latest available cmake possible. Why? cmake is regularly upgraded and became extremely powerful over the years. So use the cool new stuff 😉

Next is project(my_project), which sets our project's name. It's required. Then:

set(CMAKE_CXX_STANDARD 20)

Sets the C++ standard that we want to use. And the final line:

add_executable(my_project main.cpp)

Specifies that we want to bundle main.cpp into an executable called my_project. And yes within one CMakeLists.txt you can specify more different executables that all use different parts of your code. Just add another add_executable() with another name of the output executable off course.

How to build the executable with cmake?

First, create a build directory with mkdir and move into it with cd:

> mkdir build
> cd build

We create this directory, so everything produced in the built process is in one place and not all over our codebase. Build files don't belong into the version control tree (those files shouldn't be on git since they are computer specific).

Then run cmake ..

The .. part tells the cmake to look for a CMakeLists.txt file in one folder back, but spit the output files into the current directory.

> cmake ..

-- The C compiler identification is AppleClang 12.0.0.12000032
-- The CXX compiler identification is AppleClang 12.0.0.12000032
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: ~/build

The output is nothing fancy. Now let's build the actual script using make command:

> make   
Scanning dependencies of target my_project
[ 50%] Building CXX object CMakeFiles/my_project.dir/main.cpp.o
[100%] Linking CXX executable my_project
[100%] Built target my_project

Now you have an executable my_project that you can run.

If something goes wrong in the build process, you'll see errors here.

You can speed-up make process a bit with a flag -j [num_of_cores] so for example if you have 8 cores:

make -j 8

What's the difference between cmake and make?

So what's the difference between the cmake and make besides the added c:

  • make is a build system that uses Makefiles
  • cmake is a build system generator. It can produce Makefiles, Ninja build files, Xcode build files, ...

With cmake you can define how your script should be built independent of the platform and the build system and then generate the build files needed to compile your program for a specific platform.

But make is a bit slow and old, even with the parallel compiling. So there's, of course, a fester cousin on the market. Called ninja.

How to install ninja?

Ninja is slightly faster than make. To install it on macOS:

brew install ninja

Or on Debian/ubuntu:

apt-get install ninja-build

For the rest of the platforms see here .

The cool thing about ninja is that it also ships with a python module for generation of ninja files. You can find the source of the python interface here . I didn't find any other useful docs yet.

How to replace make with ninja?

To build ninja build files, use the -GNinja flag with cmake:

> cmake -GNinja ..
-- The C compiler identification is AppleClang 12.0.0.12000032
-- The CXX compiler identification is AppleClang 12.0.0.12000032
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: ~/build

While this printed output is the same, the created files are not.

Now run ninja:

> ninja
[2/2] Linking CXX executable my_project

That's it. Parallelisation is used out of the box.

All this hassle might sound like a bit too much, but once you have to start using libraries and your programs grow beyond hello world. You'll realise how powerful a build system and build system generator like cmake can be.

Get notified & read regularly 👇