3.8. Control structures

Examples on GitHub

3.8.1. Conditional blocks

3.8.1.1. Simple examples

Example of using an if command with NO/YES constants and variables with NO/YES values:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

if(YES)
  message("Condition 1")
endif()

if(NO)
  message("Condition 2")
endif()

set(A "YES")
set(B "NO")

if(A)
  message("Condition 3")
endif()

if(B)
  message("Condition 4")
endif()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hif-simple -B_builds
Condition 1
Condition 3
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

Adding else/elseif:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

set(A "TRUE")
set(B "FALSE")

if(A)
  message("Condition 1")
else()
  message("Condition 2")
endif()

if(B)
  message("Condition 3")
else()
  message("Condition 4")
endif()

set(C "OFF")
set(D "ON")

if(C)
  message("Condition 5")
elseif(D)
  message("Condition 6")
else()
  message("Condition 7")
endif()

set(E "0")
set(F "0")

if(E)
  message("Condition 8")
elseif(F)
  message("Condition 9")
else()
  message("Condition 10")
endif()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hif-else -B_builds
Condition 1
Condition 4
Condition 6
Condition 10
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.1.2. CMP0054

Some of the if commands accept <variable|string> arguments. This may lead to quite surprising behavior.

For example if we have a variable A and it is set to an empty string we can check it with:

set(A "")
if(A STREQUAL "")
  message("Value of A is empty string")
endif()

You can save the name of the variable in another variable and do the same:

set(A "")
set(B "A") # save name of the variable
if(${B} STREQUAL "")
  message("Value of ${B} is an empty string")
endif()

If a CMake policy CMP0054 is set to OLD or not present at all (before CMake 3.1), this operation ignores quotes:

set(A "")
set(B "A") # save name of the variable
if("${B}" STREQUAL "") # same as 'if(${B} STREQUAL "")'
  message("Value of ${B} is an empty string")
endif()

It means an operation depends on the context: is a variable with the name ${B} present in current scope or not?

cmake_minimum_required(VERSION 3.0)
project(foo LANGUAGES NONE)

set("Jane Doe" "")
set(A "Jane Doe")

message("A = ${A}")

if("${A}" STREQUAL "")
  message("A is empty")
endif()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hcmp0054-confuse -B_builds
A = Jane Doe
A is empty
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.1.3. Try fix

Since CMake accepts any names of the variables you can’t filter out <variable> from <variable|string> by adding “reserved” symbols:

cmake_minimum_required(VERSION 3.0)
project(foo LANGUAGES NONE)

set("Jane Doe" "")
set("xJane Doe" "x")
set("!Jane Doe" "!")
set(" Jane Doe" " ")

set(A "Jane Doe")

message("A = ${A}")

if("x${A}" STREQUAL "x")
  message("A is empty (1)")
endif()

if("!${A}" STREQUAL "!")
  message("A is empty (2)")
endif()

if(" ${A}" STREQUAL " ")
  message("A is empty (3)")
endif()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Htry-fix -B_builds
A = Jane Doe
A is empty (1)
A is empty (2)
A is empty (3)
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.1.4. Fix

To avoid such issues you should use CMake 3.1 and a CMP0054 policy:

cmake_minimum_required(VERSION 3.1)
project(foo LANGUAGES NONE)

set("Jane Doe" "")
set("xJane Doe" "x")
set("!Jane Doe" "!")
set(" Jane Doe" " ")

set(A "Jane Doe")

message("A = ${A}")

if("x${A}" STREQUAL "x")
  message("A is empty (1)")
endif()

if("!${A}" STREQUAL "!")
  message("A is empty (2)")
endif()

if(" ${A}" STREQUAL " ")
  message("A is empty (3)")
endif()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hcmp0054-fix -B_builds
A = Jane Doe
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.1.5. Workaround

For CMake before 3.1 as a workaround you can use a string(COMPARE EQUAL ...) command:

cmake_minimum_required(VERSION 3.0)
project(foo LANGUAGES NONE)

set("Jane Doe" "")
set("xJane Doe" "x")
set("!Jane Doe" "!")
set(" Jane Doe" " ")

set(A "Jane Doe")

message("A = ${A}")

string(COMPARE EQUAL "${A}" "" is_empty)
if(is_empty)
  message("A is empty")
