☰
CMake 教程 Step 11 实战:目标别名(ALIAS)与生成器表达式($<CONFIG>)——被主教程遗漏的“杂项特性“精讲
2026/10/7 16:02:36 网站建设 项目流程
  • 构建工具
  • 开发工具
  • CLI

【免费下载链接】CMake

Mirror of CMake upstream repository

项目地址:https://gitcode.com/gh_mirrors/cm/CMake
点击查看免费下载

本指南以 CMake 官方教程的收官章节 Help/guide/tutorial/Miscellaneous Features.rst(Step 11)为骨架,系统讲解两类在常规教程中不易展开、却在真实项目中举足轻重的特性:目标别名(Target Aliases)与生成器表达式(Generator Expressions)。读完本文,你将掌握如何用add_library(... ALIAS ...)让add_subdirectory与find_package两条消费路径共享同一套命名空间接口,以及如何借助$<CONFIG>这类延迟求值表达式,在编译定义、依赖注入等场景中让构建配置"注入"到目标内部,并配合仓库中 Step11 的完整示例代码完成动手练习。

一、为什么主教程之外还需要"杂项特性"

官方教程在 Miscellaneous Features.rst 开头就点明了这一章的定位:有些特性"不适合在主教程中占据篇幅,或重要程度不足以被单独讲解,但它们值得被提及",因此被归为"bonuses"(加分项)。文档同时强调了一个容易被忽略的事实:

CMake 有大量未在教程中覆盖的特性,其中一些被使用它们的项目视为必不可少(essential);另一些则被**打包者(packagers)**广泛使用,但在软件开发者日常讨论中鲜少出现。

也就是说,这一章收集的两类练习,恰恰是"本地构建者不关心、但面向包分发与依赖治理时绕不开"的能力:

  1. 目标别名(Target Aliases)——统一add_subdirectory与find_package两种依赖消费方式的接口;
  2. 生成器表达式(Generator Expressions)——将"配置阶段未知、生成阶段才可知"的信息注入构建系统。

文档也声明这份清单并非 CMake 全部能力的穷举,会随时代与相关性增长或收缩——因此这两讲本质上是"打开一扇窗",帮助读者意识到教程之外的广阔空间。

二、Step 11 工程全景:SimpleTest 与 TutorialProject 的分工

动手之前,先看清 Step 11 目录里两套工程的关系。仓库中该章节的完整布局位于 Help/guide/tutorial/Step11:

  • SimpleTest/:一个极简的测试框架工程(project(SimpleTest VERSION 0.0.1),见 SimpleTest/CMakeLists.txt)。它定义INTERFACE库SimpleTest,通过install(EXPORT ... NAMESPACE SimpleTest::)导出为SimpleTest::SimpleTest(见 SimpleTest/CMakeLists.txt),并连同SimpleTestConfig.cmake等文件一起安装到lib/cmake/SimpleTest目录。
  • TutorialProject/:教程的主工程。其测试目标TestMathFunctions通过find_package(SimpleTest REQUIRED)找到已安装的 SimpleTest,并链接SimpleTest::SimpleTest(见 Tests/CMakeLists.txt),测试用例则直接使用TEST/REQUIRE宏断言数学函数(见 Tests/TestMathFunctions.cxx)。

两套工程都通过CMakePresets.json提供tutorial预设(如 TutorialProject/CMakePresets.json 所示,预设将CMAKE_PREFIX_PATH指向install目录并关闭了 IPO),因此后续所有构建命令都基于cmake --preset tutorial执行。

Step 11 的两个练习分别落在两个工程中:练习一改TutorialProject/MathFunctions/CMakeLists.txt,练习二改SimpleTest/CMakeLists.txt。

三、练习一:目标别名(Target Aliases)

3.1 动机:add_subdirectory与find_package的命名空间鸿沟

教程主体(Step 10 之前的章节)聚焦"安装依赖并在安装树中消费它",并推荐借助包管理器完成这一过程。但正如 Miscellaneous Features.rst 所指出的,由于历史与现实的多重原因,CMake 项目并非总是这样被消费的:

  • 当依赖的源码被**整体嵌入(vendor)**父项目,通过add_subdirectory引入时,暴露出来的目标名是依赖项目内部使用的名字;
  • 这些名字没有install(EXPORT)会加上的命名空间前缀(如Tutorial::、SimpleTest::)。

于是同一个库,在"源码内嵌"与"包管理器安装"两种场景下出现了两套目标名。为了让两种工作流对使用者呈现一致的接口,CMake 提供了别名机制::command:add_library(ALIAS)与:command:add_executable(ALIAS)。

