Environment

  • Linux shell (Ubuntu 18.04)
  • Visual Studio Code

Understanding The Basics

  • makefile
  • CMakeLists.txt

The concept of makefile

To illustrate what makefile does, we first create a project folder (I named it CMakeTuto here).

Then use VSCode to open the folder.

1
2
3
mkdir CMakeTuto
cd CMakeTuto/
code .

Create a file makefile

1
2
default:
g++ main.cpp -o out

Note here makefile do not accept spaces. It only accept tabs.

Create a file main.cpp

1
2
3
4
5
#include <iostream>
int main(){
std::cout << "Hello World!\n";
return 0;
}

Now your CMakeTuto directory should be like this:

1
2
3
.
├── main.cpp
└── makefile

So what a makefile does?

Use the make command to run the makefile

1
make

make command basically run the command in makefile. (that is g++ main.cpp -o out in this case. )

Now a out file should appear. Run the out file.

1
./out

It should print the “Hello World!”.

The concept of CMake

CMake makes your makefile.

  • cmake relies on a top level file called CMakeLists.txt

now we can first delete our makefile and out in the previous section.

First check our cmake version

1
cmake --version

You can also use the command cmake to check the usage.

Common usage: cmake [options] <path-to-source>

Create a file CMakeLists.txt, leave the file blank.

Now your directory should be:

1
2
3
.
├── CMakeLists.txt
└── main.cpp

Lets create a directory build.

1
mkdir build

Note if you want to use cmake command, you need to go to the build directory (which is build).

1
2
cd build
cmake ..
  • .. means upper level folder (which is CMakeTuto in this case)
  • . means this folder

Now your directory should be like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ │ ├── 3.10.2
│ │ │ ├── CMakeCCompiler.cmake
│ │ │ ├── CMakeCXXCompiler.cmake
│ │ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ │ ├── CMakeSystem.cmake
│ │ │ ├── CompilerIdC
│ │ │ │ ├── a.out
│ │ │ │ ├── CMakeCCompilerId.c
│ │ │ │ └── tmp
│ │ │ └── CompilerIdCXX
│ │ │ ├── a.out
│ │ │ ├── CMakeCXXCompilerId.cpp
│ │ │ └── tmp
│ │ ├── cmake.check_cache
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── CMakeOutput.log
│ │ ├── CMakeTmp
│ │ ├── feature_tests.bin
│ │ ├── feature_tests.c
│ │ ├── feature_tests.cxx
│ │ ├── Makefile2
│ │ ├── Makefile.cmake
│ │ ├── progress.marks
│ │ └── TargetDirectories.txt
│ ├── cmake_install.cmake
│ └── Makefile
├── CMakeLists.txt
└── main.cpp

8 directories, 24 files

Notice now the Makefile is generated.

If You look into that Makefile and you will see it just build what our CMakeLists.txt require.

(Note we haven’t write anything into CMakeLists.txt yet)

We won’t teach those generated files.

Edit your CMakeLists.txt

Now write into your CMakeLists.txt.

1
2
3
4
5
cmake_minimum_required(VERSION 3.9.0)

project(HelloWorld) #replace "HelloWorld" with your project name

add_executable(${PROJECT_NAME} main.cpp)

run the cmake command in your build folder again.

1
cmake ..

If You look into that Makefile again and you will see it just build what our CMakeLists.txt require.

Lets run the Makefile using make command.

1
make

You should see this outcome:

1
2
3
4
Scanning dependencies of target HelloWorld
[ 50%] Building CXX object CMakeFiles/HelloWorld.dir/main.cpp.o
[100%] Linking CXX executable HelloWorld
[100%] Built target HelloWorld

And now you should see a HelloWorld executable is in the build folder.

Lets run the HelloWorld executable.

1
./HelloWorld

Now you know the basics of cmake.

Another Example - Linking more than 1 file

File structure:

1
2
3
4
.
├── adder.cpp
├── CMakeLists.txt
└── main.cpp

adder.cpp

1
2
3
4
float add(float a, float b)
{
return a+b;
}

CMakeLists.txt

1
2
3
4
5
cmake_minimum_required(VERSION 3.9.0)

project(HelloWorld) #replace "HelloWorld" with your project name

add_executable(${PROJECT_NAME} main.cpp adder.cpp) #add the files here

main.cpp

1
2
3
4
5
6
7
8
//function prototype
float add(float a, float b);

#include <iostream>
int main(){
std::cout << add(6.9, 13.2) << std::endl;
return 0;
}

Build and run the program

1
2
3
4
5
mkdir build
cd build
cmake ..
make
./HelloWorld

It should print out 20.1 .

Libraries and Subdirectories

Levels and Libraries

In practical situations we often put our files into a folder.

We need to put a sublevel CMakeLists.txt in each subfolder.

Example:

1
2
3
4
5
6
7
8
9
.
├── Adder
│ ├── adder.cpp
│ ├── adder.h
│ └── CMakeLists.txt
├── CMakeLists.txt
└── main.cpp

1 directory, 5 files

adder.cpp

1
2
3
4
5
6
#include "adder.h"

float add(float a, float b)
{
return a+b;
}

adder.h

1
float add(float a, float b);

main.cpp

1
2
3
4
5
6
7
#include <iostream>
#include "Adder/adder.h"

int main(){
std::cout << add(6.9, 13.2) << std::endl;
return 0;
}

the root CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cmake_minimum_required(VERSION 3.9.0)

project(HelloWorld) #replace "HelloWorld" with your project name

add_executable(${PROJECT_NAME} main.cpp) #build the main

add_subdirectory(Adder) #build the libraries

target_include_directories(${PROJECT_NAME} PUBLIC Adder)

#--uncomment this if your cmake version >3.13.0
#--target_link_directories(${PROJECT_NAME} PRIVATE Adder)

target_link_libraries(${PROJECT_NAME} adder) #links the libraries to core project

the sublevel Adder/CMakeLists.txt

1
add_library(adder adder.cpp) #build as a library in the most simplistic way possible

Build and run the program

1
2
3
4
mkdir build && cd build
cmake ..
make
./HelloWorld

It should print out 20.1 .

Reference