Mastering CMake (2)

Setting Up Your First CMake Project

cmake_minimum_required(VERSION 3.20)
project(Hello)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_executable(Hello hello.cpp)

project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [DESCRIPTION <project-description-string>]
        [HOMEPAGE_URL <url-string>]
        [LANGUAGES <language-name>...])

cmake_minimum_required(VERSION 3.20.0)
project(Rental CXX)
add_executable(Rental
               main.cpp
               cars/car.cpp  
               # more files in other directories 
)

Working with Targets

Essentially, it’s a recipe that a buildsystem uses to compile a list of files into another file. It can be a .cpp implementation file compiled into an .o object file, a group of .o files packaged into an .a static library, and many other combinations.

All that’s required is an add_executable() command with the name of the executable target and a list of the files that are to be its elements

In CMake, we can create a target using one of three commands:

  • add_executable()
  • add_library()
  • add_custom_target()

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [<source>...])

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 ...])

for build you can specify a target or build all project

--build binary_directroy --target [all|target]

add source to target

add_executable(main main.cpp)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  target_sources(main PRIVATE gui_linux.cpp)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
  target_sources(main PRIVATE gui_windows.cpp)
endif()

preprocessing definitions

add paths to include

target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [item1...]
  [<INTERFACE|PUBLIC|PRIVATE> [item2...] ...])

add definition

set(VAR 8)
add_executable(defined definitions.cpp)
target_compile_definitions(defined PRIVATE ABC
  "DEF=${VAR}")
int main() {
#if defined(ABC)
    std::cout << "ABC is defined!" << std::endl;
#endif
#if (DEF < 2*4-3)
    std::cout << "DEF is greater than 5!" << std::endl;
#endif
}

compiler flags

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DNO_DEBUG")
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

Managing Dependencies with CMake

  • A static library has a .a extension on Unix-like systems and .lib on Windows.
  • Shared libraries have a .so extension on Unix-like systems and .dll on Windows.

if you install library like

apt install protobuf-compiler libprotobuf-dev

find_package(Protobuf REQUIRED)
target_link_libraries(main PRIVATE ${Protobuf_LIBRARIES})
target_include_directories(main PRIVATE 
  ${Protobuf_INCLUDE_DIRS}
  ${CMAKE_CURRENT_BINARY_DIR})

find_package(Protobuf REQUIRED) asks CMake to run the bundled FindProtobuf.cmake find-module and set up the Protobuf library for us. That find-module will scan commonly used paths and (because we provided the REQUIRED keyword) terminate if a library is not found. It will also specify useful variables and functions (such as the one on the next line).
target_link_libraries adds libraries (static or shared) found by find_package() to the linking command of our main target.

  • <PKG_NAME>_FOUND
  • <PKG_NAME>_INCLUDE_DIRS or <PKG_NAME>_INCLUDES
  • <PKG_NAME>_LIBRARIES or <PKG_NAME>_LIBRARIES or <PKG_NAME>_LIBS
  • <PKG_NAME>_DEFINITIONS
  • IMPORTED targets specified by the find-module or config-file

find_package(<Name> [version] [EXACT] [QUIET] [REQUIRED])

for cmake files have format <package>Config.cmake

find_package(Foo CONFIG REQUIRED)
target_link_libraries(boo foo)
add_library(MyLib SHARED ./lib/MyLib.cpp)
target_link_directories(MyLib PUBLIC ./lib)
target_include_directories(MyLib PUBLIC ./lib)
find_package(TBB REQUIRED)
target_link_libraries(MyLib PUBLIC tbb)

#libraries linked to MyLib target like tbb will used inside test target because it linked with MyLib and MyLib is public link with tbb

add_executable(test main.cpp ${res_files})
find_package(Qt6 REQUIRED COMPONENTS Core)
target_link_libraries(test PRIVATE Qt6::Core)
target_link_libraries(test  PUBLIC MyLib)

Mastering CMake (1)

Command Lines

Generating a project buildsystem

cmake [<options>] -S <path-to-source> -B <path-to-build>
cmake [<options>] <path-to-source>
cmake [<options>] <path-to-existing-build>
#Build in the ./build directory, and use a source from the current directory:
cmake -B build
#Build in the current directory, but take the source from one directory up (note that -S is optional):
cmake -S ..

choose generator
This can be overridden by the CMAKE_GENERATOR environment variable or by specifying the generator directly on the command line

cmake -G <generator-name> <path-to-source>

We can provide a path to the CMake script, which (only) contains a list of set() commands to specify variables that will be used to initialize an empty build tree.

cmake -D <var>[:<type>]=<value> <path-to-source>
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release

to build project

cmake --build <dir>

Options for parallel builds

cmake --build <dir> --parallel [<number-of-jobs>]
cmake --build <dir> -j [<number-of-jobs>]

