CMAKE-GENERATOR-EXPRESSIONS(7)

๐Ÿ’… NAME

cmake-generator-expressions โ€” literally CMake Generator Expressions, bestie. That's it, that's the page โœจ


๐Ÿฆ‹ INTRODUCTION

Okay so picture this: generator expressions get evaluated while the build system is being generated, and their whole vibe is producing info that's specific to each build configuration. They look like $<...>. Iconic.

For example:

target_include_directories(tgt PRIVATE /opt/include/$<CXX_COMPILER_ID>)

That bad boy expands to /opt/include/GNU, /opt/include/Clang, etc. โ€” depends which C++ compiler is doing the work. Chef's kiss. ๐Ÿ’…

Generator expressions are allowed in the context of so many target properties, like LINK_LIBRARIES, INCLUDE_DIRECTORIES, COMPILE_DEFINITIONS and others. They also show up when you use commands to populate those properties โ€” target_link_libraries(), target_include_directories(), target_compile_definitions() and friends. Basically they unlock conditional linking, conditional definitions at compile time, conditional include directories, and more. The conditions can be based on the build configuration, target properties, platform info, or literally any other queryable info. โœจ

And yes bestie, generator expressions can be nested:

target_compile_definitions(tgt PRIVATE
  $<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,4.2.0>:OLD_COMPILER>
)

The above would expand to OLD_COMPILER if the CMAKE_CXX_COMPILER_VERSION is less than 4.2.0. Slay.

โš ๏ธ Note: generator expressions are supported in the values of many properties, but not all of them. Check a property's documentation for explicit mention of generator expressions before you use them. No cap, read the docs first.

๐ŸŒธ WHITESPACE AND QUOTING

Generator expressions are typically parsed after command arguments. So if an expression contains spaces, new lines, semicolons, or other characters that might get read as command argument separators, you gotta wrap the whole expression in quotes when you pass it to a command. If you don't, the expression might get split up and then it's not a generator expression anymore โ€” it's just a sad little string. ๐Ÿ˜ญ

When you're using add_custom_command() or add_custom_target(), use the VERBATIM and COMMAND_EXPAND_LISTS options to get robust argument splitting and quoting.

# WRONG: Embedded space will be treated as an argument separator.
# This ends up not being seen as a generator expression at all.
add_custom_target(run_some_tool
  COMMAND some_tool -I$<JOIN:$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>, -I>
  VERBATIM
)
# Better, but still not robust. Quotes prevent the space from splitting the
# expression. However, the tool will receive the expanded value as a single
# argument.
add_custom_target(run_some_tool
  COMMAND some_tool "-I$<JOIN:$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>, -I>"
  VERBATIM
)
# Nearly correct. Using a semicolon to separate arguments and adding the
# COMMAND_EXPAND_LISTS option means that paths with spaces will be handled
# correctly. Quoting the whole expression ensures it is seen as a generator
# expression. But if the target property is empty, we will get a bare -I
# with nothing after it.
add_custom_target(run_some_tool
  COMMAND some_tool "-I$<JOIN:$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>,;-I>"
  COMMAND_EXPAND_LISTS
  VERBATIM
)

Using variables to build up a more complex generator expression is also a great way to reduce errors and improve readability. The example above can be improved further, like so:

# The $<BOOL:...> check prevents adding anything if the property is empty,
# assuming the property value cannot be one of CMake's false constants.
set(prop "$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>")
add_custom_target(run_some_tool
  COMMAND some_tool "$<$<BOOL:${prop}>:-I$<JOIN:${prop},;-I>>"
  COMMAND_EXPAND_LISTS
  VERBATIM
)

Finally, the above example can be expressed in a more simple and robust way using an alternate generator expression:

add_custom_target(run_some_tool
  COMMAND some_tool "$<LIST:TRANSFORM,$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>,PREPEND,-I>"
  COMMAND_EXPAND_LISTS
  VERBATIM
)

For tools that expect -I's value to be a separate argument, use the semicolon trick we learned earlier:

add_custom_target(run_some_tool
  COMMAND some_tool "$<LIST:TRANSFORM,$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>,PREPEND,-I;>"
  COMMAND_EXPAND_LISTS
  VERBATIM
)

A common mistake is trying to split a generator expression across multiple lines with indenting:

# WRONG: New lines and spaces all treated as argument separators, so the
# generator expression is split and not recognized correctly.
target_compile_definitions(tgt PRIVATE
  $<$<AND:
      $<CXX_COMPILER_ID:GNU>,
      $<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,5>
    >:HAVE_5_OR_LATER>
)

Again โ€” use helper variables with well-chosen names to build up a readable expression instead:

set(is_gnu "$<CXX_COMPILER_ID:GNU>")
set(v5_or_later "$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,5>")
set(meet_requirements "$<AND:${is_gnu},${v5_or_later}>")
target_compile_definitions(tgt PRIVATE
  "$<${meet_requirements}:HAVE_5_OR_LATER>"
)

๐Ÿ› DEBUGGING

So here's the thing โ€” generator expressions get evaluated during generation of the buildsystem, not while CMakeLists.txt files are being processed. Which means you literally cannot inspect their result with the message() command. Sorry bestie, it's just not happening. ๐Ÿ’€

One possible way to generate debug messages is to add a custom target:

add_custom_target(genexdebug COMMAND ${CMAKE_COMMAND} -E echo "$<...>")

After running cmake, you can build the genexdebug target to print the result of the $<...> expression (i.e. run the command cmake --build ... --target genexdebug). ๐Ÿซถ

Another way is to write debug messages to a file with file(GENERATE):

file(GENERATE OUTPUT filename CONTENT "$<...>")

๐Ÿ‘‘ GENERATOR EXPRESSION REFERENCE

๐Ÿ™ˆ Note: this reference deviates from most of the CMake documentation in that it omits the angular brackets <...> around placeholders like condition, string, target, etc. Why? To prevent any chance of those placeholders being misinterpreted as actual generator expressions. iykyk.

โœจ Conditional Expressions

A fundamental category of generator expressions relates to conditional logic. Two forms of conditional generator expressions are supported:

  • $<condition:true_string> โ€” evaluates to true_string if condition is 1, or an empty string if condition evaluates to 0. Any other value for condition results in an error. ๐Ÿ”ฅ
  • $<IF:condition,true_string,false_string> โ€” Added in version 3.8. Evaluates to true_string if condition is 1, or false_string if condition is 0. Any other value for condition results in an error. Added in version 3.28: this generator expression short-circuits such that generator expressions in false_string will not evaluate when condition is 1, and generator expressions in true_string will not evaluate when condition is 0. ๐Ÿ‘‘

Typically, the condition is itself a generator expression. For instance, the following expression expands to DEBUG_MODE when the Debug configuration is used, and the empty string for all other configurations:

$<$<CONFIG:Debug>:DEBUG_MODE>

Boolean-like condition values other than 1 or 0 can be handled by wrapping them with the $<BOOL:...> generator expression:

  • $<BOOL:string> โ€” converts string to 0 or 1. Evaluates to 0 if any of the following is true:
    • string is empty,
    • string is a case-insensitive equal of 0, FALSE, OFF, N, NO, IGNORE, or NOTFOUND, or
    • string ends in the suffix -NOTFOUND (case-sensitive).

Otherwise it evaluates to 1. โญ

The $<BOOL:...> generator expression is often used when a condition is provided by a CMake variable:

$<$<BOOL:${HAVE_SOME_FEATURE}>:-DENABLE_SOME_FEATURE>

๐Ÿงš Logical Operators

The common boolean logic operators are supported, and yes they ate:

  • $<AND:conditions> โ€” where conditions is a comma-separated list of boolean expressions, all of which must evaluate to either 1 or 0. The whole expression evaluates to 1 if all conditions are 1. If any condition is 0, the whole expression evaluates to 0.
  • $<OR:conditions> โ€” where conditions is a comma-separated list of boolean expressions, all of which must evaluate to either 1 or 0. The whole expression evaluates to 1 if at least one of the conditions is 1. If all conditions evaluate to 0, the whole expression evaluates to 0.
  • $<NOT:condition> โ€” condition must be 0 or 1. The result of the expression is 0 if condition is 1, else 1.

Added in version 3.28: logical operators short-circuit such that generator expressions in the arguments list will not be evaluated once a return value can be determined. ๐ŸŽ€


๐ŸŒˆ Primary Comparison Expressions

CMake supports a variety of generator expressions that compare things. This section covers the primary and most widely used comparison types. Other more specific comparison types are documented in their own separate sections further below.


๐Ÿ’œ Numeric Comparisons

  • $<EQUAL:value1,value2> โ€” 1 if value1 and value2 are numerically equal, else 0.

๐Ÿ“ Version Comparisons

  • $<VERSION_LESS:v1,v2> โ€” 1 if v1 is a version less than v2, else 0.
  • $<VERSION_GREATER:v1,v2> โ€” 1 if v1 is a version greater than v2, else 0.
  • $<VERSION_EQUAL:v1,v2> โ€” 1 if v1 is the same version as v2, else 0.
  • $<VERSION_LESS_EQUAL:v1,v2> โ€” Added in version 3.7. 1 if v1 is a version less than or equal to v2, else 0.
  • $<VERSION_GREATER_EQUAL:v1,v2> โ€” Added in version 3.7. 1 if v1 is a version greater than or equal to v2, else 0.

๐ŸŽ€ String Expressions

Most of the expressions in this section are closely associated with the string() command โ€” they provide the same capabilities, but in the form of a generator expression. ๐Ÿ’–

okay so real talk before we get into the fun stuff ๐Ÿ’– โ€” in every single one of these string-related generator expressions, the string can't contain any commas if that expression expects something to come after the string. Like, $<STRING:FIND,string,value> needs a value after the string, and the comma is literally what splits them apart, so the string itself can't have a comma in it. Also note: this restriction does not apply to the string() command โ€” it's a string-handling-generator-expression thing only, iykyk. And when you do need a comma up in there, the $<COMMA> generator expression has your back. ๐Ÿซถ

โœจ String Comparisons โœจ

Heads up bestie: these comparisons are case-sensitive. Want case-insensitive? Combine one with a string transforming generator expression. Like this, which evaluates to 1 if ${foo} is any of BAR, Bar, bar, etc.:

$<STREQUAL:$<STRING:TOUPPER,${foo}>,BAR>
  • $<STREQUAL:string1,string2> โ€” 1 if string1 and string2 are lexicographically equal, else 0.
  • $<STRLESS:string1,string2> โ€” Added in version 4.3. 1 if string1 is lexicographically less than string2, else 0.
  • $<STRGREATER:string1,string2> โ€” Added in version 4.3. 1 if string1 is lexicographically greater than string2, else 0.
  • $<STRLESS_EQUAL:string1,string2> โ€” Added in version 4.3. 1 if string1 is lexicographically less than or equal to string2, else 0.
  • $<STRGREATER_EQUAL:string1,string2> โ€” Added in version 4.3. 1 if string1 is lexicographically greater than or equal to string2, else 0.

๐Ÿฆ‹ String Queries ๐Ÿฆ‹

  • $<STRING:LENGTH,string> โ€” Added in version 4.3. The length of the given string in bytes. And yes, that means if string has multi-byte characters in it, what you get back is not the number of characters. Bytes only, no cap. ๐Ÿ’€
  • $<STRING:SUBSTRING,string,begin,length> โ€” Added in version 4.3. The substring of the given string. If length is -1 or greater than the string length, you get the remainder of the string starting at begin. Both begin and length are counted in bytes, so be careful if string could contain multi-byte characters. ๐Ÿ™ˆ
  • $<STRING:FIND,string[,FROM:(BEGIN|END)],substring> โ€” Added in version 4.3. The position where the given substring was found in the supplied string. If the substring isn't found, you get a position of -1. The FROM: option decides how the search goes down:
    • BEGIN โ€” the search starts at the beginning of the string. This is the default.
    • END โ€” the search starts from the end of the string.

Also important: $<STRING:FIND> treats all strings as ASCII-only characters. The index it returns is counted in bytes too, so strings with multi-byte characters can absolutely lead to unexpected results. โš ๏ธ

  • $<STRING:MATCH,string[,SEEK:(ONCE|ALL)],regular_expression> โ€” Added in version 4.3. Matches regular_expression inside the string. The SEEK: option controls the match behavior:
    • ONCE โ€” match only the first occurrence. This is the default.
    • ALL โ€” match as many times as possible and return the matches as a list.

For the syntax of the regular_expression parameter, peep the Regular expressions specification section. ๐Ÿงš

๐ŸŒธ String Generations ๐ŸŒธ

  • $<STRING:JOIN,glue,input[,input]...> โ€” Added in version 4.3. Joins all the input arguments together using the glue string.
  • $<STRING:ASCII,number[,number]...> โ€” Added in version 4.3. Converts all the numbers in the range 1-255 into their corresponding ASCII characters. Any number outside that range raises an error โ€” so don't try it. ๐Ÿšจ
  • $<STRING:TIMESTAMP[,(UTC|format)]...> โ€” Added in version 4.3. Produces a string representation of the current date and/or time. If the generator expression can't get a timestamp, the result is the empty string "". The optional UTC flag asks for the current date/time representation in Coordinated Universal Time (UTC) instead of local time. If the SOURCE_DATE_EPOCH environment variable is set, its value gets used instead of the current time โ€” see <https://reproducible-builds.org/specs/source-date-epoch/> for the details. ๐Ÿ‘‘

The optional format can contain these format specifiers:

  • %% โ€” a literal percent sign (%).
  • %d โ€” the day of the current month (01-31).
  • %H โ€” the hour on a 24-hour clock (00-23).
  • %I โ€” the hour on a 12-hour clock (01-12).
  • %j โ€” the day of the current year (001-366).
  • %m โ€” the month of the current year (01-12).
  • %b โ€” abbreviated month name (e.g. Oct).
  • %B โ€” full month name (e.g. October).
  • %M โ€” the minute of the current hour (00-59).
  • %s โ€” seconds since midnight (UTC) 1-Jan-1970 (UNIX time).
  • %S โ€” the second of the current minute. 60 represents a leap second. (00-60)
  • %f โ€” the microsecond of the current second (000000-999999).
  • %U โ€” the week number of the current year (00-53).
  • %V โ€” the ISO 8601 week number of the current year (01-53).
  • %w โ€” the day of the current week. 0 is Sunday. (0-6)
  • %a โ€” abbreviated weekday name (e.g. Fri).
  • %A โ€” full weekday name (e.g. Friday).
  • %y โ€” the last two digits of the current year (00-99).
  • %Y โ€” the current year.
  • %z โ€” the offset of the time zone from UTC, in hours and minutes, with format +hhmm or -hhmm.
  • %Z โ€” the time zone name.