else()
  message("A is not empty")
endif()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hcmp0054-workaround -B_builds
A = Jane Doe
A is not empty
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.2. Loops

3.8.2.1. foreach

CMake documentation

Example of a foreach(<variable> <list>) command:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

message("Explicit list:")
foreach(item "A" "B" "C")
  message("  ${item}")
endforeach()

message("Dereferenced list:")
set(mylist "foo" "boo" "bar")
foreach(x ${mylist})
  message("  ${x}")
endforeach()

message("Empty list")
foreach(x)
  message("  ${x}")
endforeach()

message("Dereferenced empty list")
set(empty_list)
foreach(x ${empty_list})
  message("  ${x}")
endforeach()

message("List with empty element:")
foreach(i "")
  message("  '${i}'")
endforeach()

message("Separate lists:")
set(mylist a b c)
foreach(x "${mylist}" "x;y;z")
  message("  ${x}")
endforeach()

message("Combined list:")
set(combined_list "${mylist}" "x;y;z")
foreach(x ${combined_list})
  message("  ${x}")
endforeach()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hforeach -B_builds
Explicit list:
  A
  B
  C
Dereferenced list:
  foo
  boo
  bar
Empty list
Dereferenced empty list
List with empty element:
  ''
Separate lists:
  a;b;c
  x;y;z
Combined list:
  a
  b
  c
  x
  y
  z
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

As you may notice foreach(x "${mylist}" "x;y;z") is not treated as a single list but as a list with two elements: ${mylist} and x;y;z. If you want to merge two lists you should do it explicitly set(combined_list "${mylist}" "x;y;z") or use foreach(x ${mylist} x y z) form.

3.8.2.2. foreach with range

Example of usage of a foreach(... RANGE ...) command:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

message("Simple range:")
foreach(x RANGE 10)
  message("  ${x}")
endforeach()

message("Range with limits:")
foreach(x RANGE 3 8)
  message("  ${x}")
endforeach()

message("Range with step:")
foreach(x RANGE 10 14 2)
  message("  ${x}")
endforeach()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hforeach-range -B_builds
Simple range:
  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
Range with limits:
  3
  4
  5
  6
  7
  8
Range with step:
  10
  12
  14
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.2.3. while

Example of usage of a while command:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

set(a "")
set(condition TRUE)

message("Loop with condition as variable:")
while(condition)
  set(a "${a}x")
  message("  a = ${a}")
  string(COMPARE NOTEQUAL "${a}" "xxxxx" condition)
endwhile()

set(a "")

message("Loop with explicit condition:")
while(NOT a STREQUAL "xxxxx")
  set(a "${a}x")
  message("  a = ${a}")
endwhile()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hwhile -B_builds
Loop with condition as variable:
  a = x
  a = xx
  a = xxx
  a = xxxx
  a = xxxxx
Loop with explicit condition:
  a = x
  a = xx
  a = xxx
  a = xxxx
  a = xxxxx
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.2.4. break

CMake documentation

Exit from a loop with a break command:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

message("Stop 'while' loop:")
set(a "")
while(TRUE)
  set(a "${a}x")
  message("  ${a}")
  string(COMPARE EQUAL "${a}" "xxx" done)
  if(done)
    break()
  endif()
endwhile()

message("Stop 'foreach' loop:")
foreach(x RANGE 10)
  message("  ${x}")
  if(x EQUAL 4)
    break()
  endif()
endforeach()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hbreak -B_builds
Stop 'while' loop:
  x
  xx
  xxx
Stop 'foreach' loop:
  0
  1
  2
  3
  4
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.2.5. continue

Since CMake 3.2 it’s possible to continue the loop:

cmake_minimum_required(VERSION 3.2)
project(foo NONE)

message("Loop with 'continue':")
foreach(x RANGE 10)
  if(x EQUAL 2 OR x EQUAL 5)
    message("  skip ${x}")
    continue()
  endif()
  message("  process ${x}")
endforeach()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hcontinue -B_builds
Loop with 'continue':
  process 0
  process 1
  skip 2
  process 3
  process 4
  skip 5
  process 6
  process 7
  process 8
  process 9
  process 10
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

CMake documentation

3.8.3. Functions

CMake documentation

3.8.3.1. Simple

Function without arguments:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

