Demystifying Modern CMake: Interface Libraries, Generator Expressions, and Build Phases
Category: C++ & Build Systems
Reading Time: 8 min read
If you've been working with C++ in recent years, you've likely encountered CMake code snippets like this header-only library setup:
add_library(vactor INTERFACE)
add_library(vactor::vactor ALIAS vactor)
target_include_directories(vactor INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
$<INSTALL_INTERFACE:include>
)
While this looks short, it packs in three critical Modern CMake concepts: Interface targets, Generator Expressions, and explicit separation of Build vs. Install environments. Let's break down how this works under the hood.
In traditional CMake, every target compiled source files into .a, .so, or .lib binaries. But header-only C++ libraries don't generate binaries.
By specifying INTERFACE in add_library(vactor INTERFACE), you tell CMake:
"This library has no binary files to compile directly. It only acts as a container for properties (include directories, compile flags, options) that downstream targets will inherit."
How does CMake know which header file to compile?
It doesn't—and doesn't need to! CMake doesn't pass header files directly to compilers. Instead, it passes directory flags (like -I /path/to/headers) to the compiler. The compiler's C++ preprocessor then finds #include <vactor/vactor.hpp> when parsing code.
2. Why the ALIAS Pattern?
Creating an alias target like add_library(vactor::vactor ALIAS vactor) is a modern best practice:
- Namespaced Uniformity: When installing libraries and importing them via
find_package(), CMake targets are usually namespaced (Package::Target). Using an ALIAS ensures internal subprojects use the exact same target name as external consumers.
- Early Error Catching: If you misspell an un-namespaced target in
target_link_libraries(), CMake might assume it's a raw system library name and defer the error to link-time. Namespaced targets with :: trigger immediate CMake configure-time errors.
3. Decoding Generator Expressions ($<...>)
The syntax $<KEYWORD:VALUE> is a CMake Generator Expression. Standard variables like ${MY_VAR} are evaluated sequentially when CMake reads the script. Generator expressions, however, are deferred evaluation rules.
In our snippet:
$<BUILD_INTERFACE:...>: Evaluates to the given path ONLY when building within the local source tree or as a subproject via add_subdirectory().
$<INSTALL_INTERFACE:...>: Evaluates to the given path ONLY when exported and imported via find_package().
Without this separation, absolute build directory paths from your local machine would leak into installed config files, breaking the build on end-user machines!
4. The Four Phases of CMake
To fully grasp generator expressions, it helps to understand that CMake execution spans four separate execution phases:
- Configure Phase (
cmake -S . -B build)
CMake reads CMakeLists.txt line-by-line, runs system checks, evaluates standard variables (${VAR}) and if() logic, constructing the internal target graph.
- Generate Phase (End of
cmake -S . -B build)
CMake evaluates all $<...> generator expressions using the full target graph and writes native build files (Ninja rules, Makefiles, or MSBuild .vcxproj files).
- Build Phase (
cmake --build build)
The underlying build tool (Ninja, Make, MSVC) executes the compiler (g++/clang++) using the generated command-line flags.
- Runtime Phase
The user runs the compiled executable. CMake is no longer active.
Summary: Generator Expression Quick Reference
| Generator Expression |
Description / Usage |
$<CONFIG:Debug> |
Evaluates to 1 if current target configuration is Debug, else 0. |
$<COMPILE_LANGUAGE:CXX> |
Evaluates to 1 if current file is compiled with C++ compiler. |
$<TARGET_FILE:target> |
Returns full output path to the target binary file. |
$<CXX_COMPILER_ID:GNU> |
Evaluates to 1 if using GCC compiler. |
By leveraging INTERFACE targets and generator expressions, you ensure your C++ libraries stay portable, clean, and easy to consume across both local and installed environments!
Comprehensive Guide to CMake Generator Expressions ($<...>)
CMake Generator Expressions are evaluated during build system generation (the Generate Phase) [cite: 1.1.4, 1.2.1]. They allow conditional compilation, string transformation, target queries, and cross-platform flag customization [cite: 1.1.2, 1.2.5].
1. Conditional & Logical Expressions
Boolean generator expressions evaluate to 1 (true) or 0 (false) [cite: 1.2.5]. They are most commonly used inside conditional output blocks like $<$<CONDITION>:true_string> [cite: 1.1.4, 1.2.5].
Conditional Output
| Expression |
Description |
$<$<CONDITION>:string> |
Evaluates to string if CONDITION is 1, otherwise evaluates to an empty string [cite: 1.1.4, 1.2.5]. |
$<IF:condition,true_val,false_val> |
Evaluates to true_val if condition is 1, or false_val if 0 [cite: 1.1.4]. |
Logical Operators
| Expression |
Description |
$<BOOL:string> |
Converts string to 0 or 1 using standard CMake boolean logic (e.g., OFF, FALSE, 0, empty evaluate to 0) [cite: 1.1.1, 1.2.5]. |
$<AND:cond1,cond2,...> |
Evaluates to 1 if all conditions evaluate to 1, otherwise 0 [cite: 1.2.5]. |
$<OR:cond1,cond2,...> |
Evaluates to 1 if at least one condition evaluates to 1, otherwise 0 [cite: 1.2.5]. |
$<NOT:condition> |
Evaluates to 0 if condition is 1, otherwise 1 [cite: 1.2.5]. |
2. Comparisons (Strings, Numbers, Versions)
String & List Comparisons
| Expression |
Description |
$<STREQUAL:str1,str2> |
1 if str1 and str2 are equal (case-sensitive), else 0 [cite: 1.2.5]. |
$<IN_LIST:item,list> |
1 if item is present in the semicolon-separated list, else 0 [cite: 1.2.5]. |
Numeric Comparisons
| Expression |
Description |
$<EQUAL:val1,val2> |
1 if numbers val1 and val2 are equal, else 0 [cite: 1.2.5]. |
$<LESS:val1,val2> |
1 if val1 is strictly less than val2, else 0. |
$<GREATER:val1,val2> |
1 if val1 is strictly greater than val2, else 0. |
$<LESS_EQUAL:val1,val2> |
1 if val1 is less than or equal to val2, else 0. |
$<GREATER_EQUAL:val1,val2> |
1 if val1 is greater than or equal to val2, else 0. |
Version Comparisons
| Expression |
Description |
$<VERSION_EQUAL:v1,v2> |
1 if version v1 equals v2, else 0 [cite: 1.2.5]. |
$<VERSION_LESS:v1,v2> |
1 if version v1 is less than v2, else 0 [cite: 1.2.5]. |
$<VERSION_GREATER:v1,v2> |
1 if version v1 is greater than v2, else 0 [cite: 1.2.5]. |
$<VERSION_LESS_EQUAL:v1,v2> |
1 if v1 is less than or equal to v2, else 0 [cite: 1.2.5]. |
$<VERSION_GREATER_EQUAL:v1,v2> |
1 if v1 is greater than or equal to v2, else 0 [cite: 1.2.5]. |
These queries allow writing cross-platform CMake configurations that adjust compiler flags and build settings automatically [cite: 1.2.5].
| Expression |
Description |
$<CONFIG:config_name> |
1 if current build configuration matches config_name (e.g., Debug, Release) [cite: 1.2.5]. |
$<PLATFORM_ID:id_list> |
1 if the host/target platform matches any ID in the list (e.g., Linux, Windows, Darwin) [cite: 1.2.5]. |
$<C_COMPILER_ID:id_list> |
1 if the C compiler matches an ID in the list (e.g., GNU, Clang, MSVC) [cite: 1.2.5]. |
$<CXX_COMPILER_ID:id_list> |
1 if the C++ compiler matches an ID in the list [cite: 1.2.5]. |
$<C_COMPILER_VERSION:ver> |
1 if C compiler version matches ver [cite: 1.2.5]. |
$<CXX_COMPILER_VERSION:ver> |
1 if C++ compiler version matches ver [cite: 1.2.5]. |
$<COMPILE_LANGUAGE:lang> |
1 if the source file currently being compiled uses language lang (e.g., C, CXX, CUDA) [cite: 1.2.1, 1.2.3]. |
$<COMPILE_LANG_AND_ID:lang,ids> |
1 if language matches lang AND compiler ID matches ids. |
$<COMPILE_FEATURES:features> |
1 if all specified compile features are available for the target [cite: 1.2.2]. |
4. Target & Artifact Queries
These expressions extract build artifact metadata, output filenames, and locations dynamically across all platforms [cite: 1.1.3].
File Paths & Artifact Names
| Expression |
Description |
$<TARGET_FILE:target> |
Full path to the main binary file produced by target (e.g., /usr/lib/libfoo.so or C:/app.exe) [cite: 1.1.3, 1.2.1]. |
$<TARGET_FILE_NAME:target> |
Filename of the target binary file (e.g., app.exe). |
$<TARGET_FILE_DIR:target> |
Directory containing the target binary file [cite: 1.2.1]. |
$<TARGET_LINKER_FILE:target> |
Full path to the file used for linking against target (.lib, .a, .so) [cite: 1.2.1]. |
$<TARGET_LINKER_FILE_NAME:target> |
Filename of the linker file. |
$<TARGET_LINKER_FILE_DIR:target> |
Directory containing the linker file. |
$<TARGET_SONAME_FILE:target> |
Full path to the file with soname (.so.1) [cite: 1.2.1]. |
$<TARGET_PDB_FILE:target> |
Full path to the Visual Studio .pdb debug symbols file. |
Property Queries & Existence
| Expression |
Description |
$<TARGET_PROPERTY:target,prop> |
Value of property prop on target [cite: 1.2.1]. |
$<TARGET_PROPERTY:prop> |
Value of property prop on the target being evaluated [cite: 1.2.1]. |
$<TARGET_NAME_IF_EXISTS:target> |
Returns target if target exists, else empty string. |
$<TARGET_EXISTS:target> |
1 if target exists, else 0. |
$<TARGET_GENEX_EVAL:target,expr> |
Evaluates expr in the context of target [cite: 1.2.1]. |
5. String & List Manipulations
| Expression |
Description |
$<LOWER_CASE:string> |
Converts string to lowercase [cite: 1.1.1]. |
$<UPPER_CASE:string> |
Converts string to uppercase [cite: 1.1.1]. |
$<MAKE_C_IDENTIFIER:string> |
Converts string into a valid C identifier (replaces non-alphanumeric chars with _). |
| Expression |
Description |
$<JOIN:list,glue> |
Joins elements in list with the delimiter string glue [cite: 1.2.1]. |
$<REMOVE_DUPLICATES:list> |
Removes duplicate entries from list. |
$<FILTER:list,operator,regex> |
Filters list entries using an INCLUDE or EXCLUDE regex operator. |
$<LIST:ACTION,list,...> |
Executes list sub-commands (LENGTH, GET, SUBLIST, FIND, TRANSFORM, etc.) [cite: 1.1.1]. |
6. Path & Interface Expressions
Path Operations
| Expression |
Description |
$<PATH:HAS_PARENT_PATH,path> |
1 if path has a parent directory, else 0. |
$<PATH:GET_FILENAME,path> |
Extracts the filename portion from path. |
$<PATH:GET_PARENT_PATH,path> |
Extracts the parent directory path from path. |
$<PATH:NORMAL_PATH,path> |
Returns normalized clean path (resolving . and ..). |
Build & Install Interfaces
| Expression |
Description |
$<BUILD_INTERFACE:paths...> |
Included only when building within the source/build tree [cite: 1.1.4]. |
$<INSTALL_INTERFACE:paths...> |
Included only when consumed from an installed package via find_package() [cite: 1.1.1, 1.1.4]. |
7. Escaping & Special Characters
Because CMake uses characters like , and > for parsing generator expressions, escaping expressions are required when passing literal special characters inside generator expressions [cite: 1.1.2, 1.2.5].
| Generator Expression |
Literal Evaluated String |
$<ANGLE-R> |
> |
$<COMMA> |
, |
$<SEMICOLON> |
; |
$<LOWER_THAN> |
< |
$<GREATER_THAN> |
> |
8. Summary Example
Combining multiple generator expressions for modern target configuration:
target_compile_options(my_app PRIVATE
$<$<AND:$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>,$<CONFIG:Debug>>:-Wall;-Wextra;-Werror>
$<$<AND:$<CXX_COMPILER_ID:MSVC>,$<COMPILE_LANGUAGE:CXX>>:/EHa->
)
Complete Reference: Built-in CMake Generator Expressions ($<...>)
CMake Generator Expressions are evaluated during build system generation (the Generate Phase). They allow conditional compilation, string transformation, target queries, and cross-platform flag customization.
1. Conditional & Logical Keys
Evaluates conditions or performs boolean operations (0 or 1).
| Expression |
Description |
$<$<CONDITION>:value> |
Conditional output (outputs value if CONDITION is 1). |
$<IF:cond,true_val,false_val> |
Conditional branch selector. |
$<BOOL:string> |
Converts string to boolean 0 or 1. |
$<AND:cond1,cond2,...> |
Logical AND operator. |
$<OR:cond1,cond2,...> |
Logical OR operator. |
$<NOT:cond> |
Logical NOT operator. |
2. Comparison Keys
String & List Comparisons
| Expression |
Description |
$<STREQUAL:str1,str2> |
Case-sensitive equality check. |
$<EQUAL:str1,str2> |
Same as STREQUAL (string comparison). |
$<IN_LIST:item,list> |
Checks if item exists inside a CMake list. |
Numeric Comparisons
| Expression |
Description |
$<EQUAL:num1,num2> |
Numeric equality. |
$<LESS:num1,num2> |
Numeric less than (<). |
$<GREATER:num1,num2> |
Numeric greater than (>). |
$<LESS_EQUAL:num1,num2> |
Numeric less than or equal (<=). |
$<GREATER_EQUAL:num1,num2> |
Numeric greater than or equal (>=). |
Version Comparisons
| Expression |
Description |
$<VERSION_EQUAL:v1,v2> |
Version string equality (=). |
$<VERSION_LESS:v1,v2> |
Version string less than (<). |
$<VERSION_GREATER:v1,v2> |
Version string greater than (>). |
$<VERSION_LESS_EQUAL:v1,v2> |
Version string less than or equal (<=). |
$<VERSION_GREATER_EQUAL:v1,v2> |
Version string greater than or equal (>=). |
| Expression |
Description |
$<CONFIG:cfg_list> |
Checks build configuration (e.g., Debug, Release). |
$<PLATFORM_ID:id_list> |
Checks target platform ID (e.g., Linux, Windows, Darwin). |
$<POLICY:policy_id> |
Checks CMake policy status (NEW/OLD). |
Compiler Identification
| Expression |
Description |
$<C_COMPILER_ID:id_list> |
Checks C compiler ID (e.g., GNU, Clang, MSVC). |
$<CXX_COMPILER_ID:id_list> |
Checks C++ compiler ID. |
$<CUDA_COMPILER_ID:id_list> |
Checks CUDA compiler ID. |
$<OBJC_COMPILER_ID:id_list> |
Checks Objective-C compiler ID. |
$<OBJCXX_COMPILER_ID:id_list> |
Checks Objective-C++ compiler ID. |
$<Fortran_COMPILER_ID:id_list> |
Checks Fortran compiler ID. |
$<HIP_COMPILER_ID:id_list> |
Checks HIP compiler ID. |
$<C_COMPILER_VERSION:ver> |
Checks C compiler version. |
$<CXX_COMPILER_VERSION:ver> |
Checks C++ compiler version. |
$<CUDA_COMPILER_VERSION:ver> |
Checks CUDA compiler version. |
$<OBJC_COMPILER_VERSION:ver> |
Checks Objective-C compiler version. |
$<OBJCXX_COMPILER_VERSION:ver> |
Checks Objective-C++ compiler version. |
$<Fortran_COMPILER_VERSION:ver> |
Checks Fortran compiler version. |
$<HIP_COMPILER_VERSION:ver> |
Checks HIP compiler version. |
Language & Features
| Expression |
Description |
$<COMPILE_LANGUAGE:lang> |
Active source file language (e.g., C, CXX, CUDA). |
$<COMPILE_LANG_AND_ID:lang,compiler_ids> |
Checks language AND compiler ID simultaneously. |
$<COMPILE_FEATURES:features> |
Checks required target compile features. |
$<LINK_LANGUAGE:lang> |
Linker language used for the binary target. |
$<LINK_LANG_AND_ID:lang,compiler_ids> |
Checks link language AND linker/compiler ID. |
4. Target & Artifact Property Keys
Paths & File Locations
| Expression |
Description |
$<TARGET_FILE:target> |
Full path to primary binary. |
$<TARGET_FILE_NAME:target> |
Filename of primary binary. |
$<TARGET_FILE_DIR:target> |
Directory containing primary binary. |
$<TARGET_FILE_BASE_NAME:target> |
Base filename without prefix/extension. |
$<TARGET_FILE_PREFIX:target> |
Target output prefix (e.g., lib). |
$<TARGET_FILE_SUFFIX:target> |
Target output suffix (e.g., .so, .exe). |
$<TARGET_LINKER_FILE:target> |
Full path to linker library file (.a, .lib, .so). |
$<TARGET_LINKER_FILE_NAME:target> |
Filename of link library file. |
$<TARGET_LINKER_FILE_DIR:target> |
Directory containing link library file. |
$<TARGET_LINKER_FILE_BASE_NAME:target> |
Base filename of link library. |
$<TARGET_LINKER_FILE_PREFIX:target> |
Linker library prefix. |
$<TARGET_LINKER_FILE_SUFFIX:target> |
Linker library suffix. |
$<TARGET_SONAME_FILE:target> |
Full path to binary file with soname (.so.1). |
$<TARGET_SONAME_FILE_NAME:target> |
Filename of soname file. |
$<TARGET_SONAME_FILE_DIR:target> |
Directory containing soname file. |
$<TARGET_PDB_FILE:target> |
Full path to Visual Studio PDB debug file. |
$<TARGET_PDB_FILE_NAME:target> |
Filename of MSVC PDB file. |
$<TARGET_PDB_FILE_DIR:target> |
Directory containing MSVC PDB file. |
$<TARGET_PDB_FILE_BASE_NAME:target> |
Base name of MSVC PDB file. |
$<TARGET_BUNDLE_DIR:target> |
Directory of macOS Application Bundle. |
$<TARGET_BUNDLE_CONTENT_DIR:target> |
Content directory of macOS Application Bundle (Contents/). |
| Expression |
Description |
$<TARGET_PROPERTY:target,prop> |
Retrieves property prop from target. |
$<TARGET_PROPERTY:prop> |
Retrieves property prop from current target. |
$<TARGET_EXISTS:target> |
Checks if a target exists (1 or 0). |
$<TARGET_NAME_IF_EXISTS:target> |
Returns target name if exists, else empty string. |
$<TARGET_GENEX_EVAL:target,expr> |
Evaluates expression in context of target. |
$<GENEX_EVAL:expr> |
Evaluates expression recursively. |
$<TARGET_POLICY:policy_id> |
Evaluates policy in target context. |
$<TARGET_OBJECTS:target> |
List of .o/.obj object files from an OBJECT library target. |
$<LINK_ONLY:target> |
Includes link options for target without inheriting compile interface definitions. |
5. String & List Manipulation Keys
| Expression |
Description |
$<LOWER_CASE:str> |
Lowercase conversion. |
$<UPPER_CASE:str> |
Uppercase conversion. |
$<MAKE_C_IDENTIFIER:str> |
Converts string into C-style identifier. |
| Expression |
Description |
$<JOIN:list,glue> |
Joins list with separator string. |
$<REMOVE_DUPLICATES:list> |
Removes duplicates from list. |
| `$<FILTER:list,INCLUDE\ |
EXCLUDE,regex>` |
Filters list using regular expressions. |
$<LIST:action,list,...> |
Generic list operations (LENGTH, GET, SUBLIST, FIND, TRANSFORM, etc.). |
6. Path & Interface Keys
Path Operations
| Expression |
Description |
$<PATH:HAS_PARENT_PATH,path> |
Checks parent directory existence. |
$<PATH:GET_FILENAME,path> |
Extracts filename component. |
$<PATH:GET_EXTENSION,path> |
Extracts extension component. |
$<PATH:GET_STEM,path> |
Extracts filename without extension. |
$<PATH:GET_RELATIVE_PART,path> |
Extracts relative portion. |
$<PATH:GET_PARENT_PATH,path> |
Extracts parent path. |
$<PATH:GET_ROOT_NAME,path> |
Extracts root drive/server name. |
$<PATH:GET_ROOT_DIRECTORY,path> |
Extracts root path directory. |
$<PATH:GET_ROOT_PATH,path> |
Extracts full root path. |
$<PATH:NORMAL_PATH,path> |
Normalizes path syntax. |
$<PATH:RELATIVE_PATH,path,base_dir> |
Computes relative path. |
$<PATH:ABSOLUTE_PATH,path,base_dir> |
Computes absolute path. |
Target Interfaces
| Expression |
Description |
$<BUILD_INTERFACE:paths...> |
Used during local build/subproject tree context. |
$<INSTALL_INTERFACE:paths...> |
Used when package is installed and imported via find_package(). |
$<BUILD_LOCAL_INTERFACE:paths...> |
Used only in current build tree (not inherited transitivity). |
7. Escaping & Output Characters
Special keys used to pass reserved syntactic characters inside generator expressions.
| Generator Expression |
Description |
$<ANGLE-R> |
Right angle bracket > |
$<COMMA> |
Literal comma , |
$<SEMICOLON> |
Literal semicolon ; |
$<LOWER_THAN> |
Left angle bracket < |
$<GREATER_THAN> |
Right angle bracket > |