Unknown format specifiers get ignored and copied to the output as-is. ๐Ÿ’ซ

If you don't give an explicit format, it defaults to:

  • %Y-%m-%dT%H:%M:%S for local time.
  • %Y-%m-%dT%H:%M:%SZ for UTC.
  • $<STRING:RANDOM[,(LENGTH:length|ALPHABET:alphabet|RANDOM_SEED:seed)]...> โ€” Added in version 4.3. Produces a random string of ASCII characters. The possible options are:
    • LENGTH:length โ€” define the length of the string. The default length is 5 characters.
    • ALPHABET:alphabet โ€” define the characters used for the generation. The alphabet is always interpreted as holding ASCII characters. The default alphabet is all numbers and upper and lower case letters.
    • RANDOM_SEED:seed โ€” specify an integer which will be used to seed the random number generator.
  • $<STRING:UUID,NAMESPACE:namespace,TYPE:(MD5|SHA1)[,NAME:name][,CASE:(LOWER|UPPER)]> โ€” Added in version 4.3. Creates a universally unique identifier (aka GUID) as per RFC4122. A UUID has the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx where each x represents a hexadecimal character. The UUID is based on the hash of the combined values of:
    • NAMESPACE:namespace โ€” the namespace, which has to be a valid UUID.
    • NAME:name โ€” name is an arbitrary string.
    • TYPE: โ€” the hash algorithm can be either:
      • MD5 โ€” Version 3 UUID.
      • SHA1 โ€” Version 5 UUID.
    • CASE: โ€” specify the case of the hexadecimal characters.
      • LOWER โ€” hexadecimal characters are all lowercase. This is the default.
      • UPPER โ€” hexadecimal characters are all uppercase.

๐Ÿ’œ String Transformations ๐Ÿ’œ

  • $<STRING:REPLACE[,(STRING|REGEX)],string,match_string,replace_string> โ€” Added in version 4.3. Replaces all occurrences of match_string in the string with replace_string. The match_string can be one of two different types:
    • STRING โ€” match_string is a literal string and the match is done by simple string comparison. This is the default.
    • REGEX โ€” match_string is a regular expression. Matches this regular_expression as many times as possible and substitutes the replace_string for the match in the string.

With REGEX, the replace_string may refer to parenthesis-delimited subexpressions of the match using \1, \2, ..., \9. Careful though: two backslashes (\\1) are required in CMake code to get a backslash through argument parsing. ๐Ÿ˜ญ

  • $<STRING:APPEND,string,input[,input]...> โ€” Added in version 4.3. Appends all the input arguments to the string.
  • $<STRING:PREPEND,string,input[,input]...> โ€” Added in version 4.3. Prepends all the input arguments to the string.
  • $<STRING:TOLOWER,string> โ€” Added in version 4.3. Content of string converted to lower case.
  • $<STRING:TOUPPER,string> โ€” Added in version 4.3. Content of string converted to upper case.
  • $<STRING:STRIP,SPACES,string> โ€” Added in version 4.3. Removes the specified elements from the string. The possible options are:
    • SPACES โ€” removes the leading and trailing spaces of the string.
  • $<STRING:QUOTE,REGEX,string> โ€” Added in version 4.3. Escapes the specified elements of the string. The possible options are:
    • REGEX โ€” escapes all characters that have special meaning in a regular expression, so the string can be used as part of a regular expression to match the input literally. ๐ŸŽ€
  • $<STRING:HEX,string> โ€” Added in version 4.3. Converts each byte in the string to its hexadecimal representation. Letters in the result (a through f) are lowercase.
  • $<STRING:HASH,string,ALGORITHM:algorithm> โ€” Added in version 4.3. Computes a cryptographic hash of the string. The supported algorithm names, as specified by the ALGORITHM: option, are:
    • MD5 โ€” Message-Digest Algorithm 5, RFC 1321.
    • SHA1 โ€” US Secure Hash Algorithm 1, RFC 3174.
    • SHA224 โ€” US Secure Hash Algorithms, RFC 4634.
    • SHA256 โ€” US Secure Hash Algorithms, RFC 4634.
    • SHA384 โ€” US Secure Hash Algorithms, RFC 4634.
    • SHA512 โ€” US Secure Hash Algorithms, RFC 4634.
    • SHA3_224 โ€” Keccak SHA-3.
    • SHA3_256 โ€” Keccak SHA-3.
    • SHA3_384 โ€” Keccak SHA-3.
    • SHA3_512 โ€” Keccak SHA-3.
  • $<STRING:MAKE_C_IDENTIFIER,string> โ€” Added in version 4.3.

okay so finishing off that last one from before ๐Ÿ’… โ€” every non-alphanumeric character in the string gets flipped to an underscore, and ngl, if the very first character of the string is a digit, an underscore gets prepended too. sneaky but iconic.

  • $<LOWER_CASE:string> โ€” the content of string, but lowercase. that's it, that's the whole vibe. โœจ
  • $<UPPER_CASE:string> โ€” bestie this is the SHOUTING version: content of string converted to upper case. ๐Ÿ“ฃ
  • $<MAKE_C_IDENTIFIER:string> โ€” content of string turned into a legit C identifier. the conversion behaves exactly like string(MAKE_C_IDENTIFIER). no cap, same rules. ๐ŸŽ€

๐Ÿ“ List Expressions ๐Ÿ“

okay so most of the stuff in this section is very closely associated with the list() command โ€” like, it gives you the exact same capabilities, just as a generator expression instead. same energy, different outfit. ๐Ÿ’…

and here's the one gotcha to remember, like actually remember it: in each of the list-related generator expressions below, the list must not contain any commas if that generator expression expects something to be provided after the list. examples time โ€” $<LIST:FIND,list,value> needs a value after the list, right? and since a comma is literally what separates the list from the value, the list itself can't contain a comma. ๐Ÿ˜ญ this restriction does not apply to the list() command โ€” it's specific to the list-handling generator expressions only. so don't go blaming list() for this one. ๐Ÿซถ


๐Ÿ” List Comparisons

  • $<IN_LIST:string,list> โ€” Added in version 3.12. gives you 1 if string is an item in the semicolon-separated list, else 0. and it uses case-sensitive comparisons, so don't try to be cute with the capitalisation. ๐Ÿ™ˆ

๐Ÿฆ‹ List Queries

  • $<LIST:LENGTH,list> โ€” Added in version 3.27. the number of items in the list. that's the count, bestie. ๐Ÿ”ข
  • $<LIST:GET,list,index,...> โ€” Added in version 3.27. expands to the list of items specified by the indices from list. pick your faves. ๐Ÿ‘‘
  • $<LIST:SUBLIST,list,begin,length> โ€” Added in version 3.27. a sublist of the given list. if length is 0, you get an empty list back. if length is -1, or the list is smaller than begin + length, then you get the remaining items of the list starting at begin. so it justโ€ฆ gives you the rest. generous. ๐Ÿ’–
  • $<LIST:FIND,list,value> โ€” Added in version 3.27. the index of the first item in list with the specified value, or -1 if value is not in the list at all. sad but honest. ๐Ÿ’€

๐ŸŒธ List Transformations

  • $<LIST:JOIN,list,glue> โ€” Added in version 3.27. converts list to a single string with the content of the glue string inserted between each item. conceptually this is the same operation as $<JOIN:list,glue>, but the two behave differently when it comes to empty items: $<LIST:JOIN,list,glue> preserves all empty items, whereas $<JOIN:list,glue> drops all empty items from the list. know the difference, it matters. ๐Ÿ”ฅ
  • $<LIST:APPEND,list,item,...> โ€” Added in version 3.27. the list with each item appended. multiple items get separated by commas. easy. โœจ
  • $<LIST:PREPEND,list,item,...> โ€” Added in version 3.27. the list with each item inserted at the beginning. if there are multiple items, separate them with commas โ€” and the order of the prepended items is preserved, so they stay in the order you gave them. ๐ŸŽ€
  • $<LIST:INSERT,list,index,item,...> โ€” Added in version 3.27. the list with the item (or multiple items) inserted at the specified index. multiple items get separated by commas.
๐Ÿšจ it is an error to specify an out-of-range index. valid indexes are 0 to N, where N is the length of the list, inclusive. an empty list has length 0. so like, behave.
  • $<LIST:POP_BACK,list> โ€” Added in version 3.27. the list with the last item removed. bye bestie ๐Ÿ‘‹
  • $<LIST:POP_FRONT,list> โ€” Added in version 3.27. the list with the first item removed. also bye ๐Ÿ‘‹
  • $<LIST:REMOVE_ITEM,list,value,...> โ€” Added in version 3.27. the list with all instances of the given value (or values) removed. if multiple values are given, they get separated by commas. ๐Ÿ‘‘
  • $<LIST:REMOVE_AT,list,index,...> โ€” Added in version 3.27. the list with the item at each given index removed. ๐Ÿซถ
  • $<LIST:REMOVE_DUPLICATES,list> โ€” Added in version 3.27. the list with all duplicated items removed. the relative order of items is preserved, but if duplicates show up, only the first instance survives. the result is the same as $<REMOVE_DUPLICATES:list>. ๐Ÿ’…
  • $<LIST:FILTER,list,INCLUDE|EXCLUDE,regex> โ€” Added in version 3.27. a list of items from list which match (INCLUDE) or do not match (EXCLUDE) the regular expression regex. the result is the same as $<FILTER:list,INCLUDE|EXCLUDE,regex>. ๐ŸŒˆ
  • $<LIST:TRANSFORM,list,ACTION[,SELECTOR]> โ€” Added in version 3.27. the list transformed by applying an ACTION to all of the list items โ€” or, if you specify a SELECTOR, only to the selected ones. glow-up, but targeted. โœจ
Note: the TRANSFORM sub-command does not change the number of items in the list. if a SELECTOR is specified, only some items will be changed โ€” the other ones will remain exactly the same as before the transformation. no vanishing acts. ๐Ÿ”ฎ

ACTION specifies the action to apply to the items of the list. the actions have exactly the same semantics as for the list(TRANSFORM) command. ACTION must be one of these:

  • APPEND, PREPEND โ€” append, prepend specified value to each item of the list.
$<LIST:TRANSFORM,list,(APPEND|PREPEND),value[,SELECTOR]>
  • TOLOWER, TOUPPER โ€” convert each item of the list to lower, upper characters.
$<LIST:TRANSFORM,list,(TOLOWER|TOUPPER)[,SELECTOR]>
  • STRIP โ€” remove leading and trailing spaces from each item of the list.
$<LIST:TRANSFORM,list,STRIP[,SELECTOR]>
  • REPLACE โ€” match the regular expression as many times as possible and substitute the replacement expression for the match for each item of the list.
$<LIST:TRANSFORM,list,REPLACE,regular_expression,replace_expression[,SELECTOR]>
Changed in version 4.1: the ^ anchor now matches only at the beginning of the input element instead of the beginning of each repeated search. see policy CMP0186. ๐Ÿ‘€

SELECTOR determines which items of the list will be transformed. only one type of selector can be specified at a time. when given, SELECTOR must be one of these:

  • AT โ€” specify a list of indexes.
$<LIST:TRANSFORM,list,ACTION,AT,index[,index...]>
  • FOR โ€” specify a range with, optionally, an increment used to iterate over the range.
$<LIST:TRANSFORM,list,ACTION,FOR,start,stop[,step]>
  • REGEX โ€” specify a regular expression. only items matching the regular expression will be transformed.
$<LIST:TRANSFORM,list,ACTION,REGEX,regular_expression>
  • $<JOIN:list,glue> โ€” joins the list with the content of the glue string inserted between each item. conceptually this is the same operation as $<LIST:JOIN,list,glue>, but the two behave differently with regard to empty items: $<LIST:JOIN,list,glue> preserves all empty items, whereas $<JOIN:list,glue> drops all empty items from the list. yes i'm saying it again. it's important. ๐Ÿ˜ค
  • $<REMOVE_DUPLICATES:list> โ€” Added in version 3.15. removes duplicated items in the given list. the relative order of items is preserved, and if duplicates are encountered, only the first instance is retained. the result is the same as $<LIST:REMOVE_DUPLICATES,list>. ๐Ÿงš
  • $<FILTER:list,INCLUDE|EXCLUDE,regex> โ€” Added in version 3.15. includes or removes items from list that match the regular expression regex. the result is the same as $<LIST:FILTER,list,INCLUDE|EXCLUDE,regex>. โญ

๐Ÿ’œ List Ordering

  • $<LIST:REVERSE,list> โ€” Added in version 3.27. the list with the items in reverse order. flip it. ๐Ÿ”„
  • $<LIST:SORT,list[,(COMPARE:option|CASE:option|ORDER:option)]...> โ€” Added in version 3.27. the list sorted according to the specified options.

use one of the COMPARE options to select the comparison method for sorting:

  • STRING โ€” sorts a list of strings alphabetically. this is the default behavior if the COMPARE option is not given.
  • FILE_BASENAME โ€” sorts a list of file paths by their basenames.
  • NATURAL โ€” sorts a list of strings using natural order (see the man page for strverscmp(3)), such that contiguous digits are compared as whole numbers. for example, the following list 10.0 1.1 2.1 8.0 2.0 3.1 will be sorted as 1.1 2.0 2.1 3.1 8.0 10.0 if the NATURAL comparison is selected, whereas it will be sorted as 1.1 10.0 2.0 2.1 3.1 8.0 with the STRING comparison. look at that difference. iconic. ๐Ÿ‘‘

use one of the CASE options to select a case-sensitive or case-insensitive sort mode:

  • SENSITIVE โ€” list items are sorted in a case-sensitive manner. this is the default behavior if the CASE option is not given.
  • INSENSITIVE โ€” list items are sorted in a case-insensitive manner. the order of items which differ only by upper/lowercase is not specified, so don't come for me if they shuffle around. ๐Ÿ™ˆ

to control the sort order, one of the ORDER options can be given:

  • ASCENDING โ€” sorts the list in ascending order. this is the default behavior when the ORDER option is not given.
  • DESCENDING โ€” sorts the list in descending order.

options can be specified in any order, but it is an error to specify the same option multiple times. pick one and commit. ๐Ÿ’…

$<LIST:SORT,list,CASE:SENSITIVE,COMPARE:STRING,ORDER:DESCENDING>

๐Ÿงš Path Expressions ๐Ÿงš

most of the expressions in this section are closely associated with the cmake_path() command โ€” same capabilities, but in the form of a generator expression. you already know the pattern bestie. ๐Ÿซถ

