Generator expressions

CMake · Advanced · 15 min

They're the correct way to express per-configuration and per-compiler settings that also work in multi-config builds.

Generator expressions look like $<...> and are evaluated during generation, after configuration, separately for each target and each configuration. They're needed because some facts aren't known while CMakeLists.txt runs: in a multi-config build, the configuration isn't chosen until build time, and a target's final file path isn't fixed until generation.

Building blocks:

  • Conditions that yield 1 or 0: $<CONFIG:Debug>, $<CXX_COMPILER_ID:MSVC>, $<PLATFORM_ID:Windows>, $<BOOL:${OPT}>, combined with $<AND:...>, $<OR:...>, $<NOT:...>.
  • $<cond:value> yields value if cond is 1, otherwise nothing; $<IF:cond,a,b> picks one.
  • Information: $<TARGET_FILE:app> the built file's path, $<TARGET_FILE_DIR:app>, $<TARGET_PROPERTY:tgt,prop>.
  • $<BUILD_INTERFACE:...> and $<INSTALL_INTERFACE:...> give different values when a target is used from the build tree or after installation.

Many conditions accept lists: $<CONFIG:Debug,RelWithDebInfo> (3.19+) and $<CXX_COMPILER_ID:GNU,Clang>.

Because evaluation happens later, message() prints the literal $<...> text. To inspect results, use file(GENERATE OUTPUT dbg.txt CONTENT "..."). Keep expressions small; deeply nested ones become unreadable fast, and a helper variable holding a part helps.

Example

add_executable(app main.cpp)

target_compile_definitions(app PRIVATE
  $<$<CONFIG:Debug>:APP_DEBUG=1>                 # only in Debug builds
  APP_PLATFORM="$<PLATFORM_ID>")                  # "Linux", "Windows", ...

set(gcc_like "$<CXX_COMPILER_ID:GNU,Clang,AppleClang>")
target_compile_options(app PRIVATE
  "$<${gcc_like}:-Wall;-Wextra>"                  # quoted: no spaces inside a genex
  $<$<CXX_COMPILER_ID:MSVC>:/W4>
  $<$<AND:${gcc_like},$<CONFIG:Debug>>:-fsanitize=address>)
target_link_options(app PRIVATE
  $<$<AND:${gcc_like},$<CONFIG:Debug>>:-fsanitize=address>)

# copy a data file next to the built executable, wherever it ends up
add_custom_command(TARGET app POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy_if_different
          ${CMAKE_CURRENT_SOURCE_DIR}/config.json $<TARGET_FILE_DIR:app>)

file(GENERATE OUTPUT genex-$<CONFIG>.txt CONTENT "file: $<TARGET_FILE:app>\n")

Remember

  • $<cond:value> inserts value when cond is true for this target and configuration
  • Use $<CONFIG:...> instead of if(CMAKE_BUILD_TYPE) so multi-config works
  • message() can't show them; use file(GENERATE) to debug

Watch out for

  • Putting a space-separated list inside a genex without quoting, which splits the expression into broken pieces.

Check yourself

  1. Why use $<$<CONFIG:Debug>:-DTRACE> instead of if(CMAKE_BUILD_TYPE STREQUAL Debug)?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html