This blog post introduces basics of CMake.

In the last article on the DevOps series, Linux Basics #9 - Makefiles, we discussed how Make allows us to automatically build projects. However, as the codebase grows and demands cross-platform compatibility, Makefiles will also grow to the extent that they become intractable. CMake is a free and cross-platform build tool primarily for C++ that can automatically generate Makefiles and scripts for other build tools appropriate for any environment.
Setting Up CMake
To get started with CMake, you first need to download it using apt install cmake
on Linux.
For macOS and Windows, check out the official website and
follow their instructions. CMake analyzes the CMakeLists.txt
file placed at the top level of the directory
we are in to automatically generate build scripts, as shown below.
cmake_minimum_required(VERSION 3.15)
# Project Name, Version, Language (default is C++), etc.
project(MyProject C)
# Naming BINARY (We could have used PROJECT_NAME to set the output binary name to the project's name)
set(BINARY bin)
# Create the executables using the specified files
add_executable(${BINARY} main.c LinkedList.c)
Here, #
is used for comments, and the capital letters indicate variables we define with set and predefined variables.
We can run cmake -S <source_path> -B <build_path>
to create the appropriate build script or Makefile in the specified <build_path>
using CMakeLists.txt
in the <source_path>
. Conventionally, we create a /build
directory under the project's root directory,
where we generate the Makefile and build the executable with cmake -S . -B /build
.
Then, we can move to the /build
directory and confirm that the Makefile has been successfully generated.
We can run make
to generate the executable and then run the executable with ./bin
.
Building Libraries
From the above example, we can see that CMake allows us to simplify the build process compared to Makefiles.
CMake also allows us to easily compile source code into a library, which we can link to the main source code.
For example, we can set up a LinkedList
directory, which contains LinkedList.h
and LinkedList.c
.
Then, we can build a library as follows.
cmake_minimum_required(VERSION 3.15)
project(LinkedList C)
# Create a library (STATIC by default. We can specify it to be SHARED.)
add_library(LinkedList LinkedList.c)
The add_library
function creates the libLinkedList.a
static library, which can be linked to the main source code as follows.
cmake_minimum_required(VERSION 3.15)
project(MyProject C)
# Add library as subdirectory in `build_path` to run build on library and to reference it
add_subdirectory(LinkedList)
add_executable(${PROJECT_NAME} main.c)
# Tell where to look for header files not in standard location
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/LinkedList)
# Link Libraries
target_link_libraries(${PROJECT_NAME} LinkedList)
The add_subdirectory
command makes CMake automatically build LinkedList
using the CMakeLists.txt
file under the subdirectory,
which in this case creates a static library. We can specify the location of the header files with target_include_directories
,
so the main source code does not need to specify the full path to the header file. Then, we can link the library with target_link_libraries
.
For better organization, we often use the following file structure for setting up a library:
CMakeLists.txt
main.c
LibraryA/
├── CMakeLists.txt
├── src/
│ ├── module1.c
│ └── module2.c
├── include/
│ ├── module1.h
│ └── module2.h
For the above, we use the following LibraryA/CMakeLists.txt
:
cmake_minimum_required(VERSION 3.15)
project(LibraryA C)
# Collect C files from /src
file(GLOB SRC_FILES ${CMAKE_SOURCE_DIR}/LibraryA/src/*.c)
add_library(LibraryA STATIC ${SRC_FILES})
Then, the CMakeLists.txt
at the root level can specify the location of the header files and link the library as follows:
cmake_minimum_required(VERSION 3.15)
project(MyProject C)
add_subdirectory(LibraryA)
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} LibraryA)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/LibraryA/include)
You can download someone else's library code or submodules and use the same instructions as above to build them into libraries and use them. This simplifies the process of integrating third-party code and custom libraries into larger projects.
Installing Executables and Libraries
Instead of using a library built in the directory specified by the -B
flag,
we can install it on our machine and use the installed library.
cmake_minimum_required(VERSION 3.15)
project(LibraryA C)
file(GLOB SRC_FILES ${CMAKE_SOURCE_DIR}/src/*.c)
add_library(LibraryA STATIC ${SRC_FILES})
# Collect header files from /include
file(GLOB HEADER_FILES ${CMAKE_SOURCE_DIR}/include/*.h)
# Set public header property
set_target_properties(LibraryA PROPERTIES PUBLIC_HEADER "${HEADER_FILES}")
# Install library to local environment "lib" and header files to "include"
# (usually /usr/local/lib and /usr/local/include)
install(
TARGETS LibraryA ARCHIVE DESTINATION lib
PUBLIC_HEADER DESTINATION include
)
The ARCHIVE
argument of install is for static libraries, and we should use LIBRARY
for shared or dynamic libraries.
With the above file, we can run sudo make install
to install the library to the local environment,
allowing all the executables in the same environment to use it without specifying the path.
We can also install executables so they can be run from anywhere.
cmake_minimum_required(VERSION 3.15)
project(MyProject C)
add_subdirectory(LibraryA)
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} LibraryA)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/LibraryA/include)
# Install executable to local environment "bin" (usually /usr/local/bin)
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
By running sudo make install
using the above configuration, we can install the executable,
which can then be run by typing MyProject
from anywhere.
Conclusion
In this article, we covered what CMake is, why to use CMake, how to get started with CMake, and the very basics of CMake. The CMake series will contain only a few articles (at least according to my plan), covering almost everything you need to know about CMake, so stay tuned.
Resources
- Code, Tech, and Tutorials. 2021. CMake Tutorial EP 1 | Understanding The Basics. YouTube.
- Code, Tech, and Tutorials. 2023. CMake Tutorial EP 2 | Libraries | Installing | Pairing with Executables | RE-DONE!. YouTube.
- Code, Tech, and Tutorials. 2021. CMake Tutorial EP 3 | Git Submodules (adding glfw windowing library example). YouTube.