CMake Language

 the execution begins from the root file of the source tree (CMakeLists.txt) or a .cmake script file that was passed as an argument to cmake

Comments

# single-line comments start with a hash sign "#"
# they can be placed on an empty line
message("Hi"); # or after a command like here.
#[=[ 
bracket comment
  #[[
    nested bracket comment
  #]]
#]=]

Command invocations

command(argument1 "argument2" argument3) # comment
#[[ multiline comment ]] 

double qouted

message(${OpenCL_LIBRARY})
message("1. escape sequence: \" \n in a quoted argument")
message("2. multi...
  line")
message("3. and a variable reference: ${CMAKE_VERSION}")

Working with variables

1- Variable names are case-sensitive and can include almost any character.
2-All variables are stored internally as strings, even if some commands can interpret them as values of other data types (even lists!).
3-The basic variable manipulation commands are set() and unset(), but there are other commands that can affect variables, such as string() and list().

set(MyString1 "Text1")
set([[My String2]] "Text2")
set("My String 3" "Text3")
message(${MyString1})
message(${My\ String2})
message(${My\ String\ 3})
  • If the following reference is encountered – ${MyOuter${MyInner}} – CMake will try to evaluate MyInner first, rather than searching for a variable named MyOuter${MyInner}.
  • If the MyInner variable is successfully expanded, CMake will repeat the expansion process until no further expansion is possible.
  • The ${} syntax is used to reference normal or cache variables.
  • The $ENV{} syntax is used to reference environment variables.
  • The $CACHE{} syntax is used to reference cache variables.
set(ENV{CXX} "clang++")
cmake_minimum_required(VERSION 3.20.0)
project(Environment)
message("generated with " $ENV{myenv})

#!/bin/bash
export myenv=first
echo myenv is now $myenv
cmake -B build .
cd build
export myenv=second
echo myenv is now $myenv
cmake --build .
$ ./build.sh | grep -v "\-\-"
myenv is now first
generated with first
myenv is now second
Scanning dependencies of target EchoEnv
myenv in build is first
Built target EchoEnv

scope of variables

function(Inner)
  message("  > Inner: ${V}")
  set(V 3)
  message("  < Inner: ${V}")
endfunction()
function(Outer)
  message(" > Outer: ${V}")
  set(V 2)
  Inner()
  message(" < Outer: ${V}")
endfunction()
set(V 1)
message("> Global: ${V}")
Outer()
message("< Global: ${V}")
> Global: 1
 > Outer: 1
  > Inner: 2
  < Inner: 3
 < Outer: 2
< Global: 1
set(V 3 PARENT_SCOPE)
> Global: 1
 > Outer: 1
  > Inner: 2
  < Inner: 2
 < Outer: 3
< Global: 1

Lists

set(myList 1 2 3 4)
set(myList "a;list;of;five;elements")
set(myList a list "of;five;elements")

message("the list is:" ${myList}) 

The message() command will receive here six arguments: “the list is:“, “a“, “list“, “of“, “five“, “elements“.

list(LENGTH <list> <out-var>)
list(GET <list> <element index> [<index> ...] <out-var>)
list(JOIN <list> <glue> <out-var>)
list(SUBLIST <list> <begin> <length> <out-var>)
list(FIND <list> <value> <out-var>)
list(APPEND <list> [<element>...])
list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>)
list(INSERT <list> <index> [<element>...])
list(POP_BACK <list> [<out-var>...])
list(POP_FRONT <list> [<out-var>...])
list(PREPEND <list> [<element>...])
list(REMOVE_ITEM <list> <value>...)
list(REMOVE_AT <list> <index>...)
list(REMOVE_DUPLICATES <list>)
list(TRANSFORM <list> <ACTION> [...])
list(REVERSE <list>)
list(SORT <list> [...])

Understanding control structures in CMake

if(<condition>)
  <commands>
elseif(<condition>) # optional block, can be repeated
  <commands>
else()              # optional block
  <commands>
endif()

The provided <condition> expression is evaluated according to a very simple syntax.

The same syntax is valid for if()elseif(), and while() commands.

The if() conditions support the NOTAND, and OR logical operators:

  • NOT <condition>
  • <condition> AND <condition>
  • <condition> OR <condition>
  • (<condition>) AND (<condition> OR (<condition>))
  • EQUALLESSLESS_EQUALGREATER, and GREATER_EQUAL
  • “A” STREQUAL “${B}”
  • <file1> IS_NEWER_THAN <file2>: Checks which file is newe
  •  <VARIABLE|STRING> IN_LIST <VARIABLE>
  • <VARIABLE|STRING> MATCHES <regex>