文档给出了别名的最小示例:

add_library(MyLib INTERFACE) add_library(MyProject::MyLib ALIAS MyLib)

3.2add_library(... ALIAS ...)的语法与规则

在 Help/command/add_library.rst 的 "Alias Libraries" 一节,官方对别名目标的规则有精确定义:

add_library(<name> ALIAS <target>)
  • 别名<name>可以在后续命令中代表<target>使用,但不会作为 make 目标出现在生成的构建系统中;
  • 被引用的<target>本身不能是另一个 ALIAS 目标;
  • 别名目标可以被链接、可以读取其属性,也可以用常规的if(TARGET)子命令检测其存在;
  • 版本 3.11 起,ALIAS 可以指向GLOBAL导入目标(Imported Target);3.18 起可以指向非 GLOBAL 导入目标(此时别名仅在其创建目录及其以下作用域有效),并可通过目标属性ALIAS_GLOBAL判断别名是否为全局;
  • 版本 4.5 起,别名<name>还可作为set_property、set_target_properties、target_link_libraries等命令的操作数来修改<target>的属性;若将 ALIAS 目标传给install或export命令,实际安装/导出的是其引用的目标。

3.3 实操:为 MathFunctions 添加别名(TODO 1)

练习目标一句话概括:为MathFunctions库添加一个与导出目标一致的库别名。需要编辑的文件是 Step11/TutorialProject/MathFunctions/CMakeLists.txt,其中第 2 行留有# TODO1:注释。

对照仓库中已完成全部练习的答案工程 Complete/TutorialProject/MathFunctions/CMakeLists.txt,TODO 1 的完整解只需一行:

add_library(MathFunctions) add_library(Tutorial::MathFunctions ALIAS MathFunctions)

为什么别名要命名为Tutorial::MathFunctions?因为 TutorialProject 在 Step11/TutorialProject/CMakeLists.txt 中通过install(EXPORT TutorialTargets ... NAMESPACE Tutorial::)导出目标,find_package消费者看到的名字正是Tutorial::MathFunctions。别名让add_subdirectory的消费者也能以完全相同的方式引用它——这就是文档所说的"与 find_package 消费者所见接口保持一致"。

3.4 构建与运行

文档给出的构建流程分两步:先配置并安装SimpleTest,再构建TutorialProject。

# 在 Help/guide/Step11/SimpleTest 目录下 cmake --preset tutorial cmake --install build # 在 Help/guide/Step11/TutorialProject 目录下 cmake --preset tutorial cmake --build build

文档特别提醒:添加别名后,行为不应有任何可观察的变化——这正是别名设计的精髓:它不改变构建产物与依赖图,只统一命名空间接口。

四、练习二:生成器表达式(Generator Expressions)

4.1 概念:延迟求值的条件表达式

cmake-generator-expressions(7) 是 CMake 支持的一种复杂领域特定语言(DSL),官方文档将其概括为:

Generator expressions are evaluated during build system generation to produce information for each specific build configuration.

即:生成器表达式在构建系统生成阶段才被求值,以产出"针对每个具体构建配置"的信息。原教程文档对它的通俗解释是——最容易将其理解为"延迟求值的条件表达式"(deferred-evaluation conditionals):它表达的需求,其输入在 CMake 配置(configure)阶段尚不可知。也正因如此,这类表达式被称为"生成器"表达式:它们是在底层构建系统被生成时才求值的。

历史上,生成器表达式常与target_include_directories配合,用于表达"构建树与安装树之间"的包含目录需求;文档指出,这一用途已被文件集(FILE SET)取代(Step 11 的 CMakeLists 中大量使用的FILE_SET HEADERS正是例证)。如今,生成器表达式最常见的应用场景是多配置生成器(multi-config generators)与复杂的依赖注入系统(dependency injection systems)。

文档给出的入门示例:

target_compile_definitions(MyApp PRIVATE "MYAPP_BUILD_CONFIG=$<CONFIG>")

4.2 核心表达式$<CONFIG>深入解读

练习二使用的$<CONFIG>是生成器表达式中最常用也最直观的一个。在 cmake-generator-expressions.7.rst 中,其定义为:

  • $<CONFIG>:配置名(Configuration name),官方明确建议用它取代已弃用的CONFIGURATION表达式;
  • $<CONFIG:cfgs>:若当前配置是逗号分隔列表cfgs中的任意一项,则求值为1,否则为0。比较不区分大小写;当该表达式作用于IMPORTED目标的属性时,还会考虑MAP_IMPORTED_CONFIG_<CONFIG>的映射。自 3.19 起cfgs支持同时指定多个配置。

