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 is1, or an empty string if condition evaluates to0. 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 is1, or false_string if condition is0. 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 is1, and generator expressions in true_string will not evaluate when condition is0. ๐
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 to0or1. Evaluates to0if any of the following is true:- string is empty,
- string is a case-insensitive equal of
0,FALSE,OFF,N,NO,IGNORE, orNOTFOUND, 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 either1or0. The whole expression evaluates to1if all conditions are1. If any condition is0, the whole expression evaluates to0.$<OR:conditions>โ where conditions is a comma-separated list of boolean expressions, all of which must evaluate to either1or0. The whole expression evaluates to1if at least one of the conditions is1. If all conditions evaluate to0, the whole expression evaluates to0.$<NOT:condition>โ condition must be0or1. The result of the expression is0if condition is1, else1.
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>โ1if value1 and value2 are numerically equal, else0.
๐ Version Comparisons
$<VERSION_LESS:v1,v2>โ1if v1 is a version less than v2, else0.$<VERSION_GREATER:v1,v2>โ1if v1 is a version greater than v2, else0.$<VERSION_EQUAL:v1,v2>โ1if v1 is the same version as v2, else0.$<VERSION_LESS_EQUAL:v1,v2>โ Added in version 3.7.1if v1 is a version less than or equal to v2, else0.$<VERSION_GREATER_EQUAL:v1,v2>โ Added in version 3.7.1if v1 is a version greater than or equal to v2, else0.
๐ 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>โ1ifstring1andstring2are lexicographically equal, else0.$<STRLESS:string1,string2>โ Added in version 4.3.1ifstring1is lexicographically less thanstring2, else0.$<STRGREATER:string1,string2>โ Added in version 4.3.1ifstring1is lexicographically greater thanstring2, else0.$<STRLESS_EQUAL:string1,string2>โ Added in version 4.3.1ifstring1is lexicographically less than or equal tostring2, else0.$<STRGREATER_EQUAL:string1,string2>โ Added in version 4.3.1ifstring1is lexicographically greater than or equal tostring2, else0.
๐ฆ String Queries ๐ฆ
$<STRING:LENGTH,string>โ Added in version 4.3. The length of the givenstringin bytes. And yes, that means ifstringhas 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 givenstring. Iflengthis-1or greater than the string length, you get the remainder of the string starting atbegin. Bothbeginandlengthare counted in bytes, so be careful ifstringcould contain multi-byte characters. ๐$<STRING:FIND,string[,FROM:(BEGIN|END)],substring>โ Added in version 4.3. The position where the givensubstringwas found in the suppliedstring. If the substring isn't found, you get a position of-1. TheFROM: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. Matchesregular_expressioninside thestring. TheSEEK: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 theinputarguments together using thegluestring.$<STRING:ASCII,number[,number]...>โ Added in version 4.3. Converts all the numbers in the range1-255into 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 optionalUTCflag asks for the current date/time representation in Coordinated Universal Time (UTC) instead of local time. If theSOURCE_DATE_EPOCHenvironment 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.60represents 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.0is 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+hhmmor-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:%Sfor local time.%Y-%m-%dT%H:%M:%SZfor 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 is5characters.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 formatxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxwhere eachxrepresents 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โnameis 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 ofmatch_stringin thestringwithreplace_string. Thematch_stringcan be one of two different types:STRINGโmatch_stringis a literal string and the match is done by simple string comparison. This is the default.REGEXโmatch_stringis a regular expression. Matches thisregular_expressionas many times as possible and substitutes thereplace_stringfor 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 theinputarguments to thestring.$<STRING:PREPEND,string,input[,input]...>โ Added in version 4.3. Prepends all theinputarguments to thestring.$<STRING:TOLOWER,string>โ Added in version 4.3. Content ofstringconverted to lower case.$<STRING:TOUPPER,string>โ Added in version 4.3. Content ofstringconverted to upper case.$<STRING:STRIP,SPACES,string>โ Added in version 4.3. Removes the specified elements from thestring. The possible options are:SPACESโ removes the leading and trailing spaces of thestring.
$<STRING:QUOTE,REGEX,string>โ Added in version 4.3. Escapes the specified elements of thestring. The possible options are:REGEXโ escapes all characters that have special meaning in a regular expression, so thestringcan 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 thestringto its hexadecimal representation. Letters in the result (athroughf) are lowercase.$<STRING:HASH,string,ALGORITHM:algorithm>โ Added in version 4.3. Computes a cryptographic hash of thestring. The supported algorithm names, as specified by theALGORITHM: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 likestring(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 you1if string is an item in the semicolon-separated list, else0. 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 is0, 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-1if 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 are0toN, whereNis the length of the list, inclusive. an empty list has length0. 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 anACTIONto all of the list items โ or, if you specify aSELECTOR, only to the selected ones. glow-up, but targeted. โจ
Note: theTRANSFORMsub-command does not change the number of items in the list. if aSELECTORis 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 policyCMP0186. ๐
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 theCOMPAREoption 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 forstrverscmp(3)), such that contiguous digits are compared as whole numbers. for example, the following list10.0 1.1 2.1 8.0 2.0 3.1will be sorted as1.1 2.0 2.1 3.1 8.0 10.0if theNATURALcomparison is selected, whereas it will be sorted as1.1 10.0 2.0 2.1 3.1 8.0with theSTRINGcomparison. 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 theCASEoption 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 theORDERoption 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 ๐. Returns1if the paths are equal,0otherwise. Peepcmake_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 return1if that particular path component is present,0otherwise. 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. Returns1if the path is absolute according tocmake_path(IS_ABSOLUTE),0otherwise. Simple, clean, iconic ๐$<PATH:IS_RELATIVE,path>โ Added in version 3.24. Returns1if the path is relative according tocmake_path(IS_RELATIVE),0otherwise ๐ธ$<PATH:IS_PREFIX[,NORMALIZE],path,input>โ Added in version 3.24. Returns1if path is the prefix of input,0otherwise. When you pass theNORMALIZEoption, 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 theNORMALIZEoption, 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 theNORMALIZEoption, 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 ๐ Seecmake_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 ๐ Seecmake_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>returns0), then path comes back unchanged. Nothing happens. We move on โจ Seecmake_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 ๐ Seecmake_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. Seecmake_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. Seecmake_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 you1), it gets evaluated relative to the base directory you specified with the base_directory argument. When you pass theNORMALIZEoption, 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 deprecatedCONFIGURATIONgenerator expression, bestie, the old one is giving expired ๐$<CONFIG:cfgs>โ1if config is any one of the entries in the comma-separated list cfgs, else0. This comparison is case-insensitive ๐ซถ The mapping inMAP_IMPORTED_CONFIG_<CONFIG>is also considered by this expression when it's evaluated on a property of anIMPORTEDtarget ๐
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 inadd_custom_command()andadd_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 inadd_custom_command()andadd_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 theCMAKE_SYSTEM_NAMEvariable, they're besties ๐ซถ$<PLATFORM_ID:platform_ids>โ1if CMake's platform id matches any one of the entries in the comma-separated list platform_ids, otherwise0. Also see theCMAKE_SYSTEM_NAMEvariable โจ
๐ฅ 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>โ1if the version of the C compiler matches version, otherwise0.$<CXX_COMPILER_VERSION>โ The version of the CXX compiler being used ๐$<CXX_COMPILER_VERSION:version>โ1if the version of the C++ compiler matches version, otherwise0.$<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.1if the version of the C++ compiler matches version, otherwise0. (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>1if the version of the Objective-C++ compiler matches version, otherwise0.$<Fortran_COMPILER_VERSION>โ The version of the Fortran compiler used. ๐ง$<Fortran_COMPILER_VERSION:version>โ1if the version of the Fortran compiler matches version, otherwise0.$<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>1if the version of the HIP compiler matches version, otherwise0.$<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>1if the version of the ISPC compiler matches version, otherwise0.
๐ซถ 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.1if CMake's compiler id of the C compiler matches any one of the entries in compiler_ids, otherwise0.
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.1if CMake's compiler id of the C++ compiler matches any one of the entries in compiler_ids, otherwise0.
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.1if CMake's compiler id of the CUDA compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler id of the Objective-C compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler id of the Objective-C++ compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler id of the Fortran compiler matches any one of the entries in compiler_ids, otherwise0.
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.1if CMake's compiler id of the HIP compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler id of the ISPC compiler matches any one of the entries in compiler_ids, otherwise0.
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.1if CMake's compiler frontend variant of the C compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler frontend variant of the C++ compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler frontend variant of the CUDA compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler frontend variant of the Objective-C compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler frontend variant of the Objective-C++ compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler frontend variant of the Fortran compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler frontend variant of the HIP compiler matches any one of the entries in compiler_ids, otherwise0.$<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.1if CMake's compiler frontend variant of the ISPC compiler matches any one of the entries in compiler_ids, otherwise0.
$<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 requiredC_STANDARDorCXX_STANDARDfor 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 policyCMP0099to be set toNEW. Don't forget it, or it's giving broken. ๐
๐ Link Language and ID ๐
$<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 Features ๐
$<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 theLINK_LIBRARY_OVERRIDEandLINK_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 likefoo, wherefoois 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: TheFRAMEWORK_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 theFRAMEWORKfeatures. โจ
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:
- 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 correspondingCMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE><#variable:CMAKE_<LANG>_LINK_LIBRARY_USING_<FEATURE>> variable. - 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 correspondingCMAKE_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> orINTERFACE<#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 theLINK_LIBRARIES<#prop_tgt:LINK_LIBRARIES>,INTERFACE_LINK_LIBRARIES<#prop_tgt:INTERFACE_LINK_LIBRARIES>, andINTERFACE_LINK_LIBRARIES_DIRECT<#prop_tgt:INTERFACE_LINK_LIBRARIES_DIRECT> target properties, and be specified intarget_link_libraries()<#command:target_link_libraries> andlink_libraries()<#command:link_libraries> commands. - If a
$<LINK_LIBRARY:...>generator expression appears in theINTERFACE_LINK_LIBRARIES<#prop_tgt:INTERFACE_LINK_LIBRARIES> property of a target, it will be included in the imported target generated by ainstall(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-groupand--end-group, as supported by the GNUldlinker, use theLINK_GROUPgenerator expression instead. ๐ฅ
$<LINK_GROUP:feature,library-list>
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>_SUPPORTEDCMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>CMAKE_LINK_GROUP_USING_<FEATURE>_SUPPORTEDCMAKE_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:
- If the language-specific
CMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>_SUPPORTEDvariable is true, the feature must be defined by the correspondingCMAKE_<LANG>_LINK_GROUP_USING_<FEATURE>variable. - If no language-specific feature is supported, then the
CMAKE_LINK_GROUP_USING_<FEATURE>_SUPPORTEDvariable must be true and the feature must be defined by the correspondingCMAKE_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
OBJECTorINTERFACEwill 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 theLINK_LIBRARIES,INTERFACE_LINK_LIBRARIES, andINTERFACE_LINK_LIBRARIES_DIRECTtarget properties, and be specified intarget_link_libraries()andlink_libraries()commands. - If a
$<LINK_GROUP:...>generator expression appears in theINTERFACE_LINK_LIBRARIESproperty of a target, it will be included in the imported target generated by ainstall(EXPORT)command. It is the responsibility of the environment consuming this import to define the link feature used by this expression.
๐ฆ Link Context ๐ฆ
$<LINK_ONLY:...>
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.
$<DEVICE_LINK:list>
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.
$<HOST_LINK:list>
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. wherelinker_idsis a comma-separated list. Gives you1if CMake's linker id of the Objective-C linker matches any one of the entries inlinker_ids, otherwise0. โจ$<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. wherelinker_idsis a comma-separated list.1if CMake's linker id of the Objective-C++ linker matches any one of the entries inlinker_ids, otherwise0.$<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. wherelinker_idsis a comma-separated list.1if CMake's linker id of the Fortran linker matches any one of the entries inlinker_ids, otherwise0.$<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. wherelinker_idsis a comma-separated list.1if CMake's linker id of the HIP linker matches any one of the entries inlinker_ids, otherwise0.
<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. wherevariant_idsis a comma-separated list.1if CMake's linker frontend variant of the C linker matches any one of the entries invariant_ids, otherwise0.$<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. wherevariant_idsis a comma-separated list.1if CMake's linker frontend variant of the C++ linker matches any one of the entries invariant_ids, otherwise0.$<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. wherevariant_idsis a comma-separated list.1if CMake's linker frontend variant of the CUDA linker matches any one of the entries invariant_ids, otherwise0.$<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. wherevariant_idsis a comma-separated list.1if CMake's linker frontend variant of the Objective-C linker matches any one of the entries invariant_ids, otherwise0.$<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. wherevariant_idsis a comma-separated list.1if CMake's linker frontend variant of the Objective-C++ linker matches any one of the entries invariant_ids, otherwise0.$<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. wherevariant_idsis a comma-separated list.1if CMake's linker frontend variant of the Fortran linker matches any one of the entries invariant_ids, otherwise0.$<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. wherevariant_idsis a comma-separated list.1if CMake's linker frontend variant of the HIP linker matches any one of the entries invariant_ids, otherwise0.
๐ซถ 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.1ifsrcexists as a CMake source file, else0. 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 thedirdirectory's scope. CMake must know about the directory, either by having added it through a call toadd_subdirectory()ordirbeing 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 whichtargetwas created (targetmust 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 propertypropon the source filesrc, 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 thedirdirectory's scope. CMake must know about the directory, either by having added it through a call toadd_subdirectory()ordirbeing 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 whichtargetwas created (targetmust 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.1if thefilesetexists as a CMake file set attached to thetarget, else0.
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 propertypropon the file setfileset, 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.1iftgtexists as a CMake target, else0. โ$<TARGET_NAME_IF_EXISTS:tgt>โ Added in version 3.12. The target nametgtif 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 nametgtas written. This markstgtas being the name of a target inside a larger expression, which is required if exporting targets to multiple dependent export sets. Thetgttext must be a literal name of a target; it may not contain generator expressions. The target does not have to exist. ๐ฏ$<TARGET_POLICY:policy>โ1if the policy wasNEWwhen the 'head' target was created, else0. 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 propertypropon the targettgt, 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 ofpropon 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 theLINK_ONLYgenerator 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 theLINK_ONLYgenerator expression. See policyCMP0166. ๐ซถ
Changed in version 4.1: evaluation ofLINK_LIBRARIESitself is now transitive. See policyCMP0189. 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 theLINK_ONLYgenerator expression. โจ - For Transitive Link Properties, evaluation is transitive over the closure of the target's
INTERFACE_LINK_LIBRARIES, including entries guarded by theLINK_ONLYgenerator expression. See policyCMP0166. ๐ฆ
Changed in version 4.1: evaluation ofINTERFACE_LINK_LIBRARIESitself is now transitive. See policyCMP0189. 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 propertyPROP, named without anINTERFACE_prefix, checks theTRANSITIVE_COMPILE_PROPERTIESandTRANSITIVE_LINK_PROPERTIESproperties on targettgt, on targets named by itsLINK_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
PROPis named byTRANSITIVE_COMPILE_PROPERTIES, evaluation of the correspondingINTERFACE_PROPis transitive over the closure of the linked targets'INTERFACE_LINK_LIBRARIES, excluding entries guarded by theLINK_ONLYgenerator expression. - If
PROPis named byTRANSITIVE_LINK_PROPERTIES, evaluation of the correspondingINTERFACE_PROPis transitive over the closure of the linked targets'INTERFACE_LINK_LIBRARIES, including entries guarded by theLINK_ONLYgenerator expression.
- Evaluation of
$<TARGET_PROPERTY:tgt,INTERFACE_PROP>for some propertyINTERFACE_PROP, named with anINTERFACE_prefix, checks theTRANSITIVE_COMPILE_PROPERTIESandTRANSITIVE_LINK_PROPERTIESproperties on targettgt, and on the transitive closure of targets named by itsINTERFACE_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
PROPis named byTRANSITIVE_COMPILE_PROPERTIES, evaluation of the correspondingINTERFACE_PROPis transitive over the closure of the target'sINTERFACE_LINK_LIBRARIES, excluding entries guarded by theLINK_ONLYgenerator expression. - If
PROPis named byTRANSITIVE_LINK_PROPERTIES, evaluation of the correspondingINTERFACE_PROPis transitive over the closure of the target'sINTERFACE_LINK_LIBRARIES, including entries guarded by theLINK_ONLYgenerator expression.
Lowkey the drama: if aPROPis named by bothTRANSITIVE_COMPILE_PROPERTIESandTRANSITIVE_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.libimport library) created byadd_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 thetgtbinary 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_postfixfor$<TARGET_FILE_BASE_NAME:tgt>or$<TARGET_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.basefor$<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>โ thetgtfilename, 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 thetgtbinary 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.libfile. For executables on AIX, and for shared libraries on macOS, it could be (respectively, babes) the.impor.tbdimport file โ depends on whatever theENABLE_EXPORTStarget 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_postfixfor$<TARGET_IMPORT_FILE_BASE_NAME:tgt>or$<TARGET_IMPORT_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.basefor$<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 theIMPORT_PREFIXtarget 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.libor.tbd). See also theIMPORT_SUFFIXtarget 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.libimport 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_postfixfor$<TARGET_LINKER_FILE_BASE_NAME:tgt>or$<TARGET_LINKER_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.basefor$<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 thePREFIXandIMPORT_PREFIXtarget 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 policyCMP0112). ๐$<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 policyCMP0112). ๐
$<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_postfixfor$<TARGET_LINKER_LIBRARY_FILE_BASE_NAME:tgt>or$<TARGET_LINKER_LIBRARY_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.basefor$<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 thePREFIXtarget 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 theSUFFIXtarget 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_postfixfor$<TARGET_LINKER_IMPORT_FILE_BASE_NAME:tgt>or$<TARGET_LINKER_IMPORT_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.basefor$<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 targettgt. ๐
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 targettgt.
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 targettgt. 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 targettgt. 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), wheretgtis 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), wheretgtis 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), wheretgtis the name of a target. ๐๏ธ
Changed in version 4.2: the postfix โ as specified by theDEBUG_POSTFIXor<CONFIG>_POSTFIXtarget properties โ is always included in the PDB file name. See policyCMP0202. 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), wheretgtis 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 theDEBUG_POSTFIXor<CONFIG>_POSTFIXtarget properties โ is always included in the PDB base name, except if thePOSTFIXoption has the valueEXCLUDE. See policyCMP0202.
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_postfixfor$<TARGET_PDB_FILE_BASE_NAME:tgt>or$<TARGET_PDB_FILE_BASE_NAME:tgt,POSTFIX:INCLUDE>.basefor$<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), wheretgtis 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, ormy.bundle), wheretgtis 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, wheretgtis 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 buildingtgt. 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,SHAREDlibraries, andMODULElibraries 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 viainstall(EXPORT), and empty otherwise. That's it, that's the whole vibe.$<BUILD_INTERFACE:...>โ content of...when the property is exported usingexport(), 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, noexport()clause here. Expands to the empty string otherwise โจ$<INSTALL_PREFIX>โ the content of the install prefix when the target gets exported viainstall(EXPORT), or when it's evaluated inside theINSTALL_NAME_DIRproperty or theINSTALL_NAME_DIRargument ofinstall(RUNTIME_DEPENDENCY_SET). Empty otherwise.
Changed in version 3.27: it also evaluates to the content of the install prefix in the code argument ofinstall(CODE)or the file argument ofinstall(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 ofexpr, 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 ofexprevaluated as a generator expression in the context of thetgttarget. 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. UseCONFIGinstead. Like, please. She's retired, let her rest ๐
๐ Copyright ๐
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
- arch rolling: cmake 4.4.5-1 ยท BSD-3-Clause
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms