DEV Community

loading...
Cover image for Learning CMake 1: a really quick introduction to CMake

Learning CMake 1: a really quick introduction to CMake

iblancasa profile image Israel Blancas Updated on ・7 min read

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:

With great power comes great responsibility

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).

Mastering CMake

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:

  1. Generate the build system
  2. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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"
)
Enter fullscreen mode Exit fullscreen mode

The folder structure will be:

.
├── CMakeLists.txt
└── myapp.c
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Also, it is important to talk about CMAKE_BUILD_TYPE and BUILD_SHARED_LIBS:

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 ..
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

If the used generator was a multiconfiguration project, you will need to specify the configuration to build:

➜ iblancasa: cmake --build . --config Release
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)

pic
Editor guide