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>])