首页
/ Electron 中运行 clang-tidy 的工程实践:从本地 lint 到 CI 远程检查的完整指南

Electron 中运行 clang-tidy 的工程实践:从本地 lint 到 CI 远程检查的完整指南

2026-09-06 18:33:22作者:管翌锬

本指南围绕 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 的实现可以看到脚本做了两件事来保证编译数据库可用:

  1. 若指定的 out 目录不存在,直接抛出 "Output directory doesn't exist"
  2. 若 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.jsfindMatchingFiles 辅助函数收集。

也可以显式传入文件名列表来缩小范围,此时这些文件会作为位置参数跟在选项之后:

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 编译工具链一致。

参数构造:核心函数 runClangTidyscript/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.CallAndMessageclang-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 参数时追加 --fixscript/run-clang-tidy.tsscript/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.ccxxx.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 的一份完整参考实现。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388