理解$<CONFIG>的关键在于"两个配置阶段可能不同":在多配置生成器(如 Visual Studio、Xcode、Ninja Multi-Config)下,同一个构建目录可产出 Debug / Release / RelWithDebInfo 等多个配置的产物,而配置(configure)阶段根本不存在单一"当前配置",只有到生成(generate)阶段、为每个具体配置生成构建规则时,$<CONFIG>才被替换为对应的配置名。

4.3 实操:为 SimpleTest 添加$<CONFIG>编译定义(TODO 2)

练习二的目标是:在SimpleTest中添加一个生成器表达式,把构建配置写进一条编译定义里。需要编辑 Step11/SimpleTest/CMakeLists.txt,其中第 17-18 行留有# TODO2:注释。

答案工程的 Complete/SimpleTest/CMakeLists.txt 给出的完整解同样是一行,但与文档示例不同之处在于使用了INTERFACE关键字:

target_compile_definitions(SimpleTest INTERFACE "SIMPLETEST_CONFIG=$<CONFIG>")

SimpleTest本身是INTERFACE库(只传递接口、不产生编译产物),因此编译定义必须通过INTERFACE传播给消费者;$<CONFIG>会按"构建该可执行文件所用的配置"求值——正如文档所强调的,这个配置未必等于配置 SimpleTest 时的配置。

该宏的消费端在 Complete/SimpleTest/SimpleTest.h 与 同文件 L132-L135:运行测试时,若定义了SIMPLETEST_CONFIG,会通过SIMPLETEST_XSTRINGIFY(SIMPLETEST_CONFIG)(字符串化宏)打印一行SimpleTest built with config: <配置名>。于是运行时即可直观验证"编译定义中的配置"究竟来自哪个构建配置。

4.4 构建、运行与CMAKE_BUILD_TYPE的影响

构建命令与练习一相同:

# 在 Help/guide/Step11/SimpleTest 目录下 cmake --preset tutorial cmake --install build # 在 Help/guide/Step11/TutorialProject 目录下 cmake --preset tutorial cmake --build build

然后直接运行TestMathFunctions二进制,应当看到一条指名"构建该可执行文件所用的配置"的消息。文档特别指出验证技巧:

在单配置生成器(single-configuration generators)上,可以通过设置CMAKE_BUILD_TYPE变量来改变构建配置。

对应到仓库中的教程说明(Before You Begin.rst):CMAKE_BUILD_TYPE可通过环境变量或cmake -DCMAKE_BUILD_TYPE=<config>直接指定。修改CMAKE_BUILD_TYPE后重新构建,TestMathFunctions打印的配置名应随之变化;而在多配置生成器下,同一构建目录内不同配置的构建会产生不同的$<CONFIG>求值结果——这正是生成器表达式在多配置场景中的价值所在。

五、组合视角:两个"杂项特性"如何协同支撑依赖治理

把两个练习放在一起看,能更完整地理解它们服务于同一个目标——让一个库在"源码内嵌"与"包分发"两条消费路径下呈现一致的接口与行为:

  • 目标别名解决"接口命名"问题:add_subdirectory消费者通过Tutorial::MathFunctions引用目标,与find_package消费者所见完全一致;
  • 生成器表达式解决"行为差异化"问题:无论库以何种方式被消费,SIMPLETEST_CONFIG=$<CONFIG>都能在生成阶段把"真实构建配置"注入编译定义,让测试框架报告与最终二进制一致的配置信息,而不是配置阶段的静态值。

这两点恰是打包者(packager)日常最关心的两类细节,也呼应了 Miscellaneous Features.rst 开篇的论断:教程未覆盖的特性,往往是使用它们的项目眼中"essential"的能力。后续深入学习时,可继续查阅 Help/command/add_library.rst(目标命令全量文档)、Help/manual/cmake-generator-expressions.7.rst(生成器表达式手册,涵盖$<CONFIG>、$<LINK_ONLY>、$<TARGET_FILE>等数百个表达式),以及教程目录 Help/guide/tutorial 中的其他章节,从"杂项"走向系统化掌握。

  • 构建工具
  • 开发工具
  • CLI

【免费下载链接】CMake

Mirror of CMake upstream repository

项目地址:https://gitcode.com/gh_mirrors/cm/CMake
点击查看免费下载
上一篇:探索高效代码导航:Vim-Gutentags
下一篇:wc/wcf跨平台部署教程:Windows、Linux与macOS环境配置详解

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询