set(VAR1 FALSE)
set(VAR2 "VAR1")
if(${VAR2})
  • ONYYES, or TRUE
  • A non-zero number
  • OFFNOFALSENIGNORENOTFOUND
  • A string ending with -NOTFOUND
  • An empty string
  • Zero

Loops

while(<condition>)
  <commands>
endwhile()
foreach(<loop_variable> IN [LISTS <lists>] [ITEMS <items>])
set(MY_LIST 1 2 3) #set(MY_LIST "1;2;3")
foreach(VAR IN LISTS MY_LIST)
  message(${VAR})
endforeach()

Command definitions

  • macro() command works more like a find-and-replace instruction than an actual subroutine call such as function().
  • The function() command creates a separate scope for local variables, unlike the macro() command, which works in the variable scope of a caller.
macro(<name> [<argument>…])
  <commands>
endmacro()
function(<name> [<argument>…])
  <commands>
endfunction()
macro(MyMacro myVar)
  set(myVar "new value")
  message("argument: ${myVar}")
endmacro()
set(myVar "first value")
message("myVar is now: ${myVar}")
MyMacro("called value")
message("myVar is now: ${myVar}")

Useful commands

execute_process to run external command

execute_process(COMMAND echo "hello world")

execute_process(COMMAND <cmd1> [args1...]]
                [COMMAND <cmd2> [args2...] [...]]
                [WORKING_DIRECTORY <directory>]
                [TIMEOUT <seconds>]
                [RESULT_VARIABLE <variable>]
                [OUTPUT_VARIABLE <variable>]
                [ERROR_VARIABLE <variable>]
                [INPUT_FILE <file>]
                [OUTPUT_FILE <file>]
                [ERROR_FILE <file>]
                [OUTPUT_QUIET]
                [ERROR_QUIET]
                [OUTPUT_STRIP_TRAILING_WHITESPACE]
                [ERROR_STRIP_TRAILING_WHITESPACE])

file to read or write from or to file

file(READ <filename> <out-var> [...])
file({WRITE | APPEND} <filename> <content>...)
file(DOWNLOAD <url> [<file>] [...])
file(GLOB_RECURSE QMLS "${QML_FOLDER}/*.qml") #get files with format as QMLS list
file(COPY ${QMLS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/${QML_FOLDER}) #Copy files

include to add and execute other CMake file

include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>])

CMake

First, modify the CMakeLists.txt file to use the project() command to set the project name and version number.

cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

The easiest way to enable support for a specific C++ standard in CMake is by using the CMAKE_CXX_STANDARD variable. For this tutorial, set the CMAKE_CXX_STANDARD variable in the CMakeLists.txt file to 11 and CMAKE_CXX_STANDARD_REQUIRED to True. Make sure to add the CMAKE_CXX_STANDARD declarations above the call to add_executable.

cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(COMPILE_OPTIONS -std=c++11)

add executable files

add_executable (Tutorial "testCmake.cpp" "testCmake.h" "A.h" "A.cpp")
#the output or target of build is Tutorial

once you change the version of standard c++ to 20 you can use execution library

#include <iostream>
#include <algorithm>
#include <vector>
#include <execution>

int main(int argc, char* argv[])
{
	std::vector<uint16_t> vec{ 34,545,23,56,565,54,24,54,6,7,4,5334,5,65,7,5 };
	std::for_each(std::execution::par, vec.begin(), vec.end(), [](uint16_t v) {std::cout << v << std::endl; });
	std::cout << "!\n";
}

toolchain

CMake uses a toolchain of utilities to compile, link libraries and create archives, and other tasks to drive the build.
there are two ways to add it inside cmakeLists

set( CMAKE_TOOLCHAIN_FILE "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" )

or by command line

cmake -DCMAKE_TOOLCHAIN_FILE=/foo/bar.cmake <other args>

prefix path used to help cmake for commands like find_package or find_path

cmake -DCMAKE_PREFIX_PATH=~/deliveries/hiphop

include files and libraries

after you add CMAKE_TOOLCHAIN_FILE OR CMAKE_PREFIX_PATH to your project actually you can looking for path or library or package and add them to your cmakelist file lets start with find_path

how to add include path

#looking for path of boost/coroutine/all.hpp and save it in variable 
LIBS_PATH 
find_path(LIBS_PATH boost/coroutine/all.hpp) 

#then add it to executable target project in our situation is Tutorial
#private is mean all directories added to this target cannot access by other target
target_include_directories(Tutorial PRIVATE
                           ${LIBS_PATH}
                          )
#the difference between target_include_directories and include_directories
#the first one affect on the target only and the second one affect on the all targets in cmakelist
include_directories(${LIBS_PATH})

Packages and Libraries

