【CMake】《CMake构建实战:项目开发卷》笔记-Chapter8-生成器表达式

项目开发 - CMake教程+实战 通过Cmake 搭建一个demo,已熟悉cmake 的相关命令。 本文需要达到的目标 构建多个文件的demo vs 工程,生成exe 界面采用Qt 依赖三方库 自定义配置一些宏 1、 cmake 介绍 CMake 是一个开源、跨平台的工具系列,旨在构建、测试和打包软件 CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装。也就是项目或者工程对的搭建,例如:windows 的vs , linux 下 makefile 。要知道这些工程的搭建是非常麻烦的。 CMake 功能强大。 阅读详情

第8章 生成器表达式

生成器表达式(generator expression)是由CMake生成器进行解析的表达式,因此,这些表达式只有在CMake的生成阶段才被解析为具体的值。

CMake在生成阶段,能够根据具体选用的构建系统生成器生成特定的配置,因此生成器表达式常常用于获取与构建系统相关的信息,如当前构建模式(Debug或Release等)、构建目标对应的二进制文件名称等。一般来说,这些信息在构建系统生成器确定之前,也就是在配置阶段时,是无从知晓的。当然也有一些特殊情况,如对于单构建模式的构建系统来说,当前构建模式是在配置阶段由CMAKE_BUILD_TYPE变量确定的。不过,生成器表达式对它们同样有效,因此更为通用。

在构建跨平台程序的过程中,可能涉及不同的构建系统,而生成器表达式对它们之间的差异做了很好的封装隐藏,因此不可避免地会用到生成器表达式。CMake中很多与构建相关的命令都支持生成器表达式作为参数的一部分。本章将介绍一些常用的生成器表达式及其在一些常见命令中的用法。

生成器表达式大体分为两类:布尔型生成器表达式和字符串生成器表达式,后面将会依次介绍它们。

8.1 支持生成器表达式的命令

在介绍各种生成器表达式之前,首先来了解一下它们到底能用在哪些命令中。其实,有很多命令支持将生成器表达式作为参数的一部分。基本上只要在生成阶段工作的命令都会对生成器表达式有所支持。早在介绍string命令和file命令时,就已经提到过生成器表达式了,下面做个简单回顾。

  • string(GENEX_STRIP)命令用于删除输入字符串中的生成器表达式。事实上,该命令与生成器表达式的解析无关,因为它仅仅是根据语法删除字符串中的生成器表达式。该命令在CMake配置阶段执行。

  • file(GENERATE)命令用于在CMake生成阶段为每一个构建模式生成一个指定的文件。其中与输出文件名、文件内容、生成条件、构建目标等相关的参数中均可包含生成器表达式。该命令常用于调试生成器表达式的值。

message命令在CMake配置阶段执行,不能正确解析并输出生成器表达式的值。这也是为什么常使用file(GENERATE)命令调试生成器表达式的值,而不能用message命令。

8.1.1 创建构建目标的命令

还记得第7章讲过的下面几个命令吗?它们分别用于创建可执行文件目标和一般库目标:

add_executable(<目标名称> [<源文件>...])
add_library(<目标名称> <库类型> [<源文件>...])

这些命令的<源文件>参数中可以使用生成器表达式。这样,就可以根据不同的构建模式使用不同的源文件来编译构建目标。下面的实例展示了这一用途,其中涉及尚未讲解的生成器表达式$<CONFIG>及其语法,读者暂时不必深究。

实例

首先,为Debug和Release两种构建模式分别创建不同的主程序源文件,用于输出不同的字符串,如下所示。

#include <stdio.h>

int main() {
    printf("Debug\n");
    return 0;
}
#include <stdio.h>

int main() {
    printf("Release\n");
    return 0;
}

然后,在CMake目录程序中创建一个源文件目标,使用$<CONFIG>生成器表达式分别为Debug和Release两种构建模式设置不同的源文件,如下所示。

cmake_minimum_required(VERSION 3.20)

project(build_config_and_sources)

add_executable(main 
    $<$<CONFIG:Debug>:debug.c>
    $<$<CONFIG:Release>:release.c>
)

项目创建完毕,接下来着手构建该项目。

使用Visual Studio构建实例

Visual Studio支持多构建模式,使用该构建系统来构建该项目的过程如下:

> cd CMakeBook\src\ch008\构建模式与源文件
> mkdir build
> cd build
> cmake ..
...
> cmake --build . --config Debug
...
> cmake --build . --config Release
...

至此,我们成功构建了Debug和Release两种构建模式的main.exe可执行文件。下面立刻运行一下它们:

> .\Debug\main.exe
Debug
> .\Release\main.exe
Release

果然!两个可执行文件分别编译了不同的源文件,输出了不同的内容。

使用Makefile构建实例

这里同样演示一下如何使用Makefile构建系统来构建该项目。Makefile构建系统仅支持单构建模式,因此一个构建目录中只能使用一种构建模式来构建项目,而且在CMake配置生成阶段还要通过CMAKE_BUILD_TYPE变量指定构建模式。该项目Debug模式的构建过程如下:

> cd CMakeBook/src/ch008/构建模式与源文件
> mkdir build_debug
> cd build_debug
> cmake -DCMAKE_BUILD_TYPE=Debug ..
...
> cmake --build .
...
> ./main
Debug

可以看到最终可执行文件输出的正是“Debug”字符串。Release模式的构建过程几乎一样,这里不再赘述,读者可以自己动手尝试一下。

8.1.2 属性相关命令

除了link_libraries命令外,第7章介绍的用于设置目录属性及目标属性的命令也都支持使用生成器表达式作为参数。因此,我们可以为目录或构建目标针对不同的构建模式设置不同的属性值。

8.1.3 自定义构建规则和目标

下面是第7章中介绍的两个命令,它们分别用于创建自定义构建规则和自定义构建目标:

add_custom_command
add_custom_target

关于这两个命令中的哪些参数支持生成器表达式,第7章中均已介绍,这里不再赘述。

调试生成器表达式

前面提到file(GENERATE)命令可以用于调试生成器表达式。不过这仍稍显麻烦,需要通过输出文件来查看生成器表达式的值。其实,借助自定义构建目标,可以在构建过程中输出生成器表达式的值。如下所示的CMake目录程序展示了这一用法。

cmake_minimum_required(VERSION 3.20)

project(output_gen_exp)

# 构建debug-gen-exp自定义目标,以输出当前构建模式
add_custom_target(debug-gen-exp 
    COMMAND ${CMAKE_COMMAND} -E echo "CONFIG: $<CONFIG>"
)

使用Visual Studio生成器配置生成该项目,并构建“debug-gen-exp”自定义目标。其构建过程如下:

> cd CMakeBook\src\ch008\输出表达式
> mkdir build
> cd build
> cmake ..
...
> cmake --build . --config Debug --target debug-gen-exp
...
CONFIG: Debug
...
> cmake --build . --config Release --target debug-gen-exp
...
CONFIG: Release
...

如果使用仅支持单构建模式的生成器,如Makefile生成器,请务必在配置生成阶段指定CMake变量CMAKE_BUILD_TYPE的值为某个构建模式。否则,$<CONFIG>生成器表达式的值将为空值。

8.2 布尔型生成器表达式

在生成阶段,布尔型生成器表达式会被解析为0或1。布尔型生成器表达式通常作为分支条件的参数嵌套在字符串生成器表达式中。有关分支型字符串生成器表达式的内容参见8.3.2小节。

此前在介绍CMake命令参数时,表示生成器表达式的语法形式为“$<生成器表达式>”,其中尖括号是语法的组成部分。因此,本章介绍生成器表达式中的参数时,将不再使用尖括号表示参数占位符,而是使用花括号{和}。

8.2.1 转换字符串为布尔值:BOOL

$<BOOL:{字符串}>

该表达式会把{字符串}转换为0或1。仅当下列情况之一成立时,其值为0:

  • {字符串}为空;

  • {字符串}为假值常量,即NOTFOUND、IGNORE、OFF、NO、N、FALSE、0或以-NOTFOUND结尾(均不区分大小写)。

其他情况下,其值为1。

8.2.2 逻辑运算

本小节中的生成器表达式用于执行逻辑运算,如下表所示。其中{条件}参数可以是嵌套的布尔型生成器表达式。

表达式描述
$<AND:{条件1}[,{条件2}]...>逻辑与运算,当所有{条件}都为真时,其值为1,否则为0
$<OR:{条件1}[,{条件2}]...>逻辑或运算,当任一{条件}为真时,其值为1,否则为0
$<NOT:{条件}>逻辑非运算,当{条件}为假时,其值为1,否则为0
实例

下面是展示上述用于逻辑运算的生成器表达式的实例,其中会使用自定义构建目标命令来调试生成器表达式的值。其CMake目录程序如下所示。

cmake_minimum_required(VERSION 3.20)

project(logical_calc)

add_custom_target(debug-gen-exp ALL
    COMMAND ${CMAKE_COMMAND} -E echo 
        "$<BOOL:ABC>" # 输出:1
    COMMAND ${CMAKE_COMMAND} -E echo 
        "$<BOOL:NO>" # 输出:0
    COMMAND ${CMAKE_COMMAND} -E echo 
        "$<NOT:$<BOOL:NO>>" # 输出:1
    COMMAND ${CMAKE_COMMAND} -E echo 
        "$<AND:$<BOOL:A>,$<BOOL:X-NOTFOUND>>" # 输出:0
    COMMAND ${CMAKE_COMMAND} -E echo 
        "$<OR:$<BOOL:A>,$<BOOL:X-NOTFOUND>>" # 输出:1
)

8.2.3 关系比较

本小节中的生成器表达式均用于进行关系的比较或判断。

数值相等:EQUAL
$<EQUAL:{数值1},{数值2}>

当{数值1}与{数值2}相等时,该表达式的值为1,否则为0。该表达式仅支持比较整数。

字符串相等:STREQUAL
$<STREQUAL:{字符串1},{字符串2}>

