ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ninja多输出报错根治指南:从构建系统原理到CMake工程实践

Ninja多输出报错根治指南:从构建系统原理到CMake工程实践 1. 问题现象与根因分析1.1 这个报错长什么样什么时候出现先看一条典型的报错输出ninja: error: build.ninja:1180: multiple outputs arent (yet?) supported很多人在编译 C/C 项目时遇到这个提示第一时间会蒙圈——明明代码没报错怎么构建系统先罢工了这个报错发生在 Ninja 构建系统解析build.ninja文件阶段而不是编译器阶段。第 1180 行是对应规则rule或构建语句build statement的所在位置。Ninja 在读取构建文件时发现某条规则声明了多个输出文件但它不支持这种写法于是直接拒绝继续执行。Ninja 的设计哲学是极简、快速。它刻意不支持某些高级语法多个输出multiple outputs就是其中之一。CMake 在生成构建文件时正常情况下会把多输出规则拆成多条单输出规则中间用phony目标做串联。一旦生成逻辑异常或某些特殊工具链比如自定义命令、代码生成器配置不当就会直通通地把多输出写进build.ninjaNinja 自然不认。1.2 为什么 Ninja 不支持多个输出很多人不理解GNU Make 明明支持一条规则生成多个目标Ninja 为什么不行核心原因是 Ninja 为了增量构建的精确性把输出文件作为图调度dependency graph中的节点。一条构建语句只能有一个主输出节点这样 Ninja 才能精确判断哪个输出过期了、该重跑哪条命令、哪些下游规则需要重新执行。如果允许一条规则声明多个输出Ninja 就需要额外维护共享命令的语义还要处理多个输出之间过期状态不一致的问题——这会让增量构建的判断逻辑复杂一大截。Ninja 作者 Evan Martin 在项目文档里也明确说过这是有意的设计取舍目前没有计划支持所以报错信息里那句(yet?)其实带点自嘲。而 CMake 在这个问题上的处理方式是生成一层phony规则把所有输出目标串联起来。所以正常情况下CMake Ninja 组合不会触发这个报错。一旦你看到这个错误基本可以断定要么是 CMake 版本和 Ninja 版本存在兼容性问题要么是某个自定义命令把多输出直接暴露给了 Ninja。1.3 最容易踩坑的高发场景根据我在实际项目和社区问题里收集到的信息这个报错高发于下面这几类场景场景具体表现触发原因CMake 版本过旧使用 CMake 3.9 以下版本搭配新版本 Ninja旧版本 CMake 生成多输出规则的方式与 Ninja 新版本不兼容Ninja 版本过旧Ninja 1.7 以下版本解析多输出规则失败旧版 Ninja 对多输出规则的容错较差自定义命令多输出add_custom_command同时生成多个文件未使用BYPRODUCTS或依赖声明不当代码生成器工具链protobuf、gRPC、Qt MOC、OpenSSL 等生成多个产物工具链规则写法不符合 Ninja 预期生成器表达式误用CMake 的OUTPUT参数里写了多个文件CMake 直接透传到 Ninja 规则中我遇到最多的情况是第一种和第三种。尤其是 Windows 上使用 Visual Studio 生成器切换到 Ninja 时或者 Linux 下自己写add_custom_command生成多份文件时特别容易撞上这个报错。注意这个报错和代码本身的编译错误无关。也就是说即使你的源码完全正确构建系统配置不当一样会卡在这一步。2. 应对方案与实操步骤2.1 先检查版本组合这是成本最低的一步遇到任何构建系统的诡异报错第一件事永远是确认版本组合。Ninja 和 CMake 的版本匹配关系决定了构建文件生成时说话的方式是否一致。先分别查看两个工具的版本ninja --version cmake --version我个人的建议基线是工具建议最低版本推荐版本CMake3.163.24Ninja1.101.11如果你用的 CMake 低于 3.9或者 Ninja 低于 1.7优先升级这两个工具再重新构建。绝大部分情况下升级版本就能解决 80% 的问题。升级之后务必删掉旧的构建缓存目录重新配置。很多人只升级了工具但CMakeCache.txt还是旧的结果报错依旧白白浪费时间。rm -rf build cmake -S . -B build -G Ninja cmake --build build2.2 定位具体是哪条规则出了问题如果版本组合没问题就需要定位是哪一条规则触发了多输出报错。Ninja 的报错信息里会给出build.ninja:1180这样的行号。直接打开构建文件看那一行附近的内容sed -n 1170,1195p build/build.ninja你会看到类似这样的内容build output_a.txt output_b.txt: custom_command input.txt command python generator.py input.txt output_a.txt output_b.txt这一行声明了一个自定义生成步骤同时输出两个文件。Ninja 不认这种写法于是报错。找到目标规则之后再看回 CMakeLists.txt 里对应的add_custom_commandadd_custom_command( OUTPUT output_a.txt output_b.txt COMMAND ${PYTHON_EXECUTABLE} generator.py ${CMAKE_CURRENT_SOURCE_DIR}/input.txt DEPENDS input.txt )问题就出在OUTPUT参数里一次写了两个文件。CMake 在生成 Ninja 构建文件时某些版本/配置组合下会直接把这俩文件写进同一条build语句导致 Ninja 报错。2.3 标准解法把多输出拆成单输出既然 Ninja 不支持多输出我们的思路就是让每一条构建语句只保留一个输出其余的用BYPRODUCTS声明再通过add_custom_target串联依赖。上面那个例子可以改成这样add_custom_command( OUTPUT output_a.txt COMMAND ${PYTHON_EXECUTABLE} generator.py ${CMAKE_CURRENT_SOURCE_DIR}/input.txt ${OUTPUT_A} ${OUTPUT_B} DEPENDS input.txt BYPRODUCTS output_b.txt COMMENT Generating output_a.txt and output_b.txt )这里的关键变化是OUTPUT只写output_a.txt这一个文件。output_b.txt作为BYPRODUCTS声明——它的作用是告诉 CMake 和 Ninja这条命令会顺手产生这个文件但不需要单独为它建立构建规则。COMMAND里仍然传两个输出路径给生成器脚本保证实际生成逻辑不变。然后定义一个 target 把这条自定义命令挂进去add_custom_target(generate_outputs ALL DEPENDS output_a.txt )这样 Ninja 调度的主输出是output_a.txtoutput_b.txt只作为副产物存在。增量构建时只要output_a.txt不存在或过期了就会重新执行生成命令两个文件一起被刷新。2.4 特殊情况两个文件都是主产物有些场景下两个输出文件都可能是下游规则的输入比如同时生成头文件和源文件generator.py - config.h config.c如果下游同时依赖这两个文件简单地把其中一个丢进BYPRODUCTS会有隐患。因为BYPRODUCTS不会被 Ninja 当作正式输出节点下游依赖config.c的规则可能无法正确触发。这种时候的可靠做法是仍然只把其中一个作为OUTPUT但通过一个中间目标把依赖串联起来。set(GEN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/config.h) set(GEN_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/config.c) add_custom_command( OUTPUT ${GEN_HEADER} COMMAND ${PYTHON_EXECUTABLE} generator.py ${GEN_HEADER} ${GEN_SOURCE} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/config.in BYPRODUCTS ${GEN_SOURCE} COMMENT Generating config.h and config.c ) add_custom_target(generate_config ALL DEPENDS ${GEN_HEADER} )下游需要依赖config.c的地方改为依赖generate_config这个 target而不是直接依赖config.c文件本身add_library(foo STATIC generated_impl.c) add_dependencies(foo generate_config)注意generated_impl.c如果在源文件列表里声明为config.c一个尚未生成的文件CMake 在配置阶段可能报源文件不存在。所以在构建目录中生成源文件这种做法通常要把config.c加到 include 目录或者通过 include 方式使用而不是直接放进add_library的源文件列表。更稳妥的方案是让生成器只生成头文件源文件内容通过#include包含到固定源文件里。2.5 快速绕行方案改用 Makefile 生成器如果你只是想尽快编译通过不想折腾 CMake 改造有一个临时绕行手段cmake -S . -B build-make -G Unix Makefiles cmake --build build-makeGNU Make 天生支持一条规则生成多个目标所以同样的多输出配置在 Makefile 生成器下不会报错。但我必须提醒这只是应急方案不是根治手段。Makefile 生成器的增量构建速度远不如 Ninja在大型项目上差距非常明显。如果你做一次全量构建要 10 分钟用 Ninja 的增量可能只需要几十秒换回 Makefile 之后每次改动触发的大量重编译会让人很痛苦。所以这个方案只适合先编译出来再说的紧急情况后续还是建议回到 Ninja 并正确修改 CMake 配置。2.6 排查工具链规则中的隐藏多输出有些场景下多输出并不是你直接写在OUTPUT里的而是工具链规则隐式产生的。这类问题最难排查因为报错的行号对应的是 CMake 生成的内部规则不是用户自己写的命令。典型例子包括Qt的AUTOMOC/AUTOUIC在生成moc_*.cpp文件时protobuf的protoc同时生成.pb.h和.pb.ccOpenSSL的asn1生成器自定义CMAKE_CXX_COMPILER_LAUNCHER与编译器同时输出依赖文件遇到这类情况先确认编译器启动器如ccache、sccache和 CMake 的依赖扫描机制是否兼容。我见过一个真实案例某项目在交叉编译时启用了ccacheNinja 报出多输出错误查了半天发现是ccache版本太旧与 Ninja 的deps格式不兼容导致依赖文件解析异常。升级ccache后问题彻底消失。如果你怀疑是工具链生成器的问题建议先尝试用原始编译器直接生成一次看 Ninja 是否还会报错以此缩小排查范围。3. CMake 生成逻辑中的关键细节3.1 CMake 是如何处理多输出的既然问题根源在 CMake 与 Ninja 的配合上就有必要理解 CMake 生成 Ninja 文件时对多输出做了什么处理。CMake 的Ninja生成器内部会遍历每个自定义命令的输出文件列表然后为每个输出创建一个输出节点。当一条add_custom_command有多个输出时CMake 会在build.ninja里生成类似下面的结构build output_a.txt: CUSTOM_COMMAND input.txt command ... build output_b.txt: CUSTOM_COMMAND input.txt command ...也就是说CMake 理论上会把同一条命令复制成多条build语句每条只负责一个输出。所以正常情况下不会出现build output_a.txt output_b.txt:这样的语句。那为什么还会报错两个常见原因CMake 版本太旧当时的多输出生成逻辑不完善直接把多个输出写进了同一条build语句。某些特殊场景下 CMake 显式跳过拆分比如add_custom_target(target_name COMMAND generator.py out1 out2 BYPRODUCTS out1 out2 )add_custom_target是一条独立规则没有OUTPUT概念所有产物都声明为BYPRODUCTS。在 Ninja 中它被翻译成一条没有build前缀的规则不会触发多输出错误。但如果后续某个add_custom_command的DEPENDS指向了out1和out2Ninja 在解析时可能仍然会生成多输出构建语句。3.2 BYPRODUCTS 的正确使用方式BYPRODUCTS在 CMake 3.2 之后引入本意是声明这条命令运行时会顺带产生的文件用于 IDE 的文件浏览和增量构建辅助判断但它对 Ninja 的调度影响有限。实战中的经验是BYPRODUCTS中声明的文件不会被 Ninja 当作正式输出节点。如果其他规则直接依赖BYPRODUCTS里的文件Ninja 可能找不到对应的生成规则导致另一种错误ninja: error: output_b.txt, needed by xxx, missing and no known rule to make it。所以BYPRODUCTS只适合声明没有下游依赖的副产物。如果你有多个输出且每个输出都有下游依赖请继续用主输出 自定义 target add_dependencies的组合方式而不是指望BYPRODUCTS自动建链。3.3 生成器表达式在 OUTPUT 中的坑还有一类问题OUTPUT里写生成器表达式导致 CMake 在生成 Ninja 文件时无法正确展开最终把多个路径拼进同一条build语句。比如add_custom_command( OUTPUT $IF:$BOOL:${A},out1.txt,out2.txt ... )如果配置阶段A为真out1.txt会被展开但 Ninja 文件生成时可能因为表达式展开的顺序问题在多配置生成器如 Visual Studio中产生异常。Ninja 本身是单配置生成器不会展开$CONFIG之类的表达式所以如果在OUTPUT里混用了生成器表达式建议先用set变量存好实际路径再传给OUTPUT。if(USE_A) set(GEN_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/out1.txt) else() set(GEN_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/out2.txt) endif() add_custom_command( OUTPUT ${GEN_OUTPUT} ... )简单直接不要给 Ninja 添加不必要的解析负担。3.4 静态库与对象文件的多输出陷阱还有一类隐藏的多输出问题来自 CMake 的add_library与 Ninja 的deps策略交互。在一些老版本的 Ninja CMake 组合中静态库的构建规则会同时输出.a文件和符号表文件比如.toc如果 CMake 生成逻辑没处理好就会撞上多输出错误。遇到这种问题最直接的解决方式是把 Ninja 的deps策略改为gcccmake -S . -B build -G Ninja -DCMAKE_NINJA_DEPFILE_SUPPORTOFF或者在你的CMakeLists.txt顶部加上set(CMAKE_NINJA_FORCE_RESPONSE_FILE ON)这会让 Ninja 使用响应文件response file传递参数绕开一些与命令行长度和依赖解析相关的边界问题。注意CMAKE_NINJA_FORCE_RESPONSE_FILE会降低构建时的命令行可读性因为所有参数都打包进.rsp文件。但在某些特殊工具链环境下这是稳定的可用方案。4. 完整排查流程与一键式脚本4.1 从报错到解决的七个步骤我把实际排查流程整理成一张速查表你照着走就行步骤操作预期结果1ninja --version和cmake --version确认版本了解版本组合是否过旧2升级 CMake 至 3.16Ninja 至 1.10版本兼容性问题消除3删除 build 目录重新配置生成build.ninja重新生成4打开build.ninja定位报错行号确认是多输出规则5根据报错行号反查 CMakeLists.txt 中的规则找到对应的add_custom_command6将多输出改造为单输出 BYPRODUCTS消除多输出语句7重新构建验证报错消失增量构建正常4.2 从报错行号反查 CMake 规则的方法Ninja 的build.ninja文件每一行都有对应的 CMake 规则来源但 CMake 不会直接注释这行来自哪个 CMakeLists.txt。你需要靠文件名和命令内容来反推。比如报错行显示build output_a.txt output_b.txt: CUSTOM_COMMAND input.txt那么output_a.txt和output_b.txt一定是你项目里某个add_custom_command的OUTPUT参数内容。在项目源码目录下直接全局搜索grep -rn output_a.txt\|output_b.txt --includeCMakeLists.txt --include*.cmake .找到对应的add_custom_command之后按第 2.3 节的方法改造。如果你的项目是用FetchContent或ExternalProject引入的第三方库构建文件在_deps目录下面全局搜索时记得把build/_deps目录也带上grep -rn output_a.txt build/_deps/4.3 快速验证脚本这里提供一个可用于快速诊断的 shell 脚本直接复制保存为ninja-multi-output-check.sh#!/bin/bash # 快速诊断 ninja multiple outputs 报错 # 用法: ./ninja-multi-output-check.sh build_dir BUILD_DIR${1:-build} NINJA_FILE$BUILD_DIR/build.ninja if [ ! -f $NINJA_FILE ]; then echo [-] 找不到 $NINJA_FILE请先运行 cmake 生成构建文件 exit 1 fi echo [*] 扫描 $NINJA_FILE 中的多输出构建语句... grep -nE ^build . .: $NINJA_FILE | while read -r line; do echo [!] $line done echo [*] 扫描 CUSTOM_COMMAND 规则中的多输出... grep -nE build .:.CUSTOM_COMMAND $NINJA_FILE | while read -r line; do echo [!] $line done echo [*] 扫描 BYPRODUCTS 声明... grep -nE BYPRODUCTS $NINJA_FILE | while read -r line; do echo [!] $line done echo [*] 诊断完成。如果输出为空说明没有明显的多输出语句问题可能在工具链行为中。这个脚本只做扫描不做修改。使用前先备份build.ninjacp build/build.ninja build/build.ninja.bak4.4 Windows 环境下的额外注意事项Windows 下用 Visual Studio 的cl.exe编译时Ninja 的deps格式与 MSVC 的依赖输出有时兼容性不佳。如果你在 Windows 上报这个错优先确认使用的是 CMake 3.20这个版本之后对 MSVC Ninja 的组合支持大幅改善。编译器的响应文件.rsp没有语法问题。Ninja 在 Windows 上会把过长的命令行自动写入.rsp文件如果生成规则中引用了错误的路径可能导致构建文件解析异常。检查环境变量CL是否被意外设置。CL环境变量会被 MSVC 自动读取如果你设置过CL/DXXX之类的参数可能干扰编译命令的生成。我见过一个案例某同事在 Windows 环境变量里设置了CL/MP4来开启多进程编译结果 Ninja 生成的编译命令里带上了/MP4导致依赖扫描混乱间接触发了奇怪的构建错误。最后在系统环境变量里删掉CL问题就消失了。5. 实际项目排障案例实录5.1 案例一WebAssembly 交叉编译项目某次在 Emscripten 工具链下编译一个 C 项目Ninja 报出了完整的错误ninja: error: build.ninja:1180: multiple outputs arent (yet?) supported打开build.ninja第 1180 行附近看到的内容是build wasm_bindgen.js wasm_bindgen.wasm: CUSTOM_COMMAND ...这是一个典型的 wasm-bindgen 多输出场景——它同时生成.js胶水文件和.wasm二进制文件。当时我用的 CMake 版本正好是 3.10 左右Ninja 版本是 1.8两者搭配起来处理这种多输出规则时出了问题。改用 CMake 3.24 Ninja 1.11 之后重新生成构建文件同样的 CMakeLists 不再报错——因为新版 CMake 会自动把两个输出文件拆成两条build语句并通过phony规则串联。如果因为某些原因不能升级 CMake比如公司强制固定版本则按照第 2.3 节的改造方法把wasm_bindgen.wasm作为主输出wasm_bindgen.js放入BYPRODUCTS并在加载.js的 HTML 页面脚本里确保 wasm 文件在运行时存在。实际测试下来增量构建逻辑完全正常。5.2 案例二第三方库 FetchContent 引入时报错另一个项目通过FetchContent引入了一个开源库该库内部使用了make风格的构建脚本并显式声明了多输出规则。在 Linux 下用 Makefile 生成器一切正常切到 Ninja 后立刻报多输出错误。排查步骤grep -nE ^build . .: build/_deps/xxx-build/build.ninja结果发现第三方库的CMakeLists.txt里有这样一段add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gen.c ${CMAKE_CURRENT_BINARY_DIR}/gen.h COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen.py ... )这属于第三方库自身的配置问题。我没有去改第三方库的源码——那样升级代码库时会冲突——而是在项目顶层拦截处理方法是在FetchContent_Declare之后、FetchContent_MakeAvailable之前用变量覆盖该库的CMAKE_CURRENT_BINARY_DIR路径并在顶层 CMakeLists 里提前生成这对文件然后强制第三方库跳过自己的多输出规则。具体做法是FetchContent_Declare( xxx_lib GIT_REPOSITORY https://example.com/xxx_lib.git ... ) # 先手动生成第三方库需要的两个文件 set(XXX_GEN_C ${CMAKE_CURRENT_BINARY_DIR}/gen.c) set(XXX_GEN_H ${CMAKE_CURRENT_BINARY_DIR}/gen.h) execute_process( COMMAND ${PYTHON_EXECUTABLE} gen.py ${XXX_GEN_C} ${XXX_GEN_H} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) # 再让第三方库认为文件已经存在自动跳过生成步骤 set(XXX_SKIP_GENERATION ON) FetchContent_MakeAvailable(xxx_lib)这种提前生成 跳过原规则的思路在处理第三方库多输出问题时非常稳定而且不会留下本地补丁升级依赖时零冲突。5.3 案例三大型项目从 Make 迁移到 Ninja 时的批量报错一个朋友维护的老项目以前一直用Unix Makefiles最近想切到 Ninja 提速。结果首次cmake --build直接报了 20 多处多输出错误。逐条查看发现项目里几十个add_custom_command全都把多个输出文件写在OUTPUT里。用 Makefile 生成器时它们能正常工作切换 Ninja 后全部翻车。我给他的建议是分两步走先全局搜索所有add_custom_command找出OUTPUT中包含多个文件的地方。根据是否每个输出都有下游依赖分两类处理没有下游依赖的副产物直接放入BYPRODUCTS。有下游依赖的副产物用add_custom_targetadd_dependencies串联。用一条命令快速定位grep -rn -A2 add_custom_command --includeCMakeLists.txt . | grep -B1 OUTPUT | grep \.h\|\.c\|\.cpp\|\.js批量排查时先按文件数量排序优先处理生成文件数量最多的规则因为通常一条多输出命令会阻塞多个下游目标。改完一批就构建一次确认增量无误再继续下一批。不要一次性全改完再构建——如果中途改错了一个依赖关系排查范围会大大增加。6. 常见问题速查表6.1 直接报错场景对照报错特征可能原因首选处理方式multiple outputs arent (yet?) supported 自定义命令add_custom_command多输出拆分为单输出 BYPRODUCTS报错行号在 CMake 生成的内部规则区域CMake 与 Ninja 版本不兼容升级 CMake Ninja报错伴随missing and no known rule to make itBYPRODUCTS被下游直接依赖改用add_custom_target串联报错只在 Windows 出现Ninjadeps与 MSVC 依赖输出冲突更新 CMake 至 3.20报错出现在FetchContent引入的库中第三方库自身多输出规则顶层预生成 跳过原规则切换生成器之后才报错Makefile 正常、Ninja 不兼容按多输出改造规则迁移6.2 修改后仍然报错的排查方向如果你按照上面的方案修改了CMakeLists.txt但是报错依旧按优先级检查是否彻底删除了 build 缓存CMake 的生成逻辑是基于缓存的旧缓存可能残留旧的规则。务必rm -rf build重新配置。修改的是否是正确的那份 CMakeLists多模块项目里子目录的 CMakeLists 可能在add_subdirectory之前没有被触发你以为改了OUTPUT实际构建用的却是另一条规则。是不是build.ninja没被重新生成确认build.ninja的时间戳比CMakeLists.txt新。如果 CMake 配置阶段失败build.ninja不会更新Ninja 会继续用旧文件报错。是否还有隐藏的规则生成器比如protobuf_generate、qt5_wrap_cpp这类 CMake 封装函数它们内部的结构可能与你看到的规则不同。用cmake --trace跟踪一遍配置过程cmake --trace --trace-expand -S . -B build -G Ninja 21 | grep -E add_custom_command|OUTPUT | head -50构建设计上是不是绕不过去极少数情况下项目结构确实无法用 Ninja 表达比如完全依赖多输出的自定义构建脚本。这种项目不适合用 Ninja建议保留原来的 Makefile 生成器毕竟工具是为人服务的。6.3 关于是否应该绕过 Ninja 改用其他构建系统的建议很多人在这个报错上花了一整天后会问我是不是应该换回 Make我的观点是不建议轻易放弃 Ninja。Ninja 的增量构建速度优势明显一个几十万行代码的项目Makefile 增量构建可能需要几十秒甚至更久Ninja 往往只需要几秒钟。多输出问题本质是构建规则写法与 Ninja 语法不匹配改规则比换构建系统划算得多。唯一例外是如果项目里大量使用了第三方代码生成工具并且这些工具的 CMake 封装无法修改迁移成本确实很高。这种情况下保留 Makefile 生成器作为兜底方案Ninja 作为加速方案两个构建目录并存是一个值得考虑的折中策略。我在实际项目中就是这样操作的cmake -S . -B build-make -G Unix Makefiles cmake -S . -B build-ninja -G Ninja日常开发用 Ninja遇到无法修复的多输出问题时临时切回 Makefile两套配置互不干扰。7. 经验总结与收尾技巧这个报错本身并不复杂核心就一句话Ninja 不支持单条规则多输出而 CMake 在特定版本或配置下会把多输出直接暴露给 Ninja。解决思路也很明确升级工具版本到合理范围把多输出改造成单主输出 BYPRODUCTS用add_custom_target串联依赖批量排查第三方库中的隐藏规则。最后分享一个小技巧在CMakeLists.txt顶部加一段防御性检查在配置阶段就阻止多输出规则进入 Ninja 生成器if(CMAKE_GENERATOR MATCHES Ninja) message(STATUS 使用 Ninja 生成器将自动检查自定义命令的多输出规则) endif()真正有用的做法是在add_custom_command出现之前写一个辅助函数对OUTPUT参数做拆分检查。由于 CMake 的add_custom_command不能自定义包装我通常在项目代码审查时直接把这条规则写入规范文档所有add_custom_command的OUTPUT只允许声明一个文件多个产物一律走BYPRODUCTS add_custom_target方案。按这个规范写出来的构建配置在 Ninja 上永远都不会触发multiple outputs报错。我后来维护的每个项目都沿用这个约定再没在这个问题上浪费过时间。如果你也遇到了同样的报错不要慌打开build.ninja看行号回查 CMakeLists按本文第 2 章的方案修改即可。多数情况下十分钟内就能解决。
返回列表