CMake Introduction

"By dint of building well, you get to be a good architect." – Aristotle

I have been using CMake since about 2008 for many of my C++ projects. I highly recommend it for cross-platform development. It can generate Makefiles, but for the last few years it has expanded to use the Ninja build system. This is all done in a compiler-independent way; Visual Studio, Xcode, and gcc/g++, builds can all be generated with ease. I find Ninja gives a performance advantage over Make, which is important as your project grows. This tutorial was made with Ubuntu 20.04.

Installing

In a bash, zsh, or some other terminal on Ubuntu enter:

sudo apt install cmake cmake-curses-gui ninja-build

This installs apt packages for cmake, ninja, and a curses-style GUI wrapper for CMake.

Basic Hello World

I have borrowed some things from this page, https://cmake.org/cmake/help/v3.22/guide/tutorial/A%20Basic%20Starting%20Point.html, though I have endeavored to massage this tutorial to make it easier to follow. Note that I skip over the configured header entirely because I don't find them useful.

Start by creating a cmakeFromScratch directory:

mkdir cmakeFromScratch

Then create a Step1 directory and a build directory inside the cmakeFromScratch folder:

cd cmakeFromScratch
mkdir Step1
mkdir build
cd Step1

Add source code for your hello world program with your favorite editor to the Step1 directory, e.g.,

gvim tutorial.cxx

int main(int argc, char **argv) {
    cout << "Howdy\n";
    if (argc > 1) {
        cout << argv[1] << endl;
    }
   return 0;
}

and then create a CMakeLists.txt file as follows:

cmake_minimum_required(VERSION 3.10)

# set the project name
project(Tutorial VERSION 0.1) 

# add the executable
add_executable(Tutorial tutorial.cxx)

now run CMake as follows:

cmake -B ../build -GNinja

the -GNinja tells CMake to generate using Ninja. If you don't specify this CMake will probably still work as the default has it use Makefiles -- though you have to ensure make is installed on your system.

At any rate, if successful you should see something like this:

-- Configuring done
-- Generating done
-- Build files have been written to: cmakeFromScratch/build
-- Configuring done
-- Generating done
-- Build files have been written to: cmakeFromScratch/build

Now do the ninja build. I find it's easier if you open a separate terminal tab or window to do this, otherwise you'll find yourself changing directories a lot. Open a new terminal and enter: 

cd <path_to>/cmakeFromScratch/build
ninja

If you are successful you should see something like:

[1/2] Building CXX object CMakeFiles/Tutorial.dir/tutorial.cxx.o
[2/2] Linking CXX executable Tutorial

Run the final Tutorial executable:

./Tutorial

You should see the following output from your program:

Howdy

Now try running Tutorial with command line arguments: https://en.wikipedia.org/wiki/Command-line_interface#Arguments

./Tutorial 223

 and you should see:

Howdy
223

If you are adventurous then add some C++ 11 — https://en.wikipedia.org/wiki/C%2B%2B11 — code to tutorial.cxx: 

if (argc > 1) {
       cout << argv[1] << endl;
        const double inputValue = stod(argv[1]);
        cout << inputValue << endl;
}

Note that after you do this you can no longer specify a non-numerical argument to the Tutorial executable! Failing to do so will cause a runtime error.

Next, specify that we want to use C++ 11 in CMakeLists.txt. You could also specify that we want to see all warnings:

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
#want to see all warnings:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

I also find the "message" CMake directive comes in quite handy for debugging. Just add it to your CMakeLists.txt file as follows and it works like a print statement for CMake. You could use it to output some standard CMake variables like so:

message("PROJECT_BINARY_DIR: " ${PROJECT_BINARY_DIR})
message("CMAKE_BINARY_DIR: " ${CMAKE_BINARY_DIR})

Add a Library

I couldn't find the source code for mysqrt.cpp on this page, so I found another CMake library tutorial here. What follows is a combination of the two tutorials for adding CMake libraries.

First, create subdirectories inside the Step1 directory and change into that directory as follows:

mkdir -p lib/MathFunctions
cd lib/MathFunctions

Next, create an Operation class as follows:

// Operation.hpp
#ifndef CMAKEHELLO_OPERATIONS_HPP
#define CMAKEHELLO_OPERATIONS_HPP

class Operation {
public:
    int sum(const int &a, const int &b);
    int mult(const int &a, const int &b);
    int div(const int &a, const int &b);
    int sub(const int &a, const int &b);
};

#endif //CMAKEHELLO_OPERATIONS_HPP

Note the use of header guards in the Operation.hpp file.

Then create a .cxx file to define the operations:

//Operation.cxx

#include <exception>
#include <stdexcept>
#include <iostream>
#include "Operation.hpp"

 int Operation::sum(const int &a, const int &b{
    return a + b;
}

int Operation::mult(const int &a, const int &b){
    return a * b;
}

int Operation::div(const int &a, const int &b){
    if(b == 0) {
        throw std::overflow_error("Divide by zero exception");
    }

    return a/b;
}

int Operation::sub(const int &a, const int &b){
    return a - b;
}

Then create another CMakeLists.txt inside lib/MathFunctions and populate it with:

set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib)
add_library(MathFunctions STATIC Operation.cxx)

 Then change directories up to the Step1 folder:

 cd ../..

and add/modify the following to the CMakeLists.txt file that lives there:

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
# add the MathFunctions library
add_subdirectory(lib/MathFunctions)

# add the executable
add_executable(Tutorial tutorial.cxx)

target_link_libraries(Tutorial MathFunctions)

 Next add the following #include to your tutorial.cxx file:

#include "lib/MathFunctions/Operation.hpp"

Finally, add the following code immediately before the "return 0;" (again to tutorial.cxx):

    Operation anOp;
int i = 3;
    int j;
    j = anOp.sum(i, i);
    cout << j << endl;

Run cmake again:

cmake -B ../build -GNinja

Change into the build directory and rerun ninja and the Tutorial. As I indicated earlier, I like to use separate terminals for CMake builds and Ninja builds.

ninja
./Tutorial 88

If everything is successful you have just built a CMake library! The output should be as follows:

Howdy
88
88
6

You can make any CMake library optional as well, but that is outside the scope of this tutorial.

Find complete example code for this tutorial on the author’s github repo. Note that a github account is required to access this repository.

Credits

https://code.visualstudio.com/docs/cpp/cmake-linux

https://github.com/microsoft/vscode-cmake-tools/

https://cmake.org/cmake/help/v3.22/guide/tutorial/index.html

https://dmerej.info/blog/post/chuck-norris-part-1-cmake-ninja/

https://ninja-build.org/manual.html

https://www.kitware.com/

Previous
Previous

GNU Octave Introduction

Next
Next

g++ with MSYS & VS Code