A really quick introduction to CMake
We could say that CMake is a framework to build/test/package software. It is not a build system: it is a build system generator. With CMake, we will write how to build our software in a high-level programming language and we will generate a native build system (like Unix Makefiles or Visual Studio Solutions). It's open source and extensively used by different companies and projects.
There is a lot of information on the Internet about what CMake is and its origins. For that reason, it doesn't make sense to cover that on this post. Also, I will not explain anything about the CMake language syntax: it is really easy to follow and you can search on Google or other search engine if you have any questions.
CMake is a really powerful tool but, if you're not using it properly, it can be a nightmare. Remember:
You can download CMake from its official website.
There is a really good book called "Mastering CMake". You should read it if you need to write a complex system. This book is related to the version 3.1 (when this post was written, the latest version is 3.15 but the most of the concepts are still valid).
The basics
As part of the installation, you will receive:
-
cmake
: the CMake runtime (to generate native build systems and other magics) -
cpack
: the tool to package software -
ctest
: the tool to test software
Regarding the way to build software using CMake, can be simplified as:
- Generate the build system
- Build the source code
The cmake
command-line
If you run cmake --help
, you will see all the possible parameters to provide to CMake. Don't worry: we'll learn how to use them in the future. First, I want to show you something that is really interesting: cmake
has a set of commands that are totally cross-platform and can be used independently of the operating
system where you are running CMake. To get all these commands, just run cmake -E
:
Usage: ./cmake -E <command> [arguments...]
Available commands:
capabilities - Report capabilities built into cmake in JSON format
chdir dir cmd [args...] - run command in a given directory
compare_files [--ignore-eol] file1 file2
- check if file1 is same as file2
copy <file>... destination - copy files to destination (either file or directory)
copy_directory <dir>... destination - copy content of <dir>... directories to 'destination' directory
copy_if_different <file>... destination - copy files if it has changed
echo [<string>...] - displays arguments as text
echo_append [<string>...] - displays arguments as text but no new line
env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...
- run command in a modified environment
environment - display the current environment
make_directory <dir>... - create parent and <dir> directories
md5sum <file>... - create MD5 checksum of files
sha1sum <file>... - create SHA1 checksum of files
sha224sum <file>... - create SHA224 checksum of files
sha256sum <file>... - create SHA256 checksum of files
sha384sum <file>... - create SHA384 checksum of files
sha512sum <file>... - create SHA512 checksum of files
remove [-f] <file>... - remove the file(s), use -f to force it
remove_directory <dir>... - remove directories and their contents
rename oldname newname - rename a file or directory (on one volume)
server - start cmake in server mode
sleep <number>... - sleep for given number of seconds
tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]
- create or extract a tar or zip archive
time command [args...] - run command and display elapsed time
touch <file>... - touch a <file>.
touch_nocreate <file>... - touch a <file> but do not create it.
create_symlink old new - create a symbolic link new -> old
Why are these commands interesting? For instance: usually, I work with Linux. When I have to work with a Windows machine, I don't know some of the commands to do some regular tasks like removing a directory recursively. I can use the command from CMake (if CMake is installed in the machine ¯\(ツ)/¯):
cmake -E remove_directory foo_folder
These commands are really useful to maintain our build system totally platform independent.
Let's build something
Ok, it is the time to build a "Hello World" example. You'll be able to find a lot of them on the Internet, but I will try to explain some extra concepts.
Let's imagine we need to build this C application:
#include <stdio.h>
int main()
{
printf("Hello, people!\n");
return 0;
}
We will need to create a file called CMakeLists.txt
in the same folder where the source code is placed. This folder is called source directory. The content of the CMakeLists.txt
file will be:
# Set the minimun CMake version required by the project
cmake_minimum_required(VERSION 3.5)
# Set a name for the project
project(example)
# Create an executable with the source code file
add_executable(my_app
"${CMAKE_CURRENT_SOURCE_DIR}/myapp.c"
)
The folder structure will be:
.
├── CMakeLists.txt
└── myapp.c
Generating the build system
Now, we are going to generate the build system in order to build our source code. First, we are going to create the folder where the build will be done. This folder is called binary directory. You can create it running mkdir ../build
. The folder structure will be:
.
├── build
└── src
├── CMakeLists.txt
└── myapp.c
It is a good idea to have different source and build directories. This situation is called outsource build. Doing this separation, we can build the source code without modifying the source directory (what will make easier to deal with control-version softwares and other tools). To make possible this, we should reference the input and output files relative to the CMAKE_SOURCE_DIR
, CMAKE_CURRENT_SOURCE_DIR
, CMAKE_BINARY_DIR
and CMAKE_CURRENT_BINARY_DIR
CMake variables (these variables are automatically populated). Also, it is a good idea to add quotes to the paths to avoid issues with spaces in the path to the files and the CMake syntax.
Now, you have to move to the binary directory (cd ../build
) and then we will be able to generate the build system running CMake:
➜ iblancasa: cmake ../src
-- The C compiler identification is GNU 8.3.1
-- The CXX compiler identification is GNU 8.3.1
-- 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: /home/israel/cmake/build
CMake generators
The generator is the native build system to use build the source code.
By default, CMake will generate build files using the most common generator for your host platform (e.g., Makefiles on Unix-like systems and Visual Studio Solutions on Windows). You will need to specify the -G
option to CMake to generate the build system with the desired generator. All the valid values are described described in the CMake documentation CMake Generators Section.
There are two kind of generators:
- Single-configuration generators: like Unix Makefiles. The build system is generated for just one configuration (Debug-Static, for instance). If you want to build the source code with a different configuration, you will need to create a new binary directory and call CMake again with the new configuration.
- Multiconfiguration generators: like Visual Studio Solutions. The build system allows to build with more than one configuration (Release-Static and Debug-Static, for instance). When the build is done, it is needed to specify what configuration you want to build.
Important: pay special attention to the Visual Studio generators. You need to specify the platform with the -A
parameter. For instance, to generate the build system for Visual Studio 2019:
cmake ../build -G "Visual Studio 16 2019" -A x64
Also, it is important to talk about CMAKE_BUILD_TYPE
and BUILD_SHARED_LIBS
:
-
-DCMAKE_BUILD_TYPE
(just for single configuration generators): specifies the build mode. Valid values areRelease
andDebug
. See the CMake documentation for more details (Optional) -
-DBUILD_SHARED_LIBS
: specifies the link mode. Valid values areON
for dynamic linking andOFF
for static linking. See CMake documentation for more details. (Optional)
So, if you want to generate a build system for a Debug/Dynamic configuration, we need to call CMake as follows:
cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON ..
Building the source code
Depending on the CMake generator used, you can build the source code running directly the native tool. For instance, if you generated an Unix Makefile build system, you will be able to build just running make
.
Another option, is to use the cmake
command line. For single configuration projects, you can build running cmake --build .
:
➜ iblancasa: cmake --build .
Scanning dependencies of target my_app
[ 50%] Building C object CMakeFiles/my_app.dir/myapp.c.o
[100%] Linking C executable my_app
[100%] Built target my_app
If the used generator was a multiconfiguration project, you will need to specify the configuration to build:
➜ iblancasa: cmake --build . --config Release
And the application will be available in the binary directory (the location may be different depending on the generator used):
➜ iblancasa: ./my_app
Hello, people!
We can also specify what target do we want to build. This can be performed with the --target
parameter.
➜ iblancasa: cmake --build . --target my_app
If you are using Unix Makefiles, you can list all the targets with make help
.
The following are some of the valid targets for this Makefile:
... all (the default if no target is provided)
... clean
... depend
... rebuild_cache
... my_app
... edit_cache
... myapp.o
... myapp.i
... myapp.s
That target my_app
was created by CMake using the add_executable
builtin method.
Recap
What did we learn?
- What is CMake
- Its command-line
- How to build a "Hello World" application
- CMake generators
- Build using different configurations
Top comments (0)