We write high-level instructions in a CMakeLists.txt file (platform-agnostic).Then CMake generates build system files for the platform we choose:
On Linux/Unix: generates Makefile (for make) or build.ninja (for ninja).
On Windows: generates Visual Studio solutions (.sln).
On macOS: can generate Xcode projects.
CMake Generators: CMake support multiple build systems output a.k.a generators. Which generator is used can be controlled via CMAKE_GENERATOR or cmake -G option
Single/Multi-Configuration Generators: Software builds often have several variants e.g. Debug, Release, RelWithDebInfo, and MinSizeRel. To select build type:
single-generator(ninja,make): via cmake --DCMAKE_BUILD_TYPE=<config>
multi-generator(ninja-mul,vs): single & cmake --build --config
Usage Basics:
# install cmake$ sudo apt install cmake
# verify cmake$ cmake --version
cmake version 3.23.5
# specific project root dir, which contains the root CMakeLists.txt# default current dir$ cmake -S <dir>
# specific build dir# default current dir$ cmake -B <dir>
# specific generator to generate the build system in <dir># must delete <dir> when switch <gen>$ cmake -G "<gen>" -B <dir>
# run the build system in the build dir# single-config$ cmake --build <dir>
# multi-configs$ cmake --build <dir> --config <cfg>
# clean when reorganization$ cmake --build build --clean-first
Example:
$ cd project
# project structure$ tree
.
├── CMakeLists.txt
└── HelloWorld.cxx
1 directory, 2 files
$ cat HelloWorld.cxx
#include <cstdio>int main(){ std::printf("Hello World\n");}$ cat CMakeLists.txt
cmake_minimum_required(VERSION 3.23)project(Tutorial)add_executable(hello)target_sources(hello
PRIVATE
HelloWorld.cxx
)######################################################################## Step 1: generate build system in folder 'build' using Unix Makefiles$ cmake -G "Unix Makefiles" -B build
# Step 2: run the build system in the folder 'build'$ cmake --build build
# Step 3: run the executable$ ./build/hello
Hello World
This section help us will be able to describe executables, libraries, source and header files, and the linkage relationship between them.
A CMakeLists.txt (list file-CML) will exist within any directory where we want to provide instructions to CMake on how to handle files, and operations local to that dir or sub-dir.
There are four backbone commands of most CMake usage:
add_executable() and add_library() for describing output artifacts the software project wants to produce
target_sources() for associating input files with their respective output artifacts
target_link_libraries() for associating output artifacts with one another.
Strongly recommend that the project root CMakeLists.txt
$ cat CMakeLists.txt
# should always contain these two commands at the top/nearcmake_minium_required(VERSION 3.23)project(MyProjectName)
add_executable(): create a target, we can now start associating properties with it like source files we want to build and link.
target_sources( {INTERFACE|PUBLIC|PRIVATE} ): add source to target
The scope keyword for executable should always be PRIVATE
e.g.
$ cat CMakeLists.txt
# Set the minimum required version of CMake to be 3.23cmake_minimum_required(VERSION 3.23)# Create a project named Tutorialproject(Tutorial)# Add an executable target called Tutorial to the projectadd_executable(Tutorial)# Add the Tutorial/Tutorial.cxx source file to the Tutorial targettarget_sources(Tutorial
PRIVATE
Tutorial/Tutorial.cxx
)
=> Both Tutorial1 and Tutorial2 compile the same source file:
Use add_library to add a library to the project
Use a FILE_SET to describe a collection of header files in the target_source
# Add a library called MyLibraryadd_library(MyLibrary)target_sources(MyLibrary
PRIVATE # private source files, target cannot see library_implementation.cxx
PUBLIC # public source files# FILE_SET myHeaders # name# TYPE HEADERS # kind FILE_SET HEADERS # don't need to provide type BASE_DIRS # base locations of the file include
FILES # list of files include/library_header.h
######################################################## PRIVATE # PRIVATE is for me FILE_SET internalOnlyHeaders
TYPE HEADERS
FILES
InternalOnlyHeader.h
INTERFACE # INTERFACE is for others FILE_SET consumerOnlyHeaders
TYPE HEADERS
FILES
ConsumerOnlyHeader.h
PUBLIC # PUBLIC is for all of us FILE_SET publicHeaders
TYPE HEADERS
FILES
PublicHeader.h
)
We now can include directive were #include <MyLibrary/library_header.h>
Use target_link_libraries() to invoke linkers to combine targets/libs
# Add the MyLibrary library as a linked dependency# to the Tutorial targettarget_link_libraries(Tutorial
PRIVATE
MyLibrary
)target_link_libraries(Tutorial2
PRIVATE
MyLibrary
)
Use add_subdirectory() to incorporate the CLMs - CMakeLists.txt located in a subdirectory of the project.
The relative paths used inside that subdirectory’s CMakeLists.txt are interpreted relative to that subdirectory.
e.g
$ cd TutorialProject
# project structure$ tree
├── CMakeLists.txt
├── Tutorial/
│ ├── CMakeLists.txt
│ └── Tutorial.cxx
└── MathFunctions/
├── CMakeLists.txt
├── MathFunctions.cxx
└── MathFunctions.h
$ cat CMakeLists.txt # root CMakeLists.txtcmake_minimum_required(VERSION 3.23)project(Tutorial)# include subdirectories so CMake processes their CMakeLists.txtadd_subdirectory(MathFunctions)add_subdirectory(Tutorial)$ cat ./Tutorial/CMakeLists.txt
add_executable(Tutorial)# add source file for this executable# path is relative to this directory (Tutorial/)target_sources(Tutorial
PRIVATE
Tutorial.cxx
)# link the MathFunctions library to the executabletarget_link_libraries(Tutorial
PRIVATE
MathFunctions
)$ cat ./MathFunctions/CMakeLists.txt
# create a library target MathFunctionsadd_library(MathFunctions)target_sources(MathFunctions
PRIVATE
MathFunctions.cxx
# expose header file to other targets that link this library PUBLIC
FILE_SET HEADERS
FILES
MathFunctions.h
)
list(APPEND list "new_item") for manipulating the lists
foreach(l IN LISTS lists) to iterate over a list
e.g.
$ cat CMakeLists.txt
set(MYVAR "HelloWorld")message(${MYVAR})# create a listset(lists "first;second;third")# or set(lists first second third)# manipulate the listlist(APPEND lists "fourth")# print outmessage("This is my list: ${lists}")# iterate to print each itemforeach(i IN LISTS lists) message("item: ${i}")endforeach()$ cmake -P CMakeLists.txt
HelloWorld
This is my list: first;second;third;fourth
item: first
item: second
item: third
item: fourth
macro(name args) to create a macro
function(name args) to create a function
e.g.
$ cat CMakeLists.txt
# create a macromacro(myM arg) message("myM called with arg: ${arg}")endmacro()# create a functionfunction(myF arg) message("myF called with arg:${arg}") myM(${arg})endfunction()# call functionmyF("hello")$ cmake -P CMakeLists.txt
HelloWorld
This is my list: first;second;third;fourth
item: first
item: second
item: third
item: fourth
myF called with arg:hello
myM called with arg: hello
We can organize the project let functions and utilities live in their own .cmake files outside the project CMLs and separate from the rest of the build system.
We use the include() command to incorporate these separate ones.
A CMake Cache variables are globally visible variables and persistent vars stored in CMakeCache.
They are mainly used to configure build options and allow users to customize the build.
-D<var>:<type>=<value>
Create or update a cache entry from the command line.
option()
Define a boolean cache variable and provide a default value.
set(<var> <value> CACHE <type> <docstring>)
Create or update a cache variable, but it will not overwrite a value that was already set by the user via -D.
set() / unset()
Create or remove a normal variable that temporarily shadows the cache variable.
e.g.
$ tree
.
├── CMakeLists.txt
├── CMakePresets.json
├── MathFunctions
│ ├── CMakeLists.txt
│ ├── MathFunctions.cxx
│ └── MathFunctions.h
└── Tutorial
├── CMakeLists.txt
└── Tutorial.cxx
$ cat CMakeLists.txt
cmake_minimum_required(VERSION 3.23)project(Tutorial)# Add a default ON option for a cache variable option(TUTORIAL_BUILD_UTILITIES "Build the Tutorial executable des" ON)# Add a conditional statement around add_subdirectory(Tutorial)if(TUTORIAL_BUILD_UTILITIES) message("Build the Tutorial executable") add_subdirectory(Tutorial)endif()add_subdirectory(MathFunctions)$ cmake -B build -DTUTORIAL_BUILD_UTILITIES=false$ cmake --build build
$ ls build
# only build MathFunctionsCMakeCache.txt CMakeFiles Makefile MathFunctions cmake_install.cmake
$ cat build/CMakeCache.txt
// Build the Tutorial executable des
TUTORIAL_BUILD_UTILITIES:BOOL=false
We can now create a CMakePresents.json in the CML’s folder.
{"version":4,"configurePresets":[{"name":"example-preset",// preset name
// "binaryDir": "${sourceDir}/build" // set the build dir to skip -B flag
"cacheVariables":{// var configs
"EXAMPLE_FOO":"Bar","EXAMPLE_QUX":"Baz"}}]}// Template:
{"version":4,"configurePresets":[{"name":"example-preset","cacheVariables":{"EXAMPLE_FOO":"Bar","EXAMPLE_QUX":"Baz"}}]}
They give direct access to a target’s properties by name
e.g.
$ cat CMakeLists.txt
add_library(mylib)# set properties to the targetset_target_properties(mylib
PROPERTIES
Key Value
Key1 Value1
)# get properties from the targetget_property(KeyVar mylib Key)get_property(KeyVar1 mylib Key1)# print outmessage("Key: ${KeyVar}")message("Key1: ${KeyVar1}")$ cmake -B build
Key: Value
Key1: Value1
These two commands are using to communicate language standard and compile definition requirements for the target.
Feature command describes a minimum language standard as a target property.
Definition command describes compile definitions as target properties.
e.g.
target_compile_features(MyTarget PRIVATE cxx_std_20)# std20target_compile_definitions(MyTarget PRIVATE MY_DEFINITION)# In the source code, the definition can be checked using the preprocessor:# #ifdef MY_DEFINITION# // do something# #endif
These two commands specify directories used during compilation and linking, and map to the -L (.a, .so, .lib, .dll) and -I (.h, .hpp) compiler flags.
These commands are typically used when integrating a precompiled or vendored library into a project.
e.g.
Vendor$ tree
.
├── CMakeLists.txt
├── include
│ └── Vendor.h
└── lib
├── Vendor.cxx
├── Vendor.o
└── libVendor.a # static library , g++ -c Vendor.cxx & ar rvs libVendor.a Vendor.o$ cat CMakeLists.txt
add_library(VendorLib INTERFACE)# INTERFACE library, only describes how to use, does not build any thing# Now target that links VendorLib will automatically compile with:# #define TUTORIAL_USE_VENDORLIBtarget_compile_definitions(VendorLib
INTERFACE
TUTORIAL_USE_VENDORLIB
)# Add the include directory to VendorLib to tell the compiler where the headers are.# Now any consumer target can write:# #include <vendor.h>target_include_directories(VendorLib
INTERFACE
include
)# Add the lib directory to VendorLib to tells the linker where to search for libraries.target_link_directories(VendorLib
INTERFACE
lib
)# Add the Vendor archive to VendorLib to tell CMake that anything linking VendorLib must also link Vendor.target_link_libraries(VendorLib
INTERFACE
Vendor
)
There is an optional argument in add_library(<name> <type>) command, it’s
STATIC: an archive of object files for use when linking other targets. (.a)
SHARED: a dynamic library that maybe linked by other targets and loaded at runtime. (.so)
MODULE
OBJECT
INTERFACE: a library target which specifies usage requirements for dependents but does not compile sources and does not produce a library artifact on disk.
include(FetchContent)FetchContent_Declare( googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)FetchContent_MakeAvailable(googletest)# Download GoogleTest# Extract the archive# Run its CMakeLists.txt# Add it to your build using `add_subdirectory()`
Install Globally:
$ sudo apt install googletest # Problem: version mismatch.