Packages provide dependency information to CMake based buildsystems. Packages are found with the find_package() command. The result of using find_package() is either a set of IMPORTED targets, or a set of variables corresponding to build-relevant information.
Imagine you want to use zlib in your project, you need to find the header file zlib.h, and the library libz.so (on Linux). You can use the low-level cmake commands find_path and find_library to find them, or you can use find_package(ZLIB). The later command will try to find out all what is necessary to use zlib. It can be extra macro definitions, or dependencies.
find_package: when the CMake command find_package(SomeThing) is called, as says the documentation, there are two possibility: the module mode (that searches for a file FindSomeThing.cmake), or the config mode (that searches for a file named SomeThingConfig.cmake). 

#add zlib
find_package (ZLIB REQUIRED)
if (ZLIB_FOUND)
  include_directories(${ZLIB_INCLUDE_DIRS})
  target_link_libraries (Tutorial ${ZLIB_LIBRARIES})
endif (ZLIB_FOUND)

#add opencv
# Find Package
find_package( OpenCV REQUIRED )
if( OpenCV_FOUND )
  # Additional Include Directories
  include_directories( ${OpenCV_INCLUDE_DIRS} )
  # Additional Library Directories
  link_directories( ${OpenCV_LIB_DIR} )
  # Additional Dependencies
  target_link_libraries( project ${OpenCV_LIBS} )
endif()

#add coroutine library from boost
find_package(Boost REQUIRED COMPONENTS coroutine)
target_link_libraries(testCmake PRIVATE Boost::coroutine)

#if is there no dependencies like zlib you can use 
find_library(ZLIB_LIB zlib)
target_link_libraries (testCmake ${ZLIB_LIB})

Qt With Cmake

cmake_minimum_required(VERSION 3.1.0)

project(helloworld VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_PREFIX_PATH "/home/rip/Qt/5.12.1/gcc_64/lib/cmake")
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

if(CMAKE_VERSION VERSION_LESS "3.7.0")
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
endif()

find_package(Qt5 COMPONENTS Widgets REQUIRED)

add_executable(helloworld
    mainwindow.ui
    mainwindow.cpp
    main.cpp
    resources.qrc
)

target_link_libraries(helloworld Qt5::Widgets)

if you never add toolchain or prefix path

cmake_minimum_required (VERSION 3.9)

project ("testCmake" VERSION 1)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_executable(testCmake "src/main.cpp")
include_directories("C:/dev/vcpkg/installed/x64-windows")
find_path(ZLIB_PATH "zlib.h")
include_directories(${ZLIB_PATH})
link_directories("C:/dev/vcpkg/installed/x64-windows/lib/")
FIND_LIBRARY(mylib zlib)
message("zlib in ${mylib}")
TARGET_LINK_LIBRARIES(testCmake ${mylib})

How To Create library and attach it to our project

create subdirectory and add CMakeLists.txt to it

ADD_LIBRARY(MyLib STATIC|SHARED MyLib.cpp MyLib.h)

then back to main CmakeLists and add the folder as asubdirectory and link to it library dont warry cmake automaticly add .lib or .a to file and the last thing you can add directory as include directory

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/MyLib)
target_link_libraries(Tutorial PRIVATE MyLib)
target_include_directories(Tutorial PRIVATE MyLib)

#or 

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/MyLib)
target_link_libraries(Tutorial PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/MyLib/MyLib.lib)
target_include_directories(Tutorial PRIVATE MyLib)

cpack

#add this to end of CMakeLists
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/license.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "1")
set(CPACK_PACKAGE_VERSION_MINOR "2")
include(CPack)

after finished from build project you can call cpack command inside build directory

Miscellaneous

example Add all files in the project to executable

set(FORMS_DIR "${CMAKE_SOURCE_DIR}/forms")
set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include")
set(SOURCE_DIR "${CMAKE_SOURCE_DIR}/src")

include_directories(${FORMS_DIR})
include_directories(${INCLUDE_DIR})
include_directories(${SOURCE_DIR})

file(GLOB_RECURSE SOURCES
    "${FORMS_DIR}/*.ui"
    "${FORMS_DIR}/*.qrc"
    "${INCLUDE_DIR}/*.h"
    "${SOURCE_DIR}/*.cpp"
)

add_executable(MY_PROJECT ${SOURCES})

if (system)

if (WIN32)
    #do something
elseif (UNIX)
    #do something
endif ()
#other example
if(UNIX AND NOT APPLE)
    # for Linux, BSD, Solaris, Minix
endif()
#other example
if (MSVC)
    #do something
endif (MSVC)

copy files to binary folder

set(QML_FOLDER "qmls")
file(GLOB_RECURSE QMLS "${QML_FOLDER}/*.qml")
file(COPY ${QMLS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/${QML_FOLDER})

check compiler

if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    # using Clang
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    # using GCC
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
    # using Intel C++
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    # using Visual Studio C++
endif()

set compiler flags

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DNO_DEBUG")
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")