当{字符串1}与{字符串2}相等时,该表达式的值为1,否则为0。该比较是区分大小写的,如果希望比较时不区分大小写,可以使用LOWER_CASE或UPPER_CASE字符串变换表达式将字符串大小写统一(参见8.3.3小节)。

列表元素判断:IN_LIST
$<IN_LIST:{字符串},{列表}>

当{字符串}是{列表}(分号分隔的列表字符串)中的一个元素时,该表达式的值为1,否则为0。字符串的比较是区分大小写的。

版本号关系比较:VERSION_

用于版本号关系比较的生成器表达式如下表所示。

表达式比较关系
$<VERSION_LESS:{版本号1},{版本号2}>小于
$<VERSION_LESS_EQUAL:{版本号1},{版本号2}>小于等于
$<VERSION_EQUAL:{版本号1},{版本号2}>等于
$<VERSION_GREATER_EQUAL:{版本号1},{版本号2}>大于等于
$<VERSION_GREATER:{版本号1},{版本号2}>大于

当{版本号1}和{版本号2}满足对应关系时,该表达式的值为1,否则为0。版本号的具体比较规则参见3.8.4小节中对版本号比较的双参数条件的介绍。

实例

下面是展示上述用于关系比较的生成器表达式的实例。其CMake目录程序如下所示。

cmake_minimum_required(VERSION 3.20)

project(comparison)

add_custom_target(debug-gen-exp ALL
    COMMAND ${CMAKE_COMMAND} -E echo 
        "$<EQUAL:0,-0>" # 输出:1
    COMMAND ${CMAKE_COMMAND} -E echo 
        "$<STREQUAL:0,-0>" # 输出:0
    COMMAND ${CMAKE_COMMAND} -E echo 
        "$<IN_LIST:2,1;2;3>" # 输出:1
    COMMAND ${CMAKE_COMMAND} -E echo
        "$<VERSION_LESS:1.1.2,1.2.0>" # 输出:1
)

8.2.4 谓词查询

本小节中的生成器表达式均用于查询某些谓词的结果是否满足一定的条件,通常用来判断当前构建系统的环境配置等信息是否满足要求。

构建目标存在性:TARGET_EXISTS
$<TARGET_EXISTS:{构建目标}>

当{构建目标}存在时,该表达式的值为1,否则为0。

当前构建模式:CONFIG
$<CONFIG:{构建模式1}[,{构建模式2}]...>

当当前构建模式是指定的{构建模式}之一时,该表达式的值为1,否则为0。该表达式在比较构建模式的字符串时不区分大小写。

当前操作系统:PLATFORM_ID
$<PLATFORM_ID:{操作系统1}[,{操作系统2}]...>

当当前操作系统是指定的{操作系统}之一时,该表达式的值为1,否则为0。当前操作系统的值即变量CMAKE_SYSTEM_NAME的值,一般为Linux、Windows或Darwin(macOS)之一。

当前编译器:{编程语言}_COMPILER_ID
$<{编程语言}_COMPILER_ID:{编译器1}[,{编译器2}]...>

当前对指定{编程语言}使用的编译器是指定的{编译器}之一时,该表达式的值为1,否则为0。其中{编程语言}仅支持如下取值:

  • C;

  • CXX,即C++;

  • CUDA;

  • OBJC,即Objective-C;

  • OBJCXX,即Objective-C++;

  • Fortran;

  • HIP;

  • ISPC。

当前编译器的值即变量CMAKE_<编程语言>_COMPILER_ID的值,最常见的取值如下:

  • AppleClang,即Apple的Clang编译器;

  • Clang,即LLVM的Clang编译器;

  • GNU,即GNU的GCC编译器;

  • Intel,即Intel的编译器;

  • MSVC,即Microsoft Visual C++编译器;

  • NVIDIA,即NVIDIA的CUDA编译器。

当前编译器版本号:{编程语言}_COMPILER_VERSION
$<{编程语言}_COMPILER_VERSION:{版本号}...>

当前对指定{编程语言}使用的编译器版本号与指定的{版本号}匹配时,该表达式的值为1,否则为0。{编程语言}的取值与“当前编译器”表达式中对应参数的取值一致。

当前编译器版本号的值即变量CMAKE_<编程语言>_COMPILER_VERSION的值。

当前支持的编译特性:COMPILE_FEATURES
$<COMPILE_FEATURES:{编译特性1}[,{编译特性2}]...>

当前编译器支持的编译特性包含全部指定的{编译特性}时,该表达式的值为1,否则为0。

当前编程语言:COMPILE_LANGUAGE
$<COMPILE_LANGUAGE:{编程语言1}[,{编程语言2}]...>

当前编译单元对应的编程语言是指定的{编程语言}之一时,该表达式的值为1,否则为0。使用该表达式,可以在使用多种编程语言构建的目标中对不同编程语言的源文件进行不同的属性设置,包括编译选项、宏定义、头文件目录的设置。

对于Visual Studio和Xcode生成器而言,CMake无法分别对C和C++语言的源文件设置不同的宏定义或头文件目录;对于Visual Studio生成器而言,甚至不能为C和C++语言设置不同的编译选项。在使用这两个生成器时,当目标的源文件中包含C++源文件时,该表达式一律按编程语言为CXX来匹配参数,否则按编程语言为C进行匹配。

