1. Resource to Learn CMake
2. 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)
3. Common Commands
-
add_library(one two.cpp three.h)- Declare a target called
onethat refers to the current directory, and list all sources files to be compiled. Only the compilation unittwo.cppwill 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_libraryandadd_targetat the same time. In other words, we should separatemain.cppand "source files" in separate folder. -
For example, we can separate like
some_proj/app/main.cppandsome_proj/src/some_lib/some_file.cppThen writesome_proj/app/CMakeLists.txtsome_proj/src/CMakeLists.txtsome_proj/src/some_lib/CMakeLists.txtseparately. Note thatsome_proj/src/CMakeLists.txtcan be as simple as just one line
add_subdirectory(some_lib)as it helps point out which directory contains a
CMakeLists.txtto look at.
-
-
add_subdirectory(src)- It tells cmake compiler which directory to look for and execute a
CMakeLists.txt. If the directorysrccontains noCMakeLists.txtfile,cmakewill 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
onehave 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 thatonehave already included. -
If the include dir contains
include/four/five.h, then we can includefour/five.hin 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_directoriesis 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
anotherand its downstream linkers will need libraryonein the compilation process. -
Ya we have included the
includedirectory but very likely it just contains function declarations, we need the function body definitions by linking those libraries linking the source files. -
if
PUBLICis replaced byPRIVATE, it indicates that the downstream linkers ofanotherdo not need the libraryone, andanotheris the only target that needsone.
-
-
target_compile_definitions(one PRIVATE SOME_CONSTANT="${SOME_CONSTANT}")- Target library
onewe have a constantSOME_CONSTANTdefined by using#definein 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_CONSTANTis the variable name. -
We set
""as a default value. -
The value
<value>can also be passed by-Dargument:cmake -B build -DSOME_CONSTANT=ABCDEFG -
STRINGdefined 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
4. 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_PATHpoints to that cmake folderincludeis used to include the cmake filecmake/AddGitSubmodule.cmake. Exmaple of a.cmakefile:
# 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)
5. Which Folder to Create CMakeLists.txt?
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_subdirectorycommands.
- It can consists of simply the
- Source file subdirectories
src/one,src/two, ....- Each of the subdirectory should consists of
add_libraryfor linkage andtarget_include_directoriesto include corresponding header files. - If the project structure is simpler, the rules to
src/<lib_name>/CMakeLists.txtcan 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_directoriessomewhere else once needed.
- Directory that contains just header files needs no
6. Show all Constants in a CMake Project
cmake -LAH
7. 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
printfunction will print defined constant andprint_envwill print the defined environment variable.
8. Graph Visualization of Dependencies Between cmake Files
cmake Files8.1. Install graphviz
graphvizFor windows we can download a .msi file for installation HERE. For mac we run brew install graphviz.
8.2. MakeFile
8.2.1. 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:
8.2.2. Cmake Erorr: incompatible versions of the cygwin DLL
incompatible versions of the cygwin DLLIt 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.
9. Cross Platform Built Command
9.1. Source of Study
9.2. 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 .
9.3. How to Know Which Generator (specified by -G) is Available?
-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. ...
10. External Library (CMake Project)
10.1. 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.
10.2. By FetchContent
By adding include(FetchContent) in our CMakeLists.txt we can import functions
FetchContent_DeclareFetchContent_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.txtof 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"
10.3. By conan
conan10.3.1. 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.txtmessage("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.
10.3.2. Remarks to conan
conanI 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.
11. CMake Examples
11.1. ChatClient TCP Server
11.1.1. Repo and Video
The whole project implementes a chatting function between multiple clients.
This blog post focuses on the CMakeLists.txt files.
11.1.2. 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)
11.1.3. 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} )
11.1.4. 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 )
11.1.5. 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 )
11.2. Examples from Other Project
11.2.1. 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_DEPENDSflag that makes this far better if you need to use it.
In other words, listing the source files explicitly will be a better practice.