for all generator expressions in this section, paths are expected to be in cmake-style format. and the $<PATH:CMAKE_PATH> generator expression can be used to convert a native path to a cmake-style one. handy, right? ๐ŸŒŸ

๐Ÿ’… Path Comparisons ๐Ÿ’…

  • $<PATH_EQUAL:path1,path2> โ€” Added in version 3.24. Compares the lexical representations of two paths, bestie. No normalization is performed on either path โ€” like, none, zero, don't even think about it ๐Ÿ™ˆ. Returns 1 if the paths are equal, 0 otherwise. Peep cmake_path(COMPARE) for more deets โœจ

๐Ÿฆ‹ Path Queries ๐Ÿฆ‹

These are the generation-time versions of the Query options of the cmake_path() command, periodt. Every path here is expected to be in cmake-style format ๐Ÿ’–

  • $<PATH:HAS_*,path> โ€” Added in version 3.24. The following operations return 1 if that particular path component is present, 0 otherwise. Go look at Path Structure And Terminology for what each component actually means ๐Ÿ‘‘
    • $<PATH:HAS_ROOT_NAME,path>
    • $<PATH:HAS_ROOT_DIRECTORY,path>
    • $<PATH:HAS_ROOT_PATH,path>
    • $<PATH:HAS_FILENAME,path>
    • $<PATH:HAS_EXTENSION,path>
    • $<PATH:HAS_STEM,path>
    • $<PATH:HAS_RELATIVE_PART,path>
    • $<PATH:HAS_PARENT_PATH,path>

Okay so, the special cases โ€” write these down ๐Ÿ“

  • For HAS_ROOT_PATH, a true result only comes back if at least one of root-name or root-directory is non-empty.
  • For HAS_PARENT_PATH, the root directory also counts as having a parent, and that parent is... itself ๐Ÿ˜ญ (self-care icon behaviour tbh). The result is true except if the path consists of just a filename.
  • $<PATH:IS_ABSOLUTE,path> โ€” Added in version 3.24. Returns 1 if the path is absolute according to cmake_path(IS_ABSOLUTE), 0 otherwise. Simple, clean, iconic ๐Ÿ’…
  • $<PATH:IS_RELATIVE,path> โ€” Added in version 3.24. Returns 1 if the path is relative according to cmake_path(IS_RELATIVE), 0 otherwise ๐ŸŒธ
  • $<PATH:IS_PREFIX[,NORMALIZE],path,input> โ€” Added in version 3.24. Returns 1 if path is the prefix of input, 0 otherwise. When you pass the NORMALIZE option, both path and input get normalized before the check happens ๐Ÿงš

๐ŸŽ€ Path Decomposition ๐ŸŽ€

These give you the generation-time equivalent of the Decomposition options of the cmake_path() command. All paths are expected to be in cmake-style format ๐Ÿ’–

  • $<PATH:GET_*,...> โ€” Added in version 3.24. These operations pull out a different component, or group of components, from a path. Check Path Structure And Terminology for what each one means โœจ
Changed in version 3.27: All these operations now accept a list of paths as the argument. When you hand it a list, the operation just gets applied to each path in turn. Slay ๐Ÿ’…
  • $<PATH:GET_ROOT_NAME,path...>
  • $<PATH:GET_ROOT_DIRECTORY,path...>
  • $<PATH:GET_ROOT_PATH,path...>
  • $<PATH:GET_FILENAME,path...>
  • $<PATH:GET_EXTENSION[,LAST_ONLY],path...>
  • $<PATH:GET_STEM[,LAST_ONLY],path...>
  • $<PATH:GET_RELATIVE_PART,path...>
  • $<PATH:GET_PARENT_PATH,path...>

If the component you asked for isn't in the path at all, you get back an empty string โ€” no error, no drama, just vibes ๐Ÿซถ


๐ŸŒˆ Path Transformations ๐ŸŒˆ

These are the generation-time versions of the Modification and Generation options of the cmake_path() command. All paths are expected to be in cmake-style format, no cap ๐Ÿงข

