
做 Chromium 145 的 Windows 编译前五篇基本都是在跟环境较劲装 depot_tools、装 Visual Studio、拉源码、跑 gclient sync。等到这些前置条件都齐了真正要跟构建系统打交道的第一件事就是生成构建文件。这篇就把这一环拆开讲清楚GN 和 Ninja 是怎么分工的args.gn 里哪些参数必须懂生成完之后怎么确认没问题以及这个环节最常见的报错该怎么查。先说一个我常跟人念叨的结论生成构建文件不是点一下按钮那么简单但也不是什么高深操作。你只需要理解一个核心逻辑——Chromium 的编译被拆成了两个阶段GN 负责生成构建描述文件Ninja 负责执行真正的编译。GN 把你的定制需求调试还是发布、要哪些组件、用什么指令集翻译成一批 .ninja 文件Ninja 再按这些文件一条条调用编译器和链接器把源码变成最终产物。很多人卡在这一步其实不是能力问题而是不理解这两个工具的分工遇到报错就不知道该看哪一行。另外补充一点这篇默认你已经完成了前面的步骤depot_tools 可用、Visual Studio 2022 装好、Chromium 145 源码已经 checkout 并跑完 gclient sync。如果你还没做完这些建议先把前面几篇补齐再回来继续读否则下面任何一条命令都可能因为环境不对而失败。1. 双阶段构建GN 生成、Ninja 执行的底层分工1.1 为什么 Chromium 不直接用 Visual Studio 工程或 CMake第一反应是用 Visual Studio 不就行了的人不在少数。毕竟其他 Windows C 项目打开 .sln 就能编译为什么 Chromium 偏要搞个 gn gen这里有个体量问题。Chromium 源码量级的项目构建系统要能表达这个目标依赖哪三百个库、这些头文件在哪些平台上从哪个目录取、同一个源文件在不同配置下要用不同宏编译。Visual Studio 的 .sln/.vcxproj 对这种规模的管理能力很弱文件一多IDE 加载和工程更新就慢得让人崩溃。CMake 虽然通用但它本质上面向中小型项目复杂的依赖裁剪、跨平台条件编译、多工具链选择写起来非常别扭执行效率也跟不上。GN 是 Chromium 团队给自己造的专用工具。它只做一件事把 BUILD.gn 里的声明式描述和 args.gn 里的配置参数解析成 Ninja 能直接执行的构建规则。GN 本身不做编译、不链接、不打包这些全是 Ninja 的工作。Ninja 的设计目标就一个字快。它牺牲了所有花哨功能换取最小化重建和最大化并行。两个工具各干各的整个系统才撑得起千万行级代码的日常增量编译。顺带一提早期 Chromium 用的是 GYP 系统后来因为配置解析慢、依赖表达不清晰才换成了 GN。如果你在网上翻到老教程教你怎么用 GYP直接跳过就行145 这个版本已经完全建立在 GN 体系上。1.2 敲下 gn gen 之后系统到底做了什么很多教程只说运行 gn gen out\Default但没人告诉你这几秒发生了什么。我拆一下GN 先读取源码根目录下的.gn文件确定默认的构建文件路径和构建根目录。递归加载所有 BUILD.gn这些文件是配方里面记录了每个目标怎么编译、链接、依赖谁。读取输出目录下的 args.gn。如果没有这个文件就全部采用内置默认值。把上述信息合并成一张完整的依赖图再把每个目标的编译参数编译器路径、SDK 路径、头文件包含目录、宏定义、优化选项具体展开。最后把这些规则写成 out\Default\build.ninja、toolchain.ninja以及若干辅助 .ninja 文件同时把一份 args.gn 快照保存进输出目录。整个过程通常在几秒到十几秒之间取决于机器速度和是否首次生成。如果你看到 GN 打印出类似 Done 的信息并统计生成了多少 target、耗时多少毫秒说明生成成功如果直接报错那就去读错误第一行后面会专门讲怎么读。我特别强调一下args.gn 快照这件事输出目录里的 args.gn 是当前配置的真实记录。以后你改源码、跑 gclient sync 更新依赖再回头看这个文件能明确知道当前目录到底是用什么参数生成的。这就是为什么多目录隔离很重要见下一节。1.3 out\Default 只是一个约定不是唯一答案很多新手以为 out\Default 是固定的其实它只是一个目录名约定。你完全可以建 out\Debug、out\Release、out\arm64、out\test每个目录都有自己的 args.gn互不干扰。想切配置不需要清掉重来只需要换一个输出目录重新 gn gen 就行。这也带来了一个实用习惯永远不要在一个输出目录里反复改参数。今天调试明天发布改来改去总有一天会搞混这个目录到底对应什么配置而且增量编译缓存也会被一些怪异参数组合污染。新建目录的成本极低用 out\Debug 和 out\Release 分开各跑各的省心得多。2. 生成前体检三条命令和一次目录检查2.1 三秒钟确认 depot_tools 就绪gn、ninja 都住在 depot_tools 里。生成构建文件前先在终端里跑where gn where ninja where python三条命令都返回了路径说明 PATH 没问题。其中 gn 的路径应该指向 depot_tools 目录ninja 通常也在这里。如果where gn提示找不到多半是 PATH 没配好或者你setx PATH ...之后没有新开终端。Windows 的 setx 只对之后打开的终端生效当前已经开着的窗口是不会更新的这个坑我见得太多了。一个小提示如果你习惯用 Git Bash 跑命令gn 的 .bat 文件有时候不会被直接识别。建议这个环节统一用 Windows 终端cmd 或 PowerShell能少掉一半莫名其妙的找不到命令错误。2.2 Visual Studio 2022 与 SDKGN 是拿什么认出来的GN 在 Windows 上找 Visual Studio 靠的是 vswhere 工具它能列出机器上安装的 VS 实例和组件。只要你在安装时勾选了使用 C 的桌面开发工作负载vswhere 就能找到符合要求的 MSVC 工具链和 Windows SDK。 Chromium 145 这个阶段构建系统对 MSVC 小版本有最低要求所以如果你装的是特别老的 VS 2022 小版本gn 可能直接报Visual Studio 版本不支持。另外Windows 平台上 Chromium 默认的编译器其实是 clang-cl而不是传统印象中的 MSVC 的 cl.exe。clang-cl 需要从 VS 安装里拿 Windows SDK 头文件、标准库和链接器两者是配合关系缺一不可。所以 Visual Studio 不只是装了就能用C 工作负载和最新 Windows SDK 组件都必须装齐。还有一个很多人不知道的环境变量DEPOT_TOOLS_WIN_TOOLCHAIN0。在某些非官方环境里depot_tools 会优先去找内网工具链而不是你本机装的 VS。设成 0 就强制它使用本机工具链。如果你 gn gen 一直报找不到 VS先检查这个变量是不是被设成了 1。2.3 确认 gclient sync 真的跑完了源码 checkout 后Chromium 依赖大量 third_party 库这些库靠 gclient sync 拉取。如果 sync 没跑完就急着 gn genGN 在加载 BUILD.gn 时经常因为找不到某个依赖目录而报错。怎么判断 sync 是否完整打开 src\third_party 目录确认里面比较大型的目录比如 llvm-build、icu、angle 等都存在且不是空的。这里特别看 src\third_party\llvm-build因为 Windows 上默认用 clang-cl 做编译器clang 工具链就放在这个目录里缺失的话即使 gn gen 侥幸通过实际编译阶段也会立刻失败。还有一点如果你的源码是从一个旧分支切到 145 的切完分支之后必须重新跑一遍 gclient sync否则第三方差值版本对不上GN 生成出来的工具链描述和代码实际需要的版本会不一致。这是升级版本号之后编译炸了最常见的原因之一。3. 第一条命令以最小配置完成 gn gen3.1 最简命令和它打开编辑器的行为确认环境没问题后进入源码目录执行cd /d C:\chromium\src gn gen out\Default这是最朴素的形式不指定任何参数全部用默认值。默认配置是调试模式、非组件化直接生成。但其实我很少这么干因为默认配置对 Windows 编译来说并不友好——非组件化的调试构建链接时间非常长第一次全量链接能让人等到怀疑人生。所以更常见的做法是先gn args编辑参数再生成。运行gn args out\DefaultGN 会在 Windows 上用记事本打开一个待编辑的 args.gn 文件里面全是注释模板。你把需要的参数写进去保存退出GN 会自动重新执行生成。如果你不想用记事本可以设置环境变量GN_EDITOR指向你喜欢的编辑器。如果你连编辑器都不想开也可以在命令行直接传参gn gen out\Release --argsis_debugfalse is_component_buildfalse这种写法适合脚本化场景参数一变命令就变不容易污染已有输出目录。args.gn 的语法非常简单参数名 值布尔值是 true/false字符串要加引号注释用 #。看到以 # 开头的行都是说明文字可以直接删掉。3.2 六个高频参数逐个拆解下面这几个参数是你迟早会用到的我按重要程度排一下参数名可选值作用我的建议is_debugtrue/false是否生成调试构建带调试符号和未优化代码日常开发 true跑性能、打包时 falseis_component_buildtrue/falsetrue 时把产物拆成多个 DLL避免每次链接一整块巨型二进制Windows 上开发强烈建议 truesymbol_level0/1/2调试符号的详细程度默认调试构建用 1 足够追求速度用 0target_cpux64/x86/arm64目标 CPU 架构常规 x64跨架构可逐个建目录is_official_buildtrue/false官方级优化路径会启用更多优化和协议限制个人自用不建议开blink_symbol_level0/1/2单独控制 blink 模块的符号级别空间紧张时设 0逐个说下使用场景。is_debug是调试/发布的总开关。调试构建不优化、变量可查但体积大、运行慢发布构建优化多、体积小但调试信息少跑起来也快很多。is_component_build是我在 Windows 上反复强调的参数。Chromium 全静态链接时最终产物是一个巨大的可执行文件加一个巨大的 DLL链接一次动辄十几分钟。组件化构建把代码拆散成几十个 DLL增量编译时只重新链接改动涉及的那几个 DLL日常开发能省出大量时间。代价是最终产物不便于分发、运行时要带一堆 DLL所以发布构建一般会把它设回 false。symbol_level控制调试信息的完整度。Level 2 包含完整类型信息方便调试器查看变量类型但生成的数据会膨胀到几个 GB链接也明显变慢Level 1 已经能提供调用栈和大部分调试信息对多数场景足够。如果你只是想验证编译流程直接用 symbol_level 0 最省事。target_cpu决定编译目标架构。默认跟随宿主架构x64 机器就是 x64。如果要出 32 位版本或 ARM64 版本单独建输出目录设置即可。跨架构编译时GN 会自动选择合适的工具链组合。is_official_build不建议随便开。它开启的是官方发布优化路径会带来更激进的优化但同时也会改变一些默认值强制某些参数组合和普通开发流程不完全兼容。没有充分理由就不要碰。3.3 两套可以直接抄的 args.gn开发调试用这套is_debug true is_component_build true symbol_level 1解释一下思路调试模式保证可断点、可看变量组件化保证增量链接快符号一级保证调用栈够用又不至于把磁盘塞满。这套组合对你的机器负担最小适合每天改代码、编译、跑测试的开发循环。如果你想编译一个接近发布效果的版本用这套is_debug false is_component_build false symbol_level 1 target_cpu x64非调试 非组件化会得到一个完整的、可独立运行的 Chromium 产物符号保留一级方便出问题时定位。第一次全量编译耗时会长不少但产物是干净、可分发的。还有一套我只是想验证能不能编译通过的极速配置is_debug false is_component_build true symbol_level 0不要面子的条件下这套能让你最快看到编译结果。4. 进阶组合与参数诊断4.1 多输出目录配合不同优化目标当你从跑通进入我要一个稳定可用的产物阶段时多目录的价值就体现出来了。我的习惯是out\Debug日常开发组件化调试啥功能都开。out\Release定期构建的可分发版本静态链接符号一级。out\Min验证最小依赖关系只编译特定目标用的轻量目录。每个目录的 args.gn 独立维护互不干扰。切换构建目标时-C参数指到对应目录即可autoninja -C out\Debug chrome autoninja -C out\Release chrome这是一个很朴素的工程化技巧但确实能让你的构建生活变得有条理。配套习惯是在 args.gn 顶部写注释记录这台机器的 CPU 核心数和内存回头换机器时不至于对着参数发呆。4.2 参数被移除是常态看见报错先别慌Chromium 迭代非常快GN 参数列表也经常变动。你在网上找到的教程可能是两年前的里面写的参数在 145 里早就移除了。比如很早以前的教程会让你写enable_nacl false现在这个组件已经从构建系统里移除你再写这行GN 会直接报错提示该变量不存在。处理方式很简单删掉这一行重新 gn gen。GN 的优点之一就是报错信息很精确它会告诉你哪个参数不认识了、在哪个文件哪一行。看到 Unknown variable 开头的错误不要慌这句话本身就是答案。4.3 两个诊断命令--list 和 gn desc拿不准某个参数存在不存在、默认值是什么用这个gn args out\Default --list这条命令会把当前输出目录所有可用参数、默认值、简要说明一次性列出来。输出量很大建议配合管道和 Find 功能过滤关键字。如果你想查某个具体构建目标的信息用gn desc out\Default chrome它会显示 chrome 这个目标依赖哪些库、定义了哪些宏、属于哪个工具链。这类命令在排查我改了参数为什么没生效的场景下非常有用。比如你怀疑某个宏没被定义gn desc一眼就能确认。注意gn args --list的输出是基于当前 args.gn 展开的真实结果。你刚改完参数想确认是否生效先重新 gn gen再跑 --list 看别拿旧输出目录里的缓存文件对照。5. 可选路线用 --idevs 生成 Visual Studio 工程5.1 VS 工程其实也是 Ninja 的包装有些人就是习惯在 Visual Studio 里写代码、调试。GN 提供了生成 VS 工程的能力gn gen --idevs out\vs生成完成后out\vs 目录下会出现一个 .sln 文件通常是 all.sln和一大堆 .vcxproj 工程文件。用 VS 打开这个解决方案就能在 IDE 里浏览代码、编译目标、打断点调试。但你要理解一件事这套 VS 工程并不是把你带离了 Ninja 构建体系它只是把 Ninja 包装进了 VS 的项目框架里。实际编译仍然由 Ninja 执行VS 只是提供了一个操作界面。这意味着你不可能用 VS 的项目属性去改编译选项真正生效的还是 args.gn 和 BUILD.gn。5.2 用 --filters 和 --no-deps 控制工程规模Chromium 全量生成 VS 工程是一件很重的事情因为依赖太多.vcxproj 文件会成百上千地生成。如果你只需要研究某一模块务必加上过滤器gn gen --idevs --filters//chrome --no-deps out\vs--filters指定只生成感兴趣的目标这里是 chrome 模块--no-deps表示不要为它的所有依赖单独生成工程文件。这样 VS 解决方案会小很多加载速度也快得多。不加这两个参数的话生成过程慢不说VS 打开解决方案可能直接卡死。5.3 我的取舍IDE 用来看代码编译交给命令行个人建议除非你确实需要频繁的断点调试体验否则日常编译还是用命令行的 autoninja。原因很简单——命令行下改 args.gn、切目录、跑增量编译所有动作都能脚本化也不受 IDE 加载大型解决方案的性能影响。VS 工程生成本身也有额外开销每次源码结构大改后都需要重新生成维护成本不低。所以我的实际做法是用 Visual Studio 看代码用autoninja -C out\Debug chrome编译用gn args管配置。如果你也喜欢这种各工具干最擅长的事的节奏可以直接照这个模式走。6. 生成失败的高频问题与完整排查思路6.1 gn 不是内部或外部命令PATH 的锅占八成才对在 cmd 里敲 gn 弹出这个第一反应不要想复杂了就是 PATH 里没有 depot_tools。检查当前终端有没有继承新的 PATH最简单的方式是重开一个终端再跑where gn。如果你用setx改过路径新终端才生效。还有一类更隐蔽的情况你机器上装过多个 depot_tools或者安全软件把 depot_tools 里的部分工具隔离了。前者用where gn就能看出来返回了哪个路径后者会在首次运行时出现下载失败或工具缺失可以临时关闭实时防护再试。我不是让你关安全软件只是建议排查时留意这个变量。6.2 Visual Studio 检测失败先看 vswhere再查环境变量gn gen 报找不到 Visual Studio 安装或提示版本不受支持时排查链路大概是打开 Visual Studio Installer确认装了 VS 2022且勾选了使用 C 的桌面开发工作负载。确认该工作负载下包含最新 Windows SDK 组件。确认环境变量DEPOT_TOOLS_WIN_TOOLCHAIN不是 1建议直接设成 0。确认系统里没有同时装着会导致 vswhere 混淆的预览版 SDK。这里面 90% 的情况是第 1 步没勾全——很多人装了 VS 却没装 C 工作负载GN 自然找不到编译器和 SDK。另外如果你之前装过 VS 2019Chromium 145 已经不支持它了卸不卸载无所谓但别指望它能被识别。6.3 参数冲突GN 会把矛盾直接甩在你脸上当你同时设置is_official_build true和is_component_build true时GN 会直接报错并指出这两个参数互相矛盾。这类错误的好处是信息非常明确多半会显示哪一个参数和哪一个参数冲突冲突从哪个 BUILD.gn 文件里的哪条检查语句出发的建议改成什么值。读这种报错的姿势是只看前几行不要去搜后面大段的堆栈。GN 的定位信息做得比大多数构建系统好报错文件、行号、涉及变量都会标出来按图索骥改 args.gn 就行。如果改完参数后报错依旧还有一个万金油操作gn clean out\Default它会清掉这个目录的构建输出但保留 args.gn相当于重置生成状态。重要不要用直接删除 out 目录代替 gn clean。直接删除会把所有增量编译成果一起删掉下次全量编译会非常痛苦。gn clean 是保留了参数配置和目录结构的干净重置这才符合出问题先重置、但不是推倒重来的原则。6.4 生成成功后的第一步从一个小到不能再小的目标开始gn gen 通过只是构建的入场券。生成完成后我建议先不要直接去编译 chrome 这个庞然大物而是先编译一个很小的基础目标验证整条链路autoninja -C out\Default basebase 库是 Chromium 的基础组件编译量小、依赖关系清晰能在几分钟内验证 GN 生成的构建文件是否真的可用。跑通了再上 chrome 目标autoninja -C out\Default chrome这里提醒一下首次全量编译的心理预期Chromium 145 的全量构建在 16 核旗舰 CPU 上跑发布配置也常常需要几小时调试组件化配置会快不少但依然是小时级。等构建期间不要反复中断Ninja 对中断的恢复能力不错但高频重启会让增量状态变得混乱让它跑去干点别的。另外编译用的并行度一般不用手动设置。autoninja这个命令会自动根据 CPU 核心数计算合理的并行参数这也是我一直强调用 autoninja 而不是直接调 ninja 的原因。最后补一个我自己的习惯。每次生成构建文件之前我都会先在 args.gn 顶部写三行注释机器 CPU 核心数、内存大小、这次构建的用途。听起来很土但 Chromium 这种项目换机器、换分支、换参数组合是家常便饭。一个月后你翻回这个文件能立刻想起来当时为什么这么配比单纯靠记忆靠谱得多。另外我还会在第一次生成成功之后把gn args out\Default --list的输出存一份到构建目录旁边作为当时的参数基准。后面跑 gclient sync 升级源码导致 gn 报参数变化时拿出来对比一眼就知道哪里变了。希望你也能顺利用 GN 把 Chromium 145 的构建文件跑出来。这个门槛一过后面真正编译的日子反而没那么难熬了。