Resource to Learn CMake
Starting Template
cmake_minimum_required(VERSION 3.22) set(This SuperHelloWorld) project( ${This} VERSION 1.0.0 LANGUAGES C CXX ) list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARDCONFIGURED_FILE_INCLUDE_DIR_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(LIBRARY_MY_LIB my_lib) set(EXE_APP app) # include(FetchContent) add_subdirectory(src) # message("Using FetchContent") # FetchContent_Declare( # nlohmann_json # GIT_REPOSITORY https://github.com/nlohmann/json.git # GIT_TAG v3.11.2 # GIT_SHALLOW TRUE # ) # FetchContent_MakeAvailable(nlohmann_json)
Common Commands
-
add_library(one two.cpp three.h)
- Declare a target called
one
that refers to the current directory, and list all sources files to be compiled. Only the compilation unittwo.cpp
will be compiled, we include the headers for IDE only.
- Declare a target called
-
add_executable(${THIS}_exe main.cpp)
-
Same as
add_library
, it adds a target that refers to the current directory, and that target points to an executable. -
We cannot use
add_library
andadd_target
at the same time. In other words, we should separatemain.cpp
and "source files" in separate folder. -
For example, we can separate like
some_proj/app/main.cpp
andsome_proj/src/some_lib/some_file.cpp
Then writesome_proj/app/CMakeLists.txt
some_proj/src/CMakeLists.txt
some_proj/src/some_lib/CMakeLists.txt
separately. Note thatsome_proj/src/CMakeLists.txt
can be as simple as just one line
add_subdirectory(some_lib)
as it helps point out which directory contains a
CMakeLists.txt
to look at.
-
-
add_subdirectory(src)
- It tells cmake compiler which directory to look for and execute a
CMakeLists.txt
. If the directorysrc
contains noCMakeLists.txt
file,cmake
will give an exception.
- It tells cmake compiler which directory to look for and execute a
-
# src/one/CMakeLists.txt target_include_directories(one PUBLIC ../../include)
-
It tells cmake our source files in the target
one
have included header files in other diectory such as../../include
. -
We don't need to include it again in other target that links to
one
, by simplytarget_link_libraries
(we introduce it right below) we can directly include header files in source code thatone
have already included. -
If the include dir contains
include/four/five.h
, then we can includefour/five.h
in our source code.Note that the string to include our header file is independent of the target name we name in cmake.
-
In general,
target_include_directories
is used when the header files are from other directory.
-
-
target_link_libraries(another PUBLIC one)
-
This is to build dependency between different targets. Which means that the target
another
and its downstream linkers will need libraryone
in the compilation process. -
Ya we have included the
include
directory but very likely it just contains function declarations, we need the function body definitions by linking those libraries linking the source files. -
if
PUBLIC
is replaced byPRIVATE
, it indicates that the downstream linkers ofanother
do not need the libraryone
, andanother
is the only target that needsone
.
-
-
target_compile_definitions(one PRIVATE SOME_CONSTANT="${SOME_CONSTANT}")
- Target library
one
we have a constantSOME_CONSTANT
defined by using#define
in the header.
- Target library
-
target_compile_features(one INTERFACE cxx_std_20)
- Require specific feature for a target.
-
set( SOME_CONSTANT "" # <value> CACHE STRING "Description of the cached constant" )
-
SOME_CONSTANT
is the variable name. -
We set
""
as a default value. -
The value
<value>
can also be passed by-D
argument:cmake -B build -DSOME_CONSTANT=ABCDEFG
-
STRING
defined the data type of the cached value<value>
.
-
-
if(SOME_CONSTANT STREQUAL "") message(SEND_ERROR "SOME_CONSTANT" must not be empty) endif()
- This is a standard if statement.
-
option(COMPILE_EXECUTABLE "Whether to compile to executable" OFF)
- It defines a boolean for cmake files to use.
- We can pass this variable in command line by
cmake .. -DCOMPILE_EXECUTABLE=ON
Include Predefined CMake Functions
Usually every cmake project contains a cmake/
folder that contains custom cmake script:
# project_root/CMakeLists.txt set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") include(AddGitSubModule)
CMAKE_MODULE_PATH
points to that cmake folderinclude
is used to include the cmake filecmake/AddGitSubmodule.cmake
. Exmaple of a.cmake
file:
# project_root/cmake/AddGitSubmodule.cmake function(add_git_submodule install_destination) find_package(Git REQUIRED) if (NOT EXISTS ${install_destination}/CMakeLists.txt) execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive -- ${install_destination} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} ) endif() add_subdirectory(${dir}) endfunction(add_git_submodule)
Which Folder to Create CMakeLists.txt
?
- Root directory.
- Set all necessary variables and add appropriate
add_subdirectory
's to look forCMakeLists.txt
.
- Set all necessary variables and add appropriate
- Source file directory
src
.- It can consists of simply the
add_subdirectory
commands.
- It can consists of simply the
- Source file subdirectories
src/one
,src/two
, ....- Each of the subdirectory should consists of
add_library
for linkage andtarget_include_directories
to include corresponding header files. - If the project structure is simpler, the rules to
src/<lib_name>/CMakeLists.txt
can be moved down tosrc/CMakeLists.txt
.
- Each of the subdirectory should consists of
- Directory that does not need
CMakeLists.txt
.- Directory that contains just header files needs no
CMakeLists.txt
, as they will be included intarget_include_directories
somewhere else once needed.
- Directory that contains just header files needs no
Show all Constants in a CMake Project
cmake -LAH
Special Functions to Check Variables in CMakeLists.txt
cmake_minimum_required(VERSION 3.22) project(CMakeTutorial LANGUAGES CXX) set(FOO "test") add_executable(CMakeTutorial main.cpp) function(print) foreach(var ${ARGN}) message("[${var}]: ${${var}}") endforeach() endfunction() function(print_env) foreach(var ${ARGN}) message("[${var}]: $ENV{${var}}") endforeach() endfunction() print(PROJECT_NAME FOO)
- The
print
function will print defined constant andprint_env
will print the defined environment variable.
Graph Visualization of Dependencies Between cmake
Files
Install graphviz
For windows we can download a .msi
file for installation HERE. For mac we run brew install graphviz
.
MakeFile
Scrips
We create a file called MakeFile
:
# MakeFile dependency: cd build && cmake .. --graphviz=graph.dot && \ dot -Tpng graph.dot -o ../dep_graph.png prepare: rm -rf build mkdir build
and run cmake dependency
.
The prepare
script is also included here, it is a default command that can be run by simply calling cmake
.
From my own experience on a simple hello-world project I have:
Cmake Erorr: incompatible versions of the cygwin DLL
It is a known problem in windows, go to C:\Program Files\Git\usr\bin
and rename msys-2.0.dll
to _msys-2.0.dll
to temporarily mitigate the problem.
Later you may need to rename it back since it affects commands such as npm
and yarn
.
Cross Platform Built Command
Source of Study
How to Build a CMake Project
mkdir build cd build cmake .. -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Debug VERBOSE=1 cmake --build .
How to Know Which Generator (specified by -G
) is Available?
By
cmake --help
apart from the list of available arguments, it also prints a list of available generators for us.
In my case, I get
Visual Studio 17 2022 = Generates Visual Studio 2022 project files. Use -A option to specify architecture. Visual Studio 16 2019 = Generates Visual Studio 2019 project files. Use -A option to specify architecture. Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files. Optional [arch] can be "Win64" or "ARM". Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files. Optional [arch] can be "Win64" or "ARM". Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files. Optional [arch] can be "Win64" or "ARM". Visual Studio 11 2012 [arch] = Generates Visual Studio 2012 project files. Optional [arch] can be "Win64" or "ARM". Visual Studio 10 2010 [arch] = Deprecated. Generates Visual Studio 2010 project files. Optional [arch] can be "Win64" or "IA64". Visual Studio 9 2008 [arch] = Generates Visual Studio 2008 project files. Optional [arch] can be "Win64" or "IA64". Borland Makefiles = Generates Borland makefiles. NMake Makefiles = Generates NMake makefiles. NMake Makefiles JOM = Generates JOM makefiles. ...
External Library (CMake Project)
By Direct Cloning
git submodule add https://target/repo.git external/some_name
and then add_subdirectory(external/some_name)
to seek for and execute the CMakeLists.txt
.
By FetchContent
By adding include(FetchContent)
in our CMakeLists.txt
we can import functions
FetchContent_Declare
FetchContent_MakeAvailable
For example, suppose that we want to import the following CMake projects hosted in github:
include(FetchContent) FetchContent_Declare( nlohmann_json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG v3.11.2 GIT_SHALLOW TRUE ) FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt GIT_TAG 9.1.0 GIT_SHALLOW TRUE ) FetchContent_Declare( spdlog GIT_REPOSITORY https://github.com/gabime/spdlog GIT_TAG v1.11.0 GIT_SHALLOW TRUE ) FetchContent_Declare( cxxopts GIT_REPOSITORY https://github.com/jarro2783/cxxopts GIT_TAG v3.0.0 GIT_SHALLOW TRUE ) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 GIT_TAG v2.13.9 GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(nlohmann_json) FetchContent_MakeAvailable(fmt) FetchContent_MakeAvailable(spdlog) FetchContent_MakeAvailable(cxxopts) FetchContent_MakeAvailable(Catch2)
where the GIT_TAG
can be found in the release page of the corresponding repository. These library will be downloaded in build/_deps
directory when we execute cmake build command.
To let our target (executable target or library target) link to these library, we run
target_link_libraries( ${LIBRARY_MY_LIB} PUBLIC cxxopts::cxxopts nlohmann_json::nlohmann_json fmt::fmt spdlog::spdlog Catch2::Catch2 )
The naming convention of the target is
<project_name>:<library_name>
The<project_name>
and<library_name>
can be traced by looking theCMakeLists.txt
of the repo.
Now our target LIBRARY_MY_LIB
or any target that links to it can run the following preprocessor directives:
#include "nlohmann/json.hpp" #include "cxxopts.hpp" #include "fmt/format.h" #include "spdlog/spdlog.h" #include "catch2/catch.hpp"
By conan
Files to Create
Create a virtual environment in Python, which I name it conan
, then conda activate conan
and pip install conan
.
As if requirements.txt
in Python we have an analog in conan, we create a text file conanfile.txt
in project root and add the following content:
-
project_root/conanfile.txt
[requires] nlohmann_json/3.11.2 fmt/9.1.0 spdlog/1.11.0 catch2/2.13.9 cxxopts/3.0.0 [generators] cmake_find_package cmake_paths
-
project_root/CMakeLists.txt
message("Using Conan") include(${CMAKE_BINARY_DIR}/conan_paths.cmake) find_package(nlohmann_json) find_package(fmt) find_package(spdlog) find_package(Catch2) find_package(cxxopts)
In Makefile
of project root directory we add
ifeq '$(findstring ;,$(PATH))' ';' CONAN_FLAGS = -s compiler='Visual Studio' -s compiler.version=17 -s cppstd=20 --build missing else CONAN_FLAGS = -s cppstd=17 --build missing endif prepare_conan: rm -rf build mkdir build cd build && conan install .. $(CONAN_FLAGS)
and run make prepare_conan
.
Remarks to conan
I myself fail to work with conan
in windows, maybe unix based system can make it work.
In general the database of conan
usually lag behind to the latest release for at least half year, it is suggested not to use it when FetchContent
suffices to serve the purpose.
CMake Examples
ChatClient TCP Server
Repo and Video
The whole project implementes a chatting function between multiple clients.
This blog post focuses on the CMakeLists.txt
files.
Outermost CMakeLists.txt, the Project Level
cmake_minimum_required(VERSION 3.22.2) set(CMAKE_CXX_STANDARD 20) add_subdirectory(MOYFNetworking) add_subdirectory(MOYFClient) add_subdirectory(MOYFServer)
The Main Library: Networking
cmake_minimum_required(VERSION 3.22.2) project(MOYFNetworking) set(CMAKE_CXX_STANDARD 20) set(BOOST_ROOT "C:\\Users\\user\\Repos\\C++Libraries\\boost_1_80_0") find_package(Boost REQUIRED) file(GLOB_RECURSE SOURCES src/*.cpp) add_library(${PROJECT_NAME} ${SOURCES}) # this says when building ${PROJECT_NAME} library, what follows must also be included target_include_directories( ${PROJECT_NAME} PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> ${Boost_INCLUDE_DIRS} PRIVATE ) # PRIVATE means downstream linkers that link to ${PROJECT_NAME} # library will not have access to ${Boost_LIBRARIES} # in other words, ${Boost_LIBRARIES} is # only used by the library ${PROJECT_NAME}. # If PRIVATE is replaced by PUBLIC, the downstream linkers that links # ${PROJECT_NAME} will be able to use those libraries (${Boost_LIBRARIES} in this case). target_link_libraries( ${PROJECT_NAME} PRIVATE ${Boost_LIBRARIES} )
NetClient
cmake_minimum_required(VERSION 3.22.2) project(MOYFClient) set(CMAKE_CXX_STANDARD 20) add_executable(${PROJECT_NAME} main.cpp) target_include_directories( ${PROJECT_NAME} PUBLIC MOYFNetworking ) target_link_libraries( ${PROJECT_NAME} PUBLIC MOYFNetworking )
NetServer
cmake_minimum_required(VERSION 3.22.2) project(MOYFServer) set(CMAKE_CXX_STANDARD 20) add_executable(${PROJECT_NAME} main.cpp) target_include_directories( ${PROJECT_NAME} PUBLIC MOYFNetworking ) target_link_libraries( ${PROJECT_NAME} PUBLIC MOYFNetworking ws2_32 )
Examples from Other Project
Sockets
This is a CMakeLists.txt
file inside a directory called Sockets
.
1set(This Sockets) 2set(Sources 3 include/Sockets/ClientSocket.hpp 4 include/Sockets/DatagramSocket.hpp 5 include/Sockets/ServerSocket.hpp 6 src/Abstractions.hpp 7 src/ClientSocket.cpp 8 src/Connection.hpp 9 src/Connection.cpp 10 src/DatagramSocket.cpp 11 src/ServerSocket.cpp 12) 13if(MSVC) 14 list(APPEND Sources 15 src/AbstractionsWin32.cpp 16 ) 17else() 18 list(APPEND Sources 19 src/AbstractionsPosix.cpp 20 src/PipeSignal.cpp 21 src/PipeSignal.hpp 22 ) 23endif() 24 25add_library(${This} ${Sources}) 26set_target_properties(${This} PROPERTIES FOLDER Libraries) 27target_include_directories(${This} PUBLIC include) 28if(UNIX) 29 target_link_libraries(${This} PUBLIC pthread) 30endif(UNIX)
From the section The-Main-Library:-Networking we have read a use case
file(GLOB_RECURSE SOURCES src/*.cpp)
and plugged this into add_library
. However, the Do's and Don'ts states that Don't GLOB files because:
Make or another tool will not know if you add files without rerunning CMake. Note that CMake 3.12 adds a
CONFIGURE_DEPENDS
flag that makes this far better if you need to use it.
In other words, listing the source files explicitly will be a better practice.