Changed in version 3.27: All these operations now accept a list of paths as the argument. Give it a list and it'll apply the operation to every path in it ๐Ÿ’…
  • $<PATH:CMAKE_PATH[,NORMALIZE],path...> โ€” Added in version 3.24. Returns path. If path is a native path, it gets converted into a cmake-style path with forward slashes (/). On Windows, the long filename marker is taken into account ๐Ÿ‘€ When you pass the NORMALIZE option, the path gets normalized after the conversion.
  • $<PATH:NATIVE_PATH[,NORMALIZE],path...> โ€” Added in version 4.0. Returns path converted into native format with platform-specific slashes (\ on Windows hosts and / everywhere else). When you pass the NORMALIZE option, the path gets normalized before the conversion ๐Ÿฆ‹ (yes, before โ€” read it again, it's the opposite of the one above, iykyk)
  • $<PATH:APPEND,path...,input,...> โ€” Added in version 3.24. Returns all the input arguments appended onto path, using / as the directory-separator. Depending on what the input is, the value of path might just get discarded ๐Ÿ’€ See cmake_path(APPEND) for more.
  • $<PATH:REMOVE_FILENAME,path...> โ€” Added in version 3.24. Returns path with the filename component (the thing $<PATH:GET_FILENAME> gives you) removed. After removal, any trailing directory-separator is left alone if it was there โ€” we don't touch it, not our business ๐Ÿ™ˆ See cmake_path(REMOVE_FILENAME) for more.
  • $<PATH:REPLACE_FILENAME,path...,input> โ€” Added in version 3.24. Returns path with the filename component swapped out for input. If path has no filename component at all (i.e. $<PATH:HAS_FILENAME> returns 0), then path comes back unchanged. Nothing happens. We move on โœจ See cmake_path(REPLACE_FILENAME) for more.
  • $<PATH:REMOVE_EXTENSION[,LAST_ONLY],path...> โ€” Added in version 3.24. Returns path with the extension removed, if there even is one ๐ŸŽ€ See cmake_path(REMOVE_EXTENSION) for more.
  • $<PATH:REPLACE_EXTENSION[,LAST_ONLY],path...,input> โ€” Added in version 3.24. Returns path with the extension replaced by input, if there was one. See cmake_path(REPLACE_EXTENSION) for more ๐Ÿ’–
  • $<PATH:NORMAL_PATH,path...> โ€” Added in version 3.24. Returns path normalized according to the steps described in Normalization. That's it, that's the whole thing ๐Ÿงš
  • $<PATH:RELATIVE_PATH,path...,base_directory> โ€” Added in version 3.24. Returns path, modified so it's relative to the base_directory argument. See cmake_path(RELATIVE_PATH) for more ๐ŸŒธ
  • $<PATH:ABSOLUTE_PATH[,NORMALIZE],path...,base_directory> โ€” Added in version 3.24. Returns path as an absolute path. If path is a relative path ($<PATH:IS_RELATIVE> gives you 1), it gets evaluated relative to the base directory you specified with the base_directory argument. When you pass the NORMALIZE option, the path gets normalized after the path computation ๐Ÿ‘‘

See cmake_path(ABSOLUTE_PATH) for more details ๐Ÿ’…


๐Ÿš Shell Paths ๐Ÿš

  • $<SHELL_PATH:...> โ€” Added in version 3.4. Whatever's in ... gets converted to shell path style. Like, slashes become backslashes in Windows shells, and drive letters get converted to posix paths in MSYS shells. The ... must be an absolute path โš ๏ธ
Added in version 3.14: The ... may be a semicolon-separated list of paths, in which case each path gets converted individually and you get back a result list built with the shell path separator (: on POSIX and ; on Windows).

Fr fr, be sure to enclose the argument containing this genex in double quotes in your CMake source code so that ; does not split your arguments. Learned that the hard way, don't be me ๐Ÿ˜ญ


โญ Configuration Expressions โญ

  • $<CONFIG> โ€” The configuration name. Use this one instead of the deprecated CONFIGURATION generator expression, bestie, the old one is giving expired ๐Ÿ’€
  • $<CONFIG:cfgs> โ€” 1 if config is any one of the entries in the comma-separated list cfgs, else 0. This comparison is case-insensitive ๐Ÿซถ The mapping in MAP_IMPORTED_CONFIG_<CONFIG> is also considered by this expression when it's evaluated on a property of an IMPORTED target ๐Ÿ‘€
Changed in version 3.19: You can specify multiple configurations for cfgs now. CMake 3.18 and earlier only accepted a single configuration โ€” ancient history ๐Ÿ’…
  • $<OUTPUT_CONFIG:...> โ€” Added in version 3.20. Only valid in add_custom_command() and add_custom_target(), as the outer-most generator expression in an argument. With the Ninja Multi-Config generator, the generator expressions inside ... get evaluated using the custom command's "output config". With other generators, the content of ... is just evaluated normally โœจ
  • $<COMMAND_CONFIG:...> โ€” Added in version 3.20. Only valid in add_custom_command() and add_custom_target(), as the outer-most generator expression in an argument. With the Ninja Multi-Config generator, the generator expressions inside ... get evaluated using the custom command's "command config". With other generators, the content of ... is evaluated normally ๐ŸŒˆ

๐Ÿ‘‘ Toolchain And Language Expressions ๐Ÿ‘‘

๐Ÿ’œ Platform ๐Ÿ’œ

  • $<PLATFORM_ID> โ€” The current system's CMake platform id. Also go look at the CMAKE_SYSTEM_NAME variable, they're besties ๐Ÿซถ
  • $<PLATFORM_ID:platform_ids> โ€” 1 if CMake's platform id matches any one of the entries in the comma-separated list platform_ids, otherwise 0. Also see the CMAKE_SYSTEM_NAME variable โœจ

๐Ÿ”ฅ Compiler Version ๐Ÿ”ฅ

Also peep the CMAKE_<LANG>_COMPILER_VERSION variable โ€” it's closely related to the expressions in this sub-section, so like, keep it in mind ๐Ÿ’…

  • $<C_COMPILER_VERSION> โ€” The version of the C compiler being used ๐ŸŽ€
  • $<C_COMPILER_VERSION:version> โ€” 1 if the version of the C compiler matches version, otherwise 0.
  • $<CXX_COMPILER_VERSION> โ€” The version of the CXX compiler being used ๐Ÿ’–
  • $<CXX_COMPILER_VERSION:version> โ€” 1 if the version of the C++ compiler matches version, otherwise 0.
  • $<CUDA_COMPILER_VERSION> โ€” Added in version 3.15. The version of the CUDA compiler being used ๐ŸŒธ
  • $<CUDA_COMPILER_VERSION:version> โ€” Added in version 3.15. 1 if the version of the C++ compiler matches version, otherwise 0. (Yes, it says C++ on the CUDA one. We're not fixing it, we're just reporting it ๐Ÿ™ˆ)
  • $<OBJC_COMPILER_VERSION> โ€” Added in version 3.16. The version of the Objective-C compiler being used ๐Ÿฆ‹
  • $<OBJC_COMPILER_VERSION:version> โ€” Added in version 3.16.

...and that's exactly where we're picking back up next time, bestie ๐Ÿ’…โœจ

1 if the version of the Objective-C compiler matches version, otherwise 0. ๐Ÿ“

  • $<OBJCXX_COMPILER_VERSION> โ€” Added in version 3.16.</p> The version of the Objective-C++ compiler used. ๐Ÿ’œ
  • $<OBJCXX_COMPILER_VERSION:version> โ€” Added in version 3.16.</p> 1 if the version of the Objective-C++ compiler matches version, otherwise 0.
  • $<Fortran_COMPILER_VERSION> โ€” The version of the Fortran compiler used. ๐Ÿงš
  • $<Fortran_COMPILER_VERSION:version> โ€” 1 if the version of the Fortran compiler matches version, otherwise 0.
  • $<HIP_COMPILER_VERSION> โ€” Added in version 3.21.</p> The version of the HIP compiler used. ๐Ÿ”ฅ
  • $<HIP_COMPILER_VERSION:version> โ€” Added in version 3.21.</p> 1 if the version of the HIP compiler matches version, otherwise 0.
  • $<ISPC_COMPILER_VERSION> โ€” Added in version 3.19.</p> The version of the ISPC compiler used. ๐ŸŒˆ
  • $<ISPC_COMPILER_VERSION:version> โ€” Added in version 3.19.</p> 1 if the version of the ISPC compiler matches version, otherwise 0.

๐Ÿซถ Compiler Language, ID, and Frontend-Variant ๐Ÿซถ

Ok bestie, before we dive in โ€” heads up that there are two variables that are super closely related to basically everything in this little sub-section: CMAKE_<LANG>_COMPILER_ID and CMAKE_<LANG>_COMPILER_FRONTEND_VARIANT. Like, cousins energy. Keep them in mind. ๐Ÿ’…

  • $<C_COMPILER_ID> โ€” CMake's compiler id of the C compiler used. โœจ
  • $<C_COMPILER_ID:compiler_ids> โ€” where compiler_ids is a comma-separated list. 1 if CMake's compiler id of the C compiler matches any one of the entries in compiler_ids, otherwise 0.
Changed in version 3.15: multiple compiler_ids can be specified. CMake 3.14 and earlier only accepted a single compiler ID. ๐Ÿ’€ (we love growth tho)
  • $<CXX_COMPILER_ID> โ€” CMake's compiler id of the C++ compiler used.
  • $<CXX_COMPILER_ID:compiler_ids> โ€” where compiler_ids is a comma-separated list. 1 if CMake's compiler id of the C++ compiler matches any one of the entries in compiler_ids, otherwise 0.
Changed in version 3.15: multiple compiler_ids can be specified. CMake 3.14 and earlier only accepted a single compiler ID.
  • $<CUDA_COMPILER_ID> โ€” Added in version 3.15.</p> CMake's compiler id of the CUDA compiler used. โญ
  • $<CUDA_COMPILER_ID:compiler_ids> โ€” Added in version 3.15.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler id of the CUDA compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<OBJC_COMPILER_ID> โ€” Added in version 3.16.</p> CMake's compiler id of the Objective-C compiler used. ๐Ÿฆ‹
  • $<OBJC_COMPILER_ID:compiler_ids> โ€” Added in version 3.16.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler id of the Objective-C compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<OBJCXX_COMPILER_ID> โ€” Added in version 3.16.</p> CMake's compiler id of the Objective-C++ compiler used. ๐Ÿ’œ
  • $<OBJCXX_COMPILER_ID:compiler_ids> โ€” Added in version 3.16.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler id of the Objective-C++ compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<Fortran_COMPILER_ID> โ€” CMake's compiler id of the Fortran compiler used. ๐Ÿงš
  • $<Fortran_COMPILER_ID:compiler_ids> โ€” where compiler_ids is a comma-separated list. 1 if CMake's compiler id of the Fortran compiler matches any one of the entries in compiler_ids, otherwise 0.
Changed in version 3.15: multiple compiler_ids can be specified. CMake 3.14 and earlier only accepted a single compiler ID.
  • $<HIP_COMPILER_ID> โ€” Added in version 3.21.</p> CMake's compiler id of the HIP compiler used. ๐Ÿ”ฅ
  • $<HIP_COMPILER_ID:compiler_ids> โ€” Added in version 3.21.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler id of the HIP compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<ISPC_COMPILER_ID> โ€” Added in version 3.19.</p> CMake's compiler id of the ISPC compiler used. ๐ŸŒˆ
  • $<ISPC_COMPILER_ID:compiler_ids> โ€” Added in version 3.19.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler id of the ISPC compiler matches any one of the entries in compiler_ids, otherwise 0.

okay and now the frontend-variant squad, all of them fresh out of the 3.30 oven โœจ

  • $<C_COMPILER_FRONTEND_VARIANT> โ€” Added in version 3.30.</p> CMake's compiler frontend variant of the C compiler used. ๐ŸŽ€
  • $<C_COMPILER_FRONTEND_VARIANT:compiler_ids> โ€” Added in version 3.30.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler frontend variant of the C compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<CXX_COMPILER_FRONTEND_VARIANT> โ€” Added in version 3.30.</p> CMake's compiler frontend variant of the C++ compiler used. ๐Ÿ’–
  • $<CXX_COMPILER_FRONTEND_VARIANT:compiler_ids> โ€” Added in version 3.30.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler frontend variant of the C++ compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<CUDA_COMPILER_FRONTEND_VARIANT> โ€” Added in version 3.30.</p> CMake's compiler id of the CUDA compiler used. ๐Ÿ‘‘
  • $<CUDA_COMPILER_FRONTEND_VARIANT:compiler_ids> โ€” Added in version 3.30.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler frontend variant of the CUDA compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<OBJC_COMPILER_FRONTEND_VARIANT> โ€” Added in version 3.30.</p> CMake's compiler frontend variant of the Objective-C compiler used. ๐Ÿฆ‹
  • $<OBJC_COMPILER_FRONTEND_VARIANT:compiler_ids> โ€” Added in version 3.30.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler frontend variant of the Objective-C compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<OBJCXX_COMPILER_FRONTEND_VARIANT> โ€” Added in version 3.30.</p> CMake's compiler frontend variant of the Objective-C++ compiler used. ๐Ÿ’œ
  • $<OBJCXX_COMPILER_FRONTEND_VARIANT:compiler_ids> โ€” Added in version 3.30.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler frontend variant of the Objective-C++ compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<Fortran_COMPILER_FRONTEND_VARIANT> โ€” Added in version 3.30.</p> CMake's compiler id of the Fortran compiler used. ๐Ÿงš
  • $<Fortran_COMPILER_FRONTEND_VARIANT:compiler_ids> โ€” Added in version 3.30.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler frontend variant of the Fortran compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<HIP_COMPILER_FRONTEND_VARIANT> โ€” Added in version 3.30.</p> CMake's compiler id of the HIP compiler used. ๐Ÿ”ฅ
  • $<HIP_COMPILER_FRONTEND_VARIANT:compiler_ids> โ€” Added in version 3.30.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler frontend variant of the HIP compiler matches any one of the entries in compiler_ids, otherwise 0.
  • $<ISPC_COMPILER_FRONTEND_VARIANT> โ€” Added in version 3.30.</p> CMake's compiler id of the ISPC compiler used. ๐ŸŒˆ
  • $<ISPC_COMPILER_FRONTEND_VARIANT:compiler_ids> โ€” Added in version 3.30.</p> where compiler_ids is a comma-separated list. 1 if CMake's compiler frontend variant of the ISPC compiler matches any one of the entries in compiler_ids, otherwise 0.

  • $<COMPILE_LANGUAGE> โ€” Added in version 3.3.</p> The compile language of source files when evaluating compile options. ๐Ÿ‘€ Peep the related boolean expression $<COMPILE_LANGUAGE:languages> for the notes about the portability of this generator expression (they're important, ngl).
  • $<COMPILE_LANGUAGE:languages> โ€” Added in version 3.3.</p>
Changed in version 3.15: multiple languages can be specified for languages. CMake 3.14 and earlier only accepted a single language.

1 when the language used for compilation unit matches any of the comma-separated entries in languages, otherwise 0. This expression may be used to specify compile options, compile definitions, and include directories for source files of a particular language in a target. For example:

add_executable(myapp main.cpp foo.c bar.cpp zot.cu)
target_compile_options(myapp
  PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
)
target_compile_definitions(myapp
  PRIVATE $<$<COMPILE_LANGUAGE:CXX>:COMPILING_CXX>
          $<$<COMPILE_LANGUAGE:CUDA>:COMPILING_CUDA>
)
target_include_directories(myapp
  PRIVATE $<$<COMPILE_LANGUAGE:CXX,CUDA>:/opt/foo/headers>
)

So this specifies the use of the -fno-exceptions compile option, COMPILING_CXX compile definition, and cxx_headers include directory for C++ only (compiler id checks elided). It also specifies a COMPILING_CUDA compile definition for CUDA. ๐ŸŽฏ

Now the part you need to actually read, bestie: with Visual Studio Generators and Xcode there is no way to represent target-wide compile definitions or include directories separately for C and CXX languages. ๐Ÿ˜ญ Also, with Visual Studio Generators there is no way to represent target-wide flags separately for C and CXX languages. Under these generators, expressions for both C and C++ sources will be evaluated using CXX if there are any C++ sources and otherwise using C. A workaround is to create separate libraries for each source file language instead:

add_library(myapp_c foo.c)
add_library(myapp_cxx bar.cpp)
target_compile_options(myapp_cxx PUBLIC -fno-exceptions)
add_executable(myapp main.cpp)
target_link_libraries(myapp myapp_c myapp_cxx)
  • $<COMPILE_LANG_AND_ID:language,compiler_ids> โ€” Added in version 3.15.</p>

So yeah โ€” it spits out 1 when the language used for compilation unit matches language and CMake's compiler id of the language compiler matches any one of the comma-separated entries in compiler_ids, otherwise 0. This expression is a short form for the combination of $<COMPILE_LANGUAGE:language> and $<LANG_COMPILER_ID:compiler_ids>. It's the moment where compile options, compile definitions, and include directories get to be picky about a particular language and compiler combo inside one target, bestie ๐Ÿ’…

Like, watch:

add_executable(myapp main.cpp foo.c bar.cpp zot.cu)
target_compile_definitions(myapp
  PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,AppleClang,Clang>:COMPILING_CXX_WITH_CLANG>
          $<$<COMPILE_LANG_AND_ID:CXX,Intel>:COMPILING_CXX_WITH_INTEL>
          $<$<COMPILE_LANG_AND_ID:C,Clang>:COMPILING_C_WITH_CLANG>
)

That's different compile definitions based on both the compiler id and the compilation language, no cap. The example gives you a COMPILING_CXX_WITH_CLANG compile definition when Clang is the CXX compiler, and COMPILING_CXX_WITH_INTEL when Intel is the CXX compiler. And when the C compiler is Clang, it only sees the COMPILING_C_WITH_CLANG definition. ๐ŸŽ€

Without the COMPILE_LANG_AND_ID generator expression, the same logic would be expressed as:

target_compile_definitions(myapp
  PRIVATE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:AppleClang,Clang>>:COMPILING_CXX_WITH_CLANG>
          $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:Intel>>:COMPILING_CXX_WITH_INTEL>
          $<$<AND:$<COMPILE_LANGUAGE:C>,$<C_COMPILER_ID:Clang>>:COMPILING_C_WITH_CLANG>
)

See how much messier that is? The short form ate and left no crumbs. โœจ


๐ŸŒธ Compile Features ๐ŸŒธ

$<COMPILE_FEATURES:features> โ€” added in version 3.1 ๐Ÿฆ‹

where features is a comma-separated list. Evaluates to 1 if all of the features are available for the 'head' target, and 0 otherwise.

Heads up, genuinely ๐Ÿซถ โ€” if this expression is used while evaluating the link implementation of a target, and if any dependency transitively increases the required C_STANDARD or CXX_STANDARD for the 'head' target, an error is reported. Like, it will simply not let that slide.

See the cmake-compile-features(7) manual for info on compile features and a list of supported compilers. ๐Ÿ“š


๐Ÿงš Compile Context ๐Ÿงš

$<COMPILE_ONLY:...> โ€” added in version 3.27 ๐Ÿ’œ

Content of ..., when collecting transitive compile properties, otherwise it's the empty string. This one's intended for use in an INTERFACE_LINK_LIBRARIES and LINK_LIBRARIES target property, typically populated via the target_link_libraries() command. The whole vibe: it provides compilation usage requirements without any linking requirements. ๐Ÿ”ฅ

Use cases include header-only usage where all usages are known to not have linking requirements (e.g., all-inline or C++ template libraries). Very "I'm here for the compile, not the link" energy, honestly.

Note that for proper evaluation of this expression requires policy CMP0099 to be set to NEW. Don't forget it, or it's giving broken. ๐Ÿ’€

$<LINK_LANGUAGE> โ€” added in version 3.18 ๐ŸŒˆ

The link language of the target when evaluating link options. See the related boolean expression $<LINK_LANGUAGE:languages> for notes about the portability of this generator expression.

Note: this generator expression is not supported by the link libraries properties, to avoid side-effects due to the double evaluation of these properties. ๐Ÿ™ˆ

$<LINK_LANGUAGE:languages> โ€” added in version 3.18 ๐ŸŒˆ

1 when the language used for link step matches any of the comma-separated entries in languages, otherwise 0. You can use this to specify link libraries, link options, link directories and link dependencies of a particular language in a target. Iconic. For example:

add_library(api_C ...)
add_library(api_CXX ...)
add_library(api INTERFACE)
target_link_options(api   INTERFACE $<$<LINK_LANGUAGE:C>:-opt_c>
                                    $<$<LINK_LANGUAGE:CXX>:-opt_cxx>)
target_link_libraries(api INTERFACE $<$<LINK_LANGUAGE:C>:api_C>
                                    $<$<LINK_LANGUAGE:CXX>:api_CXX>)

add_executable(myapp1 main.c)
target_link_options(myapp1 PRIVATE api)

add_executable(myapp2 main.cpp)
target_link_options(myapp2 PRIVATE api)

This tells you to use the api target for linking targets myapp1 and myapp2. In practice, myapp1 will link with target api_C and option -opt_c because it will use C as link language. And myapp2 will link with api_CXX and option -opt_cxx because CXX will be the link language. Slay, they each got their own. ๐Ÿ’…

Note: to determine the link language of a target, it's required to collect, transitively, all the targets which will be linked to it. So, for link libraries properties, a double evaluation will be done. During the first evaluation, $<LINK_LANGUAGE:..> expressions will always return 0. The link language computed after this first pass gets used to do the second pass. To avoid inconsistency, it's required that the second pass does not change the link language. And to avoid unexpected side-effects, it's required to specify complete entities as part of the $<LINK_LANGUAGE:..> expression. For example:
add_library(lib STATIC file.cxx)
add_library(libother STATIC file.c)

# bad usage
add_executable(myapp1 main.c)
target_link_libraries(myapp1 PRIVATE lib$<$<LINK_LANGUAGE:C>:other>)

# correct usage
add_executable(myapp2 main.c)
target_link_libraries(myapp2 PRIVATE $<$<LINK_LANGUAGE:C>:libother>)

In this example, for myapp1, the first pass will, unexpectedly, determine that the link language is CXX because the evaluation of the generator expression will be an empty string โ€” so myapp1 will depends on target lib which is C++. ๐Ÿ˜ญ On the contrary, for myapp2, the first evaluation will give C as link language, so the second pass will correctly add target libother as link dependency. Big difference, fr fr.

$<LINK_LANG_AND_ID:language,compiler_ids> โ€” added in version 3.18 ๐ŸŒˆ

1 when the language used for link step matches language and the CMake's compiler id of the language linker matches any one of the comma-separated entries in compiler_ids, otherwise 0. This expression is a short form for the combination of $<LINK_LANGUAGE:language> and $<LANG_COMPILER_ID:compiler_ids>. It may be used to specify link libraries, link options, link directories and link dependencies of a particular language and linker combination in a target. For example:

add_library(libC_Clang ...)
add_library(libCXX_Clang ...)
add_library(libC_Intel ...)
add_library(libCXX_Intel ...)

add_executable(myapp main.c)
if (CXX_CONFIG)
  target_sources(myapp PRIVATE file.cxx)
endif()
target_link_libraries(myapp
  PRIVATE $<$<LINK_LANG_AND_ID:CXX,Clang,AppleClang>:libCXX_Clang>
          $<$<LINK_LANG_AND_ID:C,Clang,AppleClang>:libC_Clang>
          $<$<LINK_LANG_AND_ID:CXX,Intel>:libCXX_Intel>
          $<$<LINK_LANG_AND_ID:C,Intel>:libC_Intel>)

Different link libraries based on both the compiler id and link language โ€” that's the whole point ๐ŸŽฏ This example will have target libCXX_Clang as link dependency when Clang or AppleClang is the CXX linker, and libCXX_Intel when Intel is the CXX linker. Likewise when the C linker is Clang or AppleClang, target libC_Clang will be added as link dependency and libC_Intel when Intel is the C linker.

See the note related to $<LINK_LANGUAGE:language> for constraints about the usage of this generator expression. (Yes, the double-evaluation note above. It applies. Read it. ๐Ÿซถ)


$<LINK_LIBRARY:feature,library-list> โ€” added in version 3.24 โญ

Specify a set of libraries to link to a target, along with a feature which provides details about how they should be linked. For example:

add_library(lib1 STATIC ...)
add_library(lib2 ...)
target_link_libraries(lib2 PRIVATE "$<LINK_LIBRARY:WHOLE_ARCHIVE,lib1>")

This specifies that lib2 should link to lib1 and use the WHOLE_ARCHIVE feature when doing so. Obsessed, honestly. ๐Ÿ’–

Feature names are case-sensitive and may only contain letters, numbers and underscores. Feature names defined in all uppercase are reserved for CMake's own built-in features. The pre-defined built-in library features are:

  • DEFAULT โ€” this feature corresponds to standard linking, essentially equivalent to using no feature at all. It's typically only used with the LINK_LIBRARY_OVERRIDE and LINK_LIBRARY_OVERRIDE_<LIBRARY> target properties.
  • WHOLE_ARCHIVE โ€” force inclusion of all members of a static library when linked as a dependency of consuming Executables, Shared Libraries, and Module Libraries. This feature is only supported for the following platforms, with limitations as noted:
    • Linux.
    • All BSD variants.
    • SunOS.
    • All Apple variants. The library must be specified as a CMake target name, a library file name (such as libfoo.a), or a library file path (such as /path/to/libfoo.a). Due to a limitation of the Apple linker, it cannot be specified as a plain library name like foo, where foo is not a CMake target. ๐Ÿ˜ญ
    • Windows. When using a MSVC or MSVC-like toolchain, the MSVC version must be greater than 1900.
  • Cygwin.
  • MSYS.
Note, bestie, this one matters: since Static Libraries <#static-libraries> are archives and not linked binaries, CMake records their link dependencies for transitive use when linking consuming binaries. So WHOLE_ARCHIVE does not cause a static library's objects to be included in other static libraries. If that's the vibe you're going for, use Object Libraries <#object-libraries> instead โ€” periodt.

FRAMEWORK

Okay so this option tells the linker to go looking for the specified framework using the -framework linker option. ๐ŸŒธ It can only be used on Apple platforms, and only with a linker that actually understands the option used (i.e. the linker provided with Xcode, or one compatible with it). No cap.

The framework can be specified as a CMake framework target, a bare framework name, or a file path. If a target is given, that target must have the FRAMEWORK <#prop_tgt:FRAMEWORK> target property set to true. For a file path, if it contains a directory part, that directory will be added as a framework search path. ๐Ÿ’…

add_library(lib SHARED ...)
target_link_libraries(lib PRIVATE "$<LINK_LIBRARY:FRAMEWORK,/path/to/my_framework>")

# The constructed linker command line will contain:
#   -F/path/to -framework my_framework

File paths must conform to one of the following patterns (* is a wildcard, and optional parts are shown as [...]):

  • [/path/to/]FwName[.framework]
  • [/path/to/]FwName.framework/FwName[suffix]
  • [/path/to/]FwName.framework/Versions/*/FwName[suffix]

Note that CMake recognizes and automatically handles framework targets, even without using the $<LINK_LIBRARY:FRAMEWORK,...> expression. The generator expression can still be used with a CMake target if the project wants to be explicit about it, but it is not required to do so. The linker command line may have some differences between using the generator expression or not, but the final result should be the same. ๐Ÿฆ‹ On the other hand, if a file path is given, CMake will recognize some paths automatically, but not all cases. The project may want to use $<LINK_LIBRARY:FRAMEWORK,...> for file paths so that the expected behavior is clear. iykyk.

Added in version 3.25: The FRAMEWORK_MULTI_CONFIG_POSTFIX_<CONFIG> <#prop_tgt:FRAMEWORK_MULTI_CONFIG_POSTFIX_<CONFIG>> target property as well as the suffix of the framework library name are now supported by the FRAMEWORK features. โœจ

NEEDED_FRAMEWORK

Basically the same as the FRAMEWORK feature, except it forces the linker to link with the framework even if no symbols are used from it. ๐Ÿ‘‘ It uses the -needed_framework option and has the same linker constraints as FRAMEWORK.

REEXPORT_FRAMEWORK

Also similar to FRAMEWORK, except it tells the linker that the framework should be available to clients linking to the library being created. ๐ŸŒˆ It uses the -reexport_framework option and has the same linker constraints as FRAMEWORK.

WEAK_FRAMEWORK

Same energy as FRAMEWORK again, except it forces the linker to mark the framework and all references to it as weak imports. ๐Ÿ’€ It uses the -weak_framework option and has the same linker constraints as FRAMEWORK.

NEEDED_LIBRARY

This one is like NEEDED_FRAMEWORK, except it's for use with non-framework targets or libraries (Apple platforms only ๐Ÿ“). It uses the -needed_library or -needed-l option as appropriate, and has the same linker constraints as NEEDED_FRAMEWORK.

REEXPORT_LIBRARY

Similar to REEXPORT_FRAMEWORK, except it's for use with non-framework targets or libraries (Apple platforms only ๐ŸŒธ). It uses the -reexport_library or -reexport-l option as appropriate, and has the same linker constraints as REEXPORT_FRAMEWORK.

WEAK_LIBRARY

Similar to WEAK_FRAMEWORK, except it's for use with non-framework targets or libraries (Apple platforms only ๐Ÿ’œ). It uses the -weak_library or -weak-l option as appropriate, and has the same linker constraints as WEAK_FRAMEWORK.


Built-in and custom library features are defined in terms of the following variables:

  • CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED <#variable:CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED>
  • CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE> <#variable:CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE>>
  • CMAKE_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED <#variable:CMAKE_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED>
  • CMAKE_LINK_LIBRARY_USING_<FEATURE> <#variable:CMAKE_LINK_LIBRARY_USING_<FEATURE>>

The value used for each of these variables is the value as set at the end of the directory scope in which the target was created. ๐Ÿซถ The usage is as follows:

  1. If the language-specific CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED <#variable:CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED> variable is true, the feature must be defined by the corresponding CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE> <#variable:CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE>> variable.
  2. If no language-specific feature is supported, then the CMAKE_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED <#variable:CMAKE_LINK_LIBRARY_USING_<FEATURE>_SUPPORTED> variable must be true and the feature must be defined by the corresponding CMAKE_LINK_LIBRARY_USING_<FEATURE> <#variable:CMAKE_LINK_LIBRARY_USING_<FEATURE>> variable.

The following limitations should be noted:

  • The library-list can specify CMake targets or libraries. Any CMake target of type OBJECT <#object-libraries> or INTERFACE <#interface-libraries> will ignore the feature aspect of the expression and instead be linked in the standard way. ๐Ÿ™ˆ
  • The $<LINK_LIBRARY:...> generator expression can only be used to specify link libraries. In practice, this means it can appear in the LINK_LIBRARIES <#prop_tgt:LINK_LIBRARIES>, INTERFACE_LINK_LIBRARIES <#prop_tgt:INTERFACE_LINK_LIBRARIES>, and INTERFACE_LINK_LIBRARIES_DIRECT <#prop_tgt:INTERFACE_LINK_LIBRARIES_DIRECT> target properties, and be specified in target_link_libraries() <#command:target_link_libraries> and link_libraries() <#command:link_libraries> commands.
  • If a $<LINK_LIBRARY:...> generator expression appears in the INTERFACE_LINK_LIBRARIES <#prop_tgt:INTERFACE_LINK_LIBRARIES> property of a target, it will be included in the imported target generated by a install(EXPORT) <#export> command. โš ๏ธ It is the responsibility of the environment consuming this import to define the link feature used by this expression.
  • Each target or library involved in the link step must have at most only one kind of library feature. The absence of a feature is also incompatible with all other features. For example:
add_library(lib1 ...)
add_library(lib2 ...)
add_library(lib3 ...)

# lib1 will be associated with feature1
target_link_libraries(lib2 PUBLIC "$<LINK_LIBRARY:feature1,lib1>")

# lib1 is being linked with no feature here. This conflicts with the
# use of feature1 in the line above and would result in an error.
target_link_libraries(lib3 PRIVATE lib1 lib2)

Where it isn't possible to use the same feature throughout a build for a given target or library, the LINK_LIBRARY_OVERRIDE <#prop_tgt:LINK_LIBRARY_OVERRIDE> and LINK_LIBRARY_OVERRIDE_<LIBRARY> <#prop_tgt:LINK_LIBRARY_OVERRIDE_<LIBRARY>> target properties can be used to resolve such incompatibilities. ๐Ÿงš

  • The $<LINK_LIBRARY:...> generator expression does not guarantee that the list of specified targets and libraries will be kept grouped together. To manage constructs like --start-group and --end-group, as supported by the GNU ld linker, use the LINK_GROUP generator expression instead. ๐Ÿ”ฅ
Added in version 3.24. โœจ

Specify a group of libraries to link to a target, along with a feature which defines how that group should be linked. For example:

add_library(lib1 STATIC ...)
add_library(lib2 ...)
target_link_libraries(lib2 PRIVATE "$<LINK_GROUP:RESCAN,lib1,external>")

This specifies that lib2 should link to lib1 and external, and that both of those two libraries should be included on the linker command line according to the definition of the RESCAN feature. ๐Ÿ’–

Feature names are case-sensitive and may only contain letters, numbers and underscores. Feature names defined in all uppercase are reserved for CMake's own built-in features. ๐Ÿ‘‘ Currently, there is only one pre-defined built-in group feature:

RESCAN : Some linkers are single-pass only. For such linkers, circular references between libraries typically result in unresolved symbols. ๐Ÿ’€ This feature instructs the linker to search the specified static libraries repeatedly until no new undefined references are created.

...and honestly? this is such a slay once you get it. Okay so the tea: normally a static library gets searched, like, once in the order you specified it on the command line. And if a symbol in that library is needed to resolve an undefined symbol that's referred to by an object in a library that shows up later on the command line โ€” bestie, the linker literally cannot resolve that reference. ๐Ÿ˜ญ But! By grouping the static libraries together with the RESCAN feature, they all get searched repeatedly until all possible references are resolved. It's giving persistence.

This uses linker options like --start-group and --end-group, or on SunOS, -z rescan-start and -z rescan-end.

โš ๏ธ Real talk: using this feature has a significant performance cost. It's best to use it only when there are unavoidable circular references between two or more static libraries. Don't just sprinkle it everywhere for the vibes.

This feature is available when using toolchains that target Linux, BSD, and SunOS. It can also be used when targeting Windows platforms if the GNU toolchain is used.

Added in version 4.4: RESCAN is additionally available when using toolchains targeting Haiku. ๐ŸŒธ

Built-in and custom group features are defined in terms of the following variables:

  • CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTED
  • CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>
  • CMAKE_LINK_GROUP_USING_<FEATURE>_SUPPORTED
  • CMAKE_LINK_GROUP_USING_<FEATURE>

The value used for each of these variables is the value as set at the end of the directory scope in which the target was created. Like, that's the snapshot it takes, periodt. The usage is as follows:

  1. If the language-specific CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTED variable is true, the feature must be defined by the corresponding CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE> variable.
  2. If no language-specific feature is supported, then the CMAKE_LINK_GROUP_USING_<FEATURE>_SUPPORTED variable must be true and the feature must be defined by the corresponding CMAKE_LINK_GROUP_USING_<FEATURE> variable.

The LINK_GROUP generator expression is compatible with the LINK_LIBRARY generator expression. The libraries involved in a group can be specified using the LINK_LIBRARY generator expression. Iconic duo behavior, honestly. ๐Ÿ‘‘

Each target or external library involved in the link step is allowed to be part of multiple groups, but only if all the groups involved specify the same feature. Such groups will not be merged on the linker command line โ€” the individual groups will still be preserved. Mixing different group features for the same target or library is forbidden. ๐Ÿšซ

add_library(lib1 ...)
add_library(lib2 ...)
add_library(lib3 ...)
add_library(lib4 ...)
add_library(lib5 ...)

target_link_libraries(lib3 PUBLIC  "$<LINK_GROUP:feature1,lib1,lib2>")
target_link_libraries(lib4 PRIVATE "$<LINK_GROUP:feature1,lib1,lib3>")
# lib4 will be linked with the groups {lib1,lib2} and {lib1,lib3}.
# Both groups specify the same feature, so this is fine.

target_link_libraries(lib5 PRIVATE "$<LINK_GROUP:feature2,lib1,lib3>")
# An error will be raised here because both lib1 and lib3 are part of two
# groups with different features.

When a target or an external library is involved in the link step as part of a group and also as not part of any group, any occurrence of the non-group link item will be replaced by the groups it belongs to. Yes, it really does that. ๐Ÿ™ˆ

add_library(lib1 ...)
add_library(lib2 ...)
add_library(lib3 ...)
add_library(lib4 ...)

target_link_libraries(lib3 PUBLIC lib1)

target_link_libraries(lib4 PRIVATE lib3 "$<LINK_GROUP:feature1,lib1,lib2>")
# lib4 will only be linked with lib3 and the group {lib1,lib2}

Because lib1 is part of the group defined for lib4, that group then gets applied back to the use of lib1 for lib3. The end result will be as though the linking relationship for lib3 had been specified as:

target_link_libraries(lib3 PUBLIC "$<LINK_GROUP:feature1,lib1,lib2>")

Be aware that the precedence of the group over the non-group link item can result in circular dependencies between groups. If this occurs, a fatal error is raised because circular dependencies are not allowed for groups. ๐Ÿ’€ Like, the build will simply pass away.

add_library(lib1A ...)
add_library(lib1B ...)
add_library(lib2A ...)
add_library(lib2B ...)
add_library(lib3 ...)

# Non-group linking relationships, these are non-circular so far
target_link_libraries(lib1A PUBLIC lib2A)
target_link_libraries(lib2B PUBLIC lib1B)

# The addition of these groups creates circular dependencies
target_link_libraries(lib3 PRIVATE
  "$<LINK_GROUP:feat,lib1A,lib1B>"
  "$<LINK_GROUP:feat,lib2A,lib2B>"
)

Because of the groups defined for lib3, the linking relationships for lib1A and lib2B effectively get expanded to the equivalent of:

target_link_libraries(lib1A PUBLIC "$<LINK_GROUP:feat,lib2A,lib2B>")
target_link_libraries(lib2B PUBLIC "$<LINK_GROUP:feat,lib1A,lib1B>")

This creates a circular dependency between groups: lib1A --> lib2B --> lib1A. Round and round forever, no thoughts head empty. ๐ŸŒ€

The following limitations should also be noted:

  • The library-list can specify CMake targets or libraries. Any CMake target of type OBJECT or INTERFACE will ignore the feature aspect of the expression and instead be linked in the standard way.
  • The $<LINK_GROUP:...> generator expression can only be used to specify link libraries. In practice, this means it can appear in the LINK_LIBRARIES, INTERFACE_LINK_LIBRARIES, and INTERFACE_LINK_LIBRARIES_DIRECT target properties, and be specified in target_link_libraries() and link_libraries() commands.
  • If a $<LINK_GROUP:...> generator expression appears in the INTERFACE_LINK_LIBRARIES property of a target, it will be included in the imported target generated by a install(EXPORT) command. It is the responsibility of the environment consuming this import to define the link feature used by this expression.

Added in version 3.1.

Content of ..., except while collecting usage requirements from transitive compile properties, in which case it is the empty string. This is intended for use in an INTERFACE_LINK_LIBRARIES target property, typically populated via the target_link_libraries() command, to specify private link dependencies without other usage requirements such as include directories or compile options. It's the "link only, don't @ me about anything else" of expressions. โœจ

Added in version 3.24: LINK_ONLY may also be used in a LINK_LIBRARIES target property. See policy CMP0131.

Added in version 3.18.

Returns the list if it is the device link step, an empty list otherwise. The device link step is controlled by CUDA_SEPARABLE_COMPILATION and CUDA_RESOLVE_DEVICE_SYMBOLS properties and policy CMP0105. This expression can only be used to specify link options.

Added in version 3.18.

Returns the list if it is the normal link step, an empty list otherwise. This expression is mainly useful when a device link step is also involved (see $<DEVICE_LINK:list> generator expression). This expression can only be used to specify link options.


๐Ÿ’… Linker ID and Frontend-Variant ๐Ÿ’…

See also the CMAKE_<LANG>_COMPILER_LINKER_ID and CMAKE_<LANG>_COMPILER_LINKER_FRONTEND_VARIANT variables, which are closely related to most of the expressions in this sub-section. iykyk ๐Ÿ’ซ

$<C_COMPILER_LINKER_ID>

Added in version 4.2.

CMake's linker id of the C linker used.

$<C_COMPILER_LINKER_ID:linker_ids>

Added in version 4.2.

where linker_ids is a comma-separated list. 1 if CMake's linker id of the C linker matches any one of the entries in linker_ids, otherwise 0.

$<CXX_COMPILER_LINKER_ID>

Added in version 4.2.

CMake's linker id of the C++ linker used.

$<CXX_COMPILER_LINKER_ID:linker_ids>

Added in version 4.2.

where linker_ids is a comma-separated list. 1 if CMake's linker id of the C++ linker matches any one of the entries in linker_ids, otherwise 0.

$<CUDA_COMPILER_LINKER_ID>

Added in version 4.2.

CMake's linker id of the CUDA linker used.

$<CUDA_COMPILER_LINKER_ID:linker_ids>

Added in version 4.2.

where linker_ids is a comma-separated list. 1 if CMake's linker id of the CUDA linker matches any one of the entries in linker_ids, otherwise 0.

$<OBJC_COMPILER_LINKER_ID>

Added in version 4.2.

CMake's linker id of the Objective-C linker used.

$<OBJC_COMPILER_LINKER_ID:linker_ids>

Added in version 4.2.

  • $<OBJC_COMPILER_LINKER_ID:linker_ids> โ€” Added in version 4.2. where linker_ids is a comma-separated list. Gives you 1 if CMake's linker id of the Objective-C linker matches any one of the entries in linker_ids, otherwise 0. โœจ
  • $<OBJCXX_COMPILER_LINKER_ID> โ€” Added in version 4.2. CMake's linker id of the Objective-C++ linker used. ๐ŸŽ€
  • $<OBJCXX_COMPILER_LINKER_ID:linker_ids> โ€” Added in version 4.2. where linker_ids is a comma-separated list. 1 if CMake's linker id of the Objective-C++ linker matches any one of the entries in linker_ids, otherwise 0.
  • $<Fortran_COMPILER_LINKER_ID> โ€” Added in version 4.2. CMake's linker id of the Fortran linker used. ๐ŸŒธ
  • $<Fortran_COMPILER_LINKER_ID:linker_ids> โ€” Added in version 4.2. where linker_ids is a comma-separated list. 1 if CMake's linker id of the Fortran linker matches any one of the entries in linker_ids, otherwise 0.
  • $<HIP_COMPILER_LINKER_ID> โ€” Added in version 4.2. CMake's linker id of the HIP linker used. ๐Ÿฆ‹
  • $<HIP_COMPILER_LINKER_ID:linker_ids> โ€” Added in version 4.2. where linker_ids is a comma-separated list. 1 if CMake's linker id of the HIP linker matches any one of the entries in linker_ids, otherwise 0.

<section>okay so now we're doing the frontend variant girlies, and yes there is one for basically every language, they really said "consistency slay" ๐Ÿ’…</section>

  • $<C_COMPILER_LINKER_FRONTEND_VARIANT> โ€” Added in version 4.2. CMake's linker frontend variant of the C linker used. โญ
  • $<C_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids> โ€” Added in version 4.2. where variant_ids is a comma-separated list. 1 if CMake's linker frontend variant of the C linker matches any one of the entries in variant_ids, otherwise 0.
  • $<CXX_COMPILER_LINKER_FRONTEND_VARIANT> โ€” Added in version 4.2. CMake's linker frontend variant of the C++ linker used. ๐Ÿ’œ
  • $<CXX_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids> โ€” Added in version 4.2. where variant_ids is a comma-separated list. 1 if CMake's linker frontend variant of the C++ linker matches any one of the entries in variant_ids, otherwise 0.
  • $<CUDA_COMPILER_LINKER_FRONTEND_VARIANT> โ€” Added in version 4.2. CMake's linker frontend variant of the CUDA linker used. ๐Ÿ”ฅ
  • $<CUDA_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids> โ€” Added in version 4.2. where variant_ids is a comma-separated list. 1 if CMake's linker frontend variant of the CUDA linker matches any one of the entries in variant_ids, otherwise 0.
  • $<OBJC_COMPILER_LINKER_FRONTEND_VARIANT> โ€” Added in version 4.2. CMake's linker frontend variant of the Objective-C linker used. ๐Ÿ“
  • $<OBJC_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids> โ€” Added in version 4.2. where variant_ids is a comma-separated list. 1 if CMake's linker frontend variant of the Objective-C linker matches any one of the entries in variant_ids, otherwise 0.
  • $<OBJCXX_COMPILER_LINKER_FRONTEND_VARIANT> โ€” Added in version 4.2. CMake's linker frontend variant of the Objective-C++ linker used. ๐ŸŒˆ
  • $<OBJCXX_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids> โ€” Added in version 4.2. where variant_ids is a comma-separated list. 1 if CMake's linker frontend variant of the Objective-C++ linker matches any one of the entries in variant_ids, otherwise 0.
  • $<Fortran_COMPILER_LINKER_FRONTEND_VARIANT> โ€” Added in version 4.2. CMake's linker frontend variant of the Fortran linker used. ๐Ÿงš
  • $<Fortran_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids> โ€” Added in version 4.2. where variant_ids is a comma-separated list. 1 if CMake's linker frontend variant of the Fortran linker matches any one of the entries in variant_ids, otherwise 0.
  • $<HIP_COMPILER_LINKER_FRONTEND_VARIANT> โ€” Added in version 4.2. CMake's linker frontend variant of the HIP linker used. ๐Ÿ‘‘
  • $<HIP_COMPILER_LINKER_FRONTEND_VARIANT:variant_ids> โ€” Added in version 4.2. where variant_ids is a comma-separated list. 1 if CMake's linker frontend variant of the HIP linker matches any one of the entries in variant_ids, otherwise 0.

๐Ÿซถ Source-Dependent Expressions ๐Ÿซถ

Okay bestie, this is where it gets a little spicy so stay with me. ๐Ÿ’– The source file, as specified in the following expressions, can be nonexistent on the file system (i.e. a generated file) โ€” but it must be known from CMake. A source file becomes known from CMake if it's part of some target (library or executable) or when a source file property is defined. And like, moreover, this information is specific to the directory where the declaration occurred. Directory-scoped, no cap.

For example, these generator expressions enable a uniform behavior, for the add_custom_command() and add_custom_target() commands, regarding the source properties:

function(custom_add_library target)
  unset(sources)
  foreach(source IN LISTS ARGN)
    add_custom_command(
      OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${source}.bin
      COMMAND my-compiler -o ${CMAKE_CURRENT_BINARY_DIR}/${source}.bin
                          "$<$<SOURCE_EXISTS:${source}>:$<SOURCE_PROPERTY:${source},COMPILE_OPTIONS>>"
                          ${source})
    list(APPEND sources ${CMAKE_CURRENT_BINARY_DIR}/${source}.bin)
  endforeach()
  add_custom_target(${target}
                    DEPENDS ${sources})
endfunction()

custom_add_library(my-lib file1.x file2.x file3.x)
set_property(SOURCE file1.x PROPERTY COMPILE_OPTIONS -X)
set_property(SOURCE file2.x PROPERTY COMPILE_OPTIONS -Y)

๐ŸŒŸ Source Meta-Data ๐ŸŒŸ

These expressions look up information about a source file. Detective era fr fr ๐Ÿ”

  • $<SOURCE_EXISTS:src[,(DIRECTORY:dir|TARGET_DIRECTORY:tgt)]> โ€” Added in version 4.3. 1 if src exists as a CMake source file, else 0. By default, the source file is searched in the scope of the current source directory or the directory of the consuming target.

Directory scope can be overridden with one of the following sub-options:

  • DIRECTORY:dir โ€” The source file will be searched in the dir directory's scope. CMake must know about the directory, either by having added it through a call to add_subdirectory() or dir being the top level directory. Relative paths are treated as relative to the current source directory.
  • TARGET_DIRECTORY:target โ€” The source file will be searched in the directory scope in which target was created (target must therefore exist).

๐Ÿ’… Source Properties ๐Ÿ’…

These expressions look up the values of source file properties. Like, the properties, bestie. โœจ

  • $<SOURCE_PROPERTY:src[,(DIRECTORY:dir|TARGET_DIRECTORY:target)],prop> โ€” Added in version 4.3. Value of the property prop on the source file src, or empty if the property is not set. ๐Ÿ˜ญ An error will be raised if the source file is not known by CMake. By default, the source file's property will be read from the current source directory's scope or the directory of the consuming target.

Directory scope can be overridden with one of the following sub-options:

  • DIRECTORY:dir โ€” The source file property will be read from the dir directory's scope. CMake must know about the directory, either by having added it through a call to add_subdirectory() or dir being the top level directory. Relative paths are treated as relative to the current source directory.
  • TARGET_DIRECTORY:target โ€” The source file property will be read from the directory scope in which target was created (target must therefore exist).

๐ŸŽ€ FileSet-Dependent Expressions ๐ŸŽ€

๐ŸŒธ FileSet Meta-Data ๐ŸŒธ

These expressions look up information about a file set. Yes, file sets get their own little meta-data moment, we love to see it. ๐Ÿ‘‘

  • $<FILE_SET_EXISTS:fileset,TARGET:target> โ€” Added in version 4.3. 1 if the fileset exists as a CMake file set attached to the target, else 0.

The possible sub-options are:

  • TARGET:target โ€” The target on which the file set depends.

๐Ÿฆ‹ FileSet Properties ๐Ÿฆ‹

These expressions look up the values of file set properties.

  • $<FILE_SET_PROPERTY:fileset,TARGET:target,prop> โ€” Added in version 4.3. Value of the property prop on the file set fileset, or empty if the property is not set. An error will be raised if the file set is not known by CMake. ๐Ÿ˜ญ

The possible sub-options are:

  • TARGET:target โ€” The target on which the file set depends.

๐Ÿ’œ Target-Dependent Expressions ๐Ÿ’œ

โญ Target Meta-Data โญ

These expressions look up information about a target. Main character energy, honestly.

  • $<TARGET_EXISTS:tgt> โ€” Added in version 3.12. 1 if tgt exists as a CMake target, else 0. โœ…
  • $<TARGET_NAME_IF_EXISTS:tgt> โ€” Added in version 3.12. The target name tgt if the target exists, an empty string otherwise.
Heads up bestie: note that tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿ™ˆ
  • $<TARGET_NAME:tgt> โ€” The target name tgt as written. This marks tgt as being the name of a target inside a larger expression, which is required if exporting targets to multiple dependent export sets. The tgt text must be a literal name of a target; it may not contain generator expressions. The target does not have to exist. ๐ŸŽฏ
  • $<TARGET_POLICY:policy> โ€” 1 if the policy was NEW when the 'head' target was created, else 0. If the policy was not set, the warning message for the policy will be emitted. This generator expression only works for a subset of policies. ๐Ÿ‘€

๐Ÿ”ฅ Target Properties ๐Ÿ”ฅ

These expressions look up the values of target properties. The way that these just read your properties for you, iconic.

  • $<TARGET_PROPERTY:tgt,prop> โ€” Value of the property prop on the target tgt, or empty if the property is not set.
Note that tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿ™ˆ

Changed in version 3.26: When encountered during evaluation of Target Usage Requirements, typically in an INTERFACE_* target property, lookup of the tgt name occurs in the directory of the target specifying the requirement, rather than the directory of the consuming target for which the expression is being evaluated. ๐Ÿ“

  • $<TARGET_PROPERTY:prop> โ€” the value of prop on the target the expression is being evaluated for. If the property isn't set? You get an empty string, bestie, that's just how it is. ๐Ÿ’…
Ok so important lil plot twist: when you use this inside generator expressions in Target Usage Requirements, "the target" means the consuming target โ€” not the one that wrote the requirement. Different main character, same property name. ๐Ÿ™ˆ

So like, the expressions have special evaluation rules for a few families of properties, and honestly they each have their own personality. Let's meet them:

Target Build Specification Properties

These evaluate as a semicolon-separated list (yes, the classic CMake list vibe) that's the union of the value on the target itself plus the values of the corresponding Target Usage Requirements on the targets named by the target's LINK_LIBRARIES:

  • For Target Compile Properties, evaluating the corresponding usage requirements is transitive over the closure of the linked targets' INTERFACE_LINK_LIBRARIES โ€” and it excludes entries guarded by the LINK_ONLY generator expression. ๐Ÿ™…โ€โ™€๏ธ
  • For Target Link Properties, evaluating the corresponding usage requirements is transitive over the closure of the linked targets' INTERFACE_LINK_LIBRARIES โ€” but this time it includes entries guarded by the LINK_ONLY generator expression. See policy CMP0166. ๐Ÿซถ
Changed in version 4.1: evaluation of LINK_LIBRARIES itself is now transitive. See policy CMP0189. That's the whole character arc, ok?

Target Usage Requirement Properties

Same energy, different closet: these evaluate as a semicolon-separated list that's the union of the value on the target itself with the values of the same properties on targets named by the target's INTERFACE_LINK_LIBRARIES:

  • For Transitive Compile Properties, evaluation is transitive over the closure of the target's INTERFACE_LINK_LIBRARIES, excluding entries guarded by the LINK_ONLY generator expression. โœจ
  • For Transitive Link Properties, evaluation is transitive over the closure of the target's INTERFACE_LINK_LIBRARIES, including entries guarded by the LINK_ONLY generator expression. See policy CMP0166. ๐Ÿฆ‹
Changed in version 4.1: evaluation of INTERFACE_LINK_LIBRARIES itself is now transitive. See policy CMP0189. Two glow-ups, one release, iconic behavior.

Custom Transitive Properties

Added in version 3.30. Bestie these get processed during evaluation, and the algorithm is a whole journey โ€” lemme walk you through it:

  • Evaluation of $<TARGET_PROPERTY:tgt,PROP> for some property PROP, named without an INTERFACE_ prefix, checks the TRANSITIVE_COMPILE_PROPERTIES and TRANSITIVE_LINK_PROPERTIES properties on target tgt, on targets named by its LINK_LIBRARIES, and on the transitive closure of targets named by the linked targets' INTERFACE_LINK_LIBRARIES.

If PROP is listed by one of those properties, then it evaluates as a semicolon-separated list representing the union of the value on the target itself with the values of the corresponding INTERFACE_PROP on targets named by the target's LINK_LIBRARIES:

  • If PROP is named by TRANSITIVE_COMPILE_PROPERTIES, evaluation of the corresponding INTERFACE_PROP is transitive over the closure of the linked targets' INTERFACE_LINK_LIBRARIES, excluding entries guarded by the LINK_ONLY generator expression.
  • If PROP is named by TRANSITIVE_LINK_PROPERTIES, evaluation of the corresponding INTERFACE_PROP is transitive over the closure of the linked targets' INTERFACE_LINK_LIBRARIES, including entries guarded by the LINK_ONLY generator expression.
  • Evaluation of $<TARGET_PROPERTY:tgt,INTERFACE_PROP> for some property INTERFACE_PROP, named with an INTERFACE_ prefix, checks the TRANSITIVE_COMPILE_PROPERTIES and TRANSITIVE_LINK_PROPERTIES properties on target tgt, and on the transitive closure of targets named by its INTERFACE_LINK_LIBRARIES.

If the corresponding PROP is listed by one of those properties, then INTERFACE_PROP evaluates as a semicolon-separated list representing the union of the value on the target itself with the value of the same property on targets named by the target's INTERFACE_LINK_LIBRARIES:

  • If PROP is named by TRANSITIVE_COMPILE_PROPERTIES, evaluation of the corresponding INTERFACE_PROP is transitive over the closure of the target's INTERFACE_LINK_LIBRARIES, excluding entries guarded by the LINK_ONLY generator expression.
  • If PROP is named by TRANSITIVE_LINK_PROPERTIES, evaluation of the corresponding INTERFACE_PROP is transitive over the closure of the target's INTERFACE_LINK_LIBRARIES, including entries guarded by the LINK_ONLY generator expression.
Lowkey the drama: if a PROP is named by both TRANSITIVE_COMPILE_PROPERTIES and TRANSITIVE_LINK_PROPERTIES, the latter takes precedence. Link wins, sorry compile stans. ๐Ÿ’€

Compatible Interface Properties

These ones evaluate as a single value, combined from the target itself, from targets named by the target's LINK_LIBRARIES, and from the transitive closure of the linked targets' INTERFACE_LINK_LIBRARIES. And how do values from multiple targets combine? Based on the type of compatibility required by the COMPATIBLE_INTERFACE_* property defining it. It's giving โœจnegotiated peace treatyโœจ.


๐ŸŽ€ Target Artifacts

These expressions go looking up info about artifacts associated with a given target tgt. Unless it says otherwise, that can be any runtime artifact, namely:

  • An executable target created by add_executable().
  • A shared library target (.so, .dll โ€” but not their .lib import library) created by add_library().
  • A static library target created by add_library().

Also, quick glossary moment because this trips people up every single time: when we say "the tgt filename" we mean the name of the tgt binary file. Do not confuse that with "the target name", which is literally just the string tgt. Two different things, ok? ๐Ÿซถ


  • $<TARGET_FILE:tgt> โ€” full path to the tgt binary file. ๐Ÿ“

Heads up: tgt is not added as a dependency of the target this expression is evaluated on โ€” unless the expression is being used in add_custom_command() or add_custom_target(). Then it is. Surprise! ๐ŸŽ‰

  • $<TARGET_FILE_BASE_NAME:tgt[,POSTFIX:(INCLUDE|EXCLUDE)]> โ€” Added in version 3.15.

Added in version 4.2: the POSTFIX option can be used to control whether the <CONFIG>_POSTFIX target property value is included as part of the base name or not. The default is POSTFIX:INCLUDE. ๐Ÿ’…

This gives you the base name of tgt, i.e. $<TARGET_FILE_NAME:tgt> without prefix and suffix and, optionally, postfix. So like, if the tgt filename is libbase_postfix.so, the base name is:

  • base_postfix for $<TARGET_FILE_BASE_NAME:tgt> or $<TARGET_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.
  • base for $<TARGET_FILE_BASE_NAME:tgt,POSTFIX:EXCLUDE>.

See also the OUTPUT_NAME, ARCHIVE_OUTPUT_NAME, LIBRARY_OUTPUT_NAME and RUNTIME_OUTPUT_NAME target properties, their configuration-specific variants OUTPUT_NAME_<CONFIG>, ARCHIVE_OUTPUT_NAME_<CONFIG>, LIBRARY_OUTPUT_NAME_<CONFIG> and RUNTIME_OUTPUT_NAME_<CONFIG>, and the <CONFIG>_POSTFIX and DEBUG_POSTFIX target properties. So many names, so much lore. ๐ŸŒธ

And once again: tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿซ 

  • $<TARGET_FILE_PREFIX:tgt> โ€” Added in version 3.15.

The prefix of the tgt filename (such as lib). See also the PREFIX target property.

Note that tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_FILE_SUFFIX:tgt> โ€” Added in version 3.15.

The suffix of the tgt filename (extension such as .so or .exe). See also the SUFFIX target property.

Note that tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_FILE_NAME:tgt> โ€” the tgt filename, that's it, that's the whole thing. ๐Ÿฆ‹

Note that tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112).

  • $<TARGET_FILE_DIR:tgt> โ€” the directory of the tgt binary file. ๐Ÿ“
๐Ÿ’ก lil reminder that rolls over from the entry above: tgt is not added as a dependency of the target this expression gets evaluated on (go peep policy CMP0112, iykyk).
  • $<TARGET_IMPORT_FILE:tgt> โ€” Added in version 3.27. The full path to the linker import file, bestie. On DLL platforms that's your .lib file. For executables on AIX, and for shared libraries on macOS, it could be (respectively, babes) the .imp or .tbd import file โ€” depends on whatever the ENABLE_EXPORTS target property is set to. ๐Ÿซถ

And when there's, like, no import file attached to the target at all? It expands to an empty string. Not a crash, not a weird path. Empty. ๐Ÿ’…

  • $<TARGET_IMPORT_FILE_BASE_NAME:tgt[,POSTFIX:(INCLUDE|EXCLUDE)]> โ€” Added in version 3.27. This one gives you the base name of the linker import file of the target tgt โ€” no prefix, no suffix, and optionally no postfix either.

Added in version 4.2: the POSTFIX option is your little toggle for whether the <CONFIG>_POSTFIX target property value gets included as part of the base name or not. Default is POSTFIX:INCLUDE. โœจ

So say your target file name is libbase_postfix.tbd โ€” then the base name is:

  • base_postfix for $<TARGET_IMPORT_FILE_BASE_NAME:tgt> or $<TARGET_IMPORT_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.
  • base for $<TARGET_IMPORT_FILE_BASE_NAME:tgt,POSTFIX:EXCLUDE>.

See also the OUTPUT_NAME and ARCHIVE_OUTPUT_NAME target properties, their config-specific glow-ups OUTPUT_NAME_<CONFIG> and ARCHIVE_OUTPUT_NAME_<CONFIG>, plus the <CONFIG>_POSTFIX and DEBUG_POSTFIX target properties. ๐ŸŽ€

โš ๏ธ and again, because it matters: tgt is not added as a dependency of the target this expression is evaluated on.
  • $<TARGET_IMPORT_FILE_PREFIX:tgt> โ€” Added in version 3.27. The prefix of the import file of target tgt. See also the IMPORT_PREFIX target property. And ngl, tgt still isn't added as a dependency of the target this thing is evaluated on. ๐Ÿฆ‹
  • $<TARGET_IMPORT_FILE_SUFFIX:tgt> โ€” Added in version 3.27. The suffix of the import file of target tgt. The suffix is literally the file extension (think .lib or .tbd). See also the IMPORT_SUFFIX target property. And yeah โ€” tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿ™ˆ
  • $<TARGET_IMPORT_FILE_NAME:tgt> โ€” Added in version 3.27. Just the name of the import file of target tgt. That's it, that's the whole vibe. Note that tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿ’œ
  • $<TARGET_IMPORT_FILE_DIR:tgt> โ€” Added in version 3.27. The directory of the import file of target tgt. Note that tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿ“

  • $<TARGET_LINKER_FILE:tgt> โ€” the file used when linking to the tgt target. Usually this is the library that tgt represents (.a, .lib, .so), but for a shared library on DLL platforms it'd be the .lib import library that goes with the DLL. ๐Ÿ‘‘

Added in version 3.27: on macOS it could be the .tbd import file associated with the shared library, depending on the value of the ENABLE_EXPORTS property.

This generator expression is equivalent to the $<TARGET_LINKER_LIBRARY_FILE> or $<TARGET_LINKER_IMPORT_FILE> generator expressions โ€” which one you get depends on the characteristics of the target and the platform. โœจ

  • $<TARGET_LINKER_FILE_BASE_NAME:tgt[,POSTFIX:(INCLUDE|EXCLUDE)]> โ€” Added in version 3.15. The base name of the file used to link target tgt, i.e. $<TARGET_LINKER_FILE_NAME:tgt> minus prefix and suffix, and optionally minus postfix. ๐Ÿ’…

Added in version 4.2: the POSTFIX option lets you control whether the <CONFIG>_POSTFIX target property value is included as part of the base name or not. Default is POSTFIX:INCLUDE. ๐ŸŒธ

Example time โ€” if the target file name is libbase_postfix.a, the base name is:

  • base_postfix for $<TARGET_LINKER_FILE_BASE_NAME:tgt> or $<TARGET_LINKER_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.
  • base for $<TARGET_LINKER_FILE_BASE_NAME:tgt,POSTFIX:EXCLUDE>.

See also the OUTPUT_NAME, ARCHIVE_OUTPUT_NAME, and LIBRARY_OUTPUT_NAME target properties, their configuration-specific variants OUTPUT_NAME_<CONFIG>, ARCHIVE_OUTPUT_NAME_<CONFIG> and LIBRARY_OUTPUT_NAME_<CONFIG>, and the <CONFIG>_POSTFIX and DEBUG_POSTFIX target properties. ๐ŸŽ€

โš ๏ธ say it with me: tgt is not added as a dependency of the target this expression is evaluated on.
  • $<TARGET_LINKER_FILE_PREFIX:tgt> โ€” Added in version 3.15. The prefix of the file used to link target tgt. See also the PREFIX and IMPORT_PREFIX target properties. And tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿฆ‹
  • $<TARGET_LINKER_FILE_SUFFIX:tgt> โ€” Added in version 3.15. The suffix of the file used to link, where tgt is the name of a target. The suffix corresponds to the file extension โ€” like ".so" or ".lib", cute little extensions. ๐Ÿ“

See also the SUFFIX and IMPORT_SUFFIX target properties.

Note that tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_LINKER_FILE_NAME:tgt> โ€” the name of the file used to link target tgt. Note that tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112). ๐Ÿ’œ
  • $<TARGET_LINKER_FILE_DIR:tgt> โ€” the directory of the file used to link target tgt. Note that tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112). ๐Ÿ’œ

  • $<TARGET_LINKER_LIBRARY_FILE:tgt> โ€” Added in version 3.27. The file used when linking to the tgt target is done by using the library directly and not an import file. Usually that's the library tgt represents (.a, .so, .dylib). So on DLL platforms? It's just an empty string, bestie. ๐Ÿ”ฅ
  • $<TARGET_LINKER_LIBRARY_FILE_BASE_NAME:tgt[,POSTFIX:(INCLUDE|EXCLUDE)]> โ€” Added in version 3.27. The base name of the library file used to link target tgt, i.e. $<TARGET_LINKER_LIBRARY_FILE_NAME:tgt> without prefix and suffix, and, optionally, postfix. ๐Ÿ’…

Added in version 4.2: the POSTFIX option controls whether the <CONFIG>_POSTFIX target property value is included as part of the base name. Default is POSTFIX:INCLUDE. โœจ

For example, if the target file name is libbase_postfix.a, the base name is:

  • base_postfix for $<TARGET_LINKER_LIBRARY_FILE_BASE_NAME:tgt> or $<TARGET_LINKER_LIBRARY_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.
  • base for $<TARGET_LINKER_LIBRARY_FILE_BASE_NAME:tgt,POSTFIX:EXCLUDE>.

See also the OUTPUT_NAME, ARCHIVE_OUTPUT_NAME, and LIBRARY_OUTPUT_NAME target properties, their configuration-specific variants OUTPUT_NAME_<CONFIG>, ARCHIVE_OUTPUT_NAME_<CONFIG> and LIBRARY_OUTPUT_NAME_<CONFIG>, and the <CONFIG>_POSTFIX and DEBUG_POSTFIX target properties. ๐ŸŽ€

โš ๏ธ tgt is not added as a dependency of the target this expression is evaluated on.
  • $<TARGET_LINKER_LIBRARY_FILE_PREFIX:tgt> โ€” Added in version 3.27. The prefix of the library file used to link target tgt. See also the PREFIX target property. And tgt is not added as a dependency of the target this expression is evaluated on. ๐ŸŒˆ
  • $<TARGET_LINKER_LIBRARY_FILE_SUFFIX:tgt> โ€” Added in version 3.27. The suffix of the library file used to link target tgt. The suffix corresponds to the file extension (such as ".a" or ".dylib"). See also the SUFFIX target property. Note that tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿฆ‹
  • $<TARGET_LINKER_LIBRARY_FILE_NAME:tgt> โ€” Added in version 3.27. The name of the library file used to link target tgt. Note that tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿ’œ
  • $<TARGET_LINKER_LIBRARY_FILE_DIR:tgt> โ€” Added in version 3.27. The directory of the library file used to link target tgt. Note that tgt is not added as a dependency of the target this expression is evaluated on. ๐Ÿ“

  • $<TARGET_LINKER_IMPORT_FILE:tgt> โ€” Added in version 3.27. The file used when linking to the tgt target happens via an import file. Usually that's the import file tgt represents (.lib, .tbd). So like, when no import file is involved in the link step, you get an empty string back. ๐Ÿ’…
  • $<TARGET_LINKER_IMPORT_FILE_BASE_NAME:tgt[,POSTFIX:(INCLUDE|EXCLUDE)]> โ€” Added in version 3.27. The base name of the import file used to link target tgt, i.e. $<TARGET_LINKER_IMPORT_FILE_NAME:tgt> without prefix and suffix, and, optionally, postfix.

Added in version 4.2: the POSTFIX option can be used to control the inclusion or not of the <CONFIG>_POSTFIX target property value as part of the base name. The default is POSTFIX:INCLUDE. โœจ

For example, if the target file name is libbase_postfix.tbd, the base name is

  • base_postfix for $<TARGET_LINKER_IMPORT_FILE_BASE_NAME:tgt> or $<TARGET_LINKER_IMPORT_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.
  • base for $<TARGET_LINKER_IMPORT_FILE_BASE_NAME:tgt,POSTFIX:EXCLUDE>.

bestie also go peek at the OUTPUT_NAME and ARCHIVE_OUTPUT_NAME target properties, their per-config glow-ups OUTPUT_NAME_<CONFIG> and ARCHIVE_OUTPUT_NAME_<CONFIG>, and the <CONFIG>_POSTFIX and DEBUG_POSTFIX target properties. iykyk ๐Ÿ’…

  • $<TARGET_LINKER_IMPORT_FILE_PREFIX:tgt> โ€” โœจ Added in version 3.27. This one hands you the prefix of the import file that's used to link target tgt. ๐ŸŽ€

Also check the IMPORT_PREFIX target property if you're nosy like me.

heads up: tgt is not added as a dependency of the target this expression is evaluated on. fr fr.

  • $<TARGET_LINKER_IMPORT_FILE_SUFFIX:tgt> โ€” โœจ Added in version 3.27. The suffix of the import file used to link target tgt.

And what even is the suffix? It's the file extension, bestie โ€” think .lib or .tbd. ๐Ÿฆ‹

See also the IMPORT_SUFFIX target property.

heads up: tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_LINKER_IMPORT_FILE_NAME:tgt> โ€” โœจ Added in version 3.27. The name of the import file used to link target tgt. simple, iconic, does exactly what it says ๐Ÿ’…

heads up: tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_LINKER_IMPORT_FILE_DIR:tgt> โ€” โœจ Added in version 3.27. The directory of the import file used to link target tgt. it's giving "where does this thing live" energy ๐Ÿ 

heads up: tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_SONAME_FILE:tgt> โ€” the file with the soname (.so.3), where tgt is the name of a target. ๐ŸŒธ
  • $<TARGET_SONAME_FILE_NAME:tgt> โ€” the name of the file with the soname (.so.3).

heads up: tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112).

  • $<TARGET_SONAME_FILE_DIR:tgt> โ€” the directory of the file with the soname (.so.3).

heads up: tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112).

  • $<TARGET_SONAME_IMPORT_FILE:tgt> โ€” โœจ Added in version 3.27. the import file with the soname (.3.tbd), where tgt is the name of a target. ๐Ÿซถ
  • $<TARGET_SONAME_IMPORT_FILE_NAME:tgt> โ€” โœจ Added in version 3.27. the name of the import file with the soname (.3.tbd).

heads up: tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_SONAME_IMPORT_FILE_DIR:tgt> โ€” โœจ Added in version 3.27. the directory of the import file with the soname (.3.tbd).

heads up: tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_PDB_FILE:tgt> โ€” โœจ Added in version 3.1. Full path to the linker-generated program database file (.pdb), where tgt is the name of a target. ๐Ÿ—‚๏ธ
Changed in version 4.2: the postfix โ€” as specified by the DEBUG_POSTFIX or <CONFIG>_POSTFIX target properties โ€” is always included in the PDB file name. See policy CMP0202. no cap, always.

See also the PDB_NAME and PDB_OUTPUT_DIRECTORY target properties, plus their configuration-specific variants PDB_NAME_<CONFIG> and PDB_OUTPUT_DIRECTORY_<CONFIG>. ๐Ÿ’–

  • $<TARGET_PDB_FILE_BASE_NAME:tgt[,POSTFIX:(INCLUDE|EXCLUDE)]> โ€” โœจ Added in version 3.15. The base name of the linker-generated program database file (.pdb), where tgt is the name of a target. ๐ŸŽ€

โœจ Added in version 4.2: the POSTFIX option lets you control whether the <CONFIG>_POSTFIX target property value is included as part of the base name or not. The default is POSTFIX:INCLUDE. obsessed with a default, honestly.

Changed in version 4.2: the postfix โ€” as specified by the DEBUG_POSTFIX or <CONFIG>_POSTFIX target properties โ€” is always included in the PDB base name, except if the POSTFIX option has the value EXCLUDE. See policy CMP0202.

So the base name is the target PDB file name (see $<TARGET_PDB_FILE_NAME:tgt>) minus the prefix and suffix, and optionally minus the postfix. For example, if the target file name is base_postfix.pdb, then the base name is:

  • base_postfix for $<TARGET_PDB_FILE_BASE_NAME:tgt> or $<TARGET_PDB_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.
  • base for $<TARGET_PDB_FILE_BASE_NAME:tgt,POSTFIX:EXCLUDE>.

See also the OUTPUT_NAME and PDB_NAME target properties, their configuration-specific variants OUTPUT_NAME_<CONFIG> and PDB_NAME_<CONFIG>, and the <CONFIG>_POSTFIX and DEBUG_POSTFIX target properties. ๐Ÿฆ‹

heads up: tgt is not added as a dependency of the target this expression is evaluated on.

  • $<TARGET_PDB_FILE_NAME:tgt> โ€” โœจ Added in version 3.1. The name of the linker-generated program database file (.pdb).

heads up: tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112).

  • $<TARGET_PDB_FILE_DIR:tgt> โ€” โœจ Added in version 3.1. The directory of the linker-generated program database file (.pdb).

heads up: tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112).

  • $<TARGET_BUNDLE_DIR:tgt> โ€” โœจ Added in version 3.9. Full path to the bundle directory (/path/to/my.app, /path/to/my.framework, or /path/to/my.bundle), where tgt is the name of a target. ๐Ÿ“ apple girlies this one's for you.

heads up: tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112).

  • $<TARGET_BUNDLE_DIR_NAME:tgt> โ€” โœจ Added in version 3.24. The name of the bundle directory (my.app, my.framework, or my.bundle), where tgt is the name of a target. just the name bestie, not the whole path ๐Ÿ’…

heads up: tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112).

  • $<TARGET_BUNDLE_CONTENT_DIR:tgt> โ€” โœจ Added in version 3.9. Full path to the bundle content directory, where tgt is the name of a target. ๐ŸŒˆ

For the macOS SDK it leads to /path/to/my.app/Contents, /path/to/my.framework, or /path/to/my.bundle/Contents. For all other SDKs (e.g. iOS) it leads to /path/to/my.app, /path/to/my.framework, or /path/to/my.bundle โ€” because of the flat bundle structure. the more you know โœจ

heads up: tgt is not added as a dependency of the target this expression is evaluated on (see policy CMP0112).

  • $<TARGET_OBJECTS:tgt[,SOURCE_FILES:source_file[;source_file]...]> โ€” โœจ Added in version 3.1. A list of objects resulting from building tgt. This would typically be used on object library targets. ๐Ÿ‘‘

Additional arguments:

  • SOURCE_FILES:source_file[;source_file]... โ€” โœจ Added in version 4.4. An optional list of one or more source files for the target. Only the object files built from those source files will be returned โ€” but not necessarily in the same order they were specified. ๐Ÿ™ˆ so don't come for me about ordering.
  • $<TARGET_RUNTIME_DLLS:tgt> โ€” โœจ Added in version 3.21. A list of DLLs that the target depends on at runtime. ๐Ÿ”ฅ

How's that figured out? By the locations of all the SHARED targets in the target's transitive dependencies. If you only need the directories of the DLLs, see the TARGET_RUNTIME_DLL_DIRS generator expression instead.

โš ๏ธ lowkey important: using this generator expression on targets other than executables, SHARED libraries, and MODULE libraries is an error. and on non-DLL platforms, this expression always evaluates to an empty string. ๐Ÿ’€

Bestie, you can use this generator expression to copy all the DLLs a target depends on into its output directory, in a POST_BUILD custom command, using the cmake -E copy -t command. For example:

find_package(foo CONFIG REQUIRED) # package generated by install(EXPORT)

add_executable(exe main.c)
target_link_libraries(exe PRIVATE foo::foo foo::bar)
add_custom_command(TARGET exe POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy -t $<TARGET_FILE_DIR:exe> $<TARGET_RUNTIME_DLLS:exe>
  COMMAND_EXPAND_LISTS
)

Note: Imported Targets are supported only if they know the location of their .dll files. An imported SHARED library must have IMPORTED_LOCATION set to its .dll file. See the add_library imported libraries section for the details. And like โ€” many Find Modules produce imported targets with the UNKNOWN type, and therefore those will be ignored. ๐Ÿ™ˆ

On platforms that support runtime paths (RPATH), refer to the INSTALL_RPATH target property. On Apple platforms, refer to the INSTALL_NAME_DIR target property. ๐ŸŽ

  • $<TARGET_RUNTIME_DLL_DIRS:tgt> โ€” โœจ Added in version 3.27.

...list of the directories that hold the DLLs your target needs at runtime (peep TARGET_RUNTIME_DLLS for the โœจreceiptsโœจ). Where do those dirs come from? They're figured out from the locations of all the SHARED targets in your target's transitive dependencies. Little heads-up though, bestie: using this genex on anything that isn't an executable, a SHARED library, or a MODULE library is an error ๐Ÿšจ. And on non-DLL platforms? It literally always evaluates to an empty string. Cute, right? ๐Ÿ’…

Where's it useful? Say you wanna cook up a batch file with file(GENERATE) that sets the PATH environment variable accordingly โ€” yeah, exactly that. Obsessed ๐Ÿซถ

  • $<TARGET_INTERMEDIATE_DIR:tgt> โ€” Added in version 4.2. The full path to the directory where intermediate target files live โ€” you know, stuff like object and dependency files. The behind-the-scenes files, the ones doing the actual work while the final artifact takes all the credit ๐Ÿ’€

๐ŸŽ€ Export And Install Expressions ๐ŸŽ€

  • $<INSTALL_INTERFACE:...> โ€” gives you the content of ... when the property is exported via install(EXPORT), and empty otherwise. That's it, that's the whole vibe.
  • $<BUILD_INTERFACE:...> โ€” content of ... when the property is exported using export(), or when the target is used by another target in the same buildsystem. Otherwise? Empty string, babe. Expands to nothing.
  • $<BUILD_LOCAL_INTERFACE:...> โ€” Added in version 3.26. Content of ... when the target is used by another target in the same buildsystem โ€” just that one condition, no export() clause here. Expands to the empty string otherwise โœจ
  • $<INSTALL_PREFIX> โ€” the content of the install prefix when the target gets exported via install(EXPORT), or when it's evaluated inside the INSTALL_NAME_DIR property or the INSTALL_NAME_DIR argument of install(RUNTIME_DEPENDENCY_SET). Empty otherwise.
Changed in version 3.27: it also evaluates to the content of the install prefix in the code argument of install(CODE) or the file argument of install(SCRIPT). See, we do keep up with the times ๐Ÿ’…

๐Ÿฆ‹ Multi-level Expression Evaluation ๐Ÿฆ‹

Okay this one's giving inception, but stay with me โ€” it's actually iconic.

  • $<GENEX_EVAL:expr> โ€” Added in version 3.12. Gives you the content of expr, but evaluated as a generator expression in the current context. Lowkey the whole point: it lets you consume generator expressions whose evaluation results are themselves generator expressions. Genex-ception ๐Ÿ”ฅ
  • $<TARGET_GENEX_EVAL:tgt,expr> โ€” Added in version 3.12. Content of expr evaluated as a generator expression in the context of the tgt target. This is how you consume custom target properties that themselves contain generator expressions, bestie. Custom properties with genexes inside? Handled.

The ability to evaluate generator expressions is extremely useful when you're managing custom properties that support generator expressions. Example time, because why would I just tell you when I can show you:

add_library(foo ...)

set_property(TARGET foo PROPERTY
  CUSTOM_KEYS $<$<CONFIG:DEBUG>:FOO_EXTRA_THINGS>
)

add_custom_target(printFooKeys
  COMMAND ${CMAKE_COMMAND} -E echo $<TARGET_PROPERTY:foo,CUSTOM_KEYS>
)

Now listen โ€” this naive implementation of the printFooKeys custom command is wrong, fr fr. Why? Because the CUSTOM_KEYS target property is not evaluated, and the content gets passed along as-is, i.e. $<$<CONFIG:DEBUG>:FOO_EXTRA_THINGS>. Not the vibe. ๐Ÿ˜ญ

To actually get the expected result (that's FOO_EXTRA_THINGS if the config is Debug), you gotta evaluate the output of $<TARGET_PROPERTY:foo,CUSTOM_KEYS>:

add_custom_target(printFooKeys
  COMMAND ${CMAKE_COMMAND} -E
    echo $<TARGET_GENEX_EVAL:foo,$<TARGET_PROPERTY:foo,CUSTOM_KEYS>>
)

There we go. Slay. ๐Ÿ‘‘


๐ŸŒธ Escaped Characters ๐ŸŒธ

These expressions evaluate to specific string literals โ€” little escape hatches. Use them in place of the actual string literal wherever you need to stop it from doing its special-meaning thing. Sneaky but necessary ๐Ÿงš

  • $<ANGLE-R> โ€” a literal >. Used, for example, to compare strings that contain a >.
  • $<COMMA> โ€” a literal ,. Used, for example, to compare strings which contain a ,.
  • $<SEMICOLON> โ€” a literal ;. Used to prevent list expansion on an argument with ;. Honestly the number of times this one has saved a girl's whole build... ๐Ÿ™ˆ
  • $<QUOTE> โ€” Added in version 3.30. A literal ". Used to allow string literal quotes inside a generator expression. New-ish, and already carrying the team.

๐Ÿ’€ Deprecated Expressions ๐Ÿ’€

  • $<CONFIGURATION> โ€” configuration name. Deprecated since CMake 3.0. Use CONFIG instead. Like, please. She's retired, let her rest ๐Ÿ’…

2000-2026 Kitware, Inc. and Contributors


And that's a wrap on this whole glorious page, bestie ๐ŸŽ€๐Ÿซถ You just read through CMAKE-GENERATOR-EXPRESSIONS(7) from CMake 4.4.5, dated Oct 28, 2026 โ€” go forth and genex responsibly, mwah ๐Ÿ’–โœจ๐Ÿ‘‘

๐Ÿ“ฆ where this page comes from