A tutorial for CMake – Chapter 1: getting started

What is CMake? Why CMake?

CMake makes writing Makefiles much simpler, and makes your Makefiles cross-platform. It makes your life easier, especially for C/C++ programmers.

Getting started: cmake and make gives you an executable.

Suppose you have a “Hello World” C++ program (any you could find on a online C++ compiler website default page). To make an executable, you could do

g++ -o HelloWorld -Wall -std=c++11 HelloWorld.cpp

(-W is for warnings, -std

Then you read about make and Makefiles, you changed the compiling to Makefile-style. You could create a Makefile that looks like below

HelloWorld: HelloWorld.cpp
    g++ -o HelloWorld -Wall -std=c++11 HelloWorld.cpp
clean: 
    rm -f HelloWorld

Everything fine. You can do

make
make clean

to generate the executable and clean the compile. However, when you compress the folder and send it to your friend, he/she fails to make it. Then you now wonder, should I change g++ to another compiler? No. You use cmake in this case

First, create a file called “CMakeLists.txt” in the current folder. Add the following lines to it.

cmake_minimum_required(VERSION 2.8.9)
project (HelloWorld)
add_executable(HelloWorld HelloWorld.cpp)

Save it. In the same folder, run

cmake ./

You will see the following screen output

-- The C compiler identification is AppleClang 8.0.0.8000038
-- The CXX compiler identification is AppleClang 8.0.0.8000038
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to:
/Users/frankliuao/Downloads/cmake_tutorial

You will see a Makefile, and many CMake-related files. Then you can do make and make clean to compile and clean. When put on a Linux (you could see I was using a Mac above), the output will become

-- The C compiler identification is GNU 4.4.7
-- The CXX compiler identification is GNU 4.4.7
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /nustorm/data1/aoliu/HelloWorld/

You could see that although the two compilers are the same on both systems, the categories they belong to are different (GNU and clang). cmake is able to detect them and automatically generate Makefile to compile.

Compile your codes in a ./build/ sub-directory

As you could see, doing cmake ./ in the main dir actually creates junks in it. That’s ugly. In practice many people choose to invoke cmake  in a ./build/ sub-directory to avoid this. Plus, your source codes usually reside in the ./src/ folder.

In this example, we will build a new project called HelloComplex. I have moved the source  HelloComplex.cpp to ./src/. I’ll also show you how to add folders to include header files in them.

Suppose we want to include a header file Complex.hpp, which is nothing fancy but a class that enables some basic complex number operations. Conventionally this file will be put into the ./include/ folder. Let’s do so. The hierarchy of the main folder now looks like the following

Now, you may’ve noticed that besides CMakeLists.txt, there is now a new file cmake_make.sh, which is a BASH script as you could tell from its extension “.sh”. This is the key part of this section.

The contents of the BASH script are as follows

#!/bin/bash

if [ $# -ge 1 ]
then
  if [ $1 = "clean" ] 
  then
    echo "Cleaning cmake residuals and logs"
    if [ -e ./build/Makefile ]
    then
      cd ./build/; make clean; cd - 1>/dev/null
    fi
    rm -rf build/
    exit 0
  elif [ $1 = "make" ] 
  then
    :
  else
    echo "Command not found"
    exit 1
  fi
fi
echo "Doing a regular cmake setup for making"
if [ ! -d ./build/ ] 
then
  mkdir ./build/
fi

cd ./build/; cmake ..; make; cd - 1>/dev/null

If you are familiar with BASH scripting, it’s one of the simplest scripts; if not, basically you use it like make: ./cmake_make.sh builds the program, and ./cmake_make.sh clean cleans the build. It allows you to put CMakeLists.txt in the main directory, but does the build in the ./build/ directory instead. 

The CMakeLists.txt now reads:

cmake_minimum_required(VERSION 2.8.9)
project(HelloComplex)

enable_testing()

if(NOT EXISTS ${PROJECT_SOURCE_DIR}/bin)
  file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
endif()

if(NOT EXISTS ${PROJECT_SOURCE_DIR}/lib)
  file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
endif()

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)

include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(HelloComplex
  ${PROJECT_SOURCE_DIR}/src/HelloComplex.cpp)
add_test(exeTest HelloComplex)

Here are the explanations of the syntaxes:

First of all, most of the CMake syntaxes are accepted either in UPPERCASE or lowercase, such as cmake_minimum_required(), project(), etc. However, built-in arguments or variables, such as MAKE_DIRECTORY and ${PROJECT_SOURCE_DIR}, are better off in all capitals. User defined variables should be case-consistent throughout the file.

Syntaxes below are critically important to a good start of using CMake.

  • cmake_minimum_required(VERSION X.X): Specify the minimum version required to run this CMake file;
  • project(NAME): Specify the project name;
  • if()…endif(): Conditional statements;
  • NOT, EXIST: boolean operation and file operation keywords;
  • ${PROJECT_SOURCE_DIR}: directory of where the project is. In this case it refers to the main directory.
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY, CMAKE_ARCHIVE_OUTPUT_DIRECTORY, CMAKE_LIBRARY_OUTPUT_DIRECTORY: CMake preset variables for executable output, link output, and library output, respectively;
  • set(variable, value): sets the variable to value;
  • include_directories(): add directories to search for header files;
  • add_executable(executable_name, source_files): build the source files to excutable_name
  • enable_testing() and add_test(testname Exename arg1 arg2 …): these allow users to activate and add test commands. See here for more details.

OK. Now it should be self-explanatory. Execute the BASH script and you will find the executable in ./bin/ and the junk in ./build/!

Download the files used in this chapter here: cmake_chapter1