Using CMake Generator Expressions to Set Debug/Release Compile Definitions
Learn how to use CMake generator expressions to add MYAPP_DEBUG for Debug and MYAPP_OPTIMIZED for Release builds on a single target, with verification steps and recovery tips.
06 Jan 2026, 21:49 UTC

Desired outcome
You want a single target (executable or library) to receive different compile‑time macros depending on the build configuration: MYAPP_DEBUG for Debug builds and MYAPP_OPTIMIZED for Release builds, without creating separate targets or duplicating source files.
Prerequisites
- CMake version 3.0 or later (generator expressions were introduced in 3.0).
- A project that already uses
add_executableoradd_libraryto create a target, e.g.,myapp. - Basic familiarity with
target_compile_definitions. - Write access to the project’s
CMakeLists.txtfile and permission to runcmakeand the build tool in the build directory.
Procedure
- Open the
CMakeLists.txtfile where the target is defined. - After the
add_executable(myapp …)(oradd_library) line, add a call totarget_compile_definitionsthat uses generator expressions:
# Assuming the target is named myapp
target_compile_definitions(myapp PRIVATE
$<$:MYAPP_DEBUG>
$<$:MYAPP_OPTIMIZED>
)
Each generator expression $<CONDITION:VALUE> evaluates to VALUE only when CONDITION is true. For single‑config generators the condition is evaluated at build‑system generation time using the value of CMAKE_BUILD_TYPE. For multi‑config generators (e.g., Visual Studio, Xcode) the expression is evaluated per configuration when the build system is invoked.
- Re‑run CMake to regenerate the build system. From the project root, assuming an out‑of‑source build:
# Debug build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
# Release build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
If you already have a build directory, you can simply run cmake --build build --config Debug or cmake --build build --config Release after adjusting the cache variable or using a multi‑config generator.
- Build the target as usual, e.g.,
cmake --build build --config Debugorcmake --build build --config Release.
Expected checks
Verify that the compiler invocations contain the expected definitions:
- Inspect the generated
compile_commands.json(if you enabled-DCMAKE_EXPORT_COMPILE_COMMANDS=ON) and look for entries like:
{"arguments": ["/usr/bin/c++", "-DMYAPP_DEBUG", "-std=c++17", "-o", "CMakeFiles/myapp.dir/src/main.cpp.o", "-c", "../src/main.cpp"], "directory": "/path/to/project/build", "file": "../src/main.cpp"}
For a Release build you should see -DMYAPP_OPTIMIZED instead.
- Alternatively, enable verbose makefile output:
make VERBOSE=1 # or ninja -v
Search the output for the macro flags.
Recovery options
If the definitions are missing or appear in the wrong configuration:
- Double‑check the generator expression syntax: there must be no spaces inside the
$<>brackets, and each expression must be a separate argument (as shown). - Confirm the target name in
target_compile_definitionsmatches exactly the name used inadd_executable/add_library. - Ensure no later call to
target_compile_definitions(oradd_compile_definitions) overrides the earlier one. CMake processes calls in order; you can move the call to the end of the target’s definition block to be safe. - If you prefer to avoid ordering issues entirely, create an interface library that holds the definitions and link it:
add_library(myapp_defs INTERFACE)
target_compile_definitions(myapp_defs INTERFACE
$<$:MYAPP_DEBUG>
$<$:MYAPP_OPTIMIZED>
)
target_link_libraries(myapp PRIVATE myapp_defs)
Limitations and practical verification
Generator expressions are evaluated when CMake generates the build system (single‑config) or per configuration (multi‑config). Therefore:
- They cannot be used to give different definitions to individual source files within the same target unless you wrap each file in a separate target‑scoped
target_compile_definitionscall (e.g., viaset_source_files_properties). - Mixing raw text with generator expressions in the same argument can introduce unwanted spaces. Keep each expression as its own argument, or use
generator expressionblocks like$<IF>if you need conditional text.
To confirm the macro is actually defined during compilation, add a small test source file:
// test_macro.cpp
#include
int main() {
#if defined(MYAPP_DEBUG)
std::cout << "Debug build\n";
#elif defined(MYAPP_OPTIMIZED)
std::cout << "Release build\n";
#else
std::cout << "Unknown build\n";
#endif
return 0;
}
Re‑run CMake, build, and execute the resulting binary for both Debug and Release configurations. The printed line should match the expected configuration.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.