function(foo)
  message("Calling 'foo' function")
endfunction()

foo()
foo()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hsimple-function -B_builds
Calling 'foo' function
Calling 'foo' function
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.3.2. With arguments

Function with arguments and example of ARGV*, ARGC, ARGN usage:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

function(foo x y z)
  message("Calling function 'foo':")
  message("  x = ${x}")
  message("  y = ${y}")
  message("  z = ${z}")
endfunction()

function(boo x y z)
  message("Calling function 'boo':")
  message("  x = ${ARGV0}")
  message("  y = ${ARGV1}")
  message("  z = ${ARGV2}")
  message("  total = ${ARGC}")
endfunction()

function(bar x y z)
  message("Calling function 'bar':")
  message("  All = ${ARGV}")
  message("  Unexpected = ${ARGN}")
endfunction()

foo("1" "2" "3")
boo("4" "5" "6")
bar("7" "8" "9" "10" "11")
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hfunction-args -B_builds
Calling function 'foo':
  x = 1
  y = 2
  z = 3
Calling function 'boo':
  x = 4
  y = 5
  z = 6
  total = 3
Calling function 'bar':
  All = 7;8;9;10;11
  Unexpected = 10;11
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.3.3. CMake style

CMake documentation

cmake_parse_arguments function can be used for parsing:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

include(CMakeParseArguments) # cmake_parse_arguments

function(foo)
  set(optional FOO BOO)
  set(one X Y Z)
  set(multiple L1 L2)

  # Introduce:
  # * x_FOO
  # * x_BOO
  # * x_X
  # * x_Y
  # * x_Z
  # * x_L1
  # * x_L2
  cmake_parse_arguments(x "${optional}" "${one}" "${multiple}" "${ARGV}")

  string(COMPARE NOTEQUAL "${x_UNPARSED_ARGUMENTS}" "" has_unparsed)
  if(has_unparsed)
    message(FATAL_ERROR "Unparsed arguments: ${x_UNPARSED_ARGUMENTS}")
  endif()

  message("FOO: ${x_FOO}")
  message("BOO: ${x_BOO}")
  message("X: ${x_X}")
  message("Y: ${x_Y}")
  message("Z: ${x_Z}")

  message("L1:")
  foreach(item ${x_L1})
    message("  ${item}")
  endforeach()

  message("L2:")
  foreach(item ${x_L2})
    message("  ${item}")
  endforeach()
endfunction()

function(boo)
  set(optional "")
  set(one PARAM1 PARAM2)
  set(multiple "")

  # Introduce:
  # * foo_PARAM1
  # * foo_PARAM2
  cmake_parse_arguments(foo "${optional}" "${one}" "${multiple}" "${ARGV}")

  string(COMPARE NOTEQUAL "${foo_UNPARSED_ARGUMENTS}" "" has_unparsed)
  if(has_unparsed)
    message(FATAL_ERROR "Unparsed arguments: ${foo_UNPARSED_ARGUMENTS}")
  endif()

  message("{ param1, param2 } = { ${foo_PARAM1}, ${foo_PARAM2} }")
endfunction()

message("*** Run (1) ***")
foo(L1 item1 item2 item3 X value FOO)

message("*** Run (2) ***")
foo(L2 item1 item3 Y abc Z 123 FOO BOO)

message("*** Run (3) ***")
foo(L1 item1 L1 item2 L1 item3)

message("*** Run (4) ***")
boo(PARAM1 123 PARAM2 888)
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hcmake-style -B_builds
*** Run (1) ***
FOO: TRUE
BOO: FALSE
X: value
Y:
Z:
L1:
  item1
  item2
  item3
L2:
*** Run (2) ***
FOO: TRUE
BOO: TRUE
X:
Y: abc
Z: 123
L1:
L2:
  item1
  item3
*** Run (3) ***
FOO: FALSE
BOO: FALSE
X:
Y:
Z:
L1:
  item1
  item2
  item3
L2:
*** Run (4) ***
{ param1, param2 } = { 123, 888 }
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.3.4. CMake style limitations

Since it’s not possible to create a list with one empty element and because of internal CMakeParseArguments limitations next calls will have equivalent results:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

include(CMakeParseArguments) # cmake_parse_arguments