当前编程语言和编译器:COMPILE_LANG_AND_ID
$<COMPILE_LANG_AND_ID:{编程语言},{编译器1}[,{编译器2}]...>

该表达式等价于下面的复合表达式,可以同时对当前编译单元对应的编程语言及其使用的编译器进行判断:

$<AND:$<COMPILE_LANGUAGE:{编程语言}>,$<{编程语言}_COMPILER_ID:{编译器1}[,{编译器2}]...>
实例:“当前编程语言”表达式的应用

本小节涉及的表达式较多,这里仅对“当前编程语言”表达式进行实例展示。CMake目录程序如下所示。其中定义了一个可执行文件目标main,它包含三个源文件:a.c、b.cpp和main.c。然后,在该目录程序中分别为C语言和C++语言的编译单元设置了不同的宏定义:A=1和A=10,这里结合了“当前编程语言”表达式及尚未介绍的条件表达式来实现条件定义。有关条件表达式的内容参见8.3.2小节。

cmake_minimum_required(VERSION 3.20)

project(compile_lang)

add_executable(main a.c b.cpp main.c)
target_compile_definitions(main PRIVATE
    $<$<COMPILE_LANGUAGE:C>:A=1>
    $<$<COMPILE_LANGUAGE:CXX>:A=10>
)

可执行文件的三个源文件的代码分别如下所示。

#include <stdio.h>

void print_c() { printf("%d\n", A); }
#include <iostream>

extern "C" void print_cpp() { std::cout << A << std::endl; }
extern void print_c();
extern void print_cpp();

int main() {
    print_c();
    print_cpp();
    return 0;
}

主程序分别调用了定义在a.c和b.cpp中的函数print_c和print_cpp,用于输出各自编译单元(即源文件)中宏A的值。

由于Visual Studio生成器无法很好地区分C语言和C++语言的编译单元,下面使用Makefile生成器构建该项目,并执行构建生成的可执行文件,过程如下:

$ cd CMakeBook/src/ch008/当前编程语言
$ mkdir build
$ cd build
$ cmake ..
...
$ cmake --build . # 或make
...
$ ./main
1
10

可见,对于使用不同编程语言的编译单元,宏定义的设置不同,执行程序后输出的值也就不同。

8.3 字符串生成器表达式

顾名思义,字符串生成器表达式在生成阶段会被解析为一段字符串。与布尔型生成器表达式一样,字符串生成器表达式也可以嵌套使用在其他表达式的参数中。

8.3.1 字符转义

$<ANGLE-R> # 转义为">"
$<COMMA> # 转义为","
$<SEMICOLON> # 转义为";"

“>”``“,”``“;”这三个字符通常用于构成生成器表达式的语法结构,因此需要通过字符转义表达式来表示。

8.3.2 条件表达式:IF

$<IF:{条件},{字符串1},{字符串2}>

该表达式的值取决于{条件}的值:当其为1时,该表达式的值将被解析为{字符串1},否则将被解析为{字符串2}。

如果给定的{字符串2}为空字符串,那么不妨使用下面这个简化的形式:

$<{条件}:{字符串}>
实例

下面这个实例可以在构建过程中输出当前的构建模式是否是调试模式,CMake目录程序如下所示。

cmake_minimum_required(VERSION 3.20)

project(condition)

add_custom_target(debug-gen-exp ALL
    COMMAND ${CMAKE_COMMAND} -E echo 
        "Is Debug: <$<IF:$<CONFIG:Debug>,Yes,No>$<ANGLE-R>"
    VERBATIM
)

读者可以分别使用Debug和Release模式配置生成该项目,然后构建debug-gen-exp目标,看看是否会分别输出“Is Debug: <Yes>”和“Is Debug: <No>”。注意,目录程序中还使用了$<ANGLE-R>生成器表达式来转义右尖括号。

8.3.3 字符串变换

分隔符连接:JOIN
$<JOIN:{列表字符串},{分隔符}>

该表达式会将{列表字符串}中的每一个元素以指定的{分隔符}分隔并连接在一起,作为最终的值。例如,“$<JOIN:1;2;3,$<COMMA>>”将被解析为“1,2,3”。

大小写转换:LOWER_CASE和UPPER_CASE
$<LOWER_CASE:{字符串}> # 转小写
$<UPPER_CASE:{字符串}> # 转大写

这两个表达式的值分别为其{字符串}参数转换为小写和大写后的结果。

移除重复元素:REMOVE_DUPLICATES
$<REMOVE_DUPLICATES:{列表字符串}>

该表达式会将{列表字符串}中重复的元素移除,并将最终的列表字符串作为表达式的值。

列表筛选:FILTER
$<FILTER:{列表字符串},INCLUDE|EXCLUDE,{正则表达式}>

该表达式会将{列表字符串}中能够匹配到{正则表达式}的元素保留(若指定了INCLUDE参数)或移除(若指定了EXCLUDE参数),并将最终的列表字符串作为表达式的值。

