Electron 中运行 clang-tidy 的工程实践:从本地 lint 到 CI 远程检查的完整指南
本指南围绕 Electron 官方文档 docs/development/clang-tidy.md 展开,系统讲解如何在 Electron 的 C++ 源码上运行 clang-tidy:包括前置构建要求、npm run lint:clang-tidy 脚本的选项与底层实现、check 选择策略、并行度与内存权衡,以及 CI 中基于 ninja/siso 的远程执行方案。读完本文,你将能够对单个 .cc 文件或整个 shell/ 目录执行静态检查,并理解 Electron 是如何把 clang-tidy 真正接入大规模 C++ 工程的。
clang-tidy 是什么,Electron 如何接入它
clang-tidy 是基于 Clang 的静态分析工具,用于自动检查 C/C++/Objective-C 代码中的风格违规、编程错误与最佳实践偏离。与单纯的正则或 token 级 lint 不同,clang-tidy 会真正解析并编译代码,因此它需要知道每个文件的真实编译参数(include 路径、宏定义、编译选项等)。
在 Electron 中,clang-tidy 集成以一个 lint 脚本的形式提供,通过 npm run lint:clang-tidy 触发。查看 package.json(对应 script 字段定义在 package.json 的 lint 脚本区),该命令实际映射到 ts-node ./script/run-clang-tidy.ts。也就是说,Electron 没有采用 .clang-tidy 文件外挂或第三方插件,而是自己维护了一个 TypeScript 编写的调度脚本 script/run-clang-tidy.ts,负责解析参数、定位 LLVM 的 clang-tidy 二进制、读取编译数据库并批量派发检查任务。
使用前提:为什么必须先构建 Electron
clang-tidy 检查的是磁盘上真实的源文件,但它需要知道编译这些文件时使用过哪些编译器参数,这些信息来自构建目录中的 compile_commands.json(编译数据库)。因此文档明确强调:在运行 clang-tidy 之前,必须已经构建过 Electron,这样 out 目录中才会有完整的编译参数与生成的头文件(如 buildflags 生成的头)。
从 script/run-clang-tidy.ts 的实现可以看到脚本做了两件事来保证编译数据库可用:
- 若指定的 out 目录不存在,直接抛出
"Output directory doesn't exist"; - 若 out 目录存在,则自动执行
gn gen . --export-compile-commands重新生成/刷新其中的compile_commands.json,确保其携带当前构建的编译参数。
也就是说,你通常不需要手动导出编译数据库——脚本在每次运行前都会帮你做一次 gn gen。但构建本身(生成头文件)仍需要提前完成,原因正如 script/gen-clang-tidy-ninja.py 的文件头注释所写:clang-tidy 步骤需要 out 目录中已经生成的头文件。
基础用法与命令行选项
必选参数 --out-dir
脚本唯一必选的参数是 --out-dir,用于告知脚本从哪个构建目录读取编译信息。需要提醒的是:文档正文将此参数写作 --output-dir,但从 script/run-clang-tidy.ts 的实现(minimist 配置为 string: ['out-dir'])以及脚本打印的 usage 字符串看,实际的选项名是 --out-dir,示例命令也一直使用 --out-dir,运行时请以 --out-dir 为准。
最典型的全量用法:
npm run lint:clang-tidy -- --out-dir ../out/Testing
其中 ../out/Testing 是相对 Electron 源码树(位于 src/electron)的构建输出目录路径。脚本会把参数原样传给 node 再进入 script/run-clang-tidy.ts。
指定检查文件范围
不带任何文件名时,脚本会对全部 C/C++/Objective-C 文件执行检查。从 script/run-clang-tidy.ts 的实现来看,默认行为是递归扫描 shell/ 目录下所有以 .cc 或 .mm 结尾的翻译单元(正则 /.*\.(?:cc|mm)$/),并通过 script/lib/utils.js 的 findMatchingFiles 辅助函数收集。
也可以显式传入文件名列表来缩小范围,此时这些文件会作为位置参数跟在选项之后:
npm run lint:clang-tidy -- --out-dir ../out/Testing shell/browser/api/electron_api_app.cc
可以一次指定多个文件:
npm run lint:clang-tidy -- --out-dir ../out/Testing \
shell/browser/api/electron_api_app.cc \
shell/browser/api/electron_api_browser_window.cc
有一个硬性约束值得注意:不允许传入头文件(.h)。从实现看,script/run-clang-tidy.ts 会检查传入的文件名,若存在以 .h 结尾的路径则抛出 "Filenames must be for translation units, not headers"(退出码 3)。这是因为 clang-tidy 的检查对象是翻译单元,头文件会被其所包含的 .cc/.mm 文件间接覆盖。
脚本的完整命令行形态可以随时通过帮助选项查看:
npm run lint:clang-tidy -- --help
其打印的 usage(与源码 script/run-clang-tidy.ts 一致)为:
Usage: script/run-clang-tidy.ts [-h|--help] [--jobs|-j] [--fix] [--checks] --out-dir OUTDIR [file1 file2]
参数速查表
| 选项 | 说明 | 默认值 |
|---|---|---|
--out-dir OUTDIR |
必选。指定携带 compile_commands.json 的构建输出目录;目录不存在则报错退出 |
无 |
--checks=... |
指定 clang-tidy 检查集合,原样透传给 clang-tidy,支持通配符与 - 前缀禁用 |
空(继承 .clang-tidy 配置) |
| `--jobs | -j N` | 并行 worker 数量 |
--fix |
让 clang-tidy 自动应用可自动修复的修改 | 关闭 |
| `-h | --help` | 打印用法后退出 |
| 文件列表 | 待检查的翻译单元;缺省时全量扫描 shell/ 下 .cc/.mm 文件 |
全量 |
此外脚本还有两个受环境影响的内部行为(详见下文源码分析):非 CI 环境自动追加 --use-color 让输出带颜色;Windows 平台自动追加 MSVC 驱动模式相关参数。
调度脚本的源码级实现拆解
理解 script/run-clang-tidy.ts 的实现有助于你判断命令在真实环境中会如何表现。
二进制定位:脚本从 LLVM 构建产物中寻找 clang-tidy 可执行文件,路径由 script/run-clang-tidy.ts 计算为 SOURCE_ROOT/../third_party/llvm-build/Release+Asserts/bin。Electron 的 clang-tidy 来自 depot_tools 拉取的 LLVM 构建,而不是系统自带的 clang-tidy,这保证了版本与 Chromium/Electron 编译工具链一致。
参数构造:核心函数 runClangTidy(script/run-clang-tidy.ts)首先以 -p=${outDir} 指定编译数据库位置。随后:
- 非 CI 环境追加
--use-color(避免 CI 日志被 ANSI 颜色污染); - 传入了
--fix时追加--fix(clang-tidy 会自动改写文件); - 传入了
--checks时追加--checks=...; - Windows 平台追加
--extra-arg-before=--driver-mode=cl与--extra-arg=-Wno-unused-command-line-argument。源码注释(script/run-clang-tidy.ts)说明这是因为 Windows 的编译数据库里含有会干扰 clang-tidy 的编译命令,追加这些参数可避免无谓的告警、保证退出码干净。值得说明的是注释同时提示--driver-mode=cl的实际效果可能有限。
编译数据库过滤与文件名裁剪:compile_commands.json 可达数百 MB,脚本采用 stream-json 流式解析(script/run-clang-tidy.ts),逐条过滤出与待检查文件匹配的条目,避免整库载入内存。随后把所有文件路径转换成相对于 Electron 源码树根(SOURCE_ROOT)的短路径(script/run-clang-tidy.ts),尽量压缩单次 clang-tidy 进程的命令行长度,以便每次调用容纳更多文件。文件按命令行长度上限切块(chunkFilenames,见 script/lib/utils.js),分配给多个 worker 并发执行。
结果判定:脚本对“检查通过”的定义很严格。在 worker 内(script/run-clang-tidy.ts),只有进程退出码为 0 且 stdout 为空才算单个批次通过——因为 clang-tidy 在无任何告警的干净运行下不会向 stdout 输出内容;若存在 warning 级诊断,即使退出码仍为 0,也会因 stdout 有输出而被判失败,最终导致整个 lint 以退出码 1 结束。这符合静态检查门禁的预期:任何诊断都应被当成问题处理,而不是静默放过。
两个默认扫描例外:全量模式下,脚本会跳过两个特殊文件(script/run-clang-tidy.ts):CI 环境中的 shell/renderer/electron_smooth_round_rect.cc(存在仅在 CI 复现的编译错误,注释见代码中的 TODO)以及 shell/common/electron_natives_codecache_main.cc(它是 v8_snapshot_toolchain 下的构建期 host 工具,其传递包含的生成 buildflags 头文件在 CI 中不齐全)。对应的排除列表也在 script/gen-clang-tidy-ninja.py 中同步维护,两处注释均要求保持一致性。
check 选择策略:默认继承 Chromium,亦可完全自定义
clang-tidy 自身拥有非常庞大的检查清单(涵盖 readability、modernize、performance、bugprone、clang-analyzer 等大组)。但 Electron 默认只启用其中很少一部分,选择规则是:沿用 Chromium 的 .clang-tidy 配置。
按官方文档当时的说明,Electron 自身没有维护独立的 .clang-tidy 配置,clang-tidy 会向上查找到 Chromium 位于 src/.clang-tidy 的配置并使用其中启用的检查。在当前仓库中可以看到一份根目录 .clang-tidy 配置,它启用了 clang-analyzer-core.*、clang-analyzer-cplusplus.*、clang-analyzer-deadcode.*、clang-analyzer-nullability.*、clang-analyzer-osx.*、clang-analyzer-security.*、clang-analyzer-unix.* 等静态分析器组,同时排除 clang-analyzer-core.CallAndMessage 与 clang-analyzer-security.PointerSub;配置还设置了 HeaderFilterRegex: ''(只报告被检查翻译单元自身的问题,避免 Chromium 头文件淹没问题列表)以及 InheritParentConfig: true(允许继续向上层目录继承合并父级配置)。clang-tidy 本身按“从目标文件所在目录向根目录逐级查找并合并 .clang-tidy”的规则工作,script/run-clang-tidy.ts 中的注释也印证了这一点——脚本不显式传 --header-filter,头文件过滤行为完全由 .clang-tidy 中的 HeaderFilterRegex 决定。
用 --checks 临时增删检查
你可以通过 --checks= 选项改变本次运行的检查集合,该选项会被脚本原样透传给 clang-tidy(script/run-clang-tidy.ts)。透传意味着支持 clang-tidy 的完整 check 语法:
- 通配符:
--checks=performance*会启用所有以performance开头的检查; - 前缀
-禁用:--checks=-readability-*会从当前集合中剔除所有readability检查; - 追加语义:
--checks指定的项默认是追加到.clang-tidy中已有检查之上的,而不是替换; - 精确限制的写法:如果只想运行某几个检查,应先排除全部再添加需要的,例如运行
performance大组并排除一切其他检查:
npm run lint:clang-tidy -- --out-dir ../out/Testing \
--checks=-*,performance* shell/browser/api/electron_api_app.cc
这条命令的意思是:先把 .clang-tidy 中继承来的检查全部关掉(-*),再开启全部 performance* 检查,非常适合在本地快速验证某一类问题的覆盖面。
速度与内存:为什么默认并发数是 1
clang-tidy 是出了名的慢——本质上它会对每个文件做一次完整的前端编译再叠加分析。文档明确指出:它“总会比纯编译慢若干倍”。script/run-clang-tidy.ts 中 minimist 的默认配置 default: { jobs: 1 } 印证了默认单 worker 的设计。
用 --jobs|-j 可以开启并行:
npm run lint:clang-tidy -- --out-dir ../out/Testing -j 4 shell/browser/api/electron_api_app.cc
但并行并非免费的午餐:clang-tidy 在分析过程中内存占用很高,worker 数量一多很容易触发 OOM(out-of-memory)。文档与实现都建议:调大 -j 前务必确认机器有足够内存。脚本在并发时的分配方式是按 jobs 将文件平分,每个 worker 从公共队列中依次取出预先切好的文件块执行(script/run-clang-tidy.ts),因此 -j N 实际对应 N 个并发的 clang-tidy 进程。
自动修复:--fix
虽然官方 clang-tidy 指南文档本身未展开介绍,Electron 的脚本还支持 --fix,用于让 clang-tidy 自动应用可安全自动修复的诊断。其开关位于 minimist 的 boolean: ['fix', 'help'] 配置中,并在构造 clang-tidy 参数时追加 --fix(script/run-clang-tidy.ts、script/run-clang-tidy.ts)。注意:自动修复会直接改写磁盘上的源文件,建议先在 git 工作区下使用并检查 diff,只修复你关心的检查类别(配合上文 --checks 过滤更稳妥)。
CI 视角:基于 ninja/siso 的分布式 clang-tidy
除本地脚本外,Electron 还为 CI 提供了另一条执行路径:把 clang-tidy 组织成 ninja 构建步骤,通过 siso(远程构建执行器)在 RBE 上并行、远程缓存地执行。这部分的生成器是 script/gen-clang-tidy-ninja.py。
该 Python 脚本的定位在其模块 docstring 中写得很清楚:为 CI 的 clang-tidy job 生成 <out_dir>/clang_tidy.ninja——每个 Electron 翻译单元对应一个 clang-tidy 构建步骤。它读取 out 目录的 compile_commands.json,筛选出属于 shell/ 且以 .cc/.mm 结尾的翻译单元,解析每条编译命令(兼容 POSIX clang 与 Windows 下 clang-cl 的 /Fo 输出参数写法),把“编译器 + 全部 flags”记录成步骤变量,最后为每个单元生成 xxx.cc → xxx.cc.o.tidy 的构建边,并用一个 electron_clang_tidy phony 目标聚合所有步骤。生成的步骤以 build/run-clang-tidy-action.sh 作为命令前缀,该形态正是让 siso 扫描 #include、查 RBE action 缓存并远程执行的关键前提。
生成 ninja 文件前,脚本同样会重新执行 gn gen(使用 --add-export-compile-commands=//electron:* 限定导出范围,见 script/gen-clang-tidy-ninja.py),确保步骤携带 out 目录当前的真实 flags。其 docstring 还强调了一个易错点:如果你要手动运行它,out 目录必须已经构建过(步骤依赖生成的头文件),并且需要通过 e d 环境运行以保证 gn 在 PATH 上。
CI 编排可在 .github/workflows/pipeline-segment-electron-clang-tidy.yml 中看到完整脉络:checkout Electron 后初始化 siso 远程构建环境,用 gen-clang-tidy-ninja.py 生成 ninja 文件,再执行
e build --gen=off --target electron_clang_tidy -f clang_tidy.ninja -batch -k 0
其中 -k 0 表示即使某些目标失败也继续执行其余目标,保证一次 CI 运行能收集尽量多的诊断;job 还会添加 Clang problem matcher 以在 CI 日志中高亮 C++ 问题,并在结束后上传 siso 指标。workflow 注释明确点出:clang-tidy 是“per-translation-unit 的、被远程执行与远程缓存”的 siso action。另一个值得关注的细节是:当 macOS 主机运行时,RBE 后端 worker 实际是 Linux,因此 CI 会额外下载 Linux 版的 clang-tidy 二进制(tools/clang/scripts/update.py --package clang-tidy --host-os linux)到独立目录供远程执行使用。
所以 Electron 内部存在两条互补的使用路径:
- 本地交互式检查:
yarn lint:clang-tidy -- --out-dir ...(即本文主讲的脚本),灵活、可单文件、可自定义 checks; - CI 规模化检查:
gen-clang-tidy-ninja.py产出 ninja/siso 步骤,每个翻译单元一个远程可缓存的 action,适合作为提交门禁在大规模源码上全量运行。
如果你要为 Electron 的 C++ 改动做提交前自检,推荐顺序是:先确认本地已有可用的构建 out 目录,再针对改动涉及的 .cc/.mm 文件执行 npm run lint:clang-tidy -- --out-dir <你的 out 目录> <改动文件>;若只想验证某个检查类别,叠加 --checks=-*,类别* 即可快速收敛输出。整个链路——从 package.json 的 npm script、TypeScript 调度脚本、.clang-tidy 配置,到 Python 的 ninja 生成器与 CI workflow——都能在当前仓库中逐一对照阅读,这也是理解 Chromium 系大型项目如何落地 clang-tidy 的一份完整参考实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00