function(foo)
  set(optional "")
  set(one X)
  set(multiple "")

  # Introduce:
  # * x_X
  cmake_parse_arguments(x "${optional}" "${one}" "${multiple}" "${ARGV}")

  string(COMPARE NOTEQUAL "${x_UNPARSED_ARGUMENTS}" "" has_unparsed)
  if(has_unparsed)
    message(FATAL_ERROR "Unparsed arguments: ${x_UNPARSED_ARGUMENTS}")
  endif()

  if(DEFINED x_X)
    set(is_defined YES)
  else()
    set(is_defined NO)
  endif()

  message("X is defined: ${is_defined}")
  message("X value: '${x_X}'")
endfunction()

message("*** Run (1) ***")
foo(X "")

message("*** Run (2) ***")
foo(X)

message("*** Run (3) ***")
foo()
[examples]> rm -rf _builds
[examples]> cmake -Hcontrol-structures/cmake-style-limitations -B_builds
*** Run (1) ***
X is defined: NO
X value: ''
*** Run (2) ***
X is defined: NO
X value: ''
*** Run (3) ***
X is defined: NO
X value: ''
-- Configuring done
-- Generating done
-- Build files have been written to: /.../examples/_builds

3.8.3.5. Return value

There is no special command to return a value from a function. You can set a variable to the parent scope instead:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

function(boo)
  set(A "123" PARENT_SCOPE)
endfunction()

set(A "333")
message("Before 'boo': ${A}")
boo()
message("After 'boo': ${A}")

function(bar arg1 result)
  set("${result}" "ABC-${arg1}-XYZ" PARENT_SCOPE)
endfunction()

message("Calling 'bar' with arguments: '123' 'var_out'")
bar("123" var_out)
message("Output: ${var_out}")
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hreturn-value -B_builds
Before 'boo': 333
After 'boo': 123
Calling 'bar' with arguments: '123' 'var_out'
Output: ABC-123-XYZ
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.3.6. Return

CMake documentation

You can exit from a function using a return command:

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

function(foo A B)
  if(A)
    message("Exit on A")
    return()
  endif()

  if(B)
    message("Exit on B")
    return()
  endif()

  message("Exit")
endfunction()

foo(YES NO)
foo(NO YES)
foo(NO NO)
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hreturn -B_builds
Exit on A
Exit on B
Exit
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.3.7. CMAKE_CURRENT_LIST_DIR

Value of CMAKE_CURRENT_LIST_FILE and CMAKE_CURRENT_LIST_DIR is set to the file/directory from where the function is called, not the file where the function is defined:

# Top-level CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(foo NONE)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules")

include(foo_run)

foo_run("123")

add_subdirectory(boo)
# boo/CMakeLists.txt

foo_run("abc")
# Module cmake/Modules/foo_run.cmake

set(FOO_RUN_FILE_PATH "${CMAKE_CURRENT_LIST_FILE}")
set(FOO_RUN_DIR_PATH "${CMAKE_CURRENT_LIST_DIR}")

function(foo_run value)
  message("foo_run(${value})")

  message("Called from: ${CMAKE_CURRENT_LIST_DIR}")
  message("Defined in file: ${FOO_RUN_FILE_PATH}")
  message("Defined in directory: ${FOO_RUN_DIR_PATH}")
endfunction()
[control-structures]> rm -rf _builds
[control-structures]> cmake -Hfunction-location -B_builds
foo_run(123)
Called from: /.../control-structures/function-location
Defined in file: /.../control-structures/function-location/cmake/Modules/foo_run.cmake
Defined in directory: /.../control-structures/function-location/cmake/Modules
foo_run(abc)
Called from: /.../control-structures/function-location/boo
Defined in file: /.../control-structures/function-location/cmake/Modules/foo_run.cmake
Defined in directory: /.../control-structures/function-location/cmake/Modules
-- Configuring done
-- Generating done
-- Build files have been written to: /.../control-structures/_builds

3.8.3.8. Recommendation

To avoid function name clashing with functions from another modules do prefix name with the project name. In case if function name will match name of the module you can verify that module used in your code just by simple in-file search (and of course delete it if not):

include(foo_my_module_1)
include(foo_my_module_2)

foo_my_module_1(INPUT1 "abc" INPUT2 123 RESULT result)
foo_my_module_2(INPUT1 "${result}" INPUT2 "xyz")