生成C标识符:MAKE_C_IDENTIFIER
$<MAKE_C_IDENTIFIER:{字符串}>

该表达式会将{字符串}转换为合法的C标识符,并作为最终的值。与string(MAKE_C_IDENTIFIER)命令一样,它也将非字母或数字的字符转换为下画线。

转换为Shell路径:SHELL_PATH
$<SHELL_PATH:{绝对路径列表字符串}>

该表达式会将{绝对路径列表字符串}中的每一个绝对路径的格式转换为当前Shell所需的路径格式。

例如,在Windows操作系统中,如果使用命令提示符或者PowerShell生成该项目,这些路径会被转换为以反斜杠作为路径分隔符的格式;如果使用MSYS或者Cygwin等Shell生成该项目,这些路径就会被转换为以斜杠作为路径分隔符的POSIX路径格式。

若参数中有多个路径,那么用于分隔多个路径的分隔符也会被转换为平台中的相关字符:在Windows操作系统中为分号“;”,在类UNIX操作系统中为冒号“:”。

8.3.4 目标相关表达式

本小节中的表达式均与构建目标相关,用于查询目标相关的属性等。需要注意的是,由于这类表达式较为特殊,与特定构建目标相关,有一些支持包含生成器表达式的命令参数可能并不支持这一类表达式。这一点通常在对应命令的官方文档中都会有所强调。

目标名称:TARGET_NAME_IF_EXISTS
$<TARGET_NAME_IF_EXISTS:{目标}>

当指定{目标}存在时,该表达式解析后的值即{目标}的名称,否则其值为空。

目标的二进制文件:TARGET_FILE
$<TARGET_FILE:{目标}>

该表达式解析后的值即{目标}构建后生成的二进制文件的绝对路径。

如果希望获取路径的不同组成部分,还可以使用下表中的这些生成器表达式。

表达式描述示例
$<TARGET_FILE:{目标}>绝对路径/a/libbase.so
$<TARGET_FILE_BASE_NAME:{目标}>基本名称base
$<TARGET_FILE_PREFIX:{目标}>前缀lib
$<TARGET_FILE_SUFFIX:{目标}>后缀.so
$<TARGET_FILE_NAME:{目标}>文件名称libbase.so
$<TARGET_FILE_DIR:{目标}>目录名称/a
目标的链接文件:TARGET_LINKER_FILE
$<TARGET_LINKER_FILE:{目标}>

该表达式解析后的值即链接{目标}时所需文件的绝对路径。一般来说,它和TARGET_FILE是一致的;但在Windows操作系统中,在链接动态链接库时,链接器链接的文件是其导入库文件,也就是说,Windows动态链接库的TARGET_LINKER_FILE是其.lib导入库的路径。

该表达式同样拥有类似于上表#### 目标的二进制文件:TARGET_FILE中的变种,读者可以根据需要选用。

目标的带SONAME的文件:TARGET_SONAME_FILE
$<TARGET_SONAME_FILE:{目标}>

该表达式解析后的值即{目标}对应的带SONAME的文件(如扩展名为.so.1的文件)的绝对路径。{目标}参数必须是一个动态库目标的名称。

该表达式仅拥有下面两个变种以获取路径的不同部分,具体描述请参考表#### 目标的二进制文件:TARGET_FILE:

$<TARGET_SONAME_FILE_NAME:{目标}> # 文件名称
$<TARGET_SONAME_FILE_DIR:{目标}> # 目录名称
目标的PDB文件:TARGET_PDB_FILE
$<TARGET_PDB_FILE:{目标}>

该表达式解析后的值即{目标}对应的.pdb符号文件的绝对路径。该表达式仅适用于MSVC编译器,因此往往在if(MSVC)的条件分支中使用。

该表达式同样拥有类似于表#### 目标的二进制文件:TARGET_FILE中的变种,读者可以根据需要选用。

目标的BUNDLE目录:TARGET_BUNDLE_DIR
$<TARGET_BUNDLE_DIR:{目标}>

该表达式解析后的值即{目标}对应的Bundle目录的绝对路径。{目标}参数必须是一个Bundle目标(具有MACOSX_BUNDLE属性的可执行文件目标或具有FRAMEWORK或BUNDLE属性的库目标)的名称。

如果想获取目标Bundle内容中的目录路径,可以用下面这个表达式:

$<TARGET_BUNDLE_CONTENT_DIR:{目标}
目标属性:TARGET_PROPERTY
$<TARGET_PROPERTY:{目标},{目标属性}>
$<TARGET_PROPERTY:{目标属性}>

这两个表达式可用于获取构建目标的{目标属性},属性值会作为表达式最终的值。其中,第一个表达式可以指定{目标}参数,而第二个表达式则会根据所在上下文来确定用于获取属性的目标。如下所示的部分CMake目录程序中展示了这一点。

add_executable(main main.c)
set_target_properties(main PROPERTIES A 1)
target_compile_definitions(main PRIVATE 
    A=$<TARGET_PROPERTY:A> # 等价于 A=$<TARGET_PROPERTY:main,A>
)

