Programming/Tools/CMake

From Thalesians Wiki
< Programming
Revision as of 22:22, 22 December 2020 by Admin (talk | contribs)

Overview

CMake is an open-source, cross-platform family of tools designed to build, test, and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice. The suite of CMake tools were created by Kitware in response to the need for a powerful, cross-platform build environment for open-source projects such as ITK and VTK.

CMake is part of Kitware's collection of commercially supported open-source platforms for software development.

This tutorial

In this tutorial we demonstrate how to set up and build an (admittedly very simple) project using CMake on an Ubuntu 20.04.1 LTS system. The tutorial should be applicable to other Linux systems, possibly with minor modifications.

Preparation

First, we need to install CMake:

$ sudo apt update
...
$ sudo apt install cmake

Since we'll be using CMake with gccGNU project C and C++ compiler,— we also need to install gcc:

$ sudo apt install g++

Basic project

We'll start working from the home directory, so we

$ cd

to it. We then create a directory for the project

$ mkdir my_project

cd into it with

$ cd my_project

Now let us create a directory for source files

$ mkdir src

and the file CMakeLists.txt:

$ touch CMakeLists.txt

CMakeLists.txt contains a set of directives and instructions describing the project's source files and targets (executable, library, or both).

project(cmake_test_project)
cmake_minimum_required(VERSION 3.0)

While you can build the project from the current directory (.../my_project/), CMake will generate some files that will pollute it. Therefore it is a good idea to

$ mkdir build
$ cd build/
$ cmake ..

We should see something like this:

-- The CXX compiler identification is GNU 9.3.0
-- 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: /home/ubuntu/my_project/build

Under .../my_project/build/ you will see a pretty large Makefile containing something like this:

# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.16

# Default target executed when no arguments are given to make.
default_target: all

.PHONY : default_target

# Allow only one "make -f Makefile2" at a time, but pass parallelism.
.NOTPARALLEL:


#=============================================================================
# Special targets provided by cmake.

# Disable implicit rules so canonical targets will work.
.SUFFIXES:


# Remove some rules from gmake that .SUFFIXES does not remove.
SUFFIXES =

.SUFFIXES: .hpux_make_needs_suffix_list


# Suppress display of executed commands.
$(VERBOSE).SILENT:


# A target that is always out of date.
cmake_force:

.PHONY : cmake_force

#=============================================================================
# Set environment variables for the build.

# The shell in which to execute make rules.
SHELL = /bin/sh

# The CMake executable.
CMAKE_COMMAND = /usr/bin/cmake

# The command to remove a file.
RM = /usr/bin/cmake -E remove -f

# Escaping for special characters.
EQUALS = =

# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /home/ubuntu/my_project

# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/ubuntu/my_project/build

#=============================================================================
# Targets provided globally by CMake.

# Special rule for the target rebuild_cache
rebuild_cache:
        @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running                                                                                                              CMake to regenerate build system..."
        /usr/bin/cmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
.PHONY : rebuild_cache

# Special rule for the target rebuild_cache
rebuild_cache/fast: rebuild_cache

.PHONY : rebuild_cache/fast

# Special rule for the target edit_cache
edit_cache:
        @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No inter                                                                                                             active CMake dialog available..."
        /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available.
.PHONY : edit_cache

# Special rule for the target edit_cache
edit_cache/fast: edit_cache

.PHONY : edit_cache/fast

# The main all target
all: cmake_check_build_system
        $(CMAKE_COMMAND) -E cmake_progress_start /home/ubuntu/my_project/build/C                                                                                                             MakeFiles /home/ubuntu/my_project/build/CMakeFiles/progress.marks
        $(MAKE) -f CMakeFiles/Makefile2 all
        $(CMAKE_COMMAND) -E cmake_progress_start /home/ubuntu/my_project/build/C                                                                                                             MakeFiles 0
.PHONY : all

# The main clean target
clean:
        $(MAKE) -f CMakeFiles/Makefile2 clean
.PHONY : clean

# The main clean target
clean/fast: clean

.PHONY : clean/fast

# Prepare targets for installation.
preinstall: all
        $(MAKE) -f CMakeFiles/Makefile2 preinstall
.PHONY : preinstall

# Prepare targets for installation.
preinstall/fast:
        $(MAKE) -f CMakeFiles/Makefile2 preinstall
.PHONY : preinstall/fast

# clear depends
depend:
        $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-bui                                                                                                             ld-system CMakeFiles/Makefile.cmake 1
.PHONY : depend

# Help Target
help:
        @echo "The following are some of the valid targets for this Makefile:"
        @echo "... all (the default if no target is provided)"
        @echo "... clean"
        @echo "... depend"
        @echo "... rebuild_cache"
        @echo "... edit_cache"
.PHONY : help



#=============================================================================
# Special targets to cleanup operation of make.

# Special rule to run CMake to check the build system integrity.
# No rule that depends on this can have commands that come from listfiles
# because they might be regenerated.
cmake_check_build_system:
        $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-bui                                                                                                             ld-system CMakeFiles/Makefile.cmake 0
.PHONY : cmake_check_build_system

However, this Makefile doesn't do anything useful. That is because we haven't written any code in our project. Let's add it.

First, let us modify .../my_project/CMakeLists.txt:

project(cmake_test_project)
cmake_minimum_required(VERSION 3.0)

message(STATUS "*** Building my_project from ${PROJECT_SOURCE_DIR} ***")
add_subdirectory(${PROJECT_SOURCE_DIR}/src)

Make sure that we are under .../my_project/build and run

$ cmake ..

The following error message will appear:

-- *** Building my_project from /home/ubuntu/my_project ***
CMake Error at CMakeLists.txt:6 (add_subdirectory):
  The source directory

    /home/ubuntu/my_project/src

  does not contain a CMakeLists.txt file.


-- Configuring incomplete, errors occurred!
See also "/home/ubuntu/my_project/build/CMakeFiles/CMakeOutput.log".
See also "/home/ubuntu/my_project/build/CMakeFiles/CMakeError.log".

As the error suggests, the directory .../my_project/src/, which we have added to .../my_project/CMakeLists.txt must contain (another) CMakeLists.txt file. CMake has a hierarchical configuration system. For now, let us simply

$ touch ../src/CMakeLists.txt

(assuming we are still under .../my_project/build/). Thus we create an empty child CMakeLists.txt file that does nothing.

Let's try building again:

$ cmake ..

This time the build has been successful:

-- *** Building my_project from /home/ubuntu/my_project ***
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/my_project/build

Adding an executable

However, this build has been trivial since the project contains no code. Let us add some code to it:

$ touch ../src/main.c
$ nano ../src/main.c

and let's write the following content to .../my_project/src/main.c:

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

In order to build this file, we need to modify .../my_project/src/CMakeLists.txt:

add_executable(bin_main main.c)

This tells CMake to build an executable, bin_main out of the source files or, in this case, source file main.c.

Having made sure we are still under .../my_project/build, we again run

$ cmake ..

This has generated a Makefile. Now let's run make:

$ make

The output is

Scanning dependencies of target bin_main
[ 50%] Building C object src/CMakeFiles/bin_main.dir/main.c.o
[100%] Linking C executable bin_main
[100%] Built target bin_main

bin_main will be under .../my_project/build/src/ (since the directory structure under .../my_project/build/ mimics that above).

We can run it (assuming we are under .../my_project/build/):

$ src/bin_main
Hello, World!