- 构建工具
- 开发工具
- CLI
【免费下载链接】CMake
Mirror of CMake upstream repository
导读
RULE_LAUNCH_CUSTOM是 CMake 中用于指定"自定义规则启动器(launcher)"的目录属性,它可以让 Makefile 系生成器与 Ninja 生成器在真正执行每一条自定义命令(如add_custom_command/add_custom_target产生的规则)之前,先以前缀方式插入一条包装命令。本文基于 CMake 官方文档与源码实现,完整讲解该属性的语义、生效范围、跨生成器差异、占位符展开机制以及典型应用场景(构建问题拦截、ctest 仪表盘错误上报、构建行为插桩),帮助你准确判断"该不该用、怎么用、用了会有什么效果"。
属性语义:为自定义规则指定一个启动器
按照 Help/prop_dir/RULE_LAUNCH_CUSTOM.rst 的定义:
Specify a launcher for custom rules. (为自定义规则指定一个启动器。) See the global property of the same name for details. This overrides the global property for a directory.(同名全局属性的详细说明见全局属性文档;本属性会覆盖全局同名属性,作用范围限定在当前目录。)
它与 全局属性 RULE_LAUNCH_CUSTOM 的关系是"目录级覆盖全局级":
- 全局属性在 CMake 顶层目录设置,作用于整个项目;
- 目录属性在某一个
add_subdirectory子目录或当前目录范围内设置,只影响该目录及其(未另行覆盖的)子目录中的自定义规则; - 两个作用域同名共存时,目录属性优先。
所谓"自定义规则(custom rules)"在 CMake 语境中指由add_custom_command、add_custom_target、file(GENERATE)等命令生成、并最终落到构建系统里的命令规则,它们与编译规则、链接规则并列,分别由三个RULE_LAUNCH_*属性控制:
| 属性 | 作用对象 | 内部用途说明 |
|---|---|---|
RULE_LAUNCH_CUSTOM | 自定义规则(custom commands) | 本文主题,拦截任意自定义命令 |
RULE_LAUNCH_COMPILE | 编译规则(compile rules) | 官方文档标注"仅供ctest(1)内部使用",项目开发者应改用<LANG>_COMPILER_LAUNCHER目标属性,见 全局属性 RULE_LAUNCH_COMPILE |
RULE_LAUNCH_LINK | 链接/归档规则(link & archive rules) | 同样标注仅供ctest(1)内部使用,开发者应改用<LANG>_LINKER_LAUNCHER,见 全局属性 RULE_LAUNCH_LINK |
注意:
RULE_LAUNCH_COMPILE与RULE_LAUNCH_LINK的文档都明确建议普通项目不要直接使用,而是使用CMAKE_<LANG>_COMPILER_LAUNCHER、CMAKE_<LANG>_LINKER_LAUNCHER变量或对应的目标属性。但RULE_LAUNCH_CUSTOM没有这一限制——因为自定义命令没有对应的"编译器启动器"等价物,它至今仍是包裹自定义规则的标准手段。
生成器支持范围:并非所有生成器都生效
全局属性文档明确说明了支持范围:
Makefile Generators和Ninja生成器会用给定的启动器命令行作为自定义命令的前缀。这样做的目的是让启动器能够以高粒度拦截构建问题。其他生成器会忽略该属性,因为它们的底层构建系统没有提供包装单条命令的钩子。
也就是说:
- 生效:所有 Makefile 系生成器(Unix Makefiles、NMake Makefiles、MinGW Makefiles、MSYS Makefiles 等)以及 Ninja(含 Ninja Multi-Config);
- 同样生效:从源码看,cmFastbuildTargetGenerator.cxx 中的
MakeCustomLauncher也读取RULE_LAUNCH_CUSTOM属性(注释明确写着 "Copied from cmLocalNinjaGenerator::MakeCustomLauncher"),因此 FASTBuild 生成器同样支持; - 忽略:Visual Studio、Xcode 等其他生成器,因为它们无法把单条规则单独包一层启动器。
这一差异在排障时非常关键:同一个项目在 Unix Makefiles 下启动器生效,切换到 Visual Studio 生成器后该属性会被静默忽略,不能依赖它做与平台无关的强制拦截。
属性链与作用域解析:目录如何覆盖全局
从源码看,RULE_LAUNCH_*三个属性在 cmState.cxx 中被同时注册为目录(DIRECTORY)与目标(TARGET)两个作用域的属性,且都是链式(chained)属性:
this->DefineProperty("RULE_LAUNCH_CUSTOM", cmProperty::DIRECTORY, "", "", true); // ... this->DefineProperty("RULE_LAUNCH_CUSTOM", cmProperty::TARGET, "", "", true);而目录属性的实际取值解析发生在cmLocalGenerator::GetRuleLauncher(cmLocalGenerator.cxx):
std::string cmLocalGenerator::GetRuleLauncher(cmGeneratorTarget* target, std::string const& prop, std::string const& config) { cmValue value = this->Makefile->GetProperty(prop); if (target) { value = target->GetProperty(prop); // 目标属性优先 } if (value) { return cmGeneratorExpression::Evaluate(*value, this, config, target); } return ""; }可以总结出三层取值规则:
- 先取目录属性(
GetProperty本身会沿目录作用域链向上查找,所以子目录未设置时会继承父目录的值); - 若存在目标,再取目标属性并覆盖目录属性;
- 返回值会经过生成器表达式(generator expression)求值,因此属性值里可以写
$<CONFIG>等表达式。
目录属性文档中"覆盖全局属性"(overrides the global property for a directory)正是这一解析链在目录一级的具体体现:目录级值优先于 全局属性 中的值。
底层实现:启动器如何被展开并拼接到命令前
Ninja 生成器
Ninja 生成器在 cmLocalNinjaGenerator.cxx 的MakeCustomLauncher中实现:
cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM"); if (!cmNonempty(property_value)) { return std::string(); // 未设置则返回空 } // 展开规则变量(rule variables) cmRulePlaceholderExpander::RuleVariables vars; // ... 收集 outputs,逗号分隔后存入 vars.Output ... vars.Output = output.c_str(); vars.FilePathWithOutput = ccg.StoreContentToFile(output).c_str(); vars.Role = ccg.GetCC().GetRole().c_str(); vars.CMTargetName = ccg.GetCC().GetTarget().c_str(); vars.Config = ccg.GetOutputConfig().c_str(); std::string launcher = *property_value; rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars); if (!launcher.empty()) { launcher += " "; // 拼接时在启动器后补一个空格 } return launcher;其行为要点:
- 属性值为空时直接返回空串,不产生任何前缀;
- 属性值中的
<OUTPUT>、<OUTPUT_STORE_TO_FILE>、<ROLE>、<TARGET_NAME>、<CONFIG>等占位符会被逐一展开; - 展开完成后在启动器末尾补一个空格,再拼接到真正的自定义命令之前。
Makefile 生成器
Unix Makefile 系生成器在 cmLocalUnixMakefileGenerator3.cxx 中走同样的路径,且使用了GetRuleLauncher统一入口:
std::string launcher; std::string val = this->GetRuleLauncher( target, "RULE_LAUNCH_CUSTOM", this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE")); if (cmNonempty(val)) { // 展开规则变量:TARGET_SUPPORT_DIR、CMTargetName、CMTargetType、 // Output、FilePathWithOutput、Role、Config 等 ... } // ... std::string shellCommand = this->ConvertToOutputFormat(cmd, cmOutputConverter::SHELL); cmd = launcher + shellCommand; // 启动器 + 真正命令Makefile 实现与 Ninja 基本一致,另有两点细节:
vars.TargetSupportDir会被设置为cmTarget::GetCMFSupportDirectory对应的支持目录(见 cmLocalUnixMakefileGenerator3.cxx),因此<TARGET_SUPPORT_DIR>占位符在 Makefile 生成器下可用;- 拼接后的命令若以相对路径引用当前目录下的程序,生成器会自动补
./前缀(见 cmLocalUnixMakefileGenerator3.cxx),避免当前目录不在 PATH 中时执行失败。
启动器值中可用的占位符
启动器属性值本质上是一段命令模板,CMake 会按规则变量(rule variables)展开。与自定义规则启动器直接相关的占位符包括:
| 占位符 | 含义 | 可用性 |
|---|---|---|
<OUTPUT> | 规则输出文件列表(多个以逗号分隔,shell 转义后) | Ninja / Makefile / FASTBuild |
<OUTPUT_STORE_TO_FILE> | 把输出列表写入临时文件后得到的文件路径(StoreContentToFile) | Ninja / Makefile / FASTBuild |
<ROLE> | 自定义命令的角色(如CUSTOM_COMMAND相关角色标识) | Ninja / Makefile / FASTBuild |
<TARGET_NAME> | 关联目标的名称 | Ninja / Makefile / FASTBuild |
<CONFIG> | 当前构建配置(如 Debug/Release) | Ninja / Makefile |
<TARGET_SUPPORT_DIR> | 目标支持目录(CMake 生成辅助文件的目录) | Makefile 系生成器 |
<CMAKE_CURRENT_BINARY_DIR> | 当前二进制目录 | 由外部调用方注入(见下文 ctest 插桩) |
这些占位符的集合定义在 cmRulePlaceholderExpander.h 的RuleVariables结构体中,其中与本文相关的字段包括Output、FilePathWithOutput、TargetSupportDir、CMTargetName、CMTargetType、Config、Role等。
一个带占位符的典型取值示例:
set_property(DIRECTORY PROPERTY RULE_LAUNCH_CUSTOM "\"${CMAKE_COMMAND}\" -E echo \"running custom rule for <TARGET_NAME>: <OUTPUT>\" -- ")实战配置:目录级、全局级与目标级设置
目录级(本文主题)
在某个子目录的 CMakeLists.txt 中:
# 仅对本目录及其子目录中的自定义规则生效 set_property(DIRECTORY PROPERTY RULE_LAUNCH_CUSTOM "\"${CMAKE_COMMAND}\" -E echo \"[custom] \" -- )更实用的写法——用自定义脚本做记录/校验:
set_property(DIRECTORY PROPERTY RULE_LAUNCH_CUSTOM "${CMAKE_COMMAND}" -E env WRAPPER_MARKER=custom "${CMAKE_COMMAND}" -- )全局级
在顶层 CMakeLists.txt 中设置全局同名属性,作为整个项目所有目录的默认值:
set_property(GLOBAL PROPERTY RULE_LAUNCH_CUSTOM "\"${CMAKE_COMMAND}\" -E echo \"[global custom launcher] \" -- )目标级
属性同时在目标作用域注册(见上文 cmState.cxx),可以只包裹某个目标的规则:
set_property(TARGET my_target PROPERTY RULE_LAUNCH_CUSTOM "\"${CMAKE_COMMAND}\" -E echo \"[target custom launcher] \" -- )三个层级同时存在时,解析顺序为:目标级 > 目录级 > 全局级。
注意事项
- 启动器值中建议对路径和可执行文件加引号,防止路径含空格时被 shell 拆分;
- 属性值会被当作命令模板拼接,不要在其中以分号列表形式混入多条命令,否则会被 CMake 当作参数列表处理而非一整条模板;
- 使用前先确认当前生成器属于"生效列表"(Makefile 系、Ninja、FASTBuild),否则设置会被静默忽略;
- 需要按配置区分的场景,可在值中嵌入生成器表达式,因为 cmLocalGenerator.cxx 会对最终值执行
cmGeneratorExpression::Evaluate。
典型应用场景:从 ctest 仪表盘到构建插桩
场景一:拦截自定义规则的构建问题
全局属性文档指出该机制"旨在让启动器以高粒度拦截构建问题"。典型做法是让启动器记录每条自定义规则的执行时间、退出码、输出日志,当规则失败时把详细信息写入诊断文件,供构建仪表盘(dashboard)汇总。
场景二:ctest 启动器与仪器化插桩(ctest --launch / --instrument)
这是 CMake 内部对RULE_LAUNCH_CUSTOM最直接的使用。在 cmake.cxx 中,当CTEST_USE_LAUNCHERS开启或存在仪器化查询(instrumentation query)时,CMake 会自动写入三个全局RULE_LAUNCH_*属性,其中自定义规则部分为:
this->State->SetGlobalProperty( "RULE_LAUNCH_CUSTOM", cmStrCat( launcher, "--command-type custom", common_args, "--output-as-file-name \"<OUTPUT_STORE_TO_FILE>\" --role <ROLE> -- "));launcher由CTEST_USE_LAUNCHERS决定是ctest --launch ...还是ctest --instrument ...,common_args中包含--target-name <TARGET_NAME> --config <CONFIG> --build-dir "..."。
换句话说,当启用 ctest 启动器/插桩后,你可以在生成的构建命令中看到类似这样的前缀:
/path/to/ctest --launch --command-type custom \ --target-name <TARGET_NAME> --config <CONFIG> --build-dir "/path/to/build" \ --output-as-file-name "<OUTPUT_STORE_TO_FILE>" --role <ROLE> -- <原始自定义命令>这正是 RULE_LAUNCH_CUSTOM 作为"构建问题高粒度拦截钩子"的设计意图的直接体现:每条自定义规则都能被单独包装、单独记录结果。测试侧对ctest --launch/ctest --instrument的退出码分类(成功、非零退出、信号终止、无法 spawn 等)可参考 Tests/RunCMake/CTestLaunch/RunCMakeTest.cmake 中的回归用例。
场景三:构建过程插桩与分析
可以借助启动器为自定义命令注入环境变量、计时、资源统计或日志采集,实现"对每条规则逐一插桩"的细粒度观测——这是全局性包装工具(如对整个构建做 strace)难以做到的。由于该机制只影响"自定义规则"而非编译/链接规则,插桩范围精确可控,不会干扰编译器与链接器的调用路径。
与其他启动器类属性的边界
| 属性 | 覆盖的规则 | 推荐给普通项目? |
|---|---|---|
RULE_LAUNCH_CUSTOM | 自定义命令规则 | 是(无等价替代物) |
RULE_LAUNCH_COMPILE | 编译规则 | 否,应改用<LANG>_COMPILER_LAUNCHER/CMAKE_<LANG>_COMPILER_LAUNCHER,见 Help/prop_gbl/RULE_LAUNCH_COMPILE.rst |
RULE_LAUNCH_LINK | 链接/归档规则 | 否,应改用<LANG>_LINKER_LAUNCHER/CMAKE_<LANG>_LINKER_LAUNCHER,见 Help/prop_gbl/RULE_LAUNCH_LINK.rst |
简言之:编译与链接有现代的专用启动器属性(<LANG>_COMPILER_LAUNCHER、<LANG>_LINKER_LAUNCHER),而自定义规则只有RULE_LAUNCH_CUSTOM这一个官方入口,因此它的使用场景至今仍然成立。
小结
RULE_LAUNCH_CUSTOM为自定义规则指定启动器,目录级设置会覆盖同名 全局属性,目标级设置又优先于目录级;- 仅在 Makefile 系生成器、Ninja 与 FASTBuild 下生效,其他生成器忽略该属性;
- 属性值支持
<OUTPUT>、<OUTPUT_STORE_TO_FILE>、<ROLE>、<TARGET_NAME>、<CONFIG>、<TARGET_SUPPORT_DIR>等规则占位符,并支持生成器表达式; - 底层由
cmLocalGenerator::GetRuleLauncher(cmLocalGenerator.cxx)统一解析,Ninja 与 FASTBuild 在MakeCustomLauncher中、Makefile 系在WriteRule相关流程中完成拼接; - ctest 的启动器与插桩机制(
ctest --launch/--instrument)正是通过自动设置该属性来实现对每条自定义命令的拦截与结果分类,相关回归测试见 Tests/RunCMake/CTestLaunch/RunCMakeTest.cmake。
参考资料:目录属性文档 Help/prop_dir/RULE_LAUNCH_CUSTOM.rst、全局属性文档 Help/prop_gbl/RULE_LAUNCH_CUSTOM.rst 及其实现源码 cmLocalNinjaGenerator.cxx、cmLocalUnixMakefileGenerator3.cxx、cmFastbuildTargetGenerator.cxx、cmState.cxx。
- 构建工具
- 开发工具
- CLI
【免费下载链接】CMake
Mirror of CMake upstream repository
相关推荐
CMake 目录属性 TEST_INCLUDE_FILES:向 ctest 注入自定义 CMake 脚本的机制与实践
CMake 目录属性 TEST_INCLUDE_FILES:向 ctest 注入自定义 CMake 脚本的机制与实践 TEST_INCLUDE_FILES 是
构建工具开发工具CLICMake VS_GLOBAL_SECTION_POST_<section> 目录属性:向 Visual Studio 解决方案文件注入自定义 GlobalSection
CMake VS_GLOBAL_SECTION_POST_<section 目录属性:向 Visual Studio 解决方案文件注入自定义 GlobalSec
构建工具开发工具CLICMake 4.5 新特性详解:用 RULE_PATTERNS 文件集属性驱动自定义规则的占位符展开
CMake 4.5 新特性详解:用 RULE_PATTERNS 文件集属性驱动自定义规则的占位符展开 本篇围绕 CMake 的文件集属性 RULE_PATTER
构建工具开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考