有一点需要格外注意:如果使用要求中存在该生成器表达式,其被解析的上下文并非定义该使用要求的构建目标,而是它被传递到的构建目标,如下所示。如下所示中,最终主程序main中宏B的值是1而非2。这是因为lib的使用要求定义宏B为表达式$<TARGET_PROPERTY:A>的值,将被传递到构建目标main中,并在main的上下文中被解析成main的目标属性A的值,也就是1。

add_library(lib dummy.c)
set_target_properties(lib PROPERTIES A 2)
target_compile_definitions(lib PUBLIC
    B=$<TARGET_PROPERTY:A>
)
target_link_libraries(main PRIVATE lib)
目标的目标文件:TARGET_OBJECTS
$<TARGET_OBJECTS:{目标}>

该表达式解析后的值即{目标}对应的目标文件的列表。{目标}参数必须是一个目标文件库目标的名称,具体应用参见7.1.3小节。

8.3.5 解析生成器表达式

本小节介绍的生成器表达式更为特殊,其值是将其参数作为生成器表达式解析后的结果值,其作用类似“元生成器表达式”。

解析一般表达式:GENEX_EVAL
$<GENEX_EVAL:{生成器表达式}>

该表达式会将指定的{生成器表达式}参数在当前上下文中解析为具体的值,并作为该表达式的值。一般来说,这种用例通常出现在构建目标的自定义目标属性中,如下所示。

cmake_minimum_required(VERSION 3.20)

project(genex_eval)

add_executable(main main.c)

# 设置main的自定义目标属性CUSTOM_EXP的值
set_property(TARGET main PROPERTY CUSTOM_EXP "$<CONFIG>")

add_custom_target(debug-gen-exp ALL
    COMMAND ${CMAKE_COMMAND} -E echo # 输出Debug或Release等
    "$<GENEX_EVAL:$<TARGET_PROPERTY:main,CUSTOM_EXP>>" 
)
解析目标相关表达式:TARGET_GENEX_EVAL

有时,需要在构建目标的上下文中解析生成器表达式。例如,要解析的{生成器表达式}中包含$<TARGET_PROPERTY:{属性}>表达式时,可以使用下面这个TARGET_GENEX_EVAL表达式。

$<TARGET_GENEX_EVAL:{构建目标},{生成器表达式}>

该表达式会将指定的{生成器表达式}参数在{构建目标}的上下文中解析为具体的值,并作为该表达式的值。如下所示CMake目录程序展示了该表达式的用法。

cmake_minimum_required(VERSION 3.20)

project(genex_eval)

add_executable(main main.c)
add_executable(main2 MACOSX_BUNDLE main.c)

set_property(TARGET main PROPERTY A "$<TARGET_PROPERTY:A>")
set_property(TARGET main2 PROPERTY A "main2.A")

add_custom_target(debug-gen-exp ALL
    COMMAND ${CMAKE_COMMAND} -E echo # 输出main2.A
    "$<TARGET_GENEX_EVAL:main2,$<TARGET_PROPERTY:main,A>>" 
)

8.4 小结

本章首先介绍了CMake中可以使用生成器表达式的常用命令,然后将CMake中常用的生成器表达式进行了分类讲解。

生成器表达式是初学者学习CMake时的一个难点,因为它们的语法和用途与CMake中其他的参数形式有很大的不同,不容易理解。其实,只需时刻牢记生成器表达式是在CMake的生成阶段解析的,很多问题就容易理解了。

由于CMake是构建系统的生成器,它自身不会独立进行程序的构建,所以CMake在生成阶段就不得不与目标构建系统有所耦合,很多信息也不得不等到生成阶段才能获得。笔者认为,CMake作为构建系统的生成器,有利有弊。一方面,它总能生成用户习惯使用的构建系统的项目文件,以便发挥原生构建系统的最大优势;另一方面,它也带来了不够完美的封装性,增加了目录程序编写的复杂度。

生成器表达式其实就是为了尽可能减少这一复杂度而生的,相当于对不同构建系统的差异性做了抽象封装。例如,不同平台或不同构建系统针对同一构建目标生成的文件名称可能有所不同,而TARGET_FILE表达式能将这一不同隐藏起来,让CMake程序更容易跨平台。

总而言之,生成器表达式是CMake的一个重要特性,能够让CMake程序更加简洁易读、易维护,读者可以在实际项目中多多尝试,体会这一特性的好处。第9章,我们将一起了解CMake中另一重要的特性——模块。

cmake基础教程(40)生成器表达式 生成器表达式简单来说就是在CMake生成构建系统的时候根据不同配置动态生成特定的内容。条件链接,如针对同一个编译目标,debug版本和release版本链接不同的库文件条件定义,如针对不同编译器,定义不同的宏所以可以看到,其中的要点是条件,之所以需要自动生成,那绝大多数时候肯定是因为开发者无法提前确定某些配置,不能提前确定那往往就是有条件的。生成器表达式的格式形如$,可以嵌套,可以用在很多构建目标的属性设置和特定的CMake命令中。 阅读详情

相关推荐

CMake基础教程:生成器表达式在嵌入式系统中的应用

生成器表达式是CMake中一种特殊的语法,用于在构建过程中根据条件生成不同的构建规则。它能够根据当前的构建配置、目标平台和其他上下文信息来动态地生成构建参数。生成器表达式通常用在CMake的命令中,例如和等。

CyberBytezZ的博客 166

CMake之生成器表达式

cmake生成器使用总结

u010378559的博客 1973

2015年全国大学生电子设计大赛综合测评题

2015年全国大学生电子设计大赛综合测评题 题目如下: 一、555产生可调方波 555定时器是一种集成电路芯片,常被用于定时器、脉冲产生器和振荡电路。555可被作为电路中的延时器件、触发器或起振元件。 555定时器可组成单稳态电路、多稳态电路和无稳态电路。产生方波其实就是制造一个无稳态电路。 555定时器的引脚功能: 其中: DIS引脚与OUT脚电平一致 TRIG: 当此引脚电压降至1/3VCC(或由控制端决定的阈值电压)时输出端给出高电平。 THR:当此引脚电压升至2/3VCC(或由控制端决定的阈值电压

m0_47066484的博客 3万+

CMake I 生成器表达式

bool生成器表达式、字符串值生成器表达式。

该用户还没想到好的昵称的博客 3053

Leaflet 加载天地图完整配置指南:从插件修改到无限缩放(附源码)

本文详细介绍了Leaflet与天地图深度集成的完整配置指南,包括插件修改、无限缩放实现及源码示例。通过解析天地图服务的WMTS协议、双投影支持和密钥机制,帮助开发者突破缩放级别限制,优化地图应用性能,适用于Web地图开发和高精度GIS系统。

weixin_27180827的博客 442

一文详解AUTOSAR COM ComSignal结构体

ComSignalLength指定信号的长度,以字节为单位,影响数据的解析范围和精度。比如车速信号若为UINT16类型,ComSignalLength=16。这里需要注意必须ComSignalLength与ComSignalType匹配(比如FLOAT32类型需配置ComSignalLength=32),超出实际物理值范围时需配置缩放(Scaling)参数。ComSignalType定义信号的物理数据类型,决定如何解析原始字节数据。

menghuyouyou的博客 943

【CMake】《CMake构建实战:项目开发卷》笔记-Chapter6-CMake构建初探

从本章开始,我们不再仅仅停留在CMake脚本程序中,而是开始将CMake看作一个用于构建项目的利器了。在了解CMake的具体用法之前,先要清楚地掌握CMake项目的构建流程。换句话说,就是要清楚地掌握CMake项目的生命周期——从源程序和CMake目录程序,到构建好的二进制文件,再到这些二进制文件的安装和打包分发,最终到其他项目的源程序借助CMake使用这些二进制文件提供的功能。本章会说明CMake是怎样实现这样一个生命周期的,重点介绍其构建项目过程中的6个阶段,尤其是与构建紧密相关的阶段。

江湖人称菠萝包 1645

Innovus时钟树综合CTS实战:从原理到signoff的完整指南

时钟树综合(CTS)是数字后端实现中保障同步电路时序收敛的核心环节,其本质是在物理层面构建低偏斜、可控延迟的全局时钟分发网络。它基于静态时序分析(STA)原理,通过平衡clock skew、insertion delay、transition time与功耗面积等多维约束,对抗工艺电压温度(PVT)变异带来的时序不确定性。技术价值在于支撑GHz级高频芯片的setup/hold签核,广泛应用于CPU、GPU、AI加速器等复杂SoC的Innovus后端流程。本文聚焦CTS在Innovus工具链中的工程落地,覆盖

weixin_28523493的博客 227

Chapter 1: First Steps with CMake_《Modern CMake for C++》_Notes

First Steps with CMake

C/C++ 1089

基于STM32的智能衣柜系统开发实战(附完整源码与硬件设计)

本文详细介绍了基于STM32的智能衣柜系统开发实战,包括硬件设计、软件架构和云端功能实现。系统具备实时温湿度监控、自动加热除湿、紫外线杀菌等核心功能,通过STM32F103C8T6主控芯片和ESP8266无线模块实现智能控制。附完整源码与硬件设计,适合科技爱好者和开发者参考。

peach的博客 782

CAN(2)-GD32

GD32中CAN的特征。

小柚子的博客 554

Chapter 7: Compiling C++ Sources with CMake_《Modern CMake for C++》_Notes

Compiling C++ Sources with CMake

C/C++ 1360

基于Simulink的超级电容器充放电仿真

为了更好地理解和设计超级电容器的应用系统,通常需要进行详细的仿真分析。Simulink 提供了一个强大的仿真环境,可以用来设计、分析和验证超级电容器充放电系统的控制策略。本教程将详细介绍如何使用 Simulink 进行超级电容器充放电的仿真实例。如果你希望对超级电容器的充放电过程进行精确控制,可以添加一个简单的逻辑控制器来控制开关的状态。通过本教程,我们详细介绍了如何使用 Simulink 进行超级电容器充放电的仿真实例。2. 添加超级电容器模型。7. 添加控制器(可选)1. 调整超级电容器参数。

xiaoheshang_123的博客 519

cmake构建和简单实操

内部构建会在 同级目录下产生一大堆中间文件,这些中间文件并不是我们最终所需要的,和工程源文件放在一起会显得杂乱无章。有两种构建方式,分别为内部构建和外部构建。源文件和头文件分别在不同的目录下进行实操。将编译输出文件与源文件放到不同目录中。分别在头文件和源文件内写入内容。内部构建 (不建议使用)外部构建(推荐使用)

田乐蒙的博客 1110

Magisk如何针对性隐藏Root避免被检测

Magisk隐藏特征码有两种途径,一种是通过插件,一种是定制源码。插件的方式相对简单,但是使用范围也有限,好处是成本低好上手。定制源码难度大,成本高,好处是稳定,后续也好更新维护。

zhonglunshun的专栏 1万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

cmake 条件判断

这里写自定义目录标题if 语法 if 语法 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 ...

梅尔伍德球童-编号9527 5495

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【CMake】CMake入门(四)生成器表达式 安装与测试 系统特性检测 自定义命令以及输出文件

CTest是CMake的测试工具,它可以自动化运行单元测试,收集测试结果并生成报告。使用方式根目录加。使用add_test添加测试。使用添加测试的属性# 用法 关键字 测试名称 关键字 执行的命令 参数# 示例# 用法 测试名称 关键字 属性 值# 示例这里PROPERTIES可以设置的参数有很多,这里设置的表示以正则表达式进行匹配结果。在Linux中,有许多的工具命令,例如lsmvmkdir等。

一点一点变好 1946

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

CMake进阶:生成器表达式

CMake生成器表达式是CMake 3.0+引入的高级特性,用于在生成构建系统时动态计算值,而非配置阶段。它支持多配置构建(如VS的Debug/Release)、跨平台适配和目标类型区分等场景,通过条件表达式、字符串操作和目标相关表达式实现灵活配置。核心语法为$<条件:真结果;假结果>,支持构建配置判断、平台识别、目标类型区分等操作。使用注意事项包括版本兼容性、适用范围限制和调试技巧。掌握生成器表达式可显著提升CMake脚本的动态配置能力,特别适合复杂跨平台项目。

红军不怕远征难,万水千山只等闲 1287

CMake(九):生成器表达式

cmake系统学习

jjjstephen的博客 4356

掌握CMake基础:从零开始实战编译工程

CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为CMakeLists.txt。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再以一般的建构方式使用。

Lion_Long的博客 2003

Cmake常用命令

最低支持的版本 生成静态库和动态库 批量导入源码文件 引入头文件 导入静态库或动态库(第一种方式) 导入静态库或动态库(第二种方式) 搜索指定的预构建库并将路径存储为变量 链接第三方库或者系统库到目标库 添加一个子目录并构建该子目录 目录结构如下: 变量 列表 条件判断 一元测试命令: EXIST:判断文件或者目录是否存在,存在时为真。需要提供全路径;如果文件或者目录是符号链接(例如软连接),则只有当链接的目标存在时返回真。格式为:if(EXISTS path-to-file-or-

小久久的博客 3523

cmake生成表达式

不积小流,无以成江海。

feng__shuai的博客 828

【CMake】《CMake构建实战:项目开发卷》笔记-Chapter1-构建之旅

结合前两个例程能够发现,使用要求在被传递时存在多种可能性:1.传递到使用者的构建要求;2.传递到使用者的使用要求;3.同时传递到使用者的构建要求和使用要求。前面两个例程分别对应第一种情况和第三种情况。第二种情况一般在当头文件(接口)使用了某个库,而源程序(实现)中并没有使用这个库时才会用到,多见于伪构建目标。举个另类但还算实用的例子:当希望引用一个接口库就可以自动链接多个库时,实际上就是要将多个链接库的使用要求传递给这个接口库的使用要求。接口库是伪构建目标,不需要编译,也就不存在构建要求。

江湖人称菠萝包 1406

cmake中的生成表达式

cmake中的生成表达式。

guohw 828

CMake 生成器表达式介绍

生成器表达式在构建系统生成期间进行评估,以生成特定于每个构建配置的信息。它们的形式为$。

梦起丶的博客 1289

CMake--生成器表达式(Generator Expressions) 的完整解析

CMake生成器表达式解析与应用 生成器表达式($<...>)是CMake在生成构建系统时动态求值的工具,用于根据平台、配置等条件灵活控制编译选项、路径和属性。核心分类包括: 条件表达式(如$<IF:cond,T,F>, $<CONFIG:Debug>)实现逻辑判断; 目标属性查询(如$<TARGET_PROPERTY:lib,INCLUDES>)动态获取构建信息; 路径处理(如$<TARGET_FILE:app>)跨平台兼容; 字符串操作(如$&

weixin_45590420的博客 605
上一篇: 【CMake】《CMake构建实战:项目开发卷》笔记-Chapter7-构建目标和属性
下一篇: 【CMake】《CMake构建实战:项目开发卷》笔记-Chapter9-模块
江湖人称菠萝包
博客等级 码龄10年